Work handed to a second model, which may not ask

subagent_run gives a self-contained piece of work to a helper carrying the
parent's connection, directory, model and effort, and hands its answer back as
the tool result. The mechanism is the one scheduled runs already use -- a hidden
chat, one turn, wake_chat, and a poll -- so tools, rounds, budgets, metrics and
steps all work with no second implementation. The two alternatives were
rejected where they had already been rejected once: a nested Generation is two
replies writing one transcript, and a one-shot complete() has no tools, which
schedule/runner.py records as useless for exactly this case.

Every restriction is a property of the child's row, applied by resolve_tools
after the gates, because a rule that lives in a system message is one a page the
model just read can argue with. No questions, no recursion, nothing that writes
unless the call asked for it and the parent's own mode would not have stopped
first, and commands only from a fixed read-only list -- in every mode including
Auto, because the task text can have come from a page.

Withdrawing ask_user turned out to be half of "nobody is watching". An approval
still built a card nobody could see and parked the reply until approval_timeout,
which from every screen is the feature not working. Chat.unattended is the
question now, and not the kind: _authorise answers with a refusal instead. A
scheduled task's chat had the same hole and is covered by the same flag.

Three bounds, counted where each is knowable: per reply on the parent's
Generation, instance-wide in a set a restart clears, and per helper in settings
of its own so one runs out of room long before the reply that asked. Past the
clock the helper is stopped rather than abandoned, so a partial answer comes
back with a sentence saying so.

Also: four gates had shipped into the scope menu with no name, taking the first
tool's label instead -- the canvas switch read "Canvas written". There is a test
that refuses a family without one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jaroslav Beneš
2026-08-06 15:05:30 +02:00
parent 6fcb9c9892
commit e16bede85b
20 changed files with 2035 additions and 30 deletions
+27 -16
View File
@@ -9,7 +9,7 @@ reasoning, tool calling with web search, custom HTTP tools and MCP servers,
agent chats that work on a machine over SSH, a knowledge library, notes, memory
and skills, speech in and out, image generation over ComfyUI, users and groups,
model administration, installable as an app, reports, messages, and scheduled
work that runs on its own. 1914 tests, `ruff` clean.
work that runs on its own. 1949 tests, `ruff` clean.
What remains before the first stable release is written out below, in phases,
under [The road to 1.0.0](#the-road-to-100).
@@ -454,18 +454,29 @@ seen working.
- [x] A legend on the workflow editor saying what each placeholder fills, what
it lands as, and what it resolves to right now
### Phase 3 — subagents (`0.9.2`)
- [ ] **A model can delegate.** A bounded, unattended agent with the parent's
connection, directory, model and effort, whose findings come back as the
tool result. Built on the mechanism scheduled runs already use, so it gets
tools, rounds and budgets rather than a second loop
- [ ] **Safe by resolution, not by instruction** — no `ask_user`, no recursion,
read-only tools by default, and in an agent chat a mode that cannot run
what it was not given
- [ ] Guidance for the two uses that differ: fanning out across a research
question, and reading a codebase
### Phase 3 — subagents (`0.9.3`)
- [x] **A model can delegate.** `subagent_run` hands one self-contained piece of
work to a helper carrying the parent's connection, directory, model and
effort, and gives its answer back as the tool result. Built on the
mechanism scheduled runs already use, so it gets tools, rounds, budgets,
metrics and steps rather than a second loop
- [x] **Safe by resolution, not by instruction** — no `ask_user`, no recursion,
nothing that writes unless the call asked and the parent's mode allowed
it, and commands only from a fixed read-only list in every mode including
Auto, because the task text can have come from a page the parent read
- [x] **An unattended chat refuses instead of waiting.** Withdrawing `ask_user`
was only half: an approval still built a card nobody could see and parked
the reply for fifteen minutes, which from every screen is the feature not
working. The same flag now covers a scheduled task's chat, which had the
same hole
- [x] Its own bounds — per reply on the parent's `Generation`, instance-wide in
a set, and per helper in settings of its own, so one runs out of room long
before the reply that asked does
- [x] Guidance for the two uses that differ: fanning out across a research
question, and reading a codebase — plus what a helper reads about being
one
### Phase 4 — rebranding and customization (`0.9.3`)
### Phase 4 — rebranding and customization (`0.9.4`)
- [ ] **An instance can be somebody else's.** Name, logo, favicon and PWA icons,
a tagline, and the Middle-earth strings as editable data — defaults in
code and overrides in the database, so a later release still improves the
@@ -473,7 +484,7 @@ seen working.
- [ ] Global CSS overrides, and a custom theme defined as a set of tokens rather
than a stylesheet, since no component hard-codes a colour
### Phase 5 — extraction, embeddings and hybrid search (`0.9.4`)
### Phase 5 — extraction, embeddings and hybrid search (`0.9.5`)
- [ ] **Extraction has settings** — upload size, image edge, PDF pages,
extracted characters, orphan age, which extensions count as text
- [ ] **A dedicated embedding model**, chosen from the models flagged for it
@@ -481,7 +492,7 @@ seen working.
call the retrieval service already is. No model chosen means exactly the
keyword search there is today
### Phase 6 — permissions, quotas and sharing (`0.9.5`)
### Phase 6 — permissions, quotas and sharing (`0.9.6`)
- [ ] **"What can this user actually do?"** answered on screen, from the
resolution that already computes it
- [ ] Membership edited from one side; a searchable, paginated user list
@@ -492,14 +503,14 @@ seen working.
- [ ] Sharing as its own action with a search box, a shared-with-me filter, and
reports shareable. Sharing stays read-only
### Phase 7 — packaging and updating (`0.9.6`)
### Phase 7 — packaging and updating (`0.9.7`)
- [ ] **Docker**, with the data on a volume and a TLS proxy expected in front
- [ ] **An LXC bootstrap** that runs the existing installer in a container
- [ ] **Updating without a shell** — a page that says what is running, what is
available and what changed, and a button answered by an opt-in systemd
helper, because the service user cannot restart itself and should not
### Phase 8 — audit and finalization (`0.9.7` … `0.9.9`)
### Phase 8 — audit and finalization (`0.9.8` … `0.9.9`)
- [ ] Security review over the whole accumulated diff
- [ ] A sweep for the failure this codebase keeps cataloguing: a control that
looks like it works — a verb against a route that does not serve it, a