kolu — drive one agent from another through its terminal
You can run a coding agent (Claude Code, Codex, opencode) inside a kolu-owned
PTY and steer it from the outside: type a prompt, submit it, watch the screen
until it's done, read what it said, type the next prompt.
Reach for the kolu MCP tools first (mcp__kolu__* — served by kolu mcp,
wired in this repo's .mcp.json; other hosts: claude mcp add kolu -- kolu mcp). They speak padi's live surface directly — structured results, typed
refusals, no shell quoting hazards. Fall back to the kaval-tui / padi-tui
CLIs (the final section) when no kolu MCP is connected — a host without the
server configured, a machine with no padi running, or a RAW standalone-kaval
terminal padi doesn't track. The verbs map one-to-one, and every discipline
below (the three-step submit, the done-signal, the fold hazard) applies to both
faces because they drive the same daemon.
The loop (MCP)
lifecycle_create { intent: "🔧 parser refactor", cwd: "/abs/repo" } → { id }
lifecycle_sendInput{ id, text: "refactor the parser to use a lexer" } # 1. the text (no Enter)
wait_outputSettled { id, idleMs: 300, timeoutMs: 15000 } # 2. observe the TUI settle
lifecycle_sendInput{ id, key: "Enter" } # 3. submit (its own call)
wait_outputSettled { id, idleMs: 800, timeoutMs: 600000 } # 4. let its turn finish
screen_text { id, tail: 40 } # 5. read the screen
lifecycle_create spawns a canvas tile (a real padi-tracked terminal —
agent sensors, dock state, the works). intent labels the tile so a human
watching the canvas knows whose it is; cwd sets the working directory;
parentId: <your-or-another-terminal-id> opens it as a SPLIT beside that
tile (the sibling-reviewer layout /agent-debate uses). It spawns a shell —
launch the agent by sending its command line (claude --dangerously-skip-permissions etc.) through the three-step submit below.
lifecycle_sendInput writes text OR one named key, never both —
{ text, key } in one call is a hard, typed error (see the submit
discipline). The key vocabulary: Enter, Escape, Tab,
Up/Down/Left/Right, Home, End, Backspace, Space,
Shift-Tab, and C-<char> / M-<char> chords. An unknown key name is a
loud error, never a silent no-op. Multiline text goes as one bracketed
paste automatically.
wait_outputSettled is the agent-agnostic done-signal (below);
wait_agentState the precise one. Both return a uniform result frame —
{ result: "met", met: { …detail } } or
{ result: "timeout" | "gone" | "closed", elapsedMs?, error? } — read
result, never guess from silence; the met detail (fired/elapsedMs, or
agent/elapsedMs) nests under met.
screen_text { tail: N } reads the last N content lines (trailing
blank viewport rows are stripped) — the cheap "what's on screen now" read.
Omit tail for the whole scrollback; screen_history pages older
scrollback above it.
lifecycle_kill { id } ends a terminal. Kill exactly the terminals you
created, when you're done with them.
- The
terminals resource (surface://collections/terminals) is the live
roster — the key set; each id's full record (intent, cwd, git, agent kind +
state, parentId, foreground process) reads at
surface://collections/terminals/<id>. surface://cells/urgency lists
the terminals whose agent awaits a human right now.
Interrupt a runaway before redirecting it:
lifecycle_sendInput { id, key: "Escape" } (stop Claude Code mid-stream),
{ key: "C-c" } (SIGINT whatever's running).
The three-step submit — text · settle · Enter
Submitting a prompt to a TUI agent is three calls, because sendInput
bakes in no timing magic — it writes exactly what you pass:
lifecycle_sendInput{ id, text: "fix the failing test in parser.ts" }
wait_outputSettled { id, idleMs: 300, timeoutMs: 15000 }
lifecycle_sendInput{ id, key: "Enter" }
Why not one call? An Enter sent in the same breath as the text races Claude
Code's bracketed-paste / debounced input handling and is silently dropped,
leaving the prompt staged on the ❯ line while the send reports success (if a
turn never seems to start, this is the #1 cause — screen_text and look for
the prompt sitting unsent). The daemon cannot observe when the TUI
settled, so any fixed grace baked into the send is a race. The honest fix is
step 2: you observe the settle, then submit as its own call. { text, key } in one call is a typed hard error for exactly this reason — the
trap is unspellable, not merely discouraged.
⚠️ MULTI-LINE pastes don't submit — a known-open limitation (#1702).
The three-step flow is verified for a short prompt (a line or two). Claude
Code folds a paste into a [Pasted text +N lines] placeholder once it spans
more than a handful of lines (observed at ~a dozen lines, well under 1 KB), and
a folded paste does NOT reliably submit — idle firing proves the fold
finished, not that Enter submits it. Workaround for any multi-line message
(a brief, a report, a ruling): write it to a file and send a short prompt
pointing the agent at it — { text: "read /tmp/brief.md and carry it out" } —
then the three-step submit. Reach for the file-pointer the moment a message
runs past a couple of lines.
Step 2 fires cleanly only when the agent is AT THE PROMPT (the normal
case: idleMs: 300 fires in under a second). Messaging an agent that is
mid-turn and busy (streaming continuously), the idle window never
opens — so the timeoutMs fires instead. Treat result: "timeout" there as
"target busy": send the Enter anyway (it lands in the input buffer and
submits when the turn ends), then screen_text to confirm. A
result: "gone" is real (the terminal exited — the agent you were driving
died); surface it, don't send into the void.
The done-signals
After you submit, you need to know when the turn ends. Two tools, reading
different things:
wait_outputSettled { id, idleMs, timeoutMs } keys on raw output
quiescence — resolves met once no output byte has arrived for idleMs.
Agent-agnostic (bytes, not rendering): works identically for
claude/codex/grok/opencode. 800 is a good default; raise it for an
agent that pauses mid-thought. It can't tell "finished" from "blocked asking
you" — both are quiescence — so screen_text and read before responding.
wait_agentState { id, until: [...], timeoutMs } keys on padi's
detected agent state — precise buckets: working (thinking/tool_use),
awaiting (asking you a question), waiting (the just-finished
post-turn lull). awaiting/waiting both mean "your move". It needs a
terminal padi tracks (every lifecycle_create terminal is); a bare shell
with no agent running never enters a bucket — bound it with timeoutMs.
Always pass timeoutMs so a wedged agent fails loud (result: "timeout")
instead of hanging the loop — and keep it under your own harness's per-call
timeout (most MCP hosts cap a tool call at ~1–2 minutes by default; a longer
wait gets your call killed while the driven agent is fine). For a long turn,
poll in bounded slices: repeat wait_outputSettled { idleMs: 800, timeoutMs: 60000 } and treat each timeout as "still busy, wait again".
Mind the stale-state race — wait in two phases. wait_agentState
matches the state the instant it connects, replaying whatever it is right
now. Right after a submit, the agent may still report the previous turn's
waiting for a beat — a lone until: ["awaiting","waiting"] returns
immediately on that stale state. For a robust loop: first
until: ["working"] (it picked up the prompt), then
until: ["awaiting","waiting"] (its turn ended).
Provisioning the inner agent
- A split tile beside a terminal:
lifecycle_create { parentId: <id> } —
the record carries parentId and the canvas renders it as a sibling split.
- A worktree'd agent (fresh git worktree as the terminal's cwd) has no
MCP path in v1 —
git.worktreeCreate is a named denial. Provision with
the CLI: padi-tui create --repo /abs/repo --worktree my-branch -- claude --dangerously-skip-permissions (fast-forward the base repo first — the
worktree is cut from the checkout as it stands), then drive the returned id
over MCP as usual.
- Never hardcode the agent CLI. The inner agent defaults to the same agent
you are running as (a Claude Code orchestrator spawns
claude, a codex
one codex) — unless the human named a different agent, which wins.
create returning ≠ the agent is ready. screen_text before
dispatching: a first-run agent may sit on a one-time dialog (a trust prompt,
MCP selection) that needs its own { key: "Enter" }. Drive every boot step
by reading the screen, never by sleeping and hoping.
- Set the permission mode AT LAUNCH, then verify it from the footer. An
unattended agent launches with bypass permissions (
claude --dangerously-skip-permissions); read the footer via screen_text and
confirm before dispatching.
- Restarting the agent CLI in place: text typed at a running agent
becomes a prompt — send its quit command (
/exit in Claude Code) as its own
three-step submit, confirm the shell prompt in screen_text, then launch
again.
A terminal id is not stable across a kaval restart. kaval re-keys every
terminal when it restarts, so a cached id can go stale mid-run (a send to it
fails "not found"). Re-find the terminal through the terminals resource —
read each id's record and match on the stable intent label (set it at
create for exactly this) — then use its current id.
Fallback — the kaval-tui / padi-tui CLIs (no MCP connected)
When no kolu MCP server is available — the host has none configured, an older
kolu without the MCP face, a non-MCP agent runtime, or a raw standalone
kaval terminal padi doesn't track — the same loop runs on the CLIs. The
full CLI treatment lives in TUI.md (the three-step submit in CLI
form, --file, the done-signal exit codes, daemon discovery and the
$KAVAL_SOCKET/$KAVAL_TERMINAL_ID self-knowledge vars, worktree
provisioning, the old-daemon polling fallback). The verb map:
| MCP |
CLI |
lifecycle_create |
padi-tui create [--parent <id>] [--repo … --worktree …] -- <agent> (canvas tile) · kaval-tui create -- <agent> (raw, padi-blind) |
lifecycle_sendInput { text } |
kaval-tui send "$id" "text" (--file <path> for shell-metacharacter payloads) |
lifecycle_sendInput { key: "Enter" } |
kaval-tui send "$id" --key Enter |
wait_outputSettled |
kaval-tui wait "$id" --until idle:<ms> --timeout <ms> (also --until match:'<regex>', CLI-only) |
wait_agentState |
padi-tui wait "$id" --until working|awaiting,waiting |
screen_text { tail } |
kaval-tui snapshot "$id" --viewport (never a bare snapshot | tail — that's the buffer bottom, not the screen) |
terminals resource |
kaval-tui list (autodiscovers every daemon, self-labeling) · padi-tui status |
lifecycle_kill |
kaval-tui kill "$id" |
Interim (agent-spawn-first-class, #1872). As of PR2 a command-rooted agent
(raw kaval-tui create -- <agent>, no shell) is Dock-visible and
agent-state-tracked — kaval seeds its command from the argv — so detection is no
longer a reason to prefer the MCP lifecycle_create / padi-tui create, though
those still give the fuller shell-rooted workspace. The one residual trap: a
fresh kaval-tui create shell is clean, but launching an agent from your own
shell (or one reached via ssh/sudo -E) still needs unset CLAUDE_CODE_CHILD_SESSION CLAUDECODE CLAUDE_CODE_SESSION_ID first. Full detail +
the remaining tagged deletion point: TUI.md → interim doctrine.
Acceptance
Before calling a driven turn done:
- You submitted with a separate Enter send, sent after you observed the
settle — never text+Enter fused (a typed hard error on both faces). A prompt
left staged on the
❯ line is the #1 failure here.
- The inner agent's reply is actually in the screen read — not an empty box
or a half-rendered stream. Idle means "output stopped", not "the answer is
right"; verify the content.
- Every wait had a timeout so a wedged agent fails loud instead of hanging
the loop — and the timeout sat under your own harness's per-call cap.
- If the screen settled on a question (the agent is awaiting you), you
read it and answered — you didn't send the next task on top of a blocked
prompt.
- You killed exactly the terminals you created (
lifecycle_kill /
kaval-tui kill), and no others.
1---2name: kolu3description: Drive one AI agent from another through kolu's terminals: spawn a Claude Code / Codex / opencode session in a PTY, prompt it, watch the screen for its reply, read it, and prompt again. PRIMARY PATH: the kolu MCP server's tools (`lifecycle_create`/`lifecycle_sendInput` with named keys, the `wait_outputSettled`/`wait_agentState` done-signals, `screen_text`, the `terminals` resource) — configured in this repo's .mcp.json as `kolu mcp`. FALLBACK when the MCP is unavailable: the `kaval-tui`/`padi-tui` CLIs, same verbs, same discipline. Triggers on "drive another agent", "send a prompt to a terminal agent", "have one agent prompt another", "agent drives agent", "orchestrate agents in terminals", "make Claude drive Codex", "prompt the agent running in that terminal", or wiring a loop where one coding agent supervises another.4---56# kolu — drive one agent from another through its terminal78You can run a coding agent (Claude Code, Codex, opencode) inside a kolu-owned9PTY and steer it from the outside: type a prompt, submit it, watch the screen10until it's done, read what it said, type the next prompt.1112**Reach for the kolu MCP tools first** (`mcp__kolu__*` — served by `kolu mcp`,13wired in this repo's `.mcp.json`; other hosts: `claude mcp add kolu -- kolu14mcp`). They speak padi's live surface directly — structured results, typed15refusals, no shell quoting hazards. **Fall back to the `kaval-tui` / `padi-tui`16CLIs** (the final section) when no kolu MCP is connected — a host without the17server configured, a machine with no padi running, or a RAW standalone-kaval18terminal padi doesn't track. The verbs map one-to-one, and every discipline19below (the three-step submit, the done-signal, the fold hazard) applies to both20faces because they drive the same daemon.2122## The loop (MCP)2324```25lifecycle_create { intent: "🔧 parser refactor", cwd: "/abs/repo" } → { id }26lifecycle_sendInput{ id, text: "refactor the parser to use a lexer" } # 1. the text (no Enter)27wait_outputSettled { id, idleMs: 300, timeoutMs: 15000 } # 2. observe the TUI settle28lifecycle_sendInput{ id, key: "Enter" } # 3. submit (its own call)29wait_outputSettled { id, idleMs: 800, timeoutMs: 600000 } # 4. let its turn finish30screen_text { id, tail: 40 } # 5. read the screen31```3233- **`lifecycle_create`** spawns a canvas tile (a real padi-tracked terminal —34 agent sensors, dock state, the works). `intent` labels the tile so a human35 watching the canvas knows whose it is; `cwd` sets the working directory;36 **`parentId: <your-or-another-terminal-id>` opens it as a SPLIT beside that37 tile** (the sibling-reviewer layout `/agent-debate` uses). It spawns a shell —38 launch the agent by sending its command line (`claude39 --dangerously-skip-permissions` etc.) through the three-step submit below.40- **`lifecycle_sendInput`** writes **text OR one named key, never both** —41 `{ text, key }` in one call is a hard, typed error (see the submit42 discipline). The key vocabulary: `Enter`, `Escape`, `Tab`,43 `Up`/`Down`/`Left`/`Right`, `Home`, `End`, `Backspace`, `Space`,44 `Shift-Tab`, and `C-<char>` / `M-<char>` chords. An unknown key name is a45 loud error, never a silent no-op. Multiline text goes as one bracketed46 paste automatically.47- **`wait_outputSettled`** is the agent-agnostic done-signal (below);48 **`wait_agentState`** the precise one. Both return a uniform result frame —49 `{ result: "met", met: { …detail } }` or50 `{ result: "timeout" | "gone" | "closed", elapsedMs?, error? }` — read51 `result`, never guess from silence; the met detail (`fired`/`elapsedMs`, or52 `agent`/`elapsedMs`) nests under `met`.53- **`screen_text { tail: N }`** reads the last N *content* lines (trailing54 blank viewport rows are stripped) — the cheap "what's on screen now" read.55 Omit `tail` for the whole scrollback; `screen_history` pages older56 scrollback above it.57- **`lifecycle_kill { id }`** ends a terminal. Kill exactly the terminals you58 created, when you're done with them.59- The **`terminals` resource** (`surface://collections/terminals`) is the live60 roster — the key set; each id's full record (intent, cwd, git, agent kind +61 state, `parentId`, foreground process) reads at62 `surface://collections/terminals/<id>`. **`surface://cells/urgency`** lists63 the terminals whose agent awaits a human right now.6465> **Interrupt a runaway** before redirecting it:66> `lifecycle_sendInput { id, key: "Escape" }` (stop Claude Code mid-stream),67> `{ key: "C-c" }` (SIGINT whatever's running).6869## The three-step submit — text · settle · Enter7071Submitting a prompt to a TUI agent is **three calls**, because `sendInput`72bakes in no timing magic — it writes exactly what you pass:7374```75lifecycle_sendInput{ id, text: "fix the failing test in parser.ts" }76wait_outputSettled { id, idleMs: 300, timeoutMs: 15000 }77lifecycle_sendInput{ id, key: "Enter" }78```7980Why not one call? An Enter sent in the *same breath* as the text races Claude81Code's bracketed-paste / debounced input handling and is **silently dropped**,82leaving the prompt staged on the `❯` line while the send reports success (if a83turn never seems to start, this is the #1 cause — `screen_text` and look for84the prompt sitting unsent). The daemon **cannot observe** when the TUI85settled, so any fixed grace baked into the send is a race. The honest fix is86step 2: **you** observe the settle, then submit as its own call. `{ text,87key }` in one call is a **typed hard error** for exactly this reason — the88trap is unspellable, not merely discouraged.8990> **⚠️ MULTI-LINE pastes don't submit — a known-open limitation ([#1702](https://github.com/juspay/kolu/issues/1702)).**91> The three-step flow is verified for a **short** prompt (a line or two). Claude92> Code folds a paste into a `[Pasted text +N lines]` placeholder once it spans93> more than a handful of lines (observed at ~a dozen lines, well under 1 KB), and94> a folded paste does **NOT** reliably submit — idle firing proves the fold95> finished, not that Enter submits it. **Workaround for any multi-line message**96> (a brief, a report, a ruling): write it to a file and send a **short** prompt97> pointing the agent at it — `{ text: "read /tmp/brief.md and carry it out" }` —98> then the three-step submit. Reach for the file-pointer the moment a message99> runs past a couple of lines.100101> **Step 2 fires cleanly only when the agent is AT THE PROMPT** (the normal102> case: `idleMs: 300` fires in under a second). Messaging an agent that is103> **mid-turn and busy** (streaming continuously), the idle window never104> opens — so the `timeoutMs` fires instead. Treat `result: "timeout"` there as105> *"target busy"*: send the Enter anyway (it lands in the input buffer and106> submits when the turn ends), then `screen_text` to confirm. A107> `result: "gone"` is real (the terminal exited — the agent you were driving108> died); surface it, don't send into the void.109110## The done-signals111112After you submit, you need to know when the turn ends. Two tools, reading113different things:114115- **`wait_outputSettled { id, idleMs, timeoutMs }`** keys on **raw output116 quiescence** — resolves `met` once no output byte has arrived for `idleMs`.117 Agent-agnostic (bytes, not rendering): works identically for118 `claude`/`codex`/`grok`/`opencode`. `800` is a good default; raise it for an119 agent that pauses mid-thought. It can't tell "finished" from "blocked asking120 you" — both are quiescence — so **`screen_text` and read** before responding.121- **`wait_agentState { id, until: [...], timeoutMs }`** keys on padi's122 **detected agent state** — precise buckets: `working` (thinking/tool_use),123 `awaiting` (asking **you** a question), `waiting` (the just-finished124 post-turn lull). `awaiting`/`waiting` both mean "your move". It needs a125 terminal padi tracks (every `lifecycle_create` terminal is); a bare shell126 with no agent running never enters a bucket — bound it with `timeoutMs`.127128Always pass **`timeoutMs`** so a wedged agent fails loud (`result: "timeout"`)129instead of hanging the loop — and keep it **under your own harness's per-call130timeout** (most MCP hosts cap a tool call at ~1–2 minutes by default; a longer131wait gets *your* call killed while the driven agent is fine). For a long turn,132poll in bounded slices: repeat `wait_outputSettled { idleMs: 800, timeoutMs:13360000 }` and treat each `timeout` as "still busy, wait again".134135> **Mind the stale-state race — wait in two phases.** `wait_agentState`136> matches the state **the instant it connects**, replaying whatever it is right137> now. Right after a submit, the agent may still report the *previous* turn's138> `waiting` for a beat — a lone `until: ["awaiting","waiting"]` returns139> immediately on that stale state. For a robust loop: first140> `until: ["working"]` (it picked up the prompt), then141> `until: ["awaiting","waiting"]` (its turn ended).142143## Provisioning the inner agent144145- **A split tile beside a terminal**: `lifecycle_create { parentId: <id> }` —146 the record carries `parentId` and the canvas renders it as a sibling split.147- **A worktree'd agent** (fresh git worktree as the terminal's cwd) has **no148 MCP path in v1** — `git.worktreeCreate` is a named denial. Provision with149 the CLI: `padi-tui create --repo /abs/repo --worktree my-branch -- claude150 --dangerously-skip-permissions` (fast-forward the base repo first — the151 worktree is cut from the checkout as it stands), then drive the returned id152 over MCP as usual.153- **Never hardcode the agent CLI.** The inner agent defaults to the same agent154 *you* are running as (a Claude Code orchestrator spawns `claude`, a codex155 one `codex`) — unless the human named a different agent, which wins.156- **`create` returning ≠ the agent is ready.** `screen_text` before157 dispatching: a first-run agent may sit on a one-time dialog (a trust prompt,158 MCP selection) that needs its own `{ key: "Enter" }`. Drive every boot step159 by reading the screen, never by sleeping and hoping.160- **Set the permission mode AT LAUNCH, then verify it from the footer.** An161 unattended agent launches with bypass permissions (`claude162 --dangerously-skip-permissions`); read the footer via `screen_text` and163 confirm before dispatching.164- **Restarting the agent CLI in place**: text typed at a *running* agent165 becomes a prompt — send its quit command (`/exit` in Claude Code) as its own166 three-step submit, confirm the shell prompt in `screen_text`, then launch167 again.168169> **A terminal id is not stable across a kaval restart.** kaval re-keys every170> terminal when it restarts, so a cached id can go stale mid-run (a send to it171> fails "not found"). Re-find the terminal through the `terminals` resource —172> read each id's record and match on the stable **`intent`** label (set it at173> create for exactly this) — then use its current id.174175## Fallback — the `kaval-tui` / `padi-tui` CLIs (no MCP connected)176177When no kolu MCP server is available — the host has none configured, an older178kolu without the MCP face, a non-MCP agent runtime, or a **raw standalone179kaval** terminal padi doesn't track — the same loop runs on the CLIs. **The180full CLI treatment lives in [TUI.md](TUI.md)** (the three-step submit in CLI181form, `--file`, the done-signal exit codes, daemon discovery and the182`$KAVAL_SOCKET`/`$KAVAL_TERMINAL_ID` self-knowledge vars, worktree183provisioning, the old-daemon polling fallback). The verb map:184185| MCP | CLI |186| --- | --- |187| `lifecycle_create` | `padi-tui create [--parent <id>] [--repo … --worktree …] -- <agent>` (canvas tile) · `kaval-tui create -- <agent>` (raw, padi-blind) |188| `lifecycle_sendInput { text }` | `kaval-tui send "$id" "text"` (`--file <path>` for shell-metacharacter payloads) |189| `lifecycle_sendInput { key: "Enter" }` | `kaval-tui send "$id" --key Enter` |190| `wait_outputSettled` | `kaval-tui wait "$id" --until idle:<ms> --timeout <ms>` (also `--until match:'<regex>'`, CLI-only) |191| `wait_agentState` | `padi-tui wait "$id" --until working\|awaiting,waiting` |192| `screen_text { tail }` | `kaval-tui snapshot "$id" --viewport` (never a bare `snapshot \| tail` — that's the buffer bottom, not the screen) |193| `terminals` resource | `kaval-tui list` (autodiscovers every daemon, self-labeling) · `padi-tui status` |194| `lifecycle_kill` | `kaval-tui kill "$id"` |195196> **Interim (agent-spawn-first-class, #1872).** As of PR2 a command-rooted agent197> (raw `kaval-tui create -- <agent>`, no shell) is Dock-visible and198> agent-state-tracked — kaval seeds its command from the argv — so detection is no199> longer a reason to prefer the MCP `lifecycle_create` / `padi-tui create`, though200> those still give the fuller shell-rooted workspace. The one residual trap: a201> fresh `kaval-tui create` shell is clean, but launching an agent from your *own*202> shell (or one reached via `ssh`/`sudo -E`) still needs `unset203> CLAUDE_CODE_CHILD_SESSION CLAUDECODE CLAUDE_CODE_SESSION_ID` first. Full detail +204> the remaining tagged deletion point: [TUI.md](TUI.md) → interim doctrine.205206## Acceptance207208Before calling a driven turn done:209210- You **submitted with a separate Enter send**, sent *after* you observed the211 settle — never text+Enter fused (a typed hard error on both faces). A prompt212 left staged on the `❯` line is the #1 failure here.213- The inner agent's **reply is actually in the screen read** — not an empty box214 or a half-rendered stream. Idle means "output stopped", not "the answer is215 right"; verify the content.216- Every wait had a **timeout** so a wedged agent fails loud instead of hanging217 the loop — and the timeout sat under your own harness's per-call cap.218- If the screen settled on a **question** (the agent is awaiting you), you219 **read it and answered** — you didn't send the next task on top of a blocked220 prompt.221- You **killed exactly the terminals you created** (`lifecycle_kill` /222 `kaval-tui kill`), and no others.