Status: Implemented (2026-03-10)
This spec defines the implemented shell-specific UX/RPC profile over the common task orchestration substrate while preserving the current "feels synchronous" UX by default.
Canonical naming/lifecycle rules now live in dev-docs/specs/task-orchestration.md. Historical job wording in older drafts should be read as task.
Implemented today:
- runtime shell-task lifecycle and compatibility RPCs (
shell.start/list/status/output/wait/detach/cancel) - task-backed agent shell follow-up tools (
shell_list,shell_status,shell_logs,shell_wait,shell_result,shell_cancel,shell_stdin_write) - TUI wait-mode bang execution via
shell.start + shell.waitwhen supported - in-flight detach via
Ctrl+Bwhensupports_shell_detachis advertised /taskslist/show/cancel over retained task metadata
Legacy bang shell (!cmd) blocked until shell.exec returned.
For long-running commands, users need to keep working without losing command output.
Implemented UX goals:
- Shell execution should be internally task-based (asynchronous).
- Default behavior can still feel synchronous (wait for completion).
- User can switch active wait to background while running (for example
Ctrl+B). - Output must remain retrievable (tail/read/cached full output).
- Task model for shell execution.
- Runtime APIs for shell task lifecycle (
start/list/status/cancel/output). - TUI/agent UX for:
- wait mode (current behavior equivalent)
- task-backed background mode where the caller does not stay attached
- in-flight detach (
Ctrl+B) while waiting.
- Compatibility plan with existing
shell.execand deferred<shell_result>injection.
- Full interactive PTY attach session.
- Raw-byte/base64 stdin, terminal resize/control, or a Core-to-UI stdin RPC.
- Arbitrary job dependency graph or scheduling policy.
- Cross-device distributed job execution.
Current shipped surfaces expose two relevant modes:
- Wait mode (default bang flow): TUI starts a shell task and waits until terminal status.
- User experience is similar to the old synchronous
shell.execflow.
- User experience is similar to the old synchronous
- Detached-wait mode: the caller starts a shell task and does not stay attached.
- The agent-facing
shelltool exposes this asdetached_wait=true. - In TUI bang flow, the implemented user path is in-flight detach (
Ctrl+B) rather than a separate "start detached" command. - This detaches the wait only; the runtime still owns the child process, so it is not a persistence/daemonization mechanism.
- The agent-facing
When TUI is waiting on a shell task:
Ctrl+Bdetaches UI wait from that task.- Task continues in runtime.
- TUI returns to normal composer input.
- TUI logs
Detached shell task <task_id> (running in background).
TUI currently exposes this minimal command surface:
/tasks(summary list)/tasks show <task_id>(status + preview)/tasks cancel <task_id>
The runtime/agent shell surfaces already support output retrieval (shell.output, shell_logs, cache-backed results). A dedicated TUI /tasks tail <task_id> command can remain follow-up polish if needed.
The agent-facing shell tool defaults to stdin_mode="closed", preserving
immediate EOF. stdin_mode="pipe" is valid only with detached_wait=true.
The originating agent session can then call shell_stdin_write with the task
key, UTF-8 text, optional append_newline, and optional close.
Each encoded write is capped at 64 KiB and serialized per task. Completion is
reported only after the writable callback; stalled backpressure times out after
30 seconds and invalidates stdin. Writes require a live handle owned by the
current runtime and, when recorded, the same parent_session_id. Output and
completion remain separate through shell_logs and shell_wait.
This surface is a pipe, not a PTY. It is agent-facing only and does not change
the TUI shell.* RPC contract.
Runtime treats every shell run as a task with stable identity. Persistent services that must survive runtime exit are out of scope for this task model and should use explicit shell-native out-of-process techniques instead.
Task states:
queued(optional)runningcompletedfailedcancelled
Task record (minimum):
task_idcommandcwdstarted_atended_at?exit_code?signal?stdout_cache_id?stderr_cache_id?- truncation metadata
Output policy:
- small output inline in status/result using stream-named
stdout/stderrfields - successful terminal shell results may suppress
stderrby default to stay compact; explicit stream reads useshell_logs - large output persisted in tool-output cache, retrieved by reference
Runtime/TUI compatibility uses these shell.* RPC methods (UI -> Runtime):
shell.start- starts a shell task, returns
task_idimmediately - accepts timeout values above 300 seconds; if
timeout_secondsis omitted, the shell task runs until completion, cancellation, or runtime exit
- starts a shell task, returns
shell.list- returns recent shell tasks and state summary
shell.status- returns single-task detail
shell.output- paged/tail output retrieval
shell.cancel- cancel running shell task
shell.wait- wait for terminal status with timeout semantics
wait_timeout_secondsdefault 120, max 300- if the wait window expires first, return current task info with
still_running: true
shell.detach- detach active wait without cancelling the underlying shell task
Extend server capabilities:
supports_shell_tasks?: booleansupports_shell_detach?: boolean
Current shell.exec is implemented and used by bang flow.
Compatibility policy:
- Keep
shell.execin phase 1 for older clients. - Runtime may implement
shell.execas wrapper behavior over the shell task substrate. - New TUI path should prefer shell task APIs (
shell.start+ optionalshell.wait). - The existing
shell.execconfirm bypass remains limited to the UI-origin bang path (origin=ui_bang) and must not leak into agent-originated shell tasks built on the same substrate.
Deferred injection rules stay compatible:
- If user waits and command completes before next prompt, enqueue normal
<shell_result>. - If detached/background shell task completes later, user can:
- explicitly inject selected task result, or
- use a helper command that appends the latest completed task summary.
Initial recommendation: explicit injection to avoid accidental context pollution.
The shipped TUI keeps equivalent state for an active shell wait, pending deferred shell results, and recent task interactions.
Key handling:
Ctrl+Bis active only when an attached shell wait exists and the runtime advertisessupports_shell_detach; it issuesshell.detach { task_id }.- Outside shell wait, keep existing key behavior unchanged.
- Shell task execution reuses existing shell runner and sandbox constraints.
- Cancellation maps to process group termination where supported.
- Persist
executor_pid/executor_pgidso crash recovery can terminate orphaned shell tasks. - Task metadata retention should be bounded (LRU/time window) to avoid unbounded memory growth.
- Long output should always be recoverable via cache IDs while respecting size limits.
- Opt-in piped stdin is runtime-local and never persisted. Cancellation, timeout, process exit, runtime shutdown, backpressure timeout, and explicit close invalidate the writable handle idempotently.
shell.startreturnstask_id, state transitions torunning.- completion path stores status/output metadata plus persisted executor identifiers needed for recovery.
shell.canceltransitions running shell task tocancelled.shell.outputpaged/tail retrieval works with cache-backed output.shell.detachdetaches wait without cancelling the underlying shell task.- agent
shell_stdin_writeenforces pipe opt-in, per-call size, write ordering, close, live-runtime ownership, and originating-session ownership.
- wait mode behaves equivalent to legacy synchronous UX.
Ctrl+Bwhile waiting detaches and restores composer usability.- detached shell task remains visible in
/tasks, can be inspected/cancelled from TUI, and remains retrievable through shell/task output surfaces.
- legacy
shell.execcallers still function. - mixed old/new capability negotiation degrades safely.
- Runtime ships the common task substrate plus shell-specific
shell.*compatibility APIs behind capability flags. - TUI wait mode uses shell tasks when
supports_shell_tasksis available. Ctrl+Bperforms in-flight detach viashell.detachwhensupports_shell_detachis available.- Legacy
shell.execremains for older clients and compatibility paths.
dev-docs/specs/task-orchestration.md(canonical substrate/lifecycle/naming)dev-docs/specs/tui-bang-shell-mode.mddev-docs/specs/ui-protocol.md