Skip to content

Start a Build from the commit that holds its plan's files, which the Planner records - #888

Merged
lavaman131 merged 2 commits into
mainfrom
plan-source-build-base
Nov 13, 2026
Merged

lavaman131 merged 2 commits into
mainfrom
plan-source-build-base

Conversation

@lavaman131

Copy link
Copy Markdown
Collaborator

Start a build from the commit that holds the plan's files

What was seen

On a hosted deployment, the remote Planner's backend wrote the plan's formal files in its own
worktree. Build plan then ran on the person's machine and the coding agent stopped with "Missing
source": the files it was asked to build from were not in its worktree.

Cause

A build started from sourceFor(connection, document): the commit of the person's own checkout,
else the head of the default branch. The Planner's files lived only on the Planner's branch, and a
plan revision carried no branch or commit that would let a build find them.

Fix

Chopin now learns a plan source for a document revision: a branch the Planner pushed and the
commit on it that holds the revision's files.

  • New /mcp tool record_plan_source { id, revision, branch, commit }. The backend calls it
    after it pushes, with the launch's MCP token. Chopin checks the branch name against git's branch
    rules (no refs/ prefix) and the full 40-hex SHA (invalid-plan-source), asks GitHub for the
    commit with the caller's grant, which for an MCP token is the machine's own
    (plan-source-unavailable), and refuses a revision the document has not reached
    (revision-conflict with the current revision). It answers
    { recorded: true, revision, branch, commit }. The same triple again changes nothing; a new commit
    for the same revision replaces the old one.
  • Storage. One source per revision, the newest 20, in the document's sidecar (planSources),
    validated on restore like the other durable fields: ordered, unique, no newer than the document.
    It is written under the document lock before the tool answers.
  • Build base. A build of tasks drafted against revision R starts from the source recorded for
    the newest revision at or before R. Its checkout is { repository, branch, commit } from that
    source. As Build is pressed, the server checks the commit again on GitHub with the building
    machine's grant. If GitHub no longer has it, the press is refused with plan-source-unavailable
    and nothing is queued. The browser says "The plan's commit isn't on GitHub. Ask chopin to push it
    again". If a source is recorded between the check and the queue, the press is refused ("The plan's
    files changed. Try again") so the build never pairs a stale base with a newer revision. With no
    source, the build starts from the same checkout as before.
  • The build records it. BuildRequest.planSource. The run's read_implementation and MCP
    read_implementation return it as planSource, and the Planner's read_implementation_graph
    returns it as plan_source, so a builder can name it if something is still missing.
  • Build tab. The card's footer says Built from plan revision N · branch@short, or Building
    from…
    while the build runs, with the full SHA in its title.
  • Rebuilds keep today's base (the machine's checkout, else the first build's). The exception is
    a source recorded for a revision after the one last built (live.baseRevision): pressing Build,
    or a live rebuild, approves that revision, and the rebuild starts from that source.
  • Connector. A run whose commit can't be fetched now fails with "Couldn't fetch commit
    () of <owner/repo> to start from (fatal: …)." It used to fail with git's raw "Command
    failed".
  • GitHub client. Adds hasCommit, which treats GitHub's 422 ("No commit found for SHA") and
    404 as "absent". The shared request path stays as it was: the new answered-status list is
    per call.

The contract was agreed with the backend's author on the Planner side: the tool name, the
arguments, the error codes, and that an older server answers -32601 ("tool not found"), which the
backend treats as "unsupported" and ignores.

Docs: Plan sources in the agent connection contract, and a
paragraph in Hosted agent.

Evidence

The new browser spec records a source through /mcp and presses Build plan with the fake
connector. It then checks the build's checkout and planSource, and checks that the run's worktree
HEAD is the Planner's commit with the plan's file in it. Last, it checks the Build card's line.
The commit sits on a branch the person's checkout never checked out. An unknown commit is refused
first.

Desktop (1280px) Phone (390px)
Build card Build card

Chopin has one theme (.dark reads the same tokens; see assistant-ui.css), so there is no dark
shot. Before this change the card had no source line. The shots show the new state only.

Contrast: the line uses --color-text-secondary (gray-700, #47453f) on the card's
--color-inset (gray-100, #f8f8f6). That is 9.01:1, computed with a script from the two
tokens' sRGB values, above AA's 4.5:1. The line adds no non-text UI.

Impeccable: impeccable context on build-view.tsx, the craft floor read before the UI edit, and
impeccable detect --json on build-view.tsx and build-source.css returned no findings. The
critique in brief: one quiet line in the footer's secondary ink beside the machine, no icon (the
icon set has no branch glyph, and a stand-in glyph would be decoration), and an ellipsis rather
than wrapping so it never pushes the footer's action. Its new state appears with the footer, so it
adds no motion.

Checks

  • bun run types: pass, after rebasing onto 16c55ff.
  • bun run ci: pass.
  • After the rebase: bun test apps/server/src/tasks apps/server/src/mcp apps/server/src/mcp.test.ts apps/connector/src/workspace.test.ts apps/web/src/build-model.test.ts gave 435 pass and 0 fail.
  • Before the rebase:
    bun test apps/connector apps/server/src/tasks apps/server/src/mcp apps/server/src/mcp.test.ts apps/server/src/tool-schemas.test.ts apps/server/src/agent apps/server/src/plan apps/server/src/experiments apps/server/src/harness apps/web/src/build-model.test.ts
    gave 2230 pass and 0 fail. experiments/routes.test.ts makes git commits, so it needs
    commit.gpgsign=false on a machine that signs by default. That is the environment, not this
    change.
  • Playwright implementation-launcher and build-restart (--workers=2): 28 passed, 1 skipped
    (the opt-in live ACP mode).
  • bun run build: initial JS 259,958 B raw / 82,484 B gzip. The initial stylesheet is
    184,420 B raw / 32,675 B gzip, under its 185,000 B budget. The new rule is in build-source.css,
    loaded with the Build view's lazy chunk.

New unit tests:

  • branch rules, newest-at-or-before, replace and replay, the 20 cap, and restore validation
    (plan-source.test.ts)
  • the MCP tool's validation, unknown commit, revision conflict, replay, persistence and forbidden
    (mcp/hosted.test.ts)
  • build base selection, fallback with no source, the unfetchable-commit refusal, and the rebuild
    rule (tasks/routes.test.ts)
  • the press-versus-record race and persistence across a reopen (tasks/builds.test.ts)
  • the connector's fetch failure (workspace.test.ts)
  • the Build card line and the refusal copy (build-model.test.ts)

Judgement calls

  • A new /mcp tool, not a field on edit_implementation_graph or update_document. The code
    that knows the commit is the backend's deterministic publish step, not the model. It already
    reaches /mcp with its launch's MCP token.
  • Per revision, newest at or before R. Plan edits after the files were pushed (a title, a
    sentence) keep the last pushed files rather than dropping back to the person's checkout.
  • Checked twice on GitHub: when it is recorded (with the caller's grant) and when Build is
    pressed (with the building machine's grant, which is what the connector fetches with). An
    automatic rebuild is not re-checked. Its connector fails with the clear fetch error instead.
  • The line names the revision and short SHA, and is shown to everyone who sees the build. The
    builder's own checkout stays hidden as before. A pushed branch of the repository is not private
    to the builder.
  • Tense: Building from… while the build runs, Built from… afterwards.

Open

  • The backend force-pushes one branch per document, so an older revision's commit can become
    unreachable. GitHub usually still serves a dangling commit by SHA until garbage collection. A
    build of an older revision whose commit is gone is refused clearly at the press, not built from
    the wrong base.
  • A "Missing source" failure still comes from the coding agent if the Planner never recorded a
    source, for example on a backend or server that predates this change.

A remote Planner's backend writes a plan's formal files in its own worktree, but a build started
from the builder's checkout, or the default branch's head, so its agent never saw them and stopped
on missing sources. A plan revision carried no branch or commit to start from.

The backend now records where a revision's files are with a new /mcp tool, record_plan_source
({ id, revision, branch, commit }), after it pushes them. Chopin checks the branch name and the
40-hex SHA, asks GitHub for the commit with the caller's grant (a launch's machine grant for an MCP
token), refuses a revision the document has not reached with revision-conflict, and keeps one
source per revision, the newest 20, in the document's sidecar. A build of tasks drafted against
revision R starts from the source recorded for the newest revision at or before R: its checkout is
that commit on that branch, checked again on GitHub with the building machine's grant as Build is
pressed (refused with plan-source-unavailable when it is gone). The build records the source, its
run's read_implementation and MCP read_implementation return it as planSource, the Planner's
read_implementation_graph as plan_source, and the Build tab's card says "Built from plan revision
N · branch@short". A rebuild keeps today's base unless a source was recorded for a revision after
the one last built. A document with no source builds exactly as before. A connector that cannot
fetch a run's commit now fails naming the commit, branch and repository.

Assistant-model: Claude Opus 5.5
Assistant-workflow: inline (subagent)
Assistant-verification: bun run types
Assistant-verification: bun run ci
Assistant-verification: bun test apps/connector apps/server/src/tasks apps/server/src/mcp apps/server/src/mcp.test.ts apps/server/src/tool-schemas.test.ts apps/server/src/agent apps/server/src/plan apps/server/src/experiments apps/server/src/harness apps/web/src/build-model.test.ts (2230 pass, 0 fail)
Assistant-verification: playwright implementation-launcher and build-restart (28 passed, 1 skipped: the opt-in live ACP mode)
Assistant-verification: bun run build (initial JS 259,958 B raw / 82,484 B gzip, CSS 184,420 B raw / 32,675 B gzip)
Assistant-verification: after rebasing onto 16c55ff: bun run types; bun run ci; bun test apps/server/src/tasks apps/server/src/mcp apps/server/src/mcp.test.ts apps/connector/src/workspace.test.ts apps/web/src/build-model.test.ts (435 pass, 0 fail)
User-preference: a Build must start from the commit that holds the plan's files, not just the person's checkout
Co-authored-by: Alex Lavaee <lavaman131@github.com>
The no-middot rule forbids " · " as an inline separator; the Build card now reads "Built from plan revision N at branch@short".
@lavaman131
lavaman131 merged commit eec9491 into main Nov 13, 2026
24 checks passed
@lavaman131
lavaman131 deleted the plan-source-build-base branch November 13, 2026 02:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant