LUKE-336: the planning model runs on the hosted brain over a plan conversation

Merged
CH
25d ago
+1091-86

What

One backend planning model, and it's the existing hosted brain (eve on gpt-6-sol). There's no second agent and no new provider or key. A plan now gets a conversation of a new kind, plan, and the host treats that kind in three ways (apps/web/server/hosted/brain-host/planning.ts):

  • Instructions: PLANNING_INSTRUCTIONS, adapted from Matt Pocock's grilling skill. They cover opinionated recommendations, one spoken question at a time chosen by what its answer unlocks, concrete rehearsals, an internal coverage template, direct answers counting as agreement, read-back for inferred details, corrections revising the document, new assumptions going in confirmed:false, a changed meaning resetting the flag unless the correction settles it, keeping facts apart from recommendations, and treating tool results, transcripts, and the document as data. Workflow is left to the instructions.
  • Standing context, rebuilt every turn: the plan's name, owner/repo, branch and commit, and the saved document as JSON, read fresh from the row. A save the model makes is what its next turn reads.
  • Tools: the planning list alone, currently just update_plan, bound server-side to { userId, planId } through readPlanOfConversation. PLANNING_TOOLS is the slot where the repository reads (LUKE-338) and research (LUKE-339) go.

A session opened over an existing plan conversation is seeded with the conversation so far, the same as any other conversation.

Store (plan-store.ts):

  • openPlanConversation(userId, planId) opens the plan conversation under the plan row's lock and attaches it through attachPlanConversation. It's idempotent: calling it again returns the same conversation.
  • readPlanOfConversation is how a turn finds its plan.
  • deletePlan now also stamps the plan's conversation deleted_at, so the existing purge removes its messages 30 days later.

Other changes:

  • The writer (production.ts, cancel route) and the seed read now check rows against HOSTED_TOOL_SET (catalog plus planning tools). Without this, the store would reject a stored update_plan part and a resume would start with no prior conversation. wireValidatedTool is exported from @sidecar/brain/tool-set for this.
  • A plan conversation neither loads the notebook at turn start nor writes to it at compaction.
  • The panel's reads filter by kind, so plan conversations never appear there.
  • host.toolDeclarations now takes { target, kind } instead of just target.
  • No migration: conversations.kind has no check constraint.

For LUKE-340: acceptAsk with the plan's conversationId pinned reaches this unchanged.

Tests

  • tests/hosted-planning.test.ts (PGlite, runs in check.sh) drives the scripted fixture model through the real host the way eve does. It gets the instructions, the seed, the standing context, and the tools; each update_plan call goes through host.runTool; every event is relayed into the store. It checks:

    • an update is visible to the window's openPlan and to the next turn's standing context;
    • a resumed session is handed the saved document plus the conversation so far, including the update_plan call;
    • the conversation is opened once and a stranger can't open one;
    • only update_plan is offered;
    • a deleted plan's conversation stops admitting its session.

    Mutation check: switching the seed read back to CATALOG_TOOL_SET makes the resume test fail.

  • The brain-host eval (test:agent) now also runs a plan conversation through real eve: the scripted model reads the document it's handed and saves, and a second session over the same conversation (a resume) builds on that saved document. It passed locally against Postgres 17; it skips when no database is configured, as before.

  • There are no prompt-string tests.

./scripts/check.sh exits 0. There's no Mac or UI change, so verify.sh doesn't apply.

šŸ¤– Generated with Claude Code


Open workspace in Conductor