Agent chats run commands, and stop to ask first

The four tools an agent chat has -- shell_run, file_read, file_write,
file_list -- and the mode table wired into the loop that decides which of
them stop for approval. Verified end to end against a real Kali container
over SSH: the card shows the command, allowing it runs it there, and the
file it writes is visible from outside.

The mode is enforced in `_authorise`, in the generation loop, server-side,
keyed on each tool's declared risk. Not in the prompt: a model is told
which mode it is in so it behaves sensibly, but everything it reads -- a
web page, a README, the output of the last command -- is untrusted, and a
rule written only into a system message is one a poisoned file can argue
with. Within an agent chat every call goes through the table, including
the built-in ones, because notes_edit writes and Plan mode meaning "look
but do not touch" has to mean that too.

Two things this turned up.

The runners re-check the mode as a backstop, and that backstop refused the
very thing a person had just approved -- the mode says "ask", and asking
was exactly what happened. Approval is now threaded per call, on a copy of
the context, because a round runs its calls together and only some of them
were allowed.

And the harness said nothing at all, because `registry` maps an offered
tool *name* back to a family and did not know the agent tools existed. So
shell_run resolved to no family and the fragment naming the machine, the
directory and the mode was never admitted. The same omission cost custom
tools their guidance once already; there is a test for it now.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jaroslav Beneš
2026-08-02 00:08:48 +02:00
parent 6a849dc1ec
commit b6aab8de55
14 changed files with 1579 additions and 29 deletions
+28
View File
@@ -893,3 +893,31 @@
box-shadow: 0 0 0 2px color-mix(in srgb, var(--success) 25%, transparent);
}
.unread-dot[hidden] { display: none; }
/* --- Agent chats ----------------------------------------------------------- */
/* In the header, not the settings panel: the mode is the difference between
being interrupted and not, and it is looked at constantly. */
.agent-bar { display: flex; align-items: center; gap: var(--sp-2); }
.agent-bar__where {
display: inline-flex;
align-items: center;
gap: var(--sp-1);
color: var(--ink-muted);
font-size: var(--text-xs);
white-space: nowrap;
max-width: 14rem;
overflow: hidden;
text-overflow: ellipsis;
}
/* Chat or Agent, on the new-chat composer. */
.composer__kind {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: var(--sp-2);
padding: 0 var(--sp-2) var(--sp-2);
}
.composer__kind-agent { display: flex; gap: var(--sp-2); flex: 1 1 18rem; min-width: 0; }
.composer__kind-agent .input { flex: 1; min-width: 0; }
.select--sm, .input--sm { height: calc(var(--control-h) - 0.35rem); font-size: var(--text-xs); }