feat(service,host,features): start a task and its first coding agent in one request
PR 6 of the New task, prompt-first redesign. tasks.start (POST /api/plans) now takes an optional agent, and the service makes the task and starts a coding agent on it in the same request. This covers the wire, the host and the service. There is no desktop change here; PR 7 switches the desktop over.
What changes
Wire (task-wire.ts)
taskCreateRequestSchema.agentis the newtaskAgentStartSchema. It holds the fields of an agent's Start (idempotencyKey,model,effort,contextWindow,prompt,attachments,disabledServers) and reusescodingAgentStartRequestFieldsand thestartBriefwords-or-files check. It leaves outfromandnote, because a new task has no agent to take over from.- The prompt travels as
prompt, the same key the act and gateway params already use. Thetaskkey on an agent Start's body exists only for released desktops, and none of them sends this field. - The schema refuses a request that names both
agentandplanning: true. Withagent, the task starts withplanning: false. - New
taskStartAnswerSchema:{ plan, agent?: CodingAgentSummary, agentRefusal?: { error } }.
Service
startRequestedAgentincoder-host/start.tsis now the one shared path for a Start: it pairs the choice, maps a catalog refusal to invalid, callsstartCodingAgentand builds the summary. BothPOST /api/plans/{id}/agents(now a thin caller) andPOST /api/plansuse it.- When
POST /api/plansnames an agent:- Without a repository, it is refused
409 no-repository, the same refusal an agent Start gives for a task with no repository. Nothing is written. - It needs the developer's bearer, which the agent's session is opened over. A cookie-only start is refused unauthenticated before anything is written.
- If an agent already stands under that
idempotencyKeyon a task in the named workspace, it answers200with that task and the agent, so a retry starts neither a second task nor a second agent. A key already used anywhere else is refusedinvalid-request. - If the Start fails after its agent was made (eve took the session, then a store step failed), it answers that agent rather than a refusal.
- Otherwise it creates the task, then starts the agent. If the agent start is refused, it answers
201with the task andagentRefusal. The task is not rolled back.
- Without a repository, it is refused
- The body bound for a start now has room for a Start's prompt and files. The Start body bounds moved into
start.ts.
Host and gateway
HostedTaskClient.createanswers aTaskStarted:{ task, agent?: CodingAgentAgentAnswer }. A refusal is read the way an agent Start reads it (startRefusedAs). A start that names an agent waits up to the Start's 120s bound.- The
tasks.startanswer gainsagentStart?: CodingAgentAgentAnswer.host-operatorforwardsagent, and theTASKS_STARTact opens no call whenagentis set. - Older desktops, which send no
agent, get the same behaviour and answers as before.
Tests
coding-agents-app.test.ts, on a real database with a fake eve:- a start with an agent makes an empty-plan task and opens the agent's session with the prompt;
- a retry under the same key answers the same task and agent, with one session opened and one task listed;
- eve not taking the agent answers the task with
agentRefusal: unavailable, and the task still stands; - no repository or a null repository is refused
no-repository,planning: truewith an agent is refusedinvalid-request, and in every case nothing is made.
task-client.test.ts: the agent and its refusal as the client reads them;planning: truewith an agent is never sent.compose-tasks.test.ts:agentStartis carried to the window.tasks-acts.test.ts: an agent start opens no call.host-operator.test.ts:agentis forwarded.- JSON-schema goldens re-recorded with
LUKE_UPDATE_FIXTURES=1:taskCreateRequestSchemagainsagent, and three new schema files are added for the new exports.
pnpm check:affected passed typecheck, lint, knip and every test except two failures caused by the sandbox: discarded-effect ran out of memory at the default heap (it passes with --max-old-space-size=6144), and app-menu.test.ts found no Electron binary in the sandbox (it passes when run alone). The web tests were also run on their own and pass.
Note
A retry after a lost answer is idempotent only when the agent was started. If the first request's agent was refused, a retry makes a second task. The desktop does not hit this, because on a refusal it opens the task it was answered and goes on from there.
Made with Luke