Skip to main content

kolu

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 — a create→send→snapshot loop run with the `kaval-tui` CLI directly (no MCP). `kaval-tui` writes input, reads scrollback, and provides a hook-free done-signal (`wait --until idle:<ms>` on raw PTY output); `pulam-tui` adds a precise agent-state done-signal when you drive hooked terminals. 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.

Quellinformationen

Repository
srid/llm-debate
Letzte Quellaktivität
1. Juli 2026 um 14:20
Erkannte Sprache von SKILL.md
Englisch
Sterne
1
Forks
0

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
kolu
description
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 — a create→send→snapshot loop run with the `kaval-tui` CLI directly (no MCP). `kaval-tui` writes input, reads scrollback, and provides a hook-free done-signal (`wait --until idle:<ms>` on raw PTY output); `pulam-tui` adds a precise agent-state done-signal when you drive hooked terminals. 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.
# 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. The whole toolkit is **`kaval-tui`** — write input, read the screen, spawn, kill. The driver runs it directly; there's no server to stand up and no MCP layer. For a raw `kaval-tui`-spawned terminal, the done-signal is **`kaval-tui wait --until idle:<ms>`** — it blocks in the daemon on the raw PTY output and returns the moment the agent stops streaming, with no shell hooks (below). `pulam-tui` adds a *precise agent-state* done-signal (`wait --until <state>`), but only for **hooked** terminals (the last section); for raw terminals, reach for `kaval-tui wait`. ## The loop ```sh id=$(kaval-tui create --json -- claude | jq -r .id) # spawn the inner agent kaval-tui send "$id" "refactor the parser to use a lexer" # 1. TYPE the prompt kaval-tui send "$id" --key Enter # 2. SUBMIT it (its own step) kaval-tui wait "$id" --until idle:800 --timeout 600000 # 3. let its turn finish (below) kaval-tui snapshot "$id" --viewport # 4. read the screen kaval-tui send "$id" "now add tests for it"; kaval-tui send "$id" --key Enter # loop ``` Leaf commands, all `kaval-tui`: **create** (spawn) · **send** (type) · **send --key Enter** (submit) · **wait** (block until the turn ends) · **snapshot** (read) · **kill**. **Typing and submitting are two separate `send`s** — that is load-bearing, see below. > **Read with `snapshot --viewport`, not `| tail`.** A bare `snapshot` prints the > **whole scrollback** — thousands of lines on a long-running or compacted agent — > so `snapshot | tail -8` hands you the bottom of the buffer (often just trailing > blanks), not the live screen. `--viewport` asks the daemon for just its > terminal's last screenful — the right "what's on screen now" read, and correct > regardless of how tall your own shell is (over `--host` the remote terminal is a > different size). `--tail N` (alias `--lines N`) bounds it to the last N lines > when you want a fixed slice. ## `kaval-tui send` — type, then submit (two steps) `kaval-tui send <id> [text...]` writes input to the terminal — **exactly the text (and any `--key`s) you pass, with NO implicit Enter**. It types; it does not submit. **Submitting a prompt is its own second `send`:** ```sh kaval-tui send "$id" "fix the failing test in parser.ts" # 1. type the prompt kaval-tui send "$id" --key Enter # 2. submit it ``` **Do this as two separate `send` commands — not `send "text" --key Enter` in one call.** The separation is load-bearing: an Enter sent in the same breath as the text races Claude Code's bracketed-paste / debounced input handling — it arrives before the pasted text has registered and is **silently dropped**, leaving the prompt staged on the `❯` line while `send` reports success. A standalone follow-up `send --key Enter` lands after the text has settled, so it actually submits. (If a turn never seems to start, this is the #1 cause — `snapshot` and look for the prompt sitting unsent on the `❯` line.) Specifics: - **Multiline prompts and piped stdin go as one bracketed paste**, so they land in the input box as a block instead of submitting line-by-line. Automatic (`--paste` / `--no-paste` force it). For a big prompt, pipe it — `cat task.md | kaval-tui send "$id"` — then `send "$id" --key Enter`. - **`--key <name>`** (repeatable, sent after the text) is both the submit channel (`Enter`) and the control channel: `Escape`, `C-c`, `Enter`, `Up`/`Down`/`Left`/`Right`, `Tab`, `Home`, `End`, `Backspace`, `M-<char>`. - **`--json`** → `{ id, bytes, paste, keys }` to confirm what was written. **`send` is blind** — it writes whether or not the agent is ready for input. Always pair it with `snapshot` so you don't fire a prompt into a not-yet-ready session (e.g. before the TUI has drawn its input box, or over a trust prompt). **Interrupt a runaway** before redirecting it: ```sh kaval-tui send "$id" --key Escape # stop Claude Code mid-stream kaval-tui send "$id" --key C-c # SIGINT whatever's running ``` ## The done-signal — `kaval-tui wait --until idle:<ms>` After you submit, you need to know when the turn ends. For a raw `kaval-tui`-spawned terminal there's no agent-state feed — but the daemon already sees every output byte, so **`kaval-tui wait`** blocks on that raw stream and returns the instant the agent goes quiet, with **no shell hooks** and no busy-word guessing: ```sh kaval-tui wait "$id" --until idle:800 --timeout 600000 # block until the turn ends ``` - **`--until idle:<ms>`** resolves once no output byte has arrived for `<ms>` — the agent-agnostic "turn ended / awaiting input" signal, and the common case. `800` is a good default; raise it for an agent that pauses mid-thought, lower it for a snappier loop. It works identically for `claude`/`codex`/`grok`/ `opencode` because it keys on bytes, not on any agent's rendering. - **`--until match:'<regex>'`** resolves once *new* output matches — use it for a completion marker or a returned-prompt sentinel (e.g. `--until match:'\$ $'`). - **`--timeout <ms>`** caps the wait and **fails loud (exit 2)** so a wedged agent can't hang the loop. If the terminal **exits** before the condition fires, `wait` exits **3** (the agent you were driving died). Met → exit **0**. - **`--json`** → one result frame per outcome: `{ id, result, … }`, where `result` is `met`/`timeout`/`gone`/`interrupted`/`closed`. A `met` frame adds `fired` (`idle`/`match`), `elapsedMs`, and `matchedLine` on a match — so a driver reads the structured `result`, never just the exit code. Quiescence ≠ "the reply is correct": idle fires whether the agent **finished** or is **blocked asking you something** (both mean "your move"). So after `wait` returns, **`snapshot --viewport` and read** what's on screen before responding. > **Fallback — screen-settle polling (only for an old daemon).** If you're > driving a kaval that predates `wait` (it errors "unhandled command"), fall back > to polling `snapshot --viewport` until the screen holds still across two reads, > capped by a deadline. It's coarser and laggier (a busy agent that pauses > mid-thought can read as settled, and the poll lags the real settle), so prefer > `kaval-tui wait` whenever the daemon has it. > > ```sh > wait_until_settled() { # fallback only — kaval-tui wait is preferred > local id=$1 deadline=$(( $(date +%s) + 600 )) prev="" cur stable=0 > while [ "$stable" -lt 2 ] && [ "$(date +%s)" -lt "$deadline" ]; do > sleep 3 > cur=$(kaval-tui snapshot "$id" --viewport) # the live screen, not full scrollback > if [ "$cur" = "$prev" ]; then stable=$((stable + 1)); else stable=0; fi > prev=$cur > done > } > ``` ## `pulam-tui wait` vs `kaval-tui wait` — two done-signals They are not rivals; they read different things: - **`kaval-tui wait`** keys on **raw output quiescence/match** — works on **any** terminal, **no hooks**. This is the one to reach for when driving a raw `kaval-tui create` agent (above). It can't tell "finished" from "blocked asking you" — both are quiescence — so read the snapshot after. - **`pulam-tui wait`** keys on **agent-state buckets** (working/awaiting/waiting) — more precise (it *distinguishes* awaiting-you from finished), but only on **hooked** terminals. Use it when you're driving terminals a kolu-server spawned (below). ### `pulam-tui wait` — the precise done-signal (hooked terminals only) When you *do* have agent-state detection, `pulam-tui wait <id> --until <buckets>` is the exact done-signal — it blocks until the agent reaches a coarse state, then exits 0: - **`working`** — busy (`thinking` / `tool_use` / background task). - **`awaiting`** — `awaiting_user`: it's **asking you** a question. - **`waiting`** — the **just-finished** post-turn lull. `awaiting` and `waiting` both mean "your move", so `--until awaiting,waiting` catches a turn ending; `--timeout <ms>` fails loud (exit 2) so a wedged agent can't hang the loop; if the terminal **exits** before reaching the state, `wait` fails loud too (exit 3 — the agent you were driving died); `--json` → `{ id, agent }`. > **Mind the stale-state race — wait in two phases.** `wait` matches the agent's > state **the instant it connects**, replaying whatever it is right now. So right > after a `send`, the agent may still report the *previous* turn's > `waiting`/`awaiting` for a beat before it picks up the new prompt — and a lone > `wait --until awaiting,waiting` would return immediately on that stale state, > before the turn you asked for has even begun. For a robust loop, wait for the > pickup first, then the turn-end: > > ```sh > kaval-tui send "$id" "fix the parser"; kaval-tui send "$id" --key Enter > pulam-tui wait "$id" --until working # 1. it picked up the prompt > pulam-tui wait "$id" --until awaiting,waiting # 2. its turn ended > ``` > **Caveat — agent state needs HOOKED terminals.** Detection keys on kolu's shell > rc-hooks (the OSC marks a terminal emits as commands run). `kaval-tui create` > is the **raw** multiplexer — a plain `$SHELL`, no hooks by design — so an agent > you spawn that way often isn't detected, and `wait` will just time out. `wait` > is reliable when you drive **already-hooked** terminals: the ones a running > **kolu-server** spawned (point `kaval-tui --socket $XDG_RUNTIME_DIR/kolu/pty-host.sock` > at them), or a future `kolu-tui`. For a raw `kaval-tui create` loop, use > **`kaval-tui wait --until idle:<ms>`** above — it needs no hooks. ## Reach — which daemon you're driving Bare `kaval-tui` **autodiscovers** a running daemon on this machine. Two ways to point it elsewhere: - **`--socket <path>`** targets a specific local daemon — e.g. a running **kolu-server's** kaval (`$XDG_RUNTIME_DIR/kolu/pty-host.sock`), to drive the terminals you have open in kolu (these ARE hooked, so `pulam-tui wait` works against them once a `pulam` reads that kaval). - **`--host <ssh>`** reaches a daemon on another machine (provisioned with Nix); a remote PTY survives the link. ## Acceptance Before calling a driven turn done: - You **submitted with a separate `send --key Enter`** (not an implicit Enter, not `send "text" --key Enter` in one call) — a prompt left staged on the `❯` line is the #1 failure here. - The inner agent's **reply is actually in the `snapshot`** — not an empty box or a half-rendered stream. `wait --until idle` means "output stopped", not "the answer is right"; verify the content. - Your wait had a **`--timeout`** (or deadline) so a wedged agent fails loud (exit 2) instead of hanging the loop. - 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.
Auf GitHub ansehen