Command reference
oko sessions agents
Every agent session on this machine a supervisor needs to read: each one a live process holds, and with --hours each one written in the last N hours — who holds it, whether it is working, and what it stands on.
Invocation#
oko sessions agents [--hours N] [--socket PATH] [--json]Inputs and options#
- Without --hours, only the sessions a live process holds: one with its transcript open for writing (an OMP window in VS Code, a Codex run), or a live terminal of Oko's broker started on that transcript. The broker terminal counts even when no descriptor names the path any more — OMP rewrites a transcript in place when it records a discarded entry branch — so a session still working there is never listed as interrupted. A process that only reads a transcript, such as Transcript Lake ingesting it, holds no session. --hours N adds every session whose transcript was written in the last N hours. When the broker's terminals cannot be read, the answer carries holderFailure with the cause and only open descriptors decided who holds a session.
- --socket names the broker whose terminals are matched to the processes; the default is the installed one.
- --json prints the answer as JSON; without it the same answer is printed as lines for a person.
Output and state effect#
- Each row names the session, runtime, transcript, current process writers, state, running tool and observed waits; unreadable sources retain their causes. The pass retains real descendant ancestry and kernel process births under transcript writers and broker-owned terminals, including detached children. orphaned names attributable ended-session trees with session, age and command; unattributed processes retain their age, command and refusal. orphaned.failure names a failed process-table, kernel-identity or ownership-register operation. The listing sends no signal; use `oko sessions reap PID --reason TEXT`. Ownership must have been observed while the owner was alive and is never inferred from a terminal or command name.
- A turn ends with an answer that calls no tool, or with a failure: an assistant record OMP closed with stopReason error (the provider refused or the request broke; errorStatus and errorMessage say how, and `retry-after-ms=<ms>` at the end of errorMessage is the wait the provider stated) or aborted (the turn was interrupted). A session whose newest record is such a failure has stopped, whoever still holds its file: state is stopped, stoppedOn names the error, and the plain answer prints `stopped: provider error 429 …; retry after …`. Before this, such a session read as answering for as long as its process lived, resume refused it and the control pass left it alone.
- A stopped session whose broker terminal was typed into after its newest record (`oko sessions send`; the broker records each typing as typedAt) has state typed: somebody continued it and its harness has not written the message yet. The row carries typedAt, the plain answer prints `continued: text was typed into its terminal at <typedAt>, after its newest record (<newestAt>); the harness has not written it yet`, and the model policy, which moves only stopped sessions, does not move it a second time. Oko Desktop's Agent sessions rows print the same sentence.
- Only the two ends of each transcript are read, a filesystem block at a time, and the open files of every process are read from the kernel once, so the answer takes seconds whatever the transcripts weigh. Oko Desktop's Sessions screen shows the same rows under Agent sessions through the native API's sessions domain. A session is on tool while the newest record that calls tools has a call no result answers, matched by call id, whatever record came after — one of several parallel calls still running after the others answered is a running tool, printed `on tool <name> since <time>`, never `waiting for the model`. runningTool then carries since (when that record called), unanswered (how many of its calls no result answers) and processes [{pid, command}], every process the session's harness runs under it, from one process table (`ps`) read per listing; the row's waits add every wait Stado's register ($STADO_WAITS_DIR, else ~/.stado/waits: one file per open wait holding its `czekam` line) holds open for one of those processes, so a command waiting on Stado is shown whatever the harness prints. A table or register that cannot be read is processFailure on the listing. Real journey: tests/sessions/policy/parallel.rs.
- When an unanswered tool call has an open Stado wait of the declared consent kind, its state is consent, not model latency or an ordinary tool stall. The consent field carries that wait's exact grant in what, its place, start and register id; runningTool still names the call and its processes. The plain `oko sessions agents` line and Oko Desktop → Sessions → Agent sessions show the grant and the tool together. A missing or unreadable wait register retains processFailure and never invents consent. The ignored real session journey in tests/sessions/policy/parallel.rs requires an actually open grant wait and refuses to qualify if none is present.
- Oko writes its own database, Brama, lock and child-process waits through the shared stado-wait mechanism, not a copy of its stderr format. An open wait is registered under $STADO_WAITS_DIR (else ~/.stado/waits) with its operation, location, kind and start. Oko's own operation is visible in the shared register; `oko sessions agents` and Oko Desktop → Sessions → Agent sessions read the same register for waits belonging to a session's harness processes, even when stderr is not visible. The shared reader ends entries left by dead processes and reports an unreadable register with its cause. Completion removes the entry and leaves the outcome on stderr. No wait is inferred from a healthy process alone.
- Each row also carries role (supervisor, executor, or null), supervisor (the session an executor works under) and executors (the sessions recorded under a supervisor), read from ~/.oko/session-roles.json as `oko sessions role` recorded them; the lines for a person say “supervisor of N executor(s): …” or “executor under <id>”.
- A broker session on a Brama subscription carries subscription: {subscription_id, account, lease_session, lease_id}, read through the broker's task-leases command; the CLI and Desktop show its account. An OAuth account is pinned through OMP's auth broker; a sole measured API key uses OMP's credential store directly. The lease is released when the terminal ends. An OMP session started when Brama could not grant a lease instead reports unleased with Brama's cause, selected model, measured account and observation; its transcript's actual provider and model remain visible in `oko sessions agents` and Desktop → Sessions → Agent sessions. Jeden requires a lease.
- The answer carries subscriptionSpread, [{subscription, sessions}]: how many of the listed sessions run on each subscription the broker leased for them, printed as “sessions here per leased subscription: <id> <n>, …” and shown under the same caption in Oko Desktop's Agent sessions. It counts this machine's sessions only; the gateway's count over every machine is each member's live_sessions in `brama subscription list`.
- Each row carries provider and model: what the session actually runs on, read from the newest model record of its own transcript (OMP's model_change, or the provider and model of its newest assistant message), so a session switched between turns shows the model it switched to; modelFailure says why a transcript could not be read for it. The answer carries modelSpread, how many live sessions run on each model declared in Oko's models.json, which `oko sessions start` and `oko sessions resume` choose from; the lines for a person print the model beside the session and end with “live sessions per declared model: …”.
- For a Jeden session the model record is the model_route event of Jeden's ledger, which nests each event as {payload: {type, data}}: its data.model is the route the newest step was served on. Where a record names its type, and which record types name a model, is declared in Oko's agents.json (typePointers, modelRecords).
- The collection carries startedAt and at (finished). Each row carries observedAt, sampled after its transcript and terminal reads; silentSeconds, runningTool.seconds and provider retryPassed use that row's observation clock, never the collection-start clock. The working flag follows writer ownership and the observed turn, not elapsed time. CLI and Desktop show the collection interval and row observation beside lastWrite. This is a sequence of live observations, not an atomic snapshot. Actual host-clock anomalies remain visible rather than being clamped to zero.
- For example, while another agent is editing, run `oko sessions agents --json` or read Desktop → Sessions → Agent sessions: compare its lastWrite and observedAt within startedAt/at to distinguish fresh activity from a tool genuinely standing still. The read does not resume or modify that agent.
- A row on a tool carries runningTool.shape (the tool's name, and for a command the leading words before the first flag of the first program the line runs — a leading `cd <dir>` and `NAME=value` assignments are passed over — such as `bash: oko tasks` for `cd ~/oko && OKO_X=1 oko tasks --open`), runningTool.norm {longestSeconds, samples} — the longest any completed call of that shape took and how many completed, from ~/.oko/tool-durations.jsonl, which every pass appends the calls its transcripts show completing (a call paired with its result by id, timed by their records) — and runningTool.stalled: true when the running call has already run longer than that, false when not, null when no call of its shape ever completed. The lines for a person print `STALLED: … has run longer than the longest of <n> completed `<shape>` call(s), <s>s` under the tool line. No bound is Oko's: each is a duration a transcript recorded. A durations file that cannot be read, or holds a line that is not a kept call, refuses the read naming it; a tail that cannot be read is toolDurationFailure on that row.
- Oko Desktop's Agent sessions shows the same reading on each row on a tool: a `stalled` chip beside the state when runningTool.stalled is true, and under the standing line the same sentence the CLI prints — `STALLED: <tool> has run longer than the longest of <n> completed `<shape>` call(s), <s>s`, `within the longest of <n> completed `<shape>` call(s), <s>s`, or `no `<shape>` call has completed yet, so this one has no measured norm` — read through the native API's sessions domain; the Interrupt the running call button on the row stops it.
- A harness that refuses a tool call at its own handler limit names no hook in its refusal. Tama's core records, per session, the hooks a dispatch still ran when the harness's next signal proved it stopped waiting (its cancel, the call's result arriving, a newer turn event or the end of the turn). Each listing reads that register once through `tama hooks abandoned --json` (the installed ~/.stado/bin/tama, else tama on PATH), which inspects and signals no process. A session Tama recorded carries tamaAbandoned {session, reason, abandonedAtMs, corePid, hooks [{hook, event, processGroup, ranForMs}], text}; the plain line says `harness stopped waiting at <time>: still running in Tama: <hook> (<event>, <ms> ms) (<signal>)`, and Oko Desktop → Sessions → Agent sessions shows the same sentence on the row. When the register cannot be read the answer carries tamaFailure — `tama hooks abandoned could not start`, `exited <status>: <stderr>` (an unreadable record is named there by its path), `printed invalid JSON` or `returned a record without a session` — printed as `Tama's abandoned-dispatch register not read: <cause>` and shown as a warning panel in Desktop, and no row carries tamaAbandoned on that reading. The real `sessions-abandoned` journey drives a real Tama core (OKO_TEST_TAMA) for the session of a genuine transcript (OKO_ABANDONED_TEST_TRANSCRIPT), cancels its held edit, and requires the row, the printed line and the refusal of an unreadable record.
- The real `sessions-observation` journey reads an actually active harness through the CLI and native Sessions API, verifies fresh activity during each collection and exact observation-based durations, and retains command outputs, source revision and binary digest. Run `cargo test --manifest-path rust/Cargo.toml --test sessions-observation -- --ignored --nocapture` with OKO_TEST_SOURCE_REVISION and OKO_TEST_ACTIVE_SESSION naming the exact candidate and a real harness producing activity. Missing activity, unreadable state or a failed dependency fails the journey rather than passing an unexercised boundary.
Refusals and safe exits#
- “oko sessions agents: --hours takes a positive whole number, got <value>”, “oko sessions agents takes only flags; it was given <word>, which it would not read” and “oko sessions agents does not take <flag>; it takes --hours, --socket” (exit 2).
- “the kernel did not count this machine's processes: proc_listallpids answered <n>” when the process table cannot be read (exit 1).
- “the session roles in <path> cannot be read: <error>; …” when ~/.oko/session-roles.json was changed by hand into something Oko does not read (exit 1).
- “the model policy's decisions in <path> cannot be read: <error>; …” when ~/.oko/model-policy.json was changed by hand; each row otherwise carries the model policy's newest decision about the session as modelPolicy (null when it never decided), as `oko sessions policy` describes (exit 1).