Transcript
Source:
harness/docs/client/transcript.md· Status: current. Staged from the implemented Harness transcript documentation.
Transcript
Section titled “Transcript”The live TUI transcript is a watch surface over PEW work: it projects identity, three-state liveness, and ask visibility. It does not mint Work, license BODY, or become the archive.
Not this file: Enter/Esc chrome on approved (shell.md,
operate.md, mechanism ../runtime/approval.md);
paint DOM replica / Quickdraw grain
(../packages/quickdraw/docs/grain.md).
Constructors live in packages/harness/src/tui/ (identity.ts, liveness.ts,
work-cells.ts, tape-spans.ts, tea.ts). Quickdraw widgets have no agent
knowledge. Paint never gates the agent loop.
One sentence
Section titled “One sentence”The live tape names the current object, shows whether the turn is running, waiting for you, or idle, and pins the ask so a later cell cannot cover it.
Vocabulary
Section titled “Vocabulary”| Speech | Meaning |
|---|---|
| Watch | Live job. Identity of the current object. Viewport is follow-tail. |
| Audit | After job. JSONL / session document + git. Not the live tape’s job. |
| Work cell | One layout object from one ToolCallJob or ProgrammaticJob (plus inner invocations). Not a chat bubble. |
| Identity row | Collapsed one-liner that names the object: path, command, pattern, expr. Never "Read 1 file". No duration. No +N −M — the Diff is the change. |
| Glance liveness | turnLiveness: running | waiting-for-you | idle. Composer #busy is the admitting RPC, not this. |
| Cell liveness | toolCellState / schemeCellState. Independent of glance, except sealed speech never holds the loop spinner. |
| Ask | approvalQueueHead with approved === null (needsHuman). Durable on the Approvable leaf. Scheme-repl is a sequence, not Approvable. |
| Loop token | ReACTChunk / TurnPromptChunk / TurnCompactChunk. Continuation fuel. Not a tape row. |
| Ask pin | Layout sibling outside the viewport, above the composer. Projection of askPaintHead. Visibility only. No key binding. |
| Follow-tail | Present PaintLocal.tapeAtBottom. Viewport is overflowAnchor: "none". At bottom, present passes a scroll the engine clamps to maxScroll. Any upward move freezes. |
| Receipt | Expanded body of a result that exists. A card of intent without a result is not drawn. |
What paints
Section titled “What paints”Walk reactChunks(turn) plus ProgrammaticJob.invocations as sibling parts.
Never walkWork — that list includes judges. Side-channel tools
(update_metadata, context_need) are skipped. Compact density keeps
every floor-allow call, so the question identity is on the tape while
the job is still open. config.json hideFloorTools (default off)
restores that omit. Comm-first (agent) stays omitted. Remainder
(ask / deny / block, judge, human approved) never fuses.
| Kind | Paint? |
|---|---|
| User prompt | yes |
CompletionExpectation thought / speech |
yes when there is a body (ThinkingStream / Markdown). Live thought with text: thinking spinner on that row. Empty live thought is not a tape row. Sealed speech: void gutter — never a loop spinner after seal. |
ToolCallJob |
yes. Folded identity disclosure. edit_file / write_file are open Diff blocks, not disclosures. |
ProgrammaticJob |
yes. Folded identity of scheme_repl + sugarcoat expr(). Inners are sibling rows, not disclosure.children. |
ReACTChunk / TurnPromptChunk / TurnCompactChunk |
no row. Feed turnLiveness / emptyWaitChrome. |
| Judge completions | leftover intern PEW on the leaf. Thought/speech paint when they have a body. Empty live thought is occupancy (wait:pin unless a remainder tool already holds the spinner). Not walkWork. |
createAskTapeCut skips askPaintHead and every later tool. The head is
the pin, not a folded ? row. A scheme that contains the head still paints the
bag; a scheme after the cut is omitted whole. After a human answers a mutate
Diff, that job stays askPaintHead until outcomeJson or completed — the
suggestion does not drop before the sealed Diff is a tape row. A later
needsHuman leaf wins. Cover chrome still follows approvalQueueHead.
if turn == null || turn.closed: idleif approvalQueueHead != null: waiting-for-youif hasOpenWork || hasQueuedContinuation: runningelse: idlerunning does not require workerPresent. Minted Work waiting for an Actor is
still running.
Heartbeat sits on tape-visible live work: thought with a body → thinking on
that row; live tool → tool spinner on the question identity;
live scheme → tool spinner only while no inner is live/ask (open inner owns
the heartbeat; the bag paints a sealed ·). A floor row omitted by
hideFloorTools is not tape-visible, so it does not swallow the pin.
When there is no live work to name — empty completion, ReACT / compact gap,
hidden floor call — occupancy is wait:pin immediately above the omnibox
(waiting for response… / bootstrapping… / retry countdown), not a
transcript row. Ask (waiting-for-you) owns the slot.
Idle: no spinner.
Cell state
Section titled “Cell state”toolCellState: "ask" | "live" | "ok" | "fail"schemeCellState: "live" | "ok" | "fail"needsHuman → ask. Open, no outcome → live. outcomeJson.ok === false →
fail. Else ok. Human deny and BODY error both paint fail today — there
is no shipped denied / cancelled cell state. Fail identity stays voice at
rising prominence; the gutter is the error mark (error role), not a red row.
Live identity stays voice at rising prominence; the spinner is the flux mark.
Sigils: user ❯; sealed tool ·; sealed shell $; ask ? (pin, not a
spinner). Expand keeps that identity mark on the first row; extra body
(receipt, script, thought markdown, live thought peek) is a dim ┃ rule.
Sealed collapsed thought does not get the bar. Fail is that bar at
error + ghost, not a red row. License chips read
policyResult / judgeResult while approved is still null; floor
deny / block are not tape chips. Shell omits the chip.
Identity
Section titled “Identity”Parse argsJson. Never invent a file count. Path-shaped objects keep the
filename (tailEllipsis). Commands / expr / patterns keep the start.
name |
Object | Notes |
|---|---|---|
read_file / list_dir |
path |
|
glob / fff_search |
pattern / query, plus cwd when set |
cwd wins; path is the ingest alias |
grep |
pattern, plus path when set |
|
edit_file / write_file |
path |
Open Diff. Optional caption to the right of the path (wraps when tight). No +N −M. |
shell |
command |
Gutter $. Status: cwd token (omit .). No exit N — fail is the gutter. No license chip. |
scheme_repl |
sugarcoat expr() |
Bag is not Approvable. |
agent__send / agent__ask |
spec name | Dim status from kernel glance (turnDescription, else active / blocked). Expand: unquoted wrapped prompt, then model. Scheme inners start open. |
| MCP / pack | dim server, bright action — or pretty-mcp telegram | Args ride the composer stack at ask, not the row. |
| fallback | invocation() or truncated argsJson |
still includes name |
Disclosure XOR: folded shows summary only; open shows children only.
children re-include identity, or expand hides the path. Fold is present-side
toggle_fold, not a Work field.
Caption
Section titled “Caption”Optional model-authored description on shell and scheme_repl (and on
edit_file / write_file as a path-right token). Wire name is description;
tape field is caption. Missing, empty, whitespace-only, or non-string ≡ omit.
The call still runs. Never required.
Paint flattens whitespace, trims, and clamps to 80 Unicode code points. Collapsed
shell / scheme identity is the caption XOR the command / expr. Expand always
shows the script. Ask stack still shows the command — consent is not the
collapsed row. Caption never feeds policy, hatch, judge-cache, peel-cwd, spawn,
or outcomeJson. Do not scrape a leading # in command / expr.
Receipts
Section titled “Receipts”Cards are receipts of results, never of intent. Live cells have no result card
except a shell ShadowStream tail when tool-tails has a frame for that job.
edit_file / write_file ask Diff sits on the pin (hideHeader; the rim
already names the file). Sealed mutations stay open filename + Diff. Empty
old_string/new_string or empty content is not a Diff. write_file create
is empty-old; overwrite uses host priorContent. edit_file applies the
span to that prior so both sides are the file. A span-only replica still
paints file-absolute numbers via host priorLineStart (Diff lineStart).
Paint keeps change-adjacent lines (3 of context) and folds the rest. Expand of
other tools is Text / Markdown of the observation — no tree, no grep list,
no public Code(). Sealed shell stdout/stderr is a square ┌┐└┘ box (not
omnibox ╭╮). The command is shfmt + bash highlight on expand, wrapping at
the column (last symbol, highlight kept).
Ask pin
Section titled “Ask pin”Two projections of the same Work:
- Tape cutoff —
askPaintHeadand everything after it are not tape rows. - Pin / Diff — identity pin (
ask:pin) sits between the viewport and the composer, outside the viewport. Occupancy wait (wait:pin) sits immediately above the omnibox when the tape has no live progress; ask owns that slot (waiting-for-you). Edit/create Diff is the viewport’s last child (ask:diff) and squeezes when the clip cannot hold it. Rim-named work omits the identity line. No Diff and a named rim → nothing extra (askPinWorthPainting). MCP and shell never mount a pin: options / command ride the composer stack. Spawn does not cover. No Allow/Deny buttons. No keys. Cover owns Enter/Esc.
askPaintHead is approvalQueueHead when one exists. After a human answers a
mutate Diff (edit_file / write_file with a real Diff), the same job stays
the paint head until BODY writes a result. Cover Allow/Deny does not wait —
needsHuman is still the cover predicate. A later needsHuman leaf wins the
pin. Question overlays (#callback) are composer-owned; they are not UAC;
glance stays running; the pin stays off.
Follow-tail
Section titled “Follow-tail”Product viewport is overflowAnchor: "none". Present owns tapeAtBottom /
tapeScroll. At bottom, the frame passes a scroll the engine clamps to
maxScroll. Wheel over the tape and PageUp/PageDown write the present offset.
Picker wheel stays picker. Not overflowAnchor: "auto" — that path ignores
scroll every frame (force-pin). The ask pin is ask visibility while following;
it is not a frozen running mark.
Rejected alternatives
Section titled “Rejected alternatives”These will be re-proposed. The failure is in the name.
| Alternative | Failure |
|---|---|
| Pretty Diff / grep / code / tree widgets as the default live tape | Identity lost behind chrome. Diff is for mutations and the ask pin. |
| Spinner on last assistant speech for the whole ReACT loop | Sealed speech looks live while tools run. |
Composer #busy as loop liveness |
#busy is the admitting RPC. It goes false while findRunningTurn() is live. |
| Waiting-for-you painted as a thinking gerund | Permission wait looks like thinking. |
overflowAnchor: "auto" as follow-tail |
Engine force-pins every frame. Freeze on upward move dies. |
Count-only collapse (Read 1 file, Ran 3 commands) |
Operators wanted less body; vendors removed the name. |
Disclosure children = body only |
Expand hides the path (XOR). |
Distinct denied / cancelled cell states |
toolCellState maps every ok: false to fail. Do not claim a deny paint that is not in code. |
| Caption as policy / hatch / classifier evidence | A pretty sentence must not lift a deny. |
| Caption replacing the command with no expand path | Collapse hid the identifier. XOR keeps argv on expand and on ask. |
| Duration on identity rows | Spinner is the live mark. elapsedLive is the loop pulse / thought cluster, not the object name. |
| Paint ack / “continue when painted” | Collapsed panels must not throttle BODY. |
| Bind Enter on the pin to allow | Cover owns the write. The pin is visibility. |
walkWork for tape parts |
Leaks judges onto the ReACT watch surface. |
TUI as SSoT / frozen Ctrl+O dump |
Archive is the session document. Fold is watch UX, not redaction. |
Public Quickdraw ToolCall() / Code() |
Agent knowledge would leak into QD. Syntax stays internals. |
| Mutation-ledger pane | Identity rows are the projection. No second PEW type. |
Drop the Diff pin when approved is written |
The change vanishes, then the sealed Diff remounts; a late pin + tape Diff doubles the block. |
See also
Section titled “See also”| Doc | Role |
|---|---|
operate.md |
Operator keys |
shell.md |
Client duties, empty-draft law |
../canon/grain.md |
Causal spine |
../runtime/approval.md |
LICENSE WHEN |
../runtime/session-job.md |
Session / Job graph |
../packages/quickdraw/docs/grain.md |
Paint substrate |