refactor: delete the per-task Luke
PR 7 of the Unified agents rollout (STAG-322). It deletes everything that served only the per-task Luke, the planner brain each task ran in its own plan conversation, and keeps everything coding agents use. Every agent is now a coding agent on /eve/coder, and a call speaks into one.
Held: don't merge this until the developer says so; see "Merge conditions" below.
Merge conditions
- Desktop first. The latest desktop release, v0.18.0, predates the whole rollout. Its calls send no
agentId, and it readsplans.transcript,plans.work,messages.taskandmessages.taskWorker, all of which this PR removes. So this PR may deploy only once a desktop release that includes #4300 and later is out and in use. - With 7b. It merges together with the data PR 7b (see "For 7b" below).
What users stop seeing
Their data stays in the database until 7b; only the reads and views go.
- Luke-era calls without an agent. A task's calls from before calls spoke into agents (
voice_sessionsrows with no agent) are no longer drawn; every call shows in its agent's tab now. - Code Luke showed. The Code tab reads only coding agents'
show_codecalls, so code a task's own Luke put on screen no longer shows. - Luke's Work turns. The Work tab and its read are gone, and so are its live
plan.workframes. - Browser tabs Luke opened. The cloud tab store lists only tabs whose conversation is a coding agent's, so Luke's rows stop showing.
Deleted
The planner's eve app and service
apps/luke/(28 files).- The
lukeservice and the/eve/v1/(.*)rewrite invercel.json. lukeinfunction-rewrites.ts,vercel-ignore.sh,repository-checks.sh,knip.json, the effect-edges record, and the lockfile.- The preview probe now checks
/eve/coder/v1/health.
The brain host
brain-host/brain.ts,host.ts,model.ts,plan-edit.ts,production.ts,task-title.tsandchat.ts, with their tests.tasks/brain-tool-set.tsandtasks/brain-transcript.ts.testing/eve-scripted-model.ts.- The package exports for all of these.
Roles and tools
AGENT_ROLE.BRAINandBRAIN_WORKER.SANDBOX_POLICY.READ_SHALLOW_LAZY.run_in_repository:repository-shell.ts's lazy checkout andRUN_IN_REPOSITORY_TOOL.- The brain's tool declarations:
READ_AGENT_TOOL,SHOW_PAGE_TOOL, andshow_page's inline-htmlinput (it now takespathalone). HOSTED_TOOL_SETandEVE_MOUNT.TASKS.AGENT_TOOL.BROWSERloses BRAIN and BRAIN_WORKER, and the brain's browser wiring from #4318 goes withbrain.ts,host.tsandapps/luke/runtime.ts.
The task conversation
openTaskConversation,readTaskOfConversation,findTaskOfConversation,attachTaskConversationandsetConversation.StoredTask.conversationIdandTaskHeader.conversationId.- The task reach in
recorded-session.ts:conversationOwnerOfandfindConversationOwner. - The task
readTranscript. taskFollowedBy.findTaskWorkspace.
Routes and live reads
plans.stop,plans.transcriptandplans.work.messages.taskandmessages.taskWorker.- The live
taskConversationandtaskWorkerTranscriptreads. - Their contract, client, gateway and host plumbing.
- The
plan.draftframe,PLAN_DRAFT, and the host's draft handling. - The
plan.workframe and everything behind it: the live brain's work projection and its reads of subagent sessions (tasks/task-work.ts,tasks/child-models.ts,childTitlesByCall,delegationJournalsOf); the voice package's route for the frame; and the host'sshowWork,view.work,voiceWindowStateandwindowAppState, which only kept that work from the voice and call windows. Nothing inpackages/panelreadview.work. - The panel's Work model (
work-model.ts) and the planning workers in the side panel.
Voice
call-backend.tscollapses to the one coder backend;callBackendOfand the explore branch go.- A create without
agentIdis refused at decode, andopening.ts,live-exchange.tsandvoice-session-record.tsare agent-only.
Chat and comments
- A Luke link in a post's words is now a plain reference: it mints no member and tells no one. The Luke arms of
ChatMemberMention,ChatMemberSubject,chat-ask,chat-notifyand the composer's offer list are removed. PlanCommentWriterRef's LUKE arm is removed. An@Lukein a thread hands it to no one.- The panel's Luke picker row and
LUKE_AWAYare removed. - A shown-code note now names the agent that showed it and links to that agent's turn.
Docs
docs/TASKS.md,PRIVACY.md,apps/web/server/README.md,packages/service/README.md,CONTRIBUTING.mdandpackages/live/AGENTS.mdnow describe agents only.AGENTS.mdonly loses itsapps/lukelines.PRIVACY.mdsays a task's delete stamps the conversation its own Luke kept along with its agents'.
Kept, and the test that covers each
| Kept | Why | Covered by |
|---|---|---|
brain-host/brain-ask.ts (the asks intake) | It is how a call's spoken ask reaches an agent | brain-ask.test.ts, voice/live-brain.test.ts, voice/voice-service-exchange.test.ts |
brain-host/relay.ts | The coder relay journals every turn through it | brain-host-relay.test.ts, relay-journal-batch.test.ts, relay-pictures.test.ts, coder-host.test.ts |
turn-events.ts (projectTurnEvents) | It is how the voice follows an agent's spoken turn | agents/turn-events.test.ts, voice/live-brain.test.ts |
brain-host/seed.ts, context.ts (seed and standing context) | A retired agent session reopens with a seed, and turn messages carry context | coder-host/coder-host.test.ts ("a session opened afresh after the agent's last one retired … is seeded", and the turnMessage tests) |
brain-host/door.ts, auth.ts, conversation.ts | The coder door's admission and deployment actor | brain-host-access.test.ts, coder-host/stop.test.ts, coder-host/coder-agent.test.ts, eve-deployment-secret.test.ts |
brain-host/eve-sessions.ts (EVE_MOUNT = { CODER }) | Every eve client | eve-sessions.test.ts, brain-ask.test.ts, accounts/account-delete.test.ts |
MEMBER_KIND.LUKE ("planner") | Old chat-member rows still decode; nothing mints a new one (LUKE_HANDLE already went with #4333's handle retirement) | chat-store.test.ts (a former Luke's channel scope and roster), coding-agents-app.test.ts (a Luke link mints none) |
PLAN_COMMENT_ACTOR.LUKE, MENTION_KIND.LUKE | Old comments and links still read | apps/web/tests/plan-comments.test.ts (a thread mentioning Luke hands it to no one), mention-link.test.ts, chat-mentions.test.ts |
CONVERSATION_KIND.TASK = "plan" and task.conversation_id (now @deprecated, read only by a task's delete) | Old rows stand until 7b | task-store.test.ts ("a plan's delete stamps its agents' conversations and the one its own Luke kept…", "the conversation a plan's own Luke kept, and a worker's delegated from it, are reached by no one…"), apps/web/tests/session-sandbox-cleanup.test.ts |
task-record-live's calls part (spoken: 0) | Desktops already shipped still decode it | task-record-live.test.ts |
For 7b (the data PR, cut from this branch)
Planner chat members. chat_message.author_member_id is NOT NULL with ON DELETE CASCADE to chat_member, and so are dm_a_member_id and dm_b_member_id. Deleting a member deletes its messages and DMs, so this PR keeps the rows and only stops minting them.
Production, read-only:
- 9
plannermembers, all with atask_id, in one workspace. - 0 messages written by them, and 0 DMs.
- 11 messages that mention them.
- 1
plan_commentwith actorluke, and 1 withasked_actorluke.
So 7b can delete these rows behind a NOT EXISTS guard on any message or DM they wrote. After that it can drop the planner literal, then chat_member.task_id, then chat_member_task, in that order.
Retention gap: closed here. deleteTask reads task.conversation_id under the row lock it already takes, and stamps that conversation along with the agents'. That keeps deleting a task correct between 7 and 7b. Only the agents' conversations are handed to beforeDelete, which retires eve sessions, because Luke's ran on the luke service this PR deletes; the purge removes the stamped conversation and its session sandboxes as it does any other. 7b's stamping only needs the plan conversations left over: those no task names any more (a conversation a task moved off earlier), and, when it drops the column, those the column still names.
Access to old plan conversations
namedByWork no longer counts task.conversation_id. Left alone, the own-conversation clause of reachableConversation would treat an old plan conversation, and the workers delegated from one, as private to their opener: the opener would keep reach after leaving the workspace, and every other member would lose it. So the own clause now leaves out kind plan and any conversation whose parent is a plan, and nobody reaches them (test in task-store.test.ts). No route reads them any more.
The abandoned-turn sweep's writer
maintenance-app.ts now builds the sweep's writer over CODER_TOOL_SET instead of the deleted HOSTED_TOOL_SET. The sweep only settles a turn's end (settledLost → the writer's turnEnded). That path reads the turn row and journal without validating them against the tool set, and settles any unanswered call with the unknown-outcome envelope. The tool set matters only at construction, where every tool in it must admit that envelope. CODER_TOOL_SET is exactly the set the coder host's own writer runs on (coder-host/production.ts), so a coding agent's turn is settled as its own writer would settle it. A leftover plan turn needs nothing from the set, and the database's sweep tests already run it over tools: {} (abandoned-turns.test.ts). Main used the brain's HOSTED_TOOL_SET for every conversation, coding agents' included.
AGENTS.md rewording, needs approval
AGENTS.md is otherwise pure removals of lines and words that name deleted files. Two lines had to be reworded so the sentence still reads:
- The two lines
host through, composed with `Effect.runSync` as it loads for the same reasonand`apps/luke/host.ts` is. Notbecome one:host through, composed with `Effect.runSync` as it loads. Not. The reason they pointed at lived in the deletedapps/lukepassage. `packages/service/src/agents/brain-host/production.ts`'s `spend` isbecomes the same line withcoder-host/production.ts. The coder host'sspendis the same AI SDK middleware seam, so "two promise-shaped foreign boundaries" stays true.
Suggested, not made: line 133's comment # offline brain eval, scripted model, by hand names no deleted file, but test:agent now runs only the coding-agent eval. "offline coding-agent eval" would be accurate.
Checks
tsc --noEmitpasses in every package and app, each run on its own: the turbo typecheck is killed for memory in this sandbox, so it was run package by package.- The vitest suites pass in database, features, voice, ui, host, panel, service and web.
pnpm lint(biome and oxlint) is clean in the changed files.node scripts/check.mjs --without typecheckpasses apart from the desktop'sapp-menu.test.ts, which needs an Electron binary this sandbox lacks.pnpm discarded-effectpasses when run on its own.apps/web/server/function-externals.jsondropsnode:dns/promisesfrom four bundles, which no longer load the brain's tools.
Made with Luke