How I built sub-agents for my desktop AI companion: isolated children, one capped envelope, and no grandchildren
Ankita is my open-source desktop AI companion — a little companion, a lot more possible. It lives on my machine, runs on my models, and does real work in my workspace: reading code, editing files, running checks. Sooner
Ankita is my open-source desktop AI companion — a little companion, a lot more possible. It lives on my machine, runs on my models, and does real work in my workspace: reading code, editing files, running checks. Sooner or later, any agent like that hits the same wall: one conversation trying to do three independent jobs at once. The parent's context balloons, the work interleaves, and a long task blocks everything behind it.
The obvious fix is sub-agents — hand a piece of work to a child. The dangerous version is giving the child everything the parent has. I built the opposite: a child that gets almost nothing, runs its own tool loop, and comes back with exactly one capped envelope.
The decision first: the parent sees only the envelope
A child in Ankita sees only its brief — the prompt passed to delegate_task. It cannot ask questions and cannot delegate. If the brief is ambiguous, it picks the most reasonable interpretation and says so. When it finishes, the parent receives one result envelope: status, a summary capped at 2,000 bytes, the full final answer capped at 48,000 bytes by default, the tools used, token usage, duration, and a transcript path. The parent's history stays small because the child's intermediate work never enters it.
That cap is the whole point. An uncapped child return is just context growth with extra steps. The child must write a complete final answer — what it did or found, evidence with file:line references, files changed, commands and tests run with their results, and remaining risks — because that answer is all the parent will ever see. And the parent must verify the report before claiming the work is done; the envelope is a claim, not proof.
Three presets, and the worker is a lock
There are exactly three child types, defined in src/core/subagents/registry.mjs:
-
explore — read-only research:
read_file,list_dir,search_files,glob, read-onlygit, plus web search and fetch. It cannot edit, run commands, or change state. - reviewer — read-only code review for correctness, regressions, and missing tests, same toolset.
- worker — implementation with every local tool except the parent-only ones, run one at a time.
Read-only children overlap freely, because the parent loop already batches contiguous read-only calls. Workers are the opposite: a mutation barrier. One worker runs at a time across the whole process, keyed by the canonical workspace path — symlinks, junctions, and Windows case variants share one lock, so two workers can never edit the same workspace through different spellings of its path. The extras don't fail; they wait.
Isolation is a blocklist, not a vibe
Children are isolated by what they cannot touch: no memory, no browser, no MCP, no skills, no scheduling, no project tools, and no recursion. The blocked set in the registry is explicit — delegate_task itself, browser, schedule, watch, mcp_manage, composio, skill, project, remember, recall, find_tools — so a child cannot quietly re-acquire a parent capability through a side door. Children run with memoryEnabled: false: pinned personal facts never enter a child prompt, and the remember/recall tools are blocked outright.
Delegation isn't even always visible. The parent discovers it through find_tools — query for it, and the subagents group becomes callable. Background jobs, the daemon, Telegram DMs, and one-shot routines have no sub-agent host, so the group stays hidden there. There is no unattended delegation: a child only ever runs inside an interactive parent turn.
Child tool calls keep the parent's approval gate, and an approval requested by a child is labelled with its type and description, so you always know who is asking. Audit records carry subagent and subagentType attribution. Child transcripts are written as subagent-<id>.json, scrubbed with the same secret cleaner as normal sessions, pruned to the newest 200 per session store — and they can never overwrite a teammate-<id>.json session file. Briefs must never contain secrets, because the child writes its transcript to disk.
Budgets are bounded on every axis: depth 1 by default, up to 8 concurrent children, 24 per turn, 100 tool steps and 200 tool calls per child, a 30-minute default timeout, 1M tokens, and a 48KB result cap. A single delegate_task call can tighten those with max_steps and timeout_ms — it can never raise them.
What this deliberately doesn't do yet
No background or async delegation — no spawn/check/wait. No worktree-isolated parallel workers. No resuming or forking parent context into a child. No custom user-defined presets. Those are listed as deferred in docs/subagents.md, not silently missing. The test suite (test/core/subagents.test.mjs plus desktop state and tool-loading suites) covers presets, discovery parity, read-only scoping, envelopes, transcripts, approval attribution, depth and fan-out guards, parallel overlap, and worker serialization including cancellation.
The question I keep coming back to
The worker lock is the decision I'm least sure about. Serializing all workers per workspace path is simple and safe — no merge conflicts, no two writers racing — but it throws away the parallelism that made sub-agents attractive. The alternative is worktree-isolated parallel workers that merge when they finish: strictly more powerful, strictly more dangerous. A failed merge at 2am is worse than a slow queue.
I chose the lock. Tell me I'm wrong — if you were building this, would you take the merge risk for parallel workers, or is one-writer-per-workspace the honest ceiling for a companion that edits your real files? Where exactly would the lock break first?
Ankita is open source (MIT) at github.com/akyourowngames/A.N.K.I.T.A, and the full design is in docs/subagents.md. If you've built agent delegation before, I'd genuinely like to hear how you drew the isolation line.
Originally published by Dev.to AI. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.