feat(database): an agent may have no repository and no first message, and a call names its agent
Merged
DA
5d ago+15129+15129+01234567890123456789012345678901234567890123456789-110-110-012345678901234567890123456789
PR 1 of 7 in the Unified agents rollout. A voice chat is becoming an agent with a call attached. It can live on a task with no repository and start blank, and a call has to name the agent it speaks into. This PR changes the schema and makes every reader type-safe. No service, voice or UI behaviour changes, and today's Start still requires a repository and still writes one.
Schema (migration 20261020180912_coding_agent_optional_repository_voice_sessions_agent)
coding_agent.repositorydropsNOT NULL.repositoriesstays a list and is written[]for an agent with none.voice_sessions.agent_idis new: a nullable uuid FK tocoding_agent.id,ON DELETE SET NULL, indexed (voice_sessions_agent). Nothing writes it yet, andtask_idstays.- The migration only widens:
ADD COLUMN IF NOT EXISTS,DROP NOT NULL,CREATE INDEX IF NOT EXISTS, and the FK added after aDROP CONSTRAINT IF EXISTS. Existingagent_idvalues are all null, so the FK can't break any row. The previous deployment keeps working, and there is no backfill.drizzle-kit checkpasses.
"Exactly one of plan snapshot and prompt"
There is no check constraint for this. The rule lives in the store's CodingAgentBriefText type, which now has a third variant with neither (an agent started blank). The coder host's Start path (start.ts) keeps the old pair through HandedPlace, so it still hands the plan or a prompt and never nothing.
Readers
- The store gains
WithRepository<A>andhasRepository(agent). - Coder host: one guard at its door.
agentOfrefuses an agent with no repository withCODER_REFUSAL.NO_REPOSITORY, and its seams (checkout,prompt,skills,pull-request,recordContext) takeWithRepository<CodingAgent>. A coding session is repository work all the way through (clone, firewall token, instructions naming the repo). The orchestrator agreed that running a session with no repository belongs to PR 2, which lifts this guard. - Everywhere else, an agent with no repository skips the repository work:
- The Pull request tab and overview, Changes, and chapters answer no pull request. Writes answer not found. Review options answer repository-not-reachable.
- Published reads answer nothing published, and GitHub images answer not found.
- The terminal does not reach the agent, and steer skips the GitHub reach check.
- Replay lineage, source copy, GitHub wakes, factory world and automation runs simply don't match a repository.
- Mention notes and agent introductions leave out "working in …".
- Wire:
codingAgentPageSchema.repository(and the taskless agent line built from it) andcodingAgentPullRequestAnswerSchema.repositorybecomeNullOr. The panel and host readers handle null: no compare link, no taskless subject. The admin account page's agent repository becomes nullable as well. - Taskless inbox items keep
repository: stringon the wire. The two reads now leave out an agent with neither a task nor a repository, which no code path can create. Widening that wire would have reached the inbox UI, which is outside this PR.
Tests
packages/database/src/models/__tests__/coding-agent-store.test.ts: an agent on a task with no repository, handed nothing, is stored and read back by a teammate with no repository, no repositories, and neither plan nor prompt (in both the full read and the line). A voice session naming it outlives the agent's delete withagent_idset to null. This test fails without the change.coder-host.test.ts: the host refuses an agent with no repository withNO_REPOSITORY.- The coder-host tests that pass an agent to the checkout seams narrow it with
assert.ok(hasRepository(agent)). - The JSON Schema goldens were re-recorded with
LUKE_UPDATE_FIXTURES=1: 7 files underpackages/features/fixtures/json-schema/. The only change in each isrepositorygoing from a string to string-or-null on the agent page, the taskless agent line and the published answer, plus the view schemas and the task list built from them.
Checks run locally
- Typecheck is clean in every workspace. This sandbox has 8 GB, which isn't enough to run all of them in parallel, so they ran one at a time.
- The affected tests all pass:
drizzle-kit check, contractions, biome and oxlint, and discarded-effect. - The one exception was desktop's
app-menu.test.ts, which failed only because the sandbox had no Electron binary; that is being re-run now.
Made with Luke