feat(web): talk to the plan's coding agents in a planning call
In a planning call the developer can ask how a coding agent is doing, or tell one what to do, and Luke answers or steers the right agent.
The voice brain's three tools
Three planning tools in the authored-tool style (apps/web/server/hosted/coding-agent-tools.ts), registered through the planning list so the writer holds their rows and every turn is offered them:
list_coding_agents: each agent of the plan with its id, its title (#1959's, the model id where it has none yet, as the tab falls back), model and effort, status, the branch its row holds (written as a turn ends in the sandbox, #1965), and one line of what it last did; GitHub is asked nothing.read_coding_agent: one agent's status, what its branch published as GitHub holds it (coder-host/published.ts'sagentPublished, read afresh for the one agent; omitted where GitHub could not be asked), and a bounded tail of its newest turns, asSaid: ā¦andRan: ā¦entries (20 entries of 300 characters at most), inputs only and never a call's output.message_coding_agent: text to one agent, delivered exactly as the message box's steer is: it steers the turn under way or opens one when idle, and the line awaits its turn under aclientKeythat is eve's own id for the tool call, so a call eve runs again sends once.
The route's send path moved into coder-host/steer.ts, which the route and the tool both call, so there is one steer path.
The Work tab's vocabulary names the three calls as kinds of their own (PLAN_WORK_TOOL on the wire, CALL_KIND and its verbs and icons in the desktop), since plan-work.test.ts holds every offered tool to a kind; the voice's plan-work.ts reads a read's agent id and a message's text as the call's subject. The recorded JSON Schema of the plan-work frame (packages/hosted/fixtures/json-schema/live-contract-planWorkFrameSchema.json) was regenerated under LUKE_UPDATE_FIXTURES=1; its diff is the three new kind names in the tool enum, twice (the turn's parts and a subagent's).
The trust path
A voice turn runs as the deployment acting for the account, so the coder channel now walks the same deploymentActor the planning channel does, first in the walk, over a table of its own (CODER_DEPLOYMENT_TURNS): the typed line alone, since the message is delivered as one the developer typed. Ownership is checked at the door like every other caller's: the deployment is admitted only on a session the named account's agent recorded.
eve steers a running turn only with a message whose principal is the turn's own, read as the tuple of authenticator, type, and id (eve 0.74.0, execution/session/input-queue.js's isSteeringDelivery), and an agent's turn runs under the developer's bearer, so a message from the deployment's own principal would queue behind the turn. Dean chose to steer: the deployment actor now says whose identity it presents (DEPLOYMENT_PRESENCE). The planning channel's presents the deployment, as before. The coder channel's presents the account: the voice steer is minted as the developer's own tuple, with the deployment named as the carrier in the luke:carrier attribute, which eve's identity does not read and the record keeps. The owner check and the one-row table are unchanged; the channel comment, the actor's comment, and the authenticator's comment say so.
Behavior
The planning instructions gain a section on the plan's coding agents: use the tools when the developer asks about or addresses an agent; on an instruction, send it when it is clear which agent is meant and say briefly what was sent, and queue a question when it is ambiguous; on status, summarise in a sentence or two; never put a credential into a message, a tool input, or the return. The voice's delegation policy (packages/live/src/instructions.ts) names the three tools so the voice delegates those asks.
Tests
hosted-brain-host-tools.test.ts: the list over two agents with their status, the row's branch, and latest activity; one read as a tail bounded to 20 entries; an agent read only on its own plan; a message steering the agent's session over a fake eve beside the fake GitHub, as the deployment under the cron secret for the account, with the line awaiting its turn under the call's key and the same call sending nothing again; another account's agent refused; a deployment with no secret sending nothing.coder-agent.test.ts: the coder channel mints the deployment's typed line on the named account's own session as the developer's own principal (authenticator, type, id), which is what eve steers the running turn on, with the deployment as its carrier; a spoken turn, a stream, and another account's session are refused.hosted-planning.test.ts: the offered tool list.
A test:agent scripted-model case was not added: the eval needs a database and eve's coder service reachable from the planning service, which the fixture has no origin for, so the only thing it could assert is the tool refusing for want of a service.
Coordination
The "agent questions come back to the plan" PR's tool and plan writes are untouched; Luke can relay the developer's answer with message_coding_agent.
Verification
./scripts/check.sh exits 0. The desktop change is three rows in the Work tab's call-kind tables (verb and icon), with no new component, so there is no screenshot; verify.sh was waived for this rollout, and CI could not verify the Mac.
š¤ Generated with Claude Code