August 21, 2026 — Matteo Merola

Release Notes: v0.34 – v0.38

Five releases. The big one is delegation growing from a single helper into a squad that works in parallel — plus live read-only web transcripts, a system prompt on a diet, and an Inspect tab that finally shows what is actually being sent.

v0.38 — Fan-out: one call, several teammates

Subagents have existed since v0.20-something, but they ran one at a time: the agent spawned a helper, waited, spawned the next. For work that splits into genuinely independent pieces that is just a slow loop. spawn_subagents (plural) takes a list of tasks and runs them in parallel in a single tool call — wall-clock is roughly the slowest subtask, not the sum of them.

Results come back in request order, one entry per task, each with its own status, steps, tokens_used, stopped_reason, summary and full result. Partial failure is per-subtask: one helper erroring or being cancelled never sinks the batch. Fan-out is top-level only — a subagent can still delegate further with the singular tool, up to recursion_depth, but it cannot start a fan-out of its own. That single rule is what keeps the spend tree from branching exponentially.

The agent also no longer waits to be asked. A standing <delegation> block in the prompt gives it the test — parts that each need several tool calls and don’t depend on each other get fanned out; quick lookups it just does itself. Before that block, delegation only happened when the owner said the word “subagent” out loud: the orchestration skill explaining when to fan out was one line in an index of fifteen, so the model had to already suspect the answer to go read it.

telegram — a fan-out in flight
you  > compare the pricing, the docs and the self-host story
       for the three vendors on my shortlist

bot  > 🧩 Delegating 3 subtasks…
bot  > ✅ Subagent pricing — done (42s, 23k tokens)
bot  > ✅ Subagent docs — done (51s, 31k tokens)
bot  > ⚠️ Subagent self-host — error (18s, 7k tokens)

you  > /subagents
bot  > Active runs (oldest first)

       research      running   0:58   12 steps   [Cancel]
       └ spawned by research: deep-dive
                     running   0:21    3 steps   [Cancel]

Those notes are notifications, not messages. They ride the channel send path directly, so they never enter history, never reach the model as input, and can never trigger a turn. Every spawn announces itself at every depth (🧬 pricing spawned subagent deep-dive for a nested one), and /subagents now reads as a tree — oldest first, so parents sit above the children they spawned, each nested run carrying a └ spawned by … line. Cancelling a parent cascades to its subtree, and /stop reaches children too instead of letting a fan-out run to completion after the turn it belonged to has already aborted.

Budgets charged as you go

The first budget design reserved tokens up front and refunded the unspent remainder. It was correct and it was miserable in the field: spawns got refused before they had done anything, the model read the refusal as “delegation is broken” and fell back to doing everything itself. The reservation machinery — refunds, pool normalisation, fair-share splitting across a fan-out — existed only to make reservations tolerable, so all of it went.

What replaced it is charge-as-you-go: the per-turn pool is debited one LLM round at a time, as it is actually spent. A spawn is refused only when the spawn count is used up or the pool is already dry — never pre-emptively. When the pool drains mid-run, runs stop gracefully between rounds with stopped_reason: "turn_budget", and a stopped run spends one final tool-less round writing up what it found rather than throwing away work already paid for. The trade-off is honest: concurrent children can overshoot the pool by at most one round each. A bounded overshoot beat the complexity of preventing it.

max_concurrent became a real per-chat gate covering every run, sync or background — extra spawns queue instead of failing, and each chat has its own pool so one conversation’s fan-out can never starve another’s. A late fix also stopped budgets being charged for the whole re-sent prompt on every round instead of the new tokens, which was quietly draining pools several times faster than the numbers in the admin card suggested.

Delegation trust lists

spawnable_agents on an agent is a list of slugs it may delegate to. Left empty, the old behaviour holds: any agent may be named, and the child is narrowed to the intersection of both scopes — inherit, never widen. Filled in, it becomes a curated team: only the listed agents may be named, anything else is refused with the team in the error so the model can self-correct, and a listed specialist runs with its own tools, skills, secrets and account bindings.

That is the point, and also the thing to be careful with. The list is the owner’s explicit trust grant, which turns delegation into a deliberate capability handoff rather than a leak — but a subagent runs with system semantics and no per-action approval, so the spawn approval (which now names the target agent and the task) is the one human gate on the whole run. On agents reachable by untrusted input — group chats, GitHub webhooks — every entry in the list is a capability that input can reach for. Keep those lists short.

Two scoping bugs surfaced while building it, both of the kind that only bite at depth: narrowing dropped the trust list, so an anonymous grandchild of a team-scoped agent escaped back into the any-agent regime; and collapsing two disjoint non-empty scopes produced an empty list, which by the allowlist convention means all — a narrowing that widened.

The agent answers for its own helpers

A finished background batch used to be handed to a cheap fast model, which wrote a one-liner into the chat and filed a digest as an assistant turn the agent never wrote. The agent was never re-invoked and never saw what its own helpers found — it couldn’t act on the result, couldn’t answer a follow-up beyond the digest, and the words the owner read were a small model’s in the agent’s voice.

Now the batch routes back into the conversation it came from, through the same steering machinery the GitHub webhook already uses: a turn already running for that chat gets it injected between tool rounds, an idle chat runs it as its own turn. Either way the agent reads the raw results and its own reply is the assistant turn. The block is pre-framed as its own delegation returning rather than as a fresh instruction from the owner — a returning dispatch must not look like a new order. Wake loops are closed structurally instead of with a threshold: a turn started by a landing batch may not spawn background runs, so every chain is at most one wake deep.

Generated media travels properly now too. A synchronous child shares the spawning turn’s attachment queue, so an image it generates rides out natively with the parent’s reply; a background run has no live turn to deliver through, so it hands back an absolute saved path. Previously the path was relative to the child’s working directory, the parent couldn’t find it, and simply regenerated the image — paying twice for the same picture.

v0.38 — Live read-only web transcripts

Send /weburl and the agent replies with a link to a read-only web page showing that conversation, updating live. The token in the path is the only credential — 32 urlsafe bytes, minted once per channel/user/chat triple so the link stays stable and shareable, and scoped to exactly that context. Tokens live in the history database, which is already keyed by the same triple, so there is no new store and nothing to configure.

The page flattens whatever the context recorded into typed entries — text, reasoning, tool call, tool result — so it renders chain-of-thought and tool activity behind collapsed chips without knowing anything about providers. The system prompt lives in its own table and never passes through. Tool arguments and results render through a small collapsible JSON tree instead of one long unwrappable line, nodes built with textContent throughout so a payload can never inject markup. There is a share button that copies the URL, a translucent live pill at the bottom of the page, and a 💭 toggle for readers who want every reasoning block open by default — kept viewer-side in localStorage, because it is a reading preference that differs per person looking at the same link. Tool activity deliberately stays collapsed; the reason to expand reasoning is not the reason to expand a 400-line tool result.

v0.38 — The prompt on a diet, and an Inspect tab that shows it

Measured before touching anything: about 11k tokens reached the model before the user had said a word, and a 10.4k-character per-turn preamble was prepended to every user message and persisted, so each turn stored another copy of it.

Task reflection was deleted outright rather than trimmed. It injected 3,465 characters every turn — the last twelve rows, no relevance ranking, no scope, no expiry — and the lessons had drifted into contradicting the live prompt, one of them insisting the browser tool was unavailable while the tools block advertised it. The rest was de-duplication: “read the skill first” was stated three times, the tool blocks re-explained what the skills already carried and shrank to discovery pointers, and character.md stopped re-litigating harness policy and went back to being personality. Static prompt from ~13.1KB to 10,396 characters; preamble from 10,421 to ~6,810 characters per turn.

The Inspect tab rendered neither tool calls nor reasoning content, so on DeepSeek every tool-calling turn showed as an empty card — 137 of the last 400 messages. It now renders role-badged message cards, tool calls with pretty-printed arguments, results paired to their call by id with an output preview, and thinking in its own collapsed block, with the system prompt and tool schemas showing their size, which is rather the point of the screen. Live subagent runs moved here from the Jobs tab, grouped by fan-out with children indented under their parent, each with a cancel button; every context row has a runs button that filters the panel to that conversation.

Reliability

A fan-out multiplies every background-inference weakness by the number of children, which is how the embedder problems surfaced. Three fixes, one commit:

Elsewhere: a cancelled subagent now reports what it found instead of returning nothing, a dead backend stops the loop rather than burning the step budget on retries, scheduled job context is cleared before each invocation, the Telegram Bot API is blocked from the agent’s shell, and max_tool_rounds now defaults to 100 rather than 50 — the old ceiling was being hit by legitimate long tasks.

v0.34 – v0.37 — what came before

Full changelogs for each release are on GitHub Releases.