B6. The coding-agent Eve service, with the Start, list, messages, and Stop routes
Merged
DA
11d ago+4613+4613+0123456789012345678901234567890123456789-235-235-012345678901234567890123456789
What this is
The second Eve service beside the planning brain, running coding agents, and the four routes the desktop (B7) starts, lists, reads, and stops them through. Base is coding-agents: B3 and B4 are merged, and this branch carries a merge of the latest coding-agents.
The service (apps/web/coder/)
- A second eve app root built as the
coderservice inapps/web/vercel.json, at eve's named mount/eve/coder/v1/*: the first rewrite carries it into the service, the service's oneroutesentry turns it back into/eve/v1/*with arequest.pathtransform, and its build exportsEVE_PUBLIC_ROUTE_PREFIX=/eve/coder. That is exactly what eve's own Vercel integration emits for a named agent, so eve's door regexes are untouched.tests/vercel-services.test.tsholds it;tests/eve-layout.test.tsnow builds both roots. - Tools:
defaultTools: false, with eve'sbash,read_file,write_file,glob,web_fetch,web_searchand the code extension'sapply_patchandgrepauthored one file each undercoder/tools/. The extension itself is not mounted, so neither itsworkersubagent (AI Gateway model) nor itsghtool (Vercel Connect) is. The authored set is exactly what the store writer registers (coder-host/tool-set.ts), which the agent test holds. - Model per step (
coder-host/model.ts):defineDynamiconstep.startedreads the agent's row,providerModelOfturns the catalog id into the provider's, and the step is told@ai-sdk/anthropiconANTHROPIC_API_KEYwithproviderOptions.anthropic.effort, or@ai-sdk/openai's Responses model onOPENAI_API_KEYwithproviderOptions.openai.reasoningEffort, with the context window the catalog lists (CatalogModel.contextWindow, new) or a fallback. Nothing goes through AI Gateway. - Limits:
maxInputTokensPerSession: false, no other caps; compaction is eve's default. - Sandbox (
coder/sandbox.ts): one Vercel Sandbox per session at the 24-hour maximum, prepared withinstallCodeToolingplus pnpm and Python beside the image's Node and git, open internet. As it opens, the repository is checked out whole at its default branch through B3's firewall-scoped clone (cloneRepository, extracted from the planning checkout), on a tokenrepositoryWriteTokenmints withcontents: writeandpull_requests: writefor that one repository; the token is then set at the firewall withauthenticateGitHubfor the session's ownghandgit. It never enters the sandbox. The store hook renews it at the firewall after 50 minutes, since a token lasts an hour. - Door: the shared
ownedAuth/messageAuthwith the developer's own bearer as the only caller (coder-host/channel.ts). A Start and a Stop come from routes still holding the bearer, so eve's client gained a bearer-shapedEveCallerand amountoption; no deployment actor acts here. Follow-ups queue. - The relay is shared, not copied:
brain-host/relay.ts's ask record and Stop carrier are now one optionalasksseam, and the coder host composesStreamRelaywithout it. The turn row also recordsreasoningEffortnow. - The sandbox stops as soon as a turn ends (completed, failed, or cancelled) in the
*hook, where a sandbox tool ran in the session. - Instructions (
coder-host/instructions.ts): implement the plan given as the first message; readAGENTS.md/CLAUDE.mdand package files first; reread the code and say where the plan no longer fits; run the checks; work onluke/<slug>and never push the default branch; decide on a PR and its draft state.
The routes (apps/web/server/coding-agents-app.ts)
POST /api/plans/:id/agents{ idempotencyKey, model?, effort? }: both of the choice or neither; the account default otherwise;acceptedModelChoicerefuses an unknown model or effort and a stored default that left the catalog; a plan with no repository isno-repository(409, new refusal); one the account no longer reaches isrepository-not-reachable; the plan's document is snapshotted as the Markdown Copy puts on the clipboard (planMarkdownmoved from the desktop into@sidecar/hosted/plan-markdown); the agent and its conversation are made and the eve session opened with the snapshot as the first message; an eve that did not take it discards the agent again (discardCodingAgent), so a retry starts afresh; a retry under the same key is the same agent (200) and opens nothing; a Start that named a model writes it as the account default.GET /api/plans/:id/agents: id, model, effort, createdAt, and status from the newestturnsrow (startingbefore one; a running turn with a Stop stamp readscancelled).GET /api/agents/:id/messages?after=<seq>:<revision>:UIMessage[]past the cursor plus the new cursor; a row amended in place at a later journal revision counts as new; held open withSchedule.spacedwhile the newest turn runs (20 s hold, 500 ms poll), answering at once when idle.POST /api/agents/:id/stop: eve's cancel of the turn under way by its own id, then the row's stamp; the hook stops the sandbox as the cancelled turn ends.- Wire shapes in
packages/hosted/src/coding-agent-wire.ts, paths inservice-paths.ts, goldens recorded.
Tests (hermetic: PGlite, fake eve, fake GitHub, sandbox double, fixed catalog, TestClock)
coding-agents-app.test.ts: Start's default, named choice becoming the default, idempotency, every refusal, eve unreachable leaving no agent, the list's statuses, the transcript and its cursor, Stop and a refused Stop.coder-host.test.ts: per-step model and effort mapping with the catalog window and the fallback, the shared relay writing a coding turn (plan line, reasoning, tool part, words) with no ask record, status fromturnsfor completed/failed/cancelled, the held transcript read answering on a new message, hearing an amended journal again, and letting go at the hold.coder-host-checkout.test.ts: the write-scoped mint, the whole clone through the firewall, and that the token reaches no run and no output.coder-host-model.test.ts,coder-agent.test.ts; extendedgithub-app,hosted-eve-sessions,model-catalog,coding-agent-store,vercel-services,eve-layout,hosted-refusal.coder/evals/coder.eval.ts: the scripted coding run through eve, the relay, and the writer, wired intopnpm --filter @luke/web test:agent.
Effect edges and docs
apps/web/coder/{agent,channels/eve,hooks/store,instructions/prompt,sandbox,host}.tsadded toeffect-edges.jsonruntimeEdgesand to AGENTS.md's "Where an Effect may run" (Dean approved); the eval torunShims.pinned-state.tsmoved underserver/hosted/brain-host/so both services share it.apps/web/server/README.mdgains "The coding-agent service" and "The coding-agent routes";PRIVACY.mdnames Anthropic/OpenAI on Luke's keys and GitHub through the App;docs/PLANNING.mdpoints at them.
Vercel settings Dean may need to touch
- Nothing new is required: the
coderservice is declared invercel.json, its build prewarms its sandbox snapshot under the same credentials theeveservice's build already uses, andANTHROPIC_API_KEY/OPENAI_API_KEYare already set for every environment. Vercel should pick the third service up on the next deploy; if the dashboard shows onlywebandeve, the project's framework setting is still Services and the new entry needs no toggle.
Out of scope
Desktop UI and Settings (B7); messaging an agent after Start; Slack/Linear.
š¤ Generated with Claude Code