Client shell
Source:
harness/docs/client/shell.mdStatus: current
Client shell
Section titled “Client shell”What a client shell owes the product — TUI, CLI chrome, remote, or headless — without claiming ownership of PEW/Work.
Day-to-day operator verbs: operate.md.
Input-face contract: ../canon/proposals.md.
Grain (Client loop): ../canon/grain.md.
Approval mechanism: ../runtime/approval.md.
Live tape: transcript.md.
Duties
Section titled “Duties”| Duty | Meaning |
|---|---|
| Evaluate named actions | Send JSON-RPC on the session socket (turn, turn/rewind, compact, eval, kv/set, kv/clear, policy/set, connect/adopt, connect/drop, connect/always-allow, connect/exclude-tool, config/auto-allow, turn/cancel, turn/retry). The daemon writes PEW/Expectation / Job bodies. The Client does not mint them. |
| Interactive approval | Interactive shells write durable approved / approvalComment on the pending ApprovableJob (approvalQueueHead), only on explicit cover confirmation. The cover shadows the composer draft. The eval client never writes approval fields. |
| Project work + awareness | Show authoritative Jobs and their outcomes, PEW/Expectations and their results, and in-motion awareness. A pure Job may be running without an open PEW/Expectation. The transcript is a projection of that graph. |
| Own paint and input | TTY, paint loop, layout, decode — the shell’s process boundary. Quickdraw mailbox is paint transport only. |
| Sandbox command half | Fast local pickers and projections (/connect, /model, slash). Commands are flat Arrival Scheme programs exec’d client-side; session effects are the same JSON-RPC actions. |
Boundaries
Section titled “Boundaries”| Shell may | Shell must not |
|---|---|
| Evaluate named actions as a document peer | Claim-own PEW/Work (PEW claim apply / settle as product path) |
Project the cover from approvalQueueHead (needsHuman) |
Mint nested “approval” PEW/Expectations or dual pending lists; have the eval client write approved |
Implement the interactive input face (ask writes approved on that leaf; turn/cancel; composer confirm) |
Settle cancel by writing PEW outcome from the shell |
| Hold mirrors for paint and speculative echo | Run a session phase machine as product truth |
| Dial the session socket (catalog / faces / commands are daemon projections) | Orchestrate Actors or treat /kernel as a product dial (/kernel is a lens) |
Interactive permission is a direct durable write. Cancel is turn/cancel (daemon
terminates in-flight PEW). Confirm is composer chrome. Durable PEW/WorkResult
lives on the PEW/Expectation the Actor seals; Job outcomes live on their owning
Jobs. Map: ../canon/proposals.md.
Unattended eval
Section titled “Unattended eval”harness -e FORM, harness ./program.scm, and harness -p TEXT --agent NAME
admit one ordinary SessionTurn containing a ProgrammaticJob. The client
submits source and an auto / yolo policy, observes that turn by its stable
identities, prints the program result, and can request daemon-owned
turn/cancel. --mode always-approve normalizes to yolo before RPC.
manual and unknown tokens fail admission (policy_unavailable) rather than
falling back. -e, -p, and a .scm file are mutually exclusive. -p
requires --agent and is (agent/send AGENT TEXT) on the same admission path.
Authorization stays in the daemon. The eval client never writes approved /
approvalComment, answers approvalQueueHead, or runs policy.yaml. Questions
/ forms that require a person become interaction_required through the ordinary
operation failure path. A classifier Indeterminate retries; it is not
interaction_required. Floor deny seals policy_denied.
Unattended behavior is persisted on the admitted turn (evalSpec). TTY presence
and later TUI attachment do not change the admitted policy. Combining --resume
/ --session / --continue with an eval input is a usage error. An attached
TUI may paint the cover on a faulted auto residual so a present human can beat
retries; the eval client still does not write the bit.
Interactive input face
Section titled “Interactive input face”runningTurn = session.findRunningTurn()openWork = live Jobs and PEW/Expectations in the displayed execution scopeparked = approvalQueueHead (needsHuman) | nullSHADOW, DON’T REPLACE. Ask chrome is a second face over the composer, not a
mode of the base draft. Tea.draft stays the prompt. The cover holds a
process-local comment buffer. Confirm chords write approved /
approvalComment on approvalQueueHead and dismiss. The cover does not assign
those fields as the operator types. The cover is not a #callback overlay. The
pending change itself paints on the ask pin (Diff when one exists; identity
only when the rim cannot name the work), between the transcript viewport and the
composer — not as a tool sexp in the composer placeholder. Cover dismiss does
not take a mutate Diff pin with it: the suggestion stays until the sealed Diff
is a tape row. MCP options and shell commands ride the composer stack
(same T-join as slash). Spawn does not cover — AgentCallJob /
AgentMessageJob are COMM FIRST. The omnibox rim names edit / create /
observe / MCP / run command (coverAskAnnotation). No Diff and a named
rim → no pin. MCP and shell never mount a pin.
VISIBLE ≠ ARMED. An ask that mounts while the operator is typing must not
treat the next printable, Enter, Space, or a letter (y / n / a) as
consent. Cover chrome may paint over a nonempty draft; rendering it is not
consent and is not a mode of the draft. Confirm chords write approved /
approvalComment only while the cover is armed. Arming: empty draft at
mount, or the first-ask throttle elapsing over a nonempty draft. Focus does not
arm or unarm. Time-since-last-key does not arm and is not “done composing.”
Never single-letter grants. While the cover is visible and not armed, the draft
/ slash / queue still own printables; confirm chords are reserved and inert.
FIRST-ASK THROTTLE. When the cover arms for a first ask (no pending leaf
→ a pending leaf) and the base draft is nonempty, confirm chords stay inert so
the operator sees the face change. Empty draft: no throttle. Subsequent heads
while the cover stays up do not re-arm. Cover down then up is a new first
request. TUI: KernelRoot.approvalAskThrottleMs (config.json, default 2000,
0 = off). ../packages/harness/src/tui/tea.ts.
#awaitCallback save / zero / restore is the Question / harness/confirm path.
Reusing it for UAC zeros the prompt, paints the ask as the composer body, and
mixes cover keystrokes into the restore.
| Face | Empty-draft law |
|---|---|
| No running turn, open work, or pending input | Idle. Empty ⏎ on a closed interactive head that needsContinue (failed/cancelled/crash between rounds) fires turn { prompt: "", kind: "continue" }. Successful or no head: empty ⏎ still no-op. Persist-mod ⏎ does not continue. |
approvalQueueHead (approved === null) |
Cover up. Confirm chords write only while armed. Empty draft at mount arms immediately; nonempty draft waits approvalAskThrottleMs. Empty armed cover: ⏎ → approved = true; Esc → false on that leaf. Extra-root read with a suggested parent: persist-mod ⏎ Allow that folder. Auto remainder ask of a grantable cluster: persist-mod ⏎ this session in auto; persist-mod ⇧ ⏎ Always in auto. MCP look-up (read floor, not auto-cluster persist): persist-mod ⏎ Allow this session; persist-mod ⇧ ⏎ Always allow. MCP cousin (fails the floor): persist-mod Esc Deny this session; persist-mod ⇧ Esc Deny forever. Persist modifier is config.json superKey (default ctrl; cmd is ⌘). Super is not inferred from the OS. Nonempty armed cover: persist-mod ⏎ / persist-mod Esc write approvalComment. Persist and folder-widen do not write from a comment. Shift+Enter is a newline. Machine allow-side does not raise the cover — it is a chip on the tool row. |
| Running turn or open work, no ask, no Question, no slash | Nonempty draft: ⏎ queues (turn kind: "queue"). Empty draft + nonempty promptQueue: ⏎ steers first. Empty + empty queue: Enter no-op. Empty Esc arms, then cancels (turn/cancel { turnId }). Nonempty Esc is still draft-clear (same arm bit). Live completion retryUntilMs in the future: persist-mod ⏎ retries now (turn/retry); ask persist still wins. The admission deck paints in the composer extender unless slash autocomplete or a confirm stack owns it. Slash catalog, /compact, /rename, and other command surfaces keep their own Enter/Esc. |
| Printables, no cover | Always the base draft |
Cover is approvalQueueHead (needsHuman). A faulted auto remainder may raise
it even on an eval turn so a present human can beat retries. The eval client
does not write the bit. Operator keys: operate.md.
Process split (this monorepo)
Section titled “Process split (this monorepo)”| Piece | Seat | Owns |
|---|---|---|
| Protocol + models | @inhuman.tools/harness-sdk |
Plexus images, rooms, JSON-RPC/UI schemas, generic Client. Browser: ./peer (no node:, no unix). Paint stays in each shell. |
| Daemon — kernel + session planes | @inhuman.tools/harness-server |
Public: ensure/stop and the process log the TUI joins. Internals (reflector, actors, squeeze, drivers) stay in-package. Unix daemon.sock only. |
| Client shell — TTY, paint, input | @inhuman.tools/harness src/tui + src/eval |
TUI: structure + boxes, command sandbox. Eval client: submission, print-once watch, result, and cancellation request; no approval management. |
| Browser shell | @inhuman.tools/harness-web-app |
Loopback origin + unix upgrade proxy. Vite SPA dials the same host. |
Implementer depth (flat programs, interpreter-owned waits):
../runtime/control-effects.md.
See also
Section titled “See also”| Doc | Role |
|---|---|
operate.md |
Operator how-to |
transcript.md |
Live tape |
../canon/grain.md |
Client loop, causal spine |
../canon/proposals.md |
Cancel / Permission / confirm crossings |
../runtime/control-effects.md |
Flat execution law the command sandbox runs under |