B6. The coding-agent Eve service, with the Start, list, messages, and Stop routes

Merged
DA
11d ago
+4613-235

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 coder service in apps/web/vercel.json, at eve's named mount /eve/coder/v1/*: the first rewrite carries it into the service, the service's one routes entry turns it back into /eve/v1/* with a request.path transform, and its build exports EVE_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.ts holds it; tests/eve-layout.test.ts now builds both roots.
  • Tools: defaultTools: false, with eve's bash, read_file, write_file, glob, web_fetch, web_search and the code extension's apply_patch and grep authored one file each under coder/tools/. The extension itself is not mounted, so neither its worker subagent (AI Gateway model) nor its gh tool (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): defineDynamic on step.started reads the agent's row, providerModelOf turns the catalog id into the provider's, and the step is told @ai-sdk/anthropic on ANTHROPIC_API_KEY with providerOptions.anthropic.effort, or @ai-sdk/openai's Responses model on OPENAI_API_KEY with providerOptions.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 with installCodeTooling plus 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 token repositoryWriteToken mints with contents: write and pull_requests: write for that one repository; the token is then set at the firewall with authenticateGitHub for the session's own gh and git. 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/messageAuth with 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-shaped EveCaller and a mount option; 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 optional asks seam, and the coder host composes StreamRelay without it. The turn row also records reasoningEffort now.
  • 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; read AGENTS.md/CLAUDE.md and package files first; reread the code and say where the plan no longer fits; run the checks; work on luke/<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; acceptedModelChoice refuses an unknown model or effort and a stored default that left the catalog; a plan with no repository is no-repository (409, new refusal); one the account no longer reaches is repository-not-reachable; the plan's document is snapshotted as the Markdown Copy puts on the clipboard (planMarkdown moved 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 newest turns row (starting before one; a running turn with a Stop stamp reads cancelled).
  • 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 with Schedule.spaced while 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 in service-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 from turns for 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; extended github-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 into pnpm --filter @luke/web test:agent.

Effect edges and docs

  • apps/web/coder/{agent,channels/eve,hooks/store,instructions/prompt,sandbox,host}.ts added to effect-edges.json runtimeEdges and to AGENTS.md's "Where an Effect may run" (Dean approved); the eval to runShims. pinned-state.ts moved under server/hosted/brain-host/ so both services share it.
  • apps/web/server/README.md gains "The coding-agent service" and "The coding-agent routes"; PRIVACY.md names Anthropic/OpenAI on Luke's keys and GitHub through the App; docs/PLANNING.md points at them.

Vercel settings Dean may need to touch

  • Nothing new is required: the coder service is declared in vercel.json, its build prewarms its sandbox snapshot under the same credentials the eve service's build already uses, and ANTHROPIC_API_KEY / OPENAI_API_KEY are already set for every environment. Vercel should pick the third service up on the next deploy; if the dashboard shows only web and eve, 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


Open workspace in Conductor


Devin Review