Skip to content

The terminal UI ​

snowpea with no arguments opens the terminal UI. It is a thin client: every decision — which tool may run, what a command means, when to compact — belongs to the daemon, and this page is about the surface those decisions come out of.

Other languages: 한국어 · 日本語 · 简体中文 · Español · all pages

The launch screen ​

The first thing a session prints is the wordmark, sized to your terminal, and then three lines that say where you are:

text
██████████  ██      ██  ██████████  ██      ██  ██████████  ██████████  ██████████
██          ████    ██  ██      ██  ██      ██  ██      ██  ██          ██      ██
██████████  ██  ██  ██  ██      ██  ██      ██  ██████████  ██████████  ██████████
        ██  ██    ████  ██      ██  ██  ██  ██  ██          ██          ██      ██
        ██  ██      ██  ██      ██  ████  ████  ██          ██          ██      ██
██████████  ██      ██  ██████████  ██      ██  ██          ██████████  ██      ██
🌱 snowpea v0.1.2

          Open-source multi-vendor coding agent and personal AI assistant
        v0.1.2 · anthropic/claude-sonnet-4-5 · /home/you/project · ACCEPT

Last session: 26m ago · "add the worktree parallel session story to the IDE plan"
Press R or type /resume to continue it

The last-session block appears when the daemon still has a session open for this directory — a second terminal, a headless run, a session a crash left behind. R on an empty input replays it into this window; /resume opens the picker over every saved session instead. When the daemon has no such session, the block is not shown, because an offer that cannot be taken is worse than no offer.

The banner is printed once. It scrolls away like any other output and never comes back.

Saved sessions ​

The offer on the launch screen is only the newest one. Everything this machine has ever run is still on disk, and /sessions opens the picker over it:

text
╭──────────────────────────────────────────────────────────────────────╮
│ ❯  01J9F2… · 2026-03-14 09:41 · fix the flaky worktree test          │
│    01J9DR… · 2026-03-13 18:02 · add the scheduler reminder story     │
│    01J9C7… · 2026-03-13 11:26 · (no prompt)                          │
│    Cancel                                                            │
│ ↑↓ move · Enter resume · Esc cancel                                  │
╰──────────────────────────────────────────────────────────────────────╯

The listing includes closed sessions, not just the ones the daemon still holds open, and each row carries the last prompt that session saw, so a row is identifiable without remembering an id. Rows are newest first, scoped to this directory, and the session you are already in is not offered to you.

What you typeWhat it does
/sessionsopen the picker
/resumethe same picker
/resume <sessionId>reopen that session directly, no picker
R on an empty inputskip the picker and take the newest session in this directory
/session delete <id>delete one saved session
/session cleardelete the saved sessions for this directory
/session clear --alldelete every saved session on this machine

Resuming replays the session's history into this window and continues it — the working directory, mode, provider, model and team come back with it, whether or not the daemon still had it open.

Deleting takes the messages, the events and the session row together, and with them the attachments and speech files that session owned, because a thread deleted for what was pasted into it should not leave the paste behind. A live session is never deleted: close it first, and until then clear steps over it. Resuming, on the other hand, needs an idle turn — /resume and /sessions refuse while something is running, because swapping the transcript under a live turn would tear it in half.

The layout ​

The UI is a full-screen application. The input, the mode and status lines and the agent panel stay put at the bottom of the screen; the transcript above them is a window you scroll with the mouse wheel, PgUp/PgDn or Ctrl+U/Ctrl+D. Only the rows that changed are redrawn, so typing never repaints the screen. The wordmark greets an empty session and gives its rows to the transcript once the first prompt is sent. Quitting puts your terminal back exactly as it was. snowpea --inline keeps the older inline layout (see inline layout).

text
 › explain the retry logic                 ← the transcript: scrolls, the rest stays
 ⏺ Read 3 files (128 lines)
 ◆ The retry lives in `client.ts`…

 ✢ Pondering… (12s · ↓ 3.7k tokens)       ← the live region starts here
 > the next thing you type
 ⏵⏵ auto mode on · 1 shell · ← 2 agents
 snowpea v0.1.2 | ~/project | Model: anthropic/claude-sonnet-4-5 | Mode: ACCEPT | ctx 34% (68k/200k)
 [!!] context 85% — /compact to free space
 ● main
 ✳ executor         Implement story IDE-004               running · 27s · ↓ 159.1k tokens
 ◯ 4 idle agents

Top to bottom, the bottom panel is: the mode summary row, the status line, the context warning when there is one, then the agent rows. The mode comes first on purpose — it is the line that decides what the next turn is allowed to do, so it sits closest to what you are typing. The input sits above them, with the working indicator above that. Nothing below the input is redrawn unless it changed.

While it is working ​

A turn shows one line above the input, and that line says what is actually happening:

LineMeans
✢ Pondering… (12s · ↓ 3.7k tokens)the model is thinking; the verb changes every few seconds
✳ Running shell: npm test… (4s · …)a tool is in flight, named and with its argument
✶ 3 agents working… (1m 2s · …)the turn has delegated
✳ /ralph… (3m 10s · …)a command workflow owns the turn
⏸ Waiting for approvalit is blocked on you

The clock counts this turn and the token counts are this turn's, not the session's — the session totals are in the status line. Esc interrupts.

When a run of tool calls finishes, it collapses into one line in the scrollback rather than a card each:

text
⏺ Ran 2 shell commands (34 lines)
⏺ Read 3 files (128 lines)

A call that failed keeps its own card, with its output, because that is the one you need to read. Ctrl+O expands the newest tool call or diff while it is still live.

The agent panel under the status line lists who is working for this session: running and finished delegates, then the active team's idle roster. Tab moves the focus into it, ↑↓ pick a row and Enter opens that agent's own transcript. Clicking a row does the same once you type /mouse (or /mouse on): a terminal cannot report clicks for one region only, so while the mouse is on the terminal's own drag-to-select needs Shift held, and /mouse off (or /mouse again) gives it back. It is on when the full-screen TUI starts, off in --inline.

Modes ​

Shift+Tab cycles accept → auto → plan → accept. The mode is in the status line and in the summary row, and Modes explains what each one permits. Ctrl+P toggles plan mode on and off without cycling.

There is also a picker, for when you want a mode rather than the next one. ↓ past the newest history entry moves onto the summary row; Enter there opens it:

text
⏵⏵ auto mode on · 1 shell · ← 2 agents · Enter to choose mode
❯  accept mode
   auto mode
   plan mode

It starts on the mode you are in, Enter takes the highlighted one and Esc leaves the mode alone. Either way the mode summary row is drawn above the status line, not below it.

Choosing a model ​

/model <ref> switches the session to a model id or a named profile. /model on its own opens a picker rather than printing a list you would have to type back:

text
╭──────────────────────────────────────────────────────────────╮
│ Model                                                        │
│ ❯ fast    anthropic/claude-haiku-4-5 · used by reviewer       │
│   deep    anthropic/claude-sonnet-4-5 · default               │
│   claude-opus-4-1    anthropic                                │
│ ↑↓ move · Enter pick · Esc cancel                             │
╰──────────────────────────────────────────────────────────────╯

The rows are your model profiles first — models.profiles, with models.default and agents.models saying which is the default and which agent uses which — then whatever the vendor's endpoint reports, then the model in use if nothing else named it. Enter runs /model <ref> for the row you are on. The status line's Model: segment names the model in use, and adds where it came from when the daemon says.

Choosing ​

Every list the TUI puts in front of you — approvals, the agent's questions, /model, the MCP catalog, the tool checklist, the little menus inside /skill create and /mcp add — is the same widget with the same keys. Learn it once (M15b §2):

keysingle-selectmulti-selecta batch of questions
↑ ↓ (or j k)movemovemove inside the active question
Space—tick the rowtick the row
Enterpick and submitconfirm the setpick; on the last tab, submit everything
1–9jump to row N, never submitstick row Nsame
← →, Tab / Shift+Tab——previous / next question
Escdecline or canceldecline or canceldecline the whole batch
Other… + Enteropens a text line; Enter keeps it, Esc leaves itsamesame

The cursor starts on the first row, which is the recommended one. A footer hint is always visible and names exactly the keys that are live on that list. While a list is up it owns the keyboard: nothing you type reaches the draft behind it.

The same map drives snowpea setup, so the wizard and the TUI never disagree about what Space does.

Approvals ​

When the daemon asks, it asks with a menu. ↑/↓ move, Enter takes the highlighted row, Esc refuses:

text
╭──────────────────────────────────────────────────────╮
│ Approval required                                    │
│ shell risk=high timeout=300s                         │
│   command: rm -rf build                              │
│                                                      │
│ ❯  Yes   (y)                                         │
│    Yes, and don't ask again this session   (a)       │
│    Yes for this project   (p)                        │
│      adds an allowlist rule the daemon keeps         │
│    No   (n)                                          │
│    No, and tell it why   (r)                         │
│      the model reads what you type as the refusal    │
│ ↑↓ move · Enter confirm · 1-9 pick · Esc cancel      │
╰──────────────────────────────────────────────────────╯

When the daemon has something to warn you about — a command that reaches outside the working directory, a risk the arguments alone do not show — the request carries a note, and it is rendered in red above the arguments in both the TUI and the headless CLI:

text
shell risk=high timeout=300s
  ⚠ this deletes a directory outside the working tree
  command: rm -rf ../build

The cursor starts on Yes, so Enter means yes. y, a, p and n still work directly. While the prompt is up it owns the keyboard: nothing you type reaches the draft behind it, and Shift+Tab does not change mode.

The last row refuses and says why. It opens a one-line field; Enter denies with what you typed and the agent is handed that sentence as the tool's refusal, so its next turn has to answer it instead of retrying the same call in a slightly different shape. Esc in the field goes back to the choices; Esc on the menu is still a plain refusal.

Approvals raised by a turn with nobody watching — a scheduled job, a Telegram message — queue instead. Ctrl+R hands the keyboard to that queue; /approvals lists it.

Questions ​

An approval is the daemon asking whether it may do something. A question is the agent asking you something, through the ask_user tool, and it is drawn as a picker rather than as "(a) … (b) … (c) …" in the transcript:

text
╭──────────────────────────────────────────────────────╮
│ Renderer                                             │
│ What should the renderer be? This decides how much   │
│ engine control you keep.                             │
│                                                      │
│ ❯  1. ● three.js (recommended)                       │
│       fast start, little control over the engine     │
│    2. ○ Raw WebGL2                                   │
│       more work, complete control                    │
│    기타 / Other…                                      │
│                                                      │
│ ❯  확인 / Confirm                                     │
│ ↑↓ 이동 · Enter 확인 · 1-9 고르기 · Esc 취소          │
╰──────────────────────────────────────────────────────╯

Nothing is sent until you say so. ↑/↓ (or j/k) move; Enter on an option marks it ● and drops the cursor onto the 확인 / Confirm row at the bottom; Enter there submits. 1–9 mark a row without submitting. When the question allows several answers, Space ticks rows ([x]) and the Confirm row sends the set. The 기타 / Other… row opens a one-line text input for an answer nobody offered. Esc answers "I am not choosing", and the agent is told the question was declined rather than left to read silence as agreement.

One ask_user call may carry several questions. They arrive together and become tabs across the top, so you can look at all of them, answer in any order, and go back and change your mind before anything is sent:

text
╭──────────────────────────────────────────────────────╮
│  ✓ Renderer  │ Storage │  Netcode                    │
│ Where should the world be stored?                    │
│                                                      │
│ ❯  1. ● IndexedDB                                    │
│    2. ○ On a server                                   │
│    기타 / Other…                                      │
│                                                      │
│ ❯  다음 질문 / Next question         1/3              │
│ ↑↓ 이동 · ←→ 질문 이동 · Enter 확인 · Esc 취소        │
╰──────────────────────────────────────────────────────╯

←/→ (or Tab / Shift+Tab) switch tabs, a ✓ marks the ones you have answered, and each tab keeps its own selection. The bottom row reads 다음 질문 / Next question until the last tab, where it becomes 확인 / Confirm and submits every answer at once. Esc on any tab declines the whole batch.

A question is drawn ahead of a pending approval and owns the keyboard while it is up. It gives up after ten minutes by default — questions.timeoutSec in settings.json.

Two lines of it, from a skill:

text
ask_user(question="Where should the cache live? An in-process cache is simpler; Redis survives a restart.",
         options=[{"label": "In-process (recommended)", "description": "no new service"}, {"label": "Redis", "description": "survives a restart, one more thing to run"}])

The argument schema is a superset of Claude Code's AskUserQuestion — questions[] with header, question, options[] of {label, description}, multiSelect and an optional preview — and of Hermes' clarify, whose choices[] of plain strings and multi_select are accepted too, so a skill ported from either works unchanged. allow_other defaults to true and the "Other" row is added for you.

On a messenger binding the same question arrives as numbered text with one button per option plus an "Other" button, and a typed reply of 2 (or 1,3 for a multi-select) answers it. A chat has no tabs, so it works through a batch one message at a time and says where it is ((1/3)). Headless snowpea -c has no picker to draw, so it declines the question at once and says so on stderr instead of waiting out the timeout.

Setting up from inside the TUI ​

Keys used to be typable only in the Python wizard: a provider you picked here had nowhere to put its credential, so you quit, ran snowpea setup, and came back. Two commands close that.

text
/setup                    # every screen in turn
/setup browser            # just one: providers, search, browser or audio
/login anthropic          # asks which flow, then runs it
/login gemini browser     # straight to the browser login

They use the same question picker as everything else: the recommended row first and marked, arrows or 1-9 to pick, Esc to decline. The difference is what happens when the answer is a credential.

A key is masked as you type it. The field draws •••• rather than the characters, one dot per character so a paste that arrived short is visibly different from one that worked. The footer says hidden. The value never appears in the transcript, never in the review tab of a multi-question batch, and never in a log — only in settings.json, with the file's usual 0600.

The daemon marks those questions itself with secret on the question, so any surface that draws questions gets the same treatment without being told which providers have keys. A URL or a project id is asked in the clear, because neither is a secret and hiding it while you paste helps nobody.

/setup writes exactly what snowpea setup writes, through the same code: which provider needs a key, what the hint says and where the answer is stored are decided in one place, so the two can never drift apart.

Both refuse in a session with nobody to ask — an unattended run, a scheduled job, a delegated child — because a key prompt there either hangs until the timeout or takes silence for an answer.

Diffs ​

A file change appears where it happened, in the conversation:

text
✎ Edited README.md  (+4 −2)
--- a/README.md
+++ b/README.md
@@ -1,5 +1,7 @@
 # snowpea
-an agent
+an open-source multi-vendor coding agent
… 3 more lines (Ctrl+O)

A new file reads ✚ Created notes.md (7 lines). Long patches are cut to twelve lines; Ctrl+O opens the newest one while it is still in the live region.

Context ​

The status line carries how full the model's context window is:

text
ctx 34% (68k/200k)

It is dim until 70%, amber from there, red from 85%, and past 80% a row appears above the summary saying what to do about it:

text
[!!] context 85% — /compact to free space

/compact [instructions] summarises the conversation so far and continues from the summary; the optional instructions say what to keep. The daemon also compacts on its own before a turn would pass context.autoCompactPercent. Either way the transcript shows where it happened:

text
───────────── compacted (68.0k → 12.1k tokens) ─────────────

ctx 12.3k used with no percentage means the daemon does not know this model's window. See Commands for snowpea session context and the providers.<vendor>.context_window override.

Typing ​

↑ walks back through your earlier prompts and ↓ walks forward to what you were writing. The history is per machine, not per session: it lives in $SNOWPEA_HOME/tui-history.jsonl, keeps the last 500 entries, and does not record the same prompt twice in a row.

↓ past the newest entry does something else: it moves the cursor out of the input and into the rows below it. First the summary row, where Enter opens the mode picker:

text
⏵⏵ auto mode on · 1 shell · ← 2 agents · Enter to choose mode

Then the agent rows, one at a time. Esc or ↑ walks back up to the input.

Sending while it is working ​

You do not have to wait for a turn to finish. A prompt sent while one is running is accepted rather than refused.

By default (/busy steer), the prompt is queued only briefly and then folded into the running turn before its next model call, so it can steer what is already in progress (Hermes-style busy steer). Running subagents and team workers get the same note at their next step, prefixed as mid-task user guidance. The queued-turn id is retired with turn.dequeued reason steered; it does not start a separate turn.

/busy queue restores the legacy behavior: queued prompts drain first in, first out as separate turns, one at a time against one history, so two provider loops never run over the same conversation. Queue mode also disables the mid-turn steering broadcast to helpers.

Attachments are captured when you press Enter, so a chip sent while busy is still the file you meant when the model sees it. The queue is in memory only; it does not survive a daemon restart.

Esc stops the running turn, its in-flight tool call, its subagents and team workers, and drops the queue behind it. Interrupting means stop what I asked for, and that has to include the follow-ups still waiting, or Stop would be followed by the queue running anyway. Every dropped prompt is reported to clients as turn.dequeued with reason dropped, followed by its own turn.done, so nothing waiting on that turn id is left hanging. After a stop, the next message continues the same session.

While the queue drains the UI shows it: the working line gains a ⏳ N queued count, and the prompts themselves are listed under the input, dimmed and numbered in the order they will run.

text
✽ Noodling… (4s · ↓ 0 tokens) · ⏳ 2 queued
   1. run the tests after this
   2. then read the diff
 > ask anything, or /command

A prompt leaves the list the moment it is steered into the running turn or starts its own queued turn. Esc clears the whole queue and says so once — 2 queued prompts dropped — rather than one notice per prompt.

Handing one prompt to an agent ​

A draft that starts with $name goes to that agent rather than to the main session, and the input says so before you send it:

text
[delegate to executor]
> $executor review the queue work

The names come from the daemon's agent list. A name nothing answers to is still shown, marked (no such agent), because that is worth seeing before Enter rather than after.

Looking inside an agent ​

Enter on an agent row replaces the transcript with that agent's own conversation — the brief it was given, its tool calls, its answer:

text
╭────────────────────────────────────────────────────────────────────╮
│ ◯ executor · Implement story IDE-004      running · 28s · ↓ 159.1k │
│ › Implement story IDE-004 (Worktree parallel sessions)             │
│ ✓ read_file path=docs/stories/IDE-004.md (1 lines)                 │
│ ◆ Added the worktree manager and its tests; 3 files changed.       │
│ ↑↓ PgUp/PgDn scroll · Esc back to the main transcript              │
╰────────────────────────────────────────────────────────────────────╯

Each delegate runs in a session of its own, and this view is that session, replayed and then followed live. Esc, or Enter on ● main, comes back. Ctrl+A opens the panel out past its collapsing rules, so idle agents and anything hidden behind ↓ N more are listed.

Updates ​

When a newer release exists the status line says so, and U on an empty input — or /update — opens the confirmation. The upgrade runs, the daemon restarts, and the UI comes back on the new version.

text
snowpea v0.1.2 → v0.1.3 (U to update)

Attachments ​

Paste or drop a file path into the input and it becomes a chip instead of text:

text
[📎 screenshot.png 1.2MB] (backspace removes the last · Ctrl+X clears)
> what is wrong with this layout?

It understands what a terminal actually delivers: one path, several at once, a path whose spaces are escaped, a path whose spaces are not, a file:// URL, quotes a file manager added. Ctrl+V takes an image straight off the system clipboard — wl-paste, xclip, AppleScript or PowerShell, whichever this machine has — and saves it under $SNOWPEA_HOME/tmp/. /attach <path> does it by hand.

Backspace on an empty input removes the newest chip and Ctrl+X removes them all. Sending the prompt sends the files with it, and the transcript entry says which:

text
› what is wrong with this layout?
  📎 screenshot.png

Files are sent as paths, so nothing is copied. What the model then does with them — and the 20MB limit, the downscaling, what happens on a model that cannot see — is in Attachments and voice.

Referencing files with @ ​

Type @ in the prompt to name a file without pasting its whole contents. The editor offers completions from file.complete as you type; pick a path with ↑/↓ and Enter or Tab. Spaces need quotes: @"docs/plan.md". An optional line range narrows a text file: @src/app.py:10-40.

When you send the prompt, the daemon resolves each @ reference: images become attachments the model can see; text files are inlined with line numbers (truncated past 60k characters per file); directories get a shallow listing; anything that does not resolve is left in the text unchanged. Your message keeps the @ tokens verbatim so you can see what you asked for. The transcript shows that short text plus a refs list (path and kind), not the inlined file bodies. Image paths written without @ are attached automatically when agent.autoAttachImages is true (the default). Korean particles glued to a path (@src/a.py를, shot.png에서) are trimmed automatically. Secret paths (.env, keys, anything under your snowpea home except pasted attachments) are refused and never sent to the model.

Voice ​

Voice needs a backend, and the daemon is the one that has them. snowpea setup audio configures them; Attachments and voice lists what each one needs.

Key or commandWhat it does
/voicearm voice input
Ctrl+Space (Ctrl+T on macOS), or /recstart recording; again to stop
/tts on, /tts offspeak each reply as it finishes
Escstop a reply that is being read out

While recording, the indicator slot counts up:

text
● REC 00:07

Stopping transcribes and puts the text in the draft rather than sending it, because speech recognition is wrong often enough to need reading first. Recording happens on the daemon when the daemon has a microphone, and on this machine when it does not.

🔊 in the status line means replies are being spoken. When a capability is missing the command says so in the daemon's own words rather than doing nothing:

text
voice input needs speech-to-text: no transcription backend: install the whisper CLI, set an OpenAI API key, or …

MCP servers ​

/mcp manages the MCP servers this session can call. /mcp or /mcp list prints the table; typing /mcp offers the sub-actions, and the ones that take a server name complete it from the servers already configured.

/mcp add with nothing after it opens a form instead of asking you to remember the syntax: a name, then Command or URL, then the command line (split into arguments the way a shell would, but never run through one) or the endpoint, then any KEY=value variables — headers for a URL server — and finally whether the entry goes in this project's .mcp.json or your global one. Values you type are shown back as KEY=•••; the file keeps them, the screen never repeats them.

The draft is then probed with mcp.test before anything is written. On success the form says Connected — N tools: … and offers Enable all, Select tools (a checklist, saved as tools.include) or Cancel. On failure it shows the error and offers Retry, Save anyway — which writes the entry without a working probe, and asks a second time when the failure was a safety finding — or Cancel. Esc cancels at any step and writes nothing.

/mcp catalog lists the curated presets; picking one opens the same form filled in with its command and the variables it needs. /mcp configure <name> with no tool names opens the checklist for a server that is already saved.

The HUD shows mcp 2/3 — servers ready out of servers configured — and hides the segment when none are configured. A server that stops working is reported once in the transcript rather than on every retry.

Inline layout ​

Full screen is the default. --inline (or SNOWPEA_TUI_INLINE=1) draws the way git log does instead: finished output is handed to the terminal's own scrollback and only the bottom of the screen is live.

bash
snowpea --inline

The mouse wheel scrolls the transcript in full screen because the TUI asks the terminal for mouse reports; while it does, selecting text with the mouse needs Shift held. /mouse off gives selection back (the wheel then stops scrolling the transcript), and /mouse turns reports on again. The inline layout starts with them off.

Keys ​

KeyWhat it does
Entersend, or confirm the highlighted option
@reference files (@path or @"path with spaces")
Shift+Tabcycle mode
Ctrl+Ptoggle plan mode
↑ / ↓earlier prompts; ↓ past the newest moves into the panel
Escinterrupt the turn, stop speech, or leave an agent view
Ctrl+Oexpand the newest tool call or diff
Ctrl+Aopen the agent panel out
Ctrl+Rfocus the unattended approval queue
Ctrl+Vattach an image from the clipboard
Ctrl+Xclear the attachments
Ctrl+Space (Ctrl+T on macOS, where Ctrl+Space switches the input source)start or stop recording
Utake the offered update
Rresume the session the launch screen offered
F1help
Ctrl+Cquit

/help lists every command the daemon has, including the ones your plugins added, and repeats this table.

Markdown tables ​

Markdown tables in responses are rendered with aligned borders. Column sizing accounts for Korean, CJK and emoji terminal widths, and long paths or sentences wrap inside their cells. When there are too many columns to fit, values are stacked under their column labels instead. Inline and full-screen views use the same renderer; table source inside code fences stays literal.

Built-in and custom agents ​

/agent list includes the packaged roles (architect, critic, executor, explorer, test-engineer, verifier) alongside custom definitions. Built-in roles need no user-created files and have source builtin. A custom definition with the same name overrides the built-in; project definitions take precedence over global definitions. The same names are available through the agent argument of delegate_task.

Update notifications at startup ​

Each launch checks for updates in the background. /update always bypasses an older negative cache and checks again; when nothing newer exists it reports that the build is already up to date rather than showing an installation failure. When a banner appears, press U on an empty input or type /update to open confirmation. Choose y to install and restart, or n/Esc to defer. Lowercase u in ordinary typing is not an update shortcut.

Git main/master installs compare the installed commit, so a version bump is not required to detect new commits. Failed checks, unchanged builds and downgrades do not trigger installation. PyPI/release installs retain version-based checks.

If an older automatic update leaves startup failing with Cannot read properties of undefined (reading 'rawCall'), reinstall current main:

sh
uv tool install --force --reinstall 'snowpea-agent[images] @ git+https://github.com/Wafour-Developer/snowpea-agent@main'

After reinstalling, once no work is running, use snowpea daemon stop and then launch snowpea to load the new daemon code.

Help stays within the terminal height. Scroll with ↑/↓ or PgUp/PgDn; close with Esc, F1, q, or Enter. Esc inside help does not interrupt the running turn.

The input, connection/model status, mode summary, and agent list at the bottom are separated by rules spanning the full terminal content width, making the active region easy to distinguish.

Teams and short delegation ​

The first snowpea setup creates a default team from the built-in roles. /team create delivery architect executor verifier creates a project team from existing agents, activates it immediately, and rejects unknown names. Manage it with /team list, /team use <name>, and /team delete <name>. With a team active, the footer shows its name and members only, and automatic delegation is confined to that roster.

Type $executor fix the tests (or /delegate executor fix the tests) to delegate directly without a long command. Both run an ordinary turn: the daemon parses the prefix itself, so this behaves the same from any client, and the turn calls delegate_task, waits for the child, and answers with what it reported — not silence once the child finishes. Unknown or out-of-team names fail instead of silently becoming a generic agent. Internal delegation that omits a name deterministically uses executor when present, otherwise the first team member.

A child always comes back with something to read. Its brief says how many tool rounds it has (see tool rounds), and if it spends them all it is asked — with its tools switched off — to report what it did, what it found and what remains before its turn ends. What the delegating agent receives is that report under three lines of status: / reason: / roundsUsed:, plus the last few tool calls the child made when it stopped early. A delegation that ran out of budget says so instead of coming back empty, so the work carries on from where the child left it rather than starting again.

delegate_task takes an optional title: one line, in your language, saying what this delegation is. It is what the agent panel and the scrollback show while the child runs — 요약 표시 방식 조사 rather than Ran delegate_task. Without it the row falls back to the verb and the agent's name. The output language is appended to the brief automatically; see reply language.

Reconnecting ​

When the daemon is restarted or upgraded while the TUI is open, the client automatically initiates reconnection with bounded exponential backoff. It re-reads the updated endpoint and credentials from daemon.json, handshakes with system.hello, and seamlessly resumes the session via session.resume with sequence replay. While the connection is down, the HUD displays ⟳ reconnecting… (n) (where n is the attempt counter); once connection is restored, it returns to ● connected and displays a daemon restarted — session resumed toast notification. If the daemon process is stopped or unreachable for more than 20 seconds, the HUD displays daemon is not running — press R to start it, allowing you to press R to start the daemon and reconnect immediately.

MIT licensed. The manual is generated from the product repositories — see the banner on each page.