dlgt CLI v1 reference
Status: implemented public contract for the repository binary.
This reference is also published as raw Markdown at https://combinatrix.ai/dlgt/cli.md for agents to fetch with curl.
This document is the normative command reference. See RPC for the programmatic interface and Design for the product boundary, invariants, lifecycle rationale, security model, and acceptance criteria.
Product definition
dlgt is a local, single-binary runtime for live, addressable, and attachable Codex and Claude subagents.
The only public runtime object is a Session:
Session
One dlgt-owned harness process and PTY
One controller at a time
At most one active execution
No server-side queueProvider turns and execution receipts may remain internal for lifecycle correlation while the daemon is alive, but they are not public CLI resources and do not have public IDs.
Other terms:
Harness The provider adapter, initially codex or claude
Profile A reusable client-side launch specification
Alias A human-readable address for an active Session
Title A non-unique human description used to generate an AliasIdentifier and naming model
Every successful new returns one provider-qualified Session ID:
codex:019f6307-341e-7e81-8a33-7ab61e804345
claude:8bc7859c-4c82-4b9a-a00d-2f3c483a9629The suffix is the provider's own Codex thread ID or Claude session ID. This single session.id is both the live dlgt address and the durable resume address. There is no separate provider Session ID or resume reference.
During startup only, dlgt correlates the not-yet-bound process with an internal:<short-id>. It atomically rekeys all retained state to the provider-qualified ID before accepting the first prompt or returning success. The internal launch ID is not a public Session ID; it may appear only as error.launch_id when startup fails before provider binding.
Aliases are for humans:
title: run review
alias: @run-review-361csxBy default, new slugifies the title and adds a random suffix. A caller may request an exact alias with --alias. An alias cannot be reused while owned by a starting, idle, busy, blocked, canceling, or stopping Session. It becomes available after that Session reaches a terminal stopped state. Historical records remain addressable by Session ID.
Automation should retain the returned Session ID and use it for all later commands. Aliases are ephemeral conveniences, not primary keys.
Output contract
All control-plane commands emit one JSON document to stdout.
Success:
{"ok":true,"session":{"id":"codex:019f6307-341e-7e81-8a33-7ab61e804345","state":"idle"}}Failure:
{"ok":false,"error":{"code":"SESSION_BUSY","message":"session already has active work","session_id":"codex:019f6307-341e-7e81-8a33-7ab61e804345"}}Failures also return a non-zero process exit status. stderr is reserved for failures that occur before dlgt can serialize a valid response, such as a panic or corrupted executable startup.
The exceptions are deliberate:
attach raw interactive terminal
events --follow NDJSON event stream
logs --raw raw PTY bytes
help / skill text
rpc --stdio JSONL request/response streamPretty-printed JSON is opt-in through --pretty; default output is compact and deterministic.
Top-level help
dlgt - local subagent runtime
USAGE
dlgt <COMMAND> [OPTIONS]
DELEGATION
new Create a new Session with its first prompt
restart Restart a Session
send Send work to an existing idle Session
wait Wait for the Session's current or latest execution
cancel Interrupt the Session's active execution
SESSIONS
list, ls List Sessions
show Show Session state or historical result
attach Attach to the Session screen
stop Stop the Session and its process group
OBSERVABILITY
events Read or follow normalized lifecycle events
scrollback Read rendered plain-text terminal scrollback
logs Read raw retained PTY bytes for diagnosis
CONFIGURATION
models Discover models supported by a Harness
profiles List or inspect launch Profiles
harnesses List Harnesses and supported options
skill Print the embedded dlgt skill
RUNTIME
server Run or stop the local daemon
update Install the latest release and embedded Skills
rpc Use the JSONL RPC interfaceEach release uses its own socket at $DLGT_HOME/run/<version>/dlgt.sock. Most commands address the daemon for the invoking binary's version. send first scans all live versioned sockets for an exact provider-qualified Session ID and routes to its owning daemon; multiple matches fail rather than choosing or launching. dlgt list --all-versions queries every currently running version and annotates each Session with runtime_version and runtime_socket. Session state, results, events, and terminal history exist only while their owning daemon remains alive.
Busy Session snapshots include two integer diagnostics when state is exactly busy:
{
"state": "busy",
"busy_for_ms": 183000,
"pty_quiet_for_ms": 72000
}busy_for_ms measures from reservation of the current turn. pty_quiet_for_ms measures PTY silence, clamped to that same busy interval so output from an earlier turn cannot inflate it. If no PTY output has occurred, it equals busy_for_ms. These counters are diagnostic only; PTY silence never changes Session state or makes a busy Session accept another prompt. Both fields are omitted for every other state.
Successful commands may include an optional non-error notice:
{"ok":true,"sessions":[],"info":{"code":"UPDATE_AVAILABLE","current_version":"0.1.4","latest_version":"0.2.0","command":"dlgt update"}}Each long-lived versioned daemon performs the first update check asynchronously at startup and repeats it every six hours. Transient check failures preserve the last successful notice; a successful check with no newer release clears the notice.
dlgt update verifies the release's attested checksum manifest, then installs the archive through the installer embedded in the running binary. It never downloads executable installer code after verification. The embedded installer checks the authenticated archive digest, pins the archive target to the running binary's build target, atomically replaces the current executable, and refreshes the embedded Codex and Claude Skills. Existing older-version daemons and their live Sessions continue on their versioned sockets.
new
new is the only Session creation command and always requires its first prompt. Launch and acceptance are one atomic operation.
Its command-specific help is available through either equivalent spelling:
dlgt new --help
dlgt help newdlgt new
--title <TITLE>
[--alias <@ALIAS>]
[--profile <PROFILE>]
[--harness codex|claude]
[--model <MODEL>]
[--effort <LEVEL>]
[--cwd <DIR>]
[--harness-option <KEY=VALUE>]...
[--no-auto-approve]
[--startup-timeout <DURATION>]
[--clean-env]
[--pass-env <KEY>]...
[--env <KEY=VALUE>]...
[--unset-env <KEY>]...
[--wait --timeout <DURATION>]
[--stdin | -- <PROMPT>] (required)Rules:
--titleis required and may be non-unique.- A Profile or Harness must resolve the Harness selection.
- Model and effort are optional. Omission selects the provider default.
- By default dlgt launches workers auto-approved:
--dangerously-bypass-approvals-and-sandboxfor Codex and--permission-mode=autofor Claude.--no-auto-approve(or Profileauto_approve = false) keeps the Harness's own approval prompts. An explicitpermission-mode=...Harness option replaces the implicit Claude mode. - Before launching either Harness, dlgt records the Session working directory as trusted in that provider's local workspace state. For Claude this updates
~/.claude.jsonand suppresses only the workspace trust dialog; tool permissions follow the auto-approve rule above. --harness-option KEY=VALUEexplicitly adds--KEY=VALUEto Claude Code. It is repeatable, stored with the Session, and reused byrestart. Options whose arguments are managed by dlgt are rejected. Codex does not currently accept Harness options.--startup-timeoutis optional and defaults to 60 seconds, but startup is never unbounded.- Session creation and acceptance of the first prompt are one atomic daemon operation; omitting it returns
INVALID_ARGUMENT. --stdinreads the exact prompt from standard input and is mutually exclusive with a prompt after--. It avoids argv disclosure and length limits.- Use
--stdinwhen the required prompt should not appear in argv. --waitrequires a prompt and an explicit positive--timeout.- If an exact requested alias is active,
newfails withALIAS_IN_USEand creates no Session or provider process. - If startup succeeds but prompt acceptance fails, dlgt terminates the Harness, releases the Alias, and returns one structured launch failure. A failed audit record may remain addressable by its Session ID, but no live half-created Session is returned.
Example:
dlgt new \
--title "prompting Claude worker" \
--harness claude \
--no-auto-approve \
--cwd .The unsafe full bypass remains available only when deliberately requested:
dlgt new \
--title "unrestricted Claude worker" \
--harness claude \
--harness-option dangerously-skip-permissions=true \
--cwd .dlgt new \
--title "run review" \
--profile fable-review \
--cwd . \
-- "Review the current design"{
"ok": true,
"session": {
"id": "codex:019f6307-341e-7e81-8a33-7ab61e804345",
"alias": "@run-review-361csx",
"title": "run review",
"harness": "codex",
"state": "busy"
},
"execution_seq": 1
}Synchronous first execution:
dlgt new \
--title "run review" \
--profile fable-review \
--wait \
--timeout 15m \
-- "Review the current design"The response contains the final result instead of exposing an execution ID.
restart
dlgt restart <SESSION_ID>
[--startup-timeout <DURATION>]
[--clean-env]
[--pass-env <KEY>]...
[--env <KEY=VALUE>]...
[--unset-env <KEY>]...
[--pretty]restart replaces a Session's provider process while preserving its alias, retained history, execution sequence, and provider conversation. Codex normally keeps the same Session ID. If Claude reports a rotated provider session ID, dlgt atomically rekeys the Session and returns the new canonical session.id.
Rules:
- Active
idle,busy, andblockedSessions may be restarted, as may terminalstoppedandfailedSessions. An active execution is durably completed asinterruptedbefore the replacement process starts. starting,stopping, andrestartingSessions reject a second lifecycle operation withSESSION_UNAVAILABLE.- The Session ID must contain a provider conversation ID.
- A terminal Session should be addressed by its provider-qualified Session ID because its alias may already belong to a newer active Session.
- If another active Session now owns the old alias, restart fails with
ALIAS_IN_USE; it never renames either Session implicitly. - Restarting an active Session keeps its alias reserved throughout the process replacement.
- Startup is bounded by
--startup-timeout, which defaults to 60 seconds. - Launch environment values are freshly supplied by the invoking client and are not retained for replay.
- Existing results, events, raw output, and scrollback remain readable; new executions continue the same monotonic
execution_seq.
send
dlgt send <SESSION_ID|@ALIAS>
[--wait --timeout <DURATION>]
[--pretty]
[--stdin | -- <PROMPT>] (required)Resume a provider conversation after its owning daemon exits with the same Session ID returned by new:
dlgt send <codex:PROVIDER_THREAD_ID|claude:PROVIDER_SESSION_ID> --resume
[--model <MODEL>]
[--effort <LEVEL>]
[--cwd <DIR>]
[--harness-option <KEY=VALUE>]...
[--no-auto-approve]
[--startup-timeout <DURATION>]
[--clean-env]
[--pass-env <KEY>]...
[--env <KEY=VALUE>]...
[--unset-env <KEY>]...
[--wait --timeout <DURATION>]
[--pretty]
[--stdin | -- <PROMPT>] (required)The launch options above are accepted only with --resume, and --harness is rejected because the provider prefix selects the Harness. A matching live Session is reused; if none exists dlgt reserves the provider conversation, launches a new Session, waits for bind/readiness, and atomically accepts the prompt. Success returns the same canonical session.id and caller correlation ID. If Claude rotates its provider session ID while resuming, success returns that new canonical ID. Plain send never launches and returns SESSION_NOT_RUNNING with a --resume hint when its target is not live.
Rules:
- The target Session must already exist.
- Launch, model, and environment options are accepted only with
--resume; Profile options are not accepted bysend. - The prompt is required.
--is recommended so prompt text beginning with an option-like token is never parsed as a CLI option; multiple remaining words are joined with spaces. --stdinis the mutually exclusive safe path for long or sensitive prompts.- If the Session is idle, dlgt accepts the prompt and transitions it to busy.
- If the Session is busy, canceling, blocked, stopping, stopped, or attached, the command fails immediately and has no side effects. Busy and canceling return
SESSION_BUSY; blocked returnsSESSION_BLOCKED; attached returnsSESSION_ATTACHED; other non-idle states returnSESSION_UNAVAILABLE. --waitrequires an explicit positive--timeout.- There is no
--create,--enqueue,--after, or--fail-if-busy; creation and queueing are notsendresponsibilities, and busy rejection is always the default.
Asynchronous example:
dlgt send codex:019f6307-341e-7e81-8a33-7ab61e804345 -- "Review the revised design"{"ok":true,"session":{"id":"codex:019f6307-341e-7e81-8a33-7ab61e804345","state":"busy"},"execution_seq":2}Busy rejection:
{"ok":false,"error":{"code":"SESSION_BUSY","session_id":"codex:019f6307-341e-7e81-8a33-7ab61e804345"}}Synchronous example:
dlgt send codex:019f6307-341e-7e81-8a33-7ab61e804345 \
--wait \
--timeout 15m \
-- "Review the revised design"{
"ok": true,
"session": {"id":"codex:019f6307-341e-7e81-8a33-7ab61e804345","state":"idle"},
"result": {
"execution_seq": 2,
"status": "completed",
"final_text": "Review result...",
"error": null,
"started_at_ms": 1784024104395,
"completed_at_ms": 1784024252019,
"usage": null
}
}Retained result
Every accepted execution receives a per-Session monotonic execution_seq. This number is returned by new or send, echoed by lifecycle events and the retained result, and never accepted as a CLI selector. It is correlation data, not a public execution resource or ID.
The retained result shape is:
{
"execution_seq": 2,
"status": "completed",
"final_text": "Review result...",
"error": null,
"started_at_ms": 1784024104395,
"completed_at_ms": 1784024252019,
"usage": null
}status is one of completed, failed, canceled, or interrupted. final_text is the Harness-reported final assistant message and is always a string for completed, although it may be empty. Failed terminal states may provide partial final text and must provide a structured error. Usage is nullable because availability differs by Harness.
wait
dlgt wait <SESSION_ID|@ALIAS> --timeout <DURATION>The timeout is required and positive.
wait binds to the Session's active execution and execution_seq at request time. If the Session is already idle and has a latest retained result, it returns that result. If the Session has never accepted work, it returns NO_RESULT.
Because a Session has one controller and no queue, the public contract does not need an addressable execution or Turn identifier. The non-addressable sequence number lets callers correlate acceptance and result without expanding the object model. Internally, dlgt may retain provider IDs to reject stale lifecycle events and retain bounded history correctly while the daemon lives.
A wait timeout returns WAIT_TIMEOUT and leaves the Session busy:
{
"ok": false,
"error": {
"code": "WAIT_TIMEOUT",
"session_id": "codex:019f6307-341e-7e81-8a33-7ab61e804345",
"session_state": "busy"
}
}If the Session transitions to blocked, wait returns immediately with SESSION_BLOCKED and exit 4. It does not wait for the timeout deadline.
cancel
dlgt cancel <SESSION_ID|@ALIAS> [--timeout <DURATION>]cancel interrupts the active provider execution. It does not stop the Session. A successful cancellation terminalizes the current result as canceled or interrupted according to the normalized provider mapping and returns the Session to idle only after provider quiescence is proven.
Cancellation is bounded and defaults to 30 seconds. On timeout, dlgt returns CANCEL_TIMEOUT, leaves the Session in canceling, and continues observing provider quiescence in the background. events and wait reveal the eventual terminal state.
Canceling an idle Session is idempotent: it returns exit 0 with {"canceled":false,"reason":"NO_ACTIVE_WORK"}.
Blocked input
Input required from a human is a first-class Session state, not a failure and not an infinite wait.
{
"ok": false,
"error": {
"code": "SESSION_BLOCKED",
"session_id": "codex:019f6307-341e-7e81-8a33-7ab61e804345",
"action": "dlgt attach codex:019f6307-341e-7e81-8a33-7ab61e804345"
}
}After a human attaches, answers, and detaches, the same wait command may be issued again. Provider-specific detection may initially be conservative, but dlgt must never infer completion from a quiet screen.
Session commands
dlgt list [--all] [--all-versions] [--pretty]
dlgt show <SESSION_ID|@ALIAS> [--pretty]
dlgt attach <SESSION_ID|@ALIAS> [--steal]
dlgt stop <SESSION_ID|@ALIAS> [--force]
dlgt restart <SESSION_ID> [environment options]listreturns active Sessions.list --allincludes terminal historical Sessions.list --all-versionsqueries every live versioned daemon socket and addsruntime_versionandruntime_socketto each Session.showreturns identity, Harness, model selection, state, current timing, latest retained result, and relevant failure data.attachtakes an exclusive input lease, replays the retained terminal view, and follows the live PTY. A second attach returnsALREADY_ATTACHEDunless--stealexplicitly transfers the lease. Detach withCtrl-b d. Mirrored multi-attach is outside v1.stoprequests graceful Session termination.stop --forceterminates the provider process group.
The daemon owns provider PTYs and the Codex app-server in separate process groups. A sibling reaper has its own process group, ignores ordinary terminal and service shutdown signals, and keeps provider groups registered until normal teardown. If the daemon exits without running destructors, control-pipe EOF makes the reaper terminate every remaining registered group.
Lifecycle events
dlgt events [<SESSION_ID|@ALIAS>] [--after <SEQ>] [--follow]Without --follow, the command returns a JSON array. With --follow, it emits one normalized NDJSON event per line until interrupted or the connection ends.
The versioned event schema, complete event set, and streaming boundary are defined in RPC.
Rendered scrollback and raw logs
Normal observation uses a headless VT emulator and plain-text scrollback:
dlgt scrollback <SESSION_ID|@ALIAS>
[--lines <COUNT>]
[--before <CURSOR>]The default is the latest 100 rendered lines. v1 retains at most 10,000 rendered rows per Session. The response includes the terminal dimensions, plain-text lines, truncation state, and an opaque cursor for older pages.
{
"ok": true,
"session_id": "codex:019f6307-341e-7e81-8a33-7ab61e804345",
"screen": {"rows":24,"cols":120},
"lines": ["Review complete.","","Main concerns:","1. Timeout behavior..."],
"truncated": true,
"before": "scr_84A2"
}Raw PTY bytes are explicitly diagnostic:
dlgt logs <SESSION_ID|@ALIAS> --raw
dlgt logs <SESSION_ID|@ALIAS> --raw --jsonPlain dlgt logs without --raw is invalid. --raw writes bytes directly; --raw --json returns base64. There is no logs --follow. Live lifecycle observation uses events --follow, and live terminal observation uses attach.
Requiring --raw is an intentional capability gate. See Design for the VT rendering and raw-retention rationale.
Model discovery
dlgt models --harness codex [--include-hidden]
dlgt models --harness claudeCodex discovery uses app-server model/list and returns account-aware model IDs, display names, descriptions, defaults, supported reasoning efforts, input modalities, and service tiers.
{
"ok": true,
"harness": "codex",
"source": "app-server",
"discovery": "complete",
"models": []
}Claude Code does not currently expose an equivalent documented non-interactive picker API. dlgt always returns the stable Claude Code aliases, then augments them with current canonical IDs from the public, daily refreshed claude-models-list snapshot. Date-pinned IDs ending in -YYYYMMDD are normalized to their undated alias, so claude-haiku-4-5-20251001 is reported as claude-haiku-4-5. The snapshot is account-scoped and is not presented as the Claude Code picker. If it cannot be fetched or validated, dlgt falls back to the aliases and reports discovery as partial.
{
"ok": true,
"harness": "claude",
"source": "https://raw.githubusercontent.com/combinatrix-ai/claude-models-list/main/models.json",
"discovery": "snapshot",
"models": [
{"id":"default","kind":"alias","recommended":true},
{"id":"best","kind":"alias"},
{"id":"sonnet","kind":"alias"},
{"id":"opus","kind":"alias"},
{"id":"haiku","kind":"alias"},
{"id":"claude-fable-5","display_name":"Claude Fable 5"}
]
}Model and effort are optional at launch. Omission selects the provider's recommended default. Profiles should prefer stable provider aliases unless an exact version pin is required.
For an exact canonical Claude model ID, dlgt validates --effort against the snapshot's capabilities.effort before starting Claude Code. Floating aliases such as default, opus, and sonnet remain provider-validated because their target model can change independently of the snapshot. If the snapshot is unavailable, launch validation fails open and Claude Code remains authoritative.
Model aliases are resolved by the Harness when new launches the Session, not on each send. dlgt does not silently pin a drifting alias; show reports the provider-resolved model when the Harness makes it available.
Profiles and launch environment
dlgt profiles list
dlgt profiles show <NAME>
dlgt harnesses [<HARNESS>]Profiles are client-side launch specifications. The client expands them before RPC so the daemon does not need to reread mutable configuration.
[profiles.fable-review]
harness = "claude"
model = "best"
effort = "high"
harness_options = ["permission-mode=auto"]
clean_env = true
pass_env = ["PATH", "HOME", "SSH_AUTH_SOCK"]Environment precedence:
client snapshot or clean base < Profile < explicit launch optionsProfile harness_options are followed by explicit --harness-option values. They configure the provider CLI rather than the launch environment.
- Default launch environment is a snapshot of the invoking client's environment, never the daemon's startup environment.
--clean-envstarts from a minimal runtime base.--pass-env KEYcopies one client value with--clean-env.--env KEY=VALUEsets or overrides a value.--unset-env KEYremoves a value.- Environment options apply when creating or restarting a Session. Values are freshly snapshotted for each process launch and are never stored for replay.
- dlgt applies final lifecycle safety overrides to owned Harness children:
check_for_update_on_startup=falsefor Codex andDISABLE_AUTOUPDATER=1for Claude. They cannot be overridden per Session and do not change provider global configuration. - Launch environment values are passed in RPC memory, never argv, and are never directly serialized into Session records,
list,show,events, Profiles, or error JSON. Provider output is untrusted and can deliberately echo its environment, so results, scrollback, and especiallylogs --rawmust be treated as potentially sensitive output rather than as a redaction boundary.
Exit statuses
0 command succeeded, or a waited execution completed
1 usage, configuration, identity, launch, or RPC error
2 waited execution failed, canceled, or was interrupted
3 bounded wait timeout; the underlying execution or cancellation continues
4 Session is blocked on input
5 Session is busy and rejected a sendThe JSON error code is the primary machine-readable reason. Exit status is the shell-level summary. SESSION_BLOCKED uses exit 4 and SESSION_BUSY uses exit 5. NO_RESULT, SESSION_ATTACHED, ALREADY_ATTACHED, ALIAS_IN_USE, SESSION_NOT_RUNNING, and SESSION_UNAVAILABLE use exit 1. WAIT_TIMEOUT and CANCEL_TIMEOUT use exit 3. A Session stopped during wait produces a retained interrupted result and exit 2. Idle cancel is an idempotent exit-0 no-op.
The stable v1 structured error-code families are:
INVALID_ARGUMENT Invocation cannot be retried unchanged
NOT_FOUND Session or configuration object does not exist
NO_RESULT Session has never accepted work
SESSION_NOT_RUNNING No live Session matches; retry with --resume
ALIAS_IN_USE Exact Alias belongs to a non-terminal Session
SESSION_BUSY Active execution; retry after it terminalizes
SESSION_BLOCKED Human input is required
SESSION_ATTACHED Exclusive attach lease prevents semantic send
SESSION_UNAVAILABLE Session state cannot accept the requested operation
ALREADY_ATTACHED Another client owns the attach lease
WAIT_TIMEOUT Wait expired; execution continues
CANCEL_TIMEOUT Cancel wait expired; cancellation continues
LAUNCH_FAILED Harness startup or initial prompt acceptance failed
PROVIDER_FAILED Provider terminalized work as failed
RPC_UNAVAILABLE Daemon transport is unavailable; retry may succeed
INTERNAL dlgt runtime invariant failureCommands may add contextual fields, but must not overload one code with a different retry or human-action policy.
If new fails before provider binding, its error includes the temporary launch_id for diagnostics and never presents it as a Session ID. A failure after binding includes the canonical session_id.
Design and RPC contracts
The provider lifecycle mapping, acceptance criteria, and design rationale are in Design. The public JSONL method set and schemas are in RPC.