How I built two-way messages for my desktop AI companion's sub-agents: 8,000 bytes, 64 queued, 0 interruptions
Ankita AI is a desktop AI assistant by Krish Verma - a little companion that lives on your desktop and gets things done while you're away.
One of its tricks is delegation: the parent agent spins up a sub-agent to research a codebase or run an edit-test-fix loop in its own isolated context. Until this week, that delegation was fire-and-forget. The parent sent a brief, waited for a final answer, and found out at the end whether the brief had been wrong all along.
If the child hit an ambiguity halfway through, it had two choices: guess, or stop. Both are bad. So I built two-way messaging between parent and child - merged today as PR #77 - and the interesting part is not that messages exist, it's where they are allowed to land.
One control tool, four verbs
Both directions run through a single tool, subagent_control, with four actions:
listsendwaitcancel
The parent sends with subagent_control(action="send", subagent_id="<returned child ID>", message="Also inspect cancellation.") and waits with wait_ms=1000.
The child sends back with subagent_control(action="send", subagent_id="parent", message="...") - and the runtime enforces that children can only send to their parent. Anything else gets an error, not a silent drop.
Three numbers bound the whole design
Each message is capped at 8,000 bytes. Each recipient holds at most 64 queued messages, and a full mailbox rejects new sends explicitly instead of dropping them quietly.
And wait is bounded: it defaults to 1 second and caps at 30 seconds, returning either the child's terminal envelope or its current status.
A parent never blocks forever on a child, and a chatty child cannot flood the parent's context - 64 messages at 8,000 bytes is a ceiling the budget can reason about, not a hope.
Delivery only happens between decisions
The decision I argued with myself the longest is the delivery point. Messages are delivered once, at the next model decision, after any active tool replies have landed.
The agent drains both inboxes before its next turn and injects them as a user-role message tagged [Sub-agent inbox] - labelled as agent traffic, never pretending to be something the user said.
That is deliberately not an interrupt. A child mid-way through a long tool loop will not see your message until its next decision point, and a parent mid-tool-call will not be yanked out of it either.
The alternative - interrupting a running tool call - would mean cancelling work that might be a file write halfway through, and then explaining to the model what happened to its own half-finished call.
The cost of my choice is latency: a correction can sit unread for a whole tool loop. The benefit is that nothing is ever torn down mid-action, and the transcript stays a clean sequence of decisions and replies instead of a pile of interleaved fragments.
Two companion rules make the channel safe to actually use
Before a final reply, the parent automatically waits for outstanding background work and processes messages or results as they arrive - and drafts discarded while waiting for a child stay out of the saved history, so an abandoned half-sentence can never reappear after a reload.
And a parent that edits files while a background worker holds the workspace now joins the same canonical workspace queue through the shared tool host, including path aliases - the old behaviour let a parent edit overwrite either agent's changes.
Reads and mailbox controls stay available while the lock is held, so messaging never waits on a file write.
What the numbers actually showed
The focused backend messaging run passed 35/35. A real loopback HTTP streaming run emitted MESSAGING_HTTP_OK children=2 messages=6 requests=11 pending=0 - two children active while the parent sent replies.
A CLI subprocess check launched the real chat.mjs entry point and emitted MESSAGING_CLI_OK directions=2 waited=1 requests=10 exit=0.
The companion and background-messaging check passed 12/12, and the final complete serial gate for the whole PR ran 1,899 tests with 1,898 passes, zero failures and one platform skip.
The honest hosted run is the one I like most: eight real HTTP 200 model requests, a parent that discovered delegation, received a running handle, sent an updated price, received the child's preliminary finding, waited for its terminal receipt, and used the revised total 33 in its final answer. The child finished after 21,150 ms.
And the honest caveat, recorded in the verification notes: the model mentioned the revised price 11 as a prohibited assumption in its initial brief, so this sample does not prove clean changed-detail secrecy. The channel works; the secrecy claim is still unproven.
The desktop panel and the terminal both show message direction - "Parent to agent" and "Agent to parent" - while a run is live. The live list is ephemeral; the child's transcript keeps its delivered inbox entries and sent-message tool calls, so the conversation is auditable after the fact.
The limits I'm not hiding
Delivery between decisions means a correction can wait a whole tool loop. A send receipt means the message was accepted into the mailbox, not that the recipient has read it. A queued child's messages wait until it starts running. Restarting the app does not resume a running child. And the 30-second wait cap means the parent polls; it never parks indefinitely on a stuck child.
If you want to read the code, the bounds live in src/core/subagents/registry.mjs, the delivery path in src/core/subagents/runtime.mjs and src/core/agent.mjs, and the full lifecycle in docs/subagents.md.
Ankita is open source (MIT) at github.com/akyourowngames/A.N.K.I.T.A, and the project site is ankita-sable.vercel.app.
Tell me I'm wrong: is delivering messages only between model decisions the right line, or should a parent be able to interrupt a child mid-tool-call - and where exactly would that break first? What would you do with a child that is three tool calls into a file edit when the correction arrives? - Krish Verma, creator of Ankita AI. Feedback welcome, especially if you can break the design.
Comments
No comments yet. Start the discussion.