SDK session CLI
August 26, 2026 · View on GitHub
gjc sdk session is the broker-bound command family for operating live GJC SDK
sessions from the terminal. It replaces the removed gjc daemon session route.
The command family has six semantic verbs — list, inspect,
send, status, tail, and retire — plus the explicit raw hatch that dispatches one
SDK operation as control, query, or global.
The session CLI is advisory tooling over the SDK: every semantic verb resolves sessions through the SDK broker, and output is rendered through a versioned, credential-free DTO. Endpoint credentials are never printed.
Broker authority
list, inspect, send, status, and tail resolve sessions through the
SDK broker and Router. The Router validates indexed endpoint authority and keeps
the connection credential in SDK core; the CLI receives only credential-free
results. The broker is started on demand (ensureBroker) when discovery is
absent, and an unavailable broker fails closed with a typed operational error
(exit 1).
--agent-dir selects the broker state directory; --repo selects the
workspace directory used for saved-session resolution (default: the current
directory).
Semantic verbs
list
gjc sdk session list queries the broker session.list global and projects
every indexed session into the versioned row DTO (SESSION_ROWS_VERSION). Each
row is credential-free and carries:
sessionIdand thelocator(repo,stateRoot);endpointGeneration,pid,live,deleted(tombstone),indexSeq;hostIncarnationandidentityProvenance(composite|legacy);activity({state: active|idle, at}) andlastHeartbeatAt;terminalUncertain,lifecycleRequestId,endpointMtimeMs;ambiguouswhen the samesessionIdhas more than one unresolved authority-fencingstateRoot(cross-repo duplicate). A proven non-endpoint bookkeeping registration (the direct-session GC fence row, endpoint generation 0) stays indexed without fencing endpoint attachment; every other unresolved root, including an unproven generation-0lifecycle_terminalclaim, still fences.
inspect
gjc sdk session inspect <sessionId> renders one indexed row from the broker.
It never reads endpoint discovery records directly: a missing or unavailable
broker fails closed rather than exposing endpoint authority outside SDK core.
send
gjc sdk session send <sessionId> --text <prompt> submits an ordered
turn.prompt carrying a caller-chosen operation reference (a ULID by default,
or --op-ref). The result envelope reports accepted with the receipt and the
operation reference used for later reconciliation.
-
--waitpollsturn.resultwithkind: "prompt"until the prompt reaches a terminal state or the wait window (--timeout-ms, default 30s) elapses.send --waitnever cancels a running turn; a window that elapses before a terminal state is reported aswait_timeoutwith the last observed status. -
--textand the JSON input sources (--json-input,--json-input-file— which must be a0600regular file —--json-input-stdin) are mutually exclusive for the prompt body.
status
gjc sdk session status <sessionId> <opRef> performs a lossless turn.result
lookup with kind: "prompt" for a previously submitted operation reference and
returns the full reconciliation record plus a summary.completed flag.
See lossless prompt results.
tail
gjc sdk session tail <sessionId> replays the retained transcript from the
durable checkpoint and then follows the live event-ring frames, emitting the
default tail kinds (session lifecycle and turn lifecycle events) plus retained
transcript entries.
--strictfails closed withretention_gap(exit 1) when retained history or the event ring dropped entries before the checkpoint.--until-idleexits once the current turn reaches a terminal state; a session close exits any tail asterminal: true, while a bare live tail otherwise remains attached until--timeout-ms. Lifecycle events that carry a(generation, seq)position are reconciled and emitted in that canonical order rather than arrival order, so a retained terminal event from an earlier turn does not complete a newer turn that is still running, and a terminal event observed live is not undone by replayed history that arrives after it. Positioned event items are emitted canonically ahead of an arrival-ordered segment of events that carry no position; such an event stays visible but cannot supersede a positioned turn state. Different lifecycle kinds claiming the same(generation, seq)fail closed withprotocol_error(exit 1) because no order between them can be proven, and that conflict outranks an otherwise successful idle or close completion. A canonical position is a pair of non-negative safe integers. A replayed event-ring row that states either coordinate property must state both validly; a row claiming only one coordinate, or a null, negative, fractional, non-finite, or unsafe-integer coordinate, fails closed withprotocol_errorrather than being treated as unpositioned. Only a row that states neither coordinate is unpositioned. Retained transcript entries are deduplicated independently of event-ring rows and are not event-ring authority, so a transcript row that projects the same kind and position as a real event never suppresses it. For a specific prompt operation,status <sessionId> <opRef>remains the lossless authority.--all-eventswidens the emitted set to every event-ring kind.--cursorresumes from a saved signed checkpoint claim.session.checkpointverifies the unexpired claim and exchanges it for a fresh connection-owned cursor pinned to the exact prior revision; direct cross-connection cursor consumption remains rejected, so reconnect never echoes or rewinds a cursor.--timeout-msbounds live follow; a session whose lifecycle already ended (terminal orterminalUncertain) replays retained history and exits instead of hanging.
A deleted session has no tail (session_deleted). A stopped session replays
its retained transcript without an endpoint (offline source), bounded to the
most recent retained entries.
retire
gjc sdk session retire <sessionId> is the official semantic wrapper for the
session.reconcile_uncertain broker global. It retires an indexed
terminalUncertain create effect only when exactly one matching uncertain
create identity exists, the recorded host is proven exited, the endpoint is
absent, and any lifecycle marker/readiness leftovers are regular files bound to
that same PID and incarnation. The broker removes only those verified
leftovers, appends terminal session_closed evidence, and converts the
matching lifecycle receipt to a terminal error. Ambiguous identity, live or
unverifiable hosts, endpoint presence, malformed leftovers, and mismatches
refuse without signalling a process. Supply --idempotency-key; the JSON
input is read from a 0600 file or stdin when it contains proof material.
Raw hatch
gjc sdk session raw <control|query|global> dispatches exactly one SDK
operation and returns the broker/host response:
raw control <sessionId> --op <operation>— one control operation with--json-input*;--confirmconfirms destructive control operations.raw query <sessionId> --query <operation>— one query;--cursorpasses a continuation cursor.raw global --op <operation>— one broker global. Lifecycle globals (session.create,session.fork,session.resume,session.close,session.delete,session.reconcile_uncertain) require--idempotency-key.
session.get_endpoint is refused unconditionally: endpoint credentials remain
an SDK-core implementation detail. The raw hatch validates operation names and
adapter dispositions up front and never renders endpoint-disclosure results.
Lossless prompt results
turn.result with kind: "prompt" reports accepted, in_flight,
terminal_ok, or failed; only retained-record capacity eviction yields
unknown. turn.prompt_status remains a legacy prompt-only alias. A prompt
that is active at process restart is finalized from its durable pending outcome
(or prompt_failed when it has none), so it never reports as unknown while a
record exists.
unknown means uncertainty, never proof of non-execution: do not reuse an
operation reference as a retry mechanism (client_ref_conflict while the
record is retained; after eviction a reused ref may be admitted again with the
prior outcome unknown). Use one fresh operation reference per logical prompt
and reconcile with status.
Checkpoint gaps
tail reports a retention_gap when retained history or the event ring
dropped entries before the durable checkpoint: the gap carries the missing
sequence range (missing.from/missing.to) and a resync checkpoint.
--strict turns any gap into retention_gap with exit 1; without --strict,
tail continues from the resync position and reports the gap in the envelope.
Migration from the removed daemon session route
gjc daemon session is removed and no alias is provided. Migrate:
| Removed route | Replacement |
|---|---|
gjc daemon session list | gjc sdk session list |
gjc daemon session inspect <sessionId> | gjc sdk session inspect <sessionId> |
gjc daemon session send <sessionId> --text <prompt> | gjc sdk session send <sessionId> --text <prompt> |
gjc daemon session tail <sessionId> | gjc sdk session tail <sessionId> |
| raw control/query dispatch | `gjc sdk session raw control |
The broker-bound surface replaces the daemon-owned routing: sessions are resolved through the SDK broker with validated endpoint identity instead of direct discovery-file reads, and output is versioned and credential-free.
Exit codes and error envelope
Verbs exit 0 on success and write JSON to stdout. Failures write a JSON error
envelope to stdout with a non-zero exit: usage errors exit 2, operational
failures (broker unavailable, session unavailable, retention gap, wait
timeout) exit 1. Error details are recursively redacted of secret-shaped
fields before rendering.