Inbox and Review
One list of everything your agents are waiting on, answers from wherever you are, safer approvals, messages queued for a busy agent, and a review of what an agent changed.
The Inbox is one list of everything waiting for you, in every session and on every linked machine: approvals and questions an agent is blocked on, questions an agent put to you, mail, agents that errored, conversations to resume after a restart, and finished turns you have not looked at. From it you answer prompts, reply to agents and review what they changed.
The daemon keeps the Inbox, so every attached client sees the same list, and tuios list-attention prints it from a shell.
Setting up
Wire each harness you use to report its state. Its hooks then say when it works, waits and finishes:
tuios integration install claude-code # or codex, gemini-cli, opencode, ..., or --all
tuios doctor agents # what is installed, and agent panes missing oneWithout an integration, TUIOS still reads the process, the screen and the title, but it can say less. See Agents.
A person who never runs an agent is not shown any of this. Until an agent has been seen, the Inbox lines of the prefix menu, the palette's agent entries and the help's Agents section stay hidden. The keys work before that too.
Opening it
| Key | Does |
|---|---|
| Ctrl+B i | Open the Inbox |
| Ctrl+B o | Go to the oldest item that needs you, switching session and workspace. o again goes to the next |
| Ctrl+B O | Go to the newest finished turn you have not seen. O again goes to the next older one |
| Ctrl+B M | Open the Inbox on its mail. m there opens the whole mailbox |
The palette has them too: type agent.
Rows are grouped under Approvals, Plans, Questions, Mail, Errored, Resume and Done, oldest first in each group. Mail to another machine whose link is down waits under Waiting to send. A row shows its kind's glyph, the pane's name, what it said, its session and how long it has waited. The footer offers only the keys that act on the row under the cursor, the one that answers it first. An item closes by itself when what opened it stops being true: the agent leaves needs_input, the mail is read, or someone looks at the pane that finished.
f shows one kind at a time. / narrows the list with a selector, such as harness:codex needs:you or session:api-fan-*. See Selectors.
Every key in the Inbox is rebindable. The full list is on the Keybindings page.
From a shell:
tuios list-attention
tuios list-attention --kind approval --kind question
tuios list-attention --select 'harness:codex group:fan/add-retry'
tuios list-attention --snoozedApprovals
35s #2 api/claude approve Bash: make
Questions
46s #1 docs/claude which retry policy?
2 waiting. Open the Inbox with the prefix key then i, or jump to the oldest with the prefix key then o.Only a person at an attached client can dismiss, snooze or answer an item. An agent cannot empty the list you read to find out what the agents want.
Answering a prompt from the Inbox
Space on an approval or a question opens the prompt as the pane shows it, with its numbered options and how long the agent has waited:
| Key | Does |
|---|---|
| 1 to 9 | Choose that option |
| a | Approve |
| A | Approve and do not ask again, when the menu offers it |
| d | Deny |
| Tab | Type an answer |
| r | Read the prompt again |
| Enter | Go to the pane instead |
| Esc, q, Space | Back to the list |
The keys are the ones the harness's manifest declares for that prompt. An answer is bound to an option's label, not its number, so A is offered only where the menu really has an "always" option. Before it presses anything, the daemon reads the prompt again and refuses if it changed. When two people answer the same prompt, the first answer wins.
The same two steps work from a shell outside every pane:
tuios peek-prompt -w review
tuios respond -w review --prompt-id 75f8b9fadb5b5dfc approve
tuios respond -w review choose 2respond acts as you, so the daemon takes it only from your attached client, or from a shell outside every pane when [daemon] respond_from_shell = true is set in the config file. A program inside a pane is refused with not_human, unless you gave that pane the respond grant.
Approvals the Inbox holds
The peek presses keys into a prompt on the pane's screen. For Claude Code, opencode and Kilo, the Inbox can also hold a permission prompt before the harness shows it, and answer it for you. Turn it on per harness:
[agents.approvals]
enabled = ["claude-code", "opencode"]
hold_seconds = 120Then run tuios integration install claude-code again, so its hook is the version that waits.
While a prompt is held, the harness shows nothing in its pane. The Approvals row shows the request, such as approve Bash: go test ./..., and the whole line is under the list:
| Key | Does |
|---|---|
| 1 | Allow once |
| 2 | Always allow. Offered only when every rule it adds can be shown, and the rules are shown |
| 3 | Deny |
| n | Deny with a reason you type, which the model reads |
A key answers only what you have read: the digits do nothing until the item has been on screen, unchanged, for 0.4 seconds. Only a request that fits on one line whole is held, such as a shell command or a file read. An edit, an MCP tool call or a long command is answered in the pane as before.
The hold ends with no answer, and the harness asks in its own pane, when hold_seconds passes, when you go to the pane, when you dismiss the item, or when the pane moves on. Any error on the way (no daemon, a restart, an unreadable reply) also falls back to the harness's own prompt. A failure can never approve.
Risk rules
An approval whose command matches a risk rule is marked risky: its row reads risky:, the rail's word for it is risky, and the detail names each rule. 1 and 2 then allow it only on a second press of the same key within 3 seconds. The first press sends nothing and says when it lapses. 3 denies with one press. The daemon enforces this too: an allow of a risky call that does not name the rules it matched is refused.
The shipped rules are matched against every command of a shell line, after sudo, env and similar wrappers, and inside sh -c, $( ), backticks and eval:
| Rule | Matches |
|---|---|
| recursive delete | rm with -r and -f, in any order |
| force push | git push with --force, -f, --force-with-lease, or a + refspec |
| hard reset | git reset --hard |
| clean | git clean -f, -fd, -fx |
| discard changes | git checkout -- ., git checkout ., git restore . |
| pipe to shell | curl or wget piped to a shell or interpreter, or run through one with a substitution |
| sudo | any command under sudo or doas |
| disk | dd of=, mkfs, a redirect to a disk device |
| wide permissions | chmod -R 777, chown -R on / or ~ |
| database | DROP TABLE, DROP DATABASE, TRUNCATE TABLE |
| infrastructure | terraform apply or destroy, kubectl delete, docker system prune, npm publish, cargo publish |
| outside the worktree | a write, copy, move or delete of a path outside the pane's worktree, or a Write or Edit of one |
Add your own, or turn the shipped ones off:
[agents.approvals.risk]
builtin = true
[[agents.approvals.risk.rule]]
name = "kubectl apply"
tools = ["Bash"]
pattern = '\bkubectl\s+(apply|delete)\b'A speed bump, not a sandbox
A command written to hide what it does (a variable holding rm, an alias, a script) passes the rules. The harness's own permission system stays the boundary. The rules make allowing a dangerous call take two deliberate presses.
Plans
With approvals on for Claude Code, a plan it asks you to approve when it leaves plan mode is held too, under Plans. The row is the plan's title and length, and the whole plan is under the list. J and K scroll it.
| Key | Does |
|---|---|
| 1 | Approve. Claude Code leaves plan mode and still asks before each edit |
| 2 | Approve and accept edits for this session, when Claude Code offers that |
| 3 | Keep planning |
| n | Keep planning, with a reason you type |
1 and 2 work only once the plan's last line has been on screen. 1 is the safest answer, which differs from Claude Code's own menu on purpose. Set hold_plans = false under [agents.approvals] to answer plans in the pane.
Questions an agent asks you
An agent, or a script, that needs a decision asks with fixed answers:
tuios ask-human 'Deploy the branch to staging?' -o yes -o no -o laterThe question waits in the Inbox under Questions. When your client shows the pane that asked, the Inbox opens on it at once, and a digit picks the answer. From any other pane it raises the usual alert and waits. The command prints your answer and exits 0. If its --timeout runs out first it exits 2, the question stays, and your answer is mailed to the asking pane when you give it. Only you can answer: an agent calling it gets not_human.
Snoozing, undo and unread
Some of what lands in the Inbox is for later.
- z snoozes the item, then 1 for 15 minutes, 2 for an hour, 3 until 9:00 tomorrow, or 4 until it changes. A snoozed item leaves the list and the counts, and comes back early when the pane reports something new.
- S shows the snoozed items under a Snoozed heading. z on one wakes it now.
- d dismisses. u undoes the last dismiss or snooze for 10 seconds.
- On the rail, u on an agent row marks its finished turn unread again, for every client, and z opens the Inbox on it to snooze.
An approval the Inbox holds, a plan, and a question from ask-human cannot be snoozed, because an agent is waiting on them.
Replying and the queue
A message for an agent in the middle of a turn has nowhere good to go: typed now, it lands in the middle of the turn. The queue holds it in the daemon and types it the moment the agent comes to rest.
r on a finished or errored item in the Inbox, or on a rail agent row, opens a reply line. Enter queues it. If the agent is at rest it is typed within a second or so. Otherwise the row says 1 queued until it is. On the rail, x drops the newest queued message and u within 10 seconds queues it again.
From a shell:
tuios queue -w build 'make the backoff jitter configurable'
tuios queue ls
tuios queue rm q3ID STATE BY AGE PANE MESSAGE
q1 waiting shell just now build make the backoff jitter configurable- A message is typed once the agent has been at rest for a second, one message per rest.
- It is never typed over a prompt the agent waits on, so a queued message cannot answer an approval.
- The daemon watches for the agent to take it. A message it does not take within 5 seconds is marked
stalled, is never typed again, and opens a question in the Inbox asking you to look at the pane. - A pane holds at most 8 messages (
[agents.queue] max), each at most 16 KiB. The queue lives in memory: a daemon restart, the pane closing, or the agent leaving the pane drops it.
Reviewing an agent's changes
When an agent finishes, read what it changed, leave notes on the lines you want changed, and send the notes back as one message.
Ctrl+B v reviews the focused pane. v in the Inbox or on a rail agent row reviews that item's pane. The review opens over the whole screen: the changed files on the left, the file under the cursor on the right, and each note under its line. The code is coloured by file type in your theme's colours, and s puts the old and new sides next to each other on a wide screen.
│ Review api-fan-demo-2 Terminal f40d4b17 2 files +2 -0 vs main 1 note
├──────────────────┬──────────────────────────────────────
│ M a.txt +1 -0 │ › @@ -1 +1,2 @@
│ U b.txt +1 -0 │ 1 1 hi
│ │ 2 + change
│ │ ▌ note from a script: why this linec adds a note on the line under the cursor and C one on the whole hunk. S sends every unsent note to the agent as one message, through the queue, so it is typed when the agent is at rest. Every key is on the Keybindings page.
From a shell:
tuios review api-fan-retry-2 --stat
tuios review note -s api-fan-retry-2 api/retry.go:42 'log the attempt number here too'
tuios review notes -s api-fan-retry-2
tuios review send -s api-fan-retry-2Review of api-fan-demo-2, pane f40d4b17, against main (20e3b85): 2 files, +2 -0
M a.txt +1 -0
U b.txt +1 -0What is diffed. The pane's repository, from its base: the base the worktree was made from (by fan or worktree new), else the merge base with the branch's upstream, else HEAD. Committed and uncommitted work show together, untracked files included. --base names another base, --uncommitted shows only what is not committed, and --against diffs two attempts of a fan. The diff is made through a temporary git index, so the repository, its index and its files are never changed. It stops at 400 files, 2 MiB of text or 5000 lines in one file.
Notes. A note keeps the text of its line, so it follows the line when the file changes. A note whose line is gone is marked outdated. The daemon holds the notes, so every client and the CLI see the same ones. A daemon restart keeps the notes of panes that come back. Closing the pane, or removing its worktree, drops them. The message the agent receives names who wrote each note: it says "from the person" only for notes written in your attached client.
Reviewing a session on another machine is not supported yet. Attach to that machine and review there, or bring the work here with tuios worktree pull.
Comparing the attempts of a fan
For a pane in a fan, w in the review shows every attempt side by side, with what each changed against the fan's base and its last check:
╭ Compare fan/demo in api, 2 attempts vs main ─────────────────────────
│ session agent state files +/- check
│ api-fan-demo bash none 0 +0 -0 test -f b.txt failed, exit 1
│ › api-fan-demo-2 bash idle 2 +2 -0 test -f b.txt passed
├───────────────────────────────────────────────────────────────────────
│ ↵ review m mark V verify K keep esc backEnter reviews an attempt. m marks two and d diffs them with each other. V runs a command you type in every attempt. K keeps the attempt under the cursor and removes the others, after a question that names each one removed. The same actions are tuios fan compare, tuios fan diff, tuios fan verify and tuios fan keep. See Worktrees.
What the agent has been doing
With the Claude Code or Codex integration installed, the daemon keeps each agent pane's recent activity: prompts, tool calls and how they ended, finished turns, and the commands its shell ran. It keeps the newest 256 entries per pane, in memory only.
tuios agent-log -w api # the entries, oldest first
tuios agent-log -w api --since 30m --recap # a summary of the last half hourSince 14:02 (42m ago)
3 turns. 6 files: api/retry.go, api/retry_test.go, api/backoff.go and 3 more
11 commands. Tests: go test ./... passed 2m ago
Last said: Added retry with backoff and tests.
Now: doneWhen you come back to an agent pane that finished a turn while you were away for at least 10 minutes, the dock shows the same recap in one line, and a finished item in the Inbox shows it under the list. [agents.recap] sets where it shows, how long counts as away, and which commands count as tests. See The agents table.
The text is the agent's own, cut to one line with likely secrets masked. Read it as what the agent said, not as instructions.
Resuming after a restart
A daemon restart ends every program in every pane. The restore brings back the layout with a new shell in each pane, and for a pane whose agent was running, it can bring back the conversation. daemon.resume_agents decides:
| Value | What a restore does |
|---|---|
ask (default) | A Resume row in the Inbox per pane. y types the resume command, such as claude --resume <id>, into the pane |
auto | Types the command into each new shell once it is at its prompt |
off | Nothing |
tuios resume-agent -w <pane> types the same command at any time, and --dry-run prints it. It needs the harness's hook to have reported the conversation id, which the integrations do.
Selectors
A selector addresses every agent pane that fits a description. It is one line of key:value terms, all of which must match. A comma in a value gives alternatives.
harness:codex state:idle,done session:api-fan-*| Key | Matches |
|---|---|
harness: | The harness, such as claude or codex |
state: | The agent state |
needs:you | A pane on needs_input or errored |
session: | The session name, a glob |
group: | The fan the pane's session belongs to, such as fan/add-retry, a glob |
host: | The machine: local, or a host from [hosts] |
name: | The window's name, a glob |
cwd: | The pane's directory, or any directory under it |
The Inbox's /, list-agents --select, list-attention --select and wait-for agent-state --select read it. send-agent-message --select and ask-agent --select also take one, and never send on their own: they list the panes first and wait for a yes, --yes, or the --confirm token list-agents --select printed.