status model and completion
the one status vocabulary every surface shares, the exit_reason groups, and how orcr verifies that a turn is genuinely complete instead of trusting a stale idle.
every agent has exactly one status at a time, and the same value appears everywhere: the store, ls, top, wait results, JSON, and events. there is one public vocabulary and one completion discipline behind it, so a script and a human read the same state.
two lifecycles
managed and unmanaged agents have different lifecycles. unmanaged agents cannot be queued, parked, or start-tracked, so their set is smaller.
managed lifecycle:
| status | meaning |
|---|---|
queued | accepted and durable; waiting for a free concurrency slot |
starting | slot claimed; herdr pane and provider TUI being created |
working | processing (also the verification window right after herdr first reports idle, until completion is confirmed) |
idle | turn complete (verified, below); waiting for input |
blocked | needs a human: question, usage limit, or login (blocked_kind) |
parked | was idle at least idle_after; pane moved to the idle workspace to keep things tidy; still alive, still resumable; any send revives it |
ended | gone; exit_reason says why |
lost | the pane vanished outside orcr's control (herdr crash, manual close); the path stays reserved until reconciliation confirms the terminal is really gone, then it becomes ended with exit_reason: lost |
unmanaged lifecycle (tracked from herdr's reporting only): working, idle, blocked, unknown, ended. no queue, no parking, no start tracking. unknown is herdr's own catch-all, and the permanent status when the provider's herdr integration is not installed; ended means the pane closed. see managed vs unmanaged agents.
exit_reason
when an agent ends, exit_reason answers one scripting question (did the work finish?) in four groups:
| group | exit_reason | meaning |
|---|---|---|
| finished | completed | gc immediate: the turn completed and the final response was captured before the pane closed |
| finished | reaped | gc auto tidy-up: the agent had completed its turns, sat parked past kill_after, and GC released the pane; nothing was cut short |
| cut short | killed | explicit agent kill (or loop run stop / loop rm --kill-active) while it may still have had work |
| cut short | timeout | an explicit --timeout expired mid-work |
| never ran | canceled | killed while still queued or starting; no work was done |
| never ran | failed | never started properly (stuck-start guard, startup error) |
| never resolved | lost | the pane vanished and its disappearance was positively confirmed |
wait maps these to per-agent reason tokens with exit codes; the status model reference has the exhaustive table.
verified idle
completion is defined per turn. every delivered input (the first prompt, every send) increments the agent's input_seq before delivery. a turn is complete when, for the latest input:
workinghas been observed after that input's delivery began (a per-integration grace window covers fast turns), then- stable idle: a minimum idle duration, and
- the transcript has settled: no new writes to the provider's transcript file for
transcript_settle_ms, meaning the agent has genuinely stopped producing output, not just paused between tool calls.
the public status flips working to idle only once this check passes. a first idle without input-scoped working is never completion. blocked is turn-scoped and clearable by send.
turn progress is persisted in the turns table (one row per input, keyed by agent_uuid and input_seq, recording source, delivered_at, working_seen_at, completed_at, blocked_kind, and a transcript cursor), so wait and gc immediate survive a server restart. after a restart with missing turn fields the server is conservative: it waits for a fresh transition rather than trusting a stale idle.
why an old idle can never satisfy a newer send
because completion is scoped to input_seq, an idle observed before your send was delivered belongs to the previous turn. the new turn cannot be satisfied until working is seen after the new delivery, then stable idle, then transcript settle. two consecutive sends therefore each get their own turn, and the second wait is never satisfied by the first idle, so send then wait then logs --last-response reads the response from the turn you actually started.
external and synthetic turns
you can type into an agent directly, via attach --takeover or in the herdr UI. orcr cannot see that input, but it can see the consequence: a working transition with no pending orcr delivery. when that happens the server records a synthetic turn (turns.source = external, bumping input_seq), so completion tracking, wait, and GC clocks stay correct.
if a user interrupts a turn mid-flight (Esc in the TUI), the turn settles at the next stable idle and is recorded complete with whatever the transcript shows, possibly a partial response. orcr reports the transcript's reality rather than guessing intent.
delivery, not acceptance
send confirms terminal delivery, not provider acceptance. input delivery is two herdr calls (send-text, then about one second later, enter, never one). integrations test send-while-working per provider. a TUI that buffers input without submitting, or opens a modal, surfaces as the turn never completing, visible in top, not silently lost. a herdr-reported done state is normalized: treated as idle for the completion check, and as ended only when pane closure is also observed. herdr timeout values are milliseconds and never leak into orcr's user surface.
integration tuning parameters
completion timing is per-integration logic, with defaults shipped inside each integration. the named parameters are:
fast_turn_grace_ms: treat delivery-then-idle within this window as a completed fast turn rather than a never-started one.idle_stable_ms: the minimum idle duration before stable idle.transcript_settle_ms: how long the transcript must go without new writes.transcript_freshness_timeout_ms: how long to wait for the transcript to advance past the observed completion before reportingtranscript_unavailable.shutdown_grace_ms: the graceful-shutdown grace forkill.
any of them can be overridden per provider via integrations.<provider>.* in config. see configuration and providers and integrations.
agent lifecycle and GC
how --gc parks, reaps, or keeps agent panes alive, why moves are two-phase and crash-safe, and the one known caveat with background subagents.
queue and concurrency
how admission control protects the machine, with one FIFO queue, a global cap plus per-provider caps, and the stuck-start guard.