Skip to content

Change blueprint format to git and enable receiving blueprint updates - #659

Merged
kentonv merged 32 commits into
mainfrom
kenton/git-blueprints
Oct 5, 2026
Merged

kentonv merged 32 commits into
mainfrom
kenton/git-blueprints

Conversation

@kentonv

@kentonv kentonv commented Oct 4, 2026

Copy link
Copy Markdown
Member

A blueprint now encodes a git packfile instead of a Yjs snapshot.

When you update a blueprint, people who have created gadgets from the old blueprint are now given the option to update it. For now, this does not happen automatically -- you just see a dot on the blueprints button and from there can choose to update. (We can consider implementing auto-update later perhaps.)

If local changes have been made since the blueprint was first instantiated, an agent is spawned to review the merge and fix any conflicts.

You can also choose to switch blueprints when updating. This allows you to e.g. switch from an original blueprint to some other user's customized version of it.

Update dialog:

Screenshot from 2026-10-04 12-47-19

Agent fixing merge conflicts:

Screenshot from 2026-10-04 12-35-53

kentonv added 19 commits October 4, 2026 12:45
Started from my prompt:

> I'd like to revise how blueprints work.
>
> 1. The current format is based on Yjs. This is vestigial, from when gadget code was stored in Yjs, but we now store it in git. A blueprint today ought to encode a git commit. The obvious format is a git packfile, plus some metadata(?).
> 2. When someone has created a gadget from a blueprint, and then the blueprint later gets an update, people should have the option to update their gadget. If they've made their own changes to the code, they should be able to merge (with the agent resolving conflicts).
>
> Please outline how this might work, at a high level.

Followed by additional discussion and rounds of feedback.
This is commit 1 of plans/git-blueprints.md.
This is commit 2 of plans/git-blueprints.md.
This is commit 3 of plans/git-blueprints.ts.
When we don't have a git object locally yet but we know it can be pulled from a particular gatekeeper, we record metadata saying so. The object may also be marked for inclusion in a push, still without being present, since it can be fetched lazily.

When the object finally does arrive, we need to propagate this metadata to its children. E.g. when we fetch a tree object, we then want to mark all the files and subdirectories within it as being pullable from the same gatekeepers that the tree itself is marked pullable from. And if the tree was marked to be part of a push, its children should be too.

This was all implemented with the git worktrees changes, but there was a bug: The propagation only occurred if the object was explicitly pulled, not when the object arrived by some other means.

This commit ensures that propagation takes place regardless of how the object arrived.

This is particularly important as objects can now arrive from instantiating blueprints. It would be an unusual situation for a blueprint to contain a tree that had previously been marked pullable from a gatekeeper and then marked to push to some other gatekeeper... but it's possible.
This is commit 4 of plans/git-blueprints.md.
This is commit 5 of plans/git-blueprints.md.
This is commit 6 of plans/git-blueprints.md.
This is commit 7 from plans/git-blueprints.md.
…onflicts.

The agent is only run if there was something to merge. If the user never changed the code locally, we can just "fast-forward".

This is commit 8 of plans/git-blueprints.md.
This is commit 9 of plans/git-blueprints.md.
This notifies you when an update to the upsteam blueprint is available and provides the UI to request an update (including switching blueprints).

This is commit 10 of plans/git-blueprints.md
After you've chosen to apply the update, the UI shows the proposed changes, shows the agent tasked with managing the merge, and potentially blocks you from accepting if not all merge conflicts are resolved.

This is commit 11 of plans/git-blueprints.md.
This is commit 12 of plans/git-blueprints.md.

This completes the plan.
This scans the chat history to try to determine each gadget's starting blueprint from the `createGadget` tool calls that created them.

The scan is bounded to avoid spending too much time for workspaces with long chat histories.

From this we can only figure out which blueprint to use as the upstream, but not which version was used. So, we can't actually tell whether there's updates available. This just guides the UI better if the user tries to do a manual update.
In this case we can hide the "update" option in the UI since we know it can't possibly be useful.
We no longer offer a picker among all known blueprints. Instead, if you switch blueprints you must specify the new source blueprint using copy/paste.
It was a bit too technical before, talking about "merge conflicts". The wording is now simpler, but advanced details can be shown by clicking.
@github-actions github-actions Bot added workshop/frontend Changes to the Workshop frontend kernel Changes to the Workshop kernel workshop/shared Changes to shared Workshop APIs labels Oct 4, 2026
Comment thread packages/workshop-frontend/src/ChatInterface.tsx
Comment thread packages/workshop-frontend/src/ChatInterface.tsx
@ask-bonk

ask-bonk Bot commented Oct 4, 2026

Copy link
Copy Markdown

Review: 2 findings.

Posted 2 actionable inline comments in a single review.

github run

@kentonv
kentonv force-pushed the kenton/git-blueprints branch from 0a739a1 to 5c09e58 Compare October 4, 2026 18:29
@github-actions

github-actions Bot commented Oct 4, 2026

Copy link
Copy Markdown

Preview: pr659-kenton-git-blueprints

https://pr659-kenton-git-blueprints-router.cloudflare-os-previews.workers.dev

Dashboard · deleted when this PR closes

Comment thread packages/workshop-backend/src/blueprint-release.ts
@ask-bonk

ask-bonk Bot commented Oct 4, 2026

Copy link
Copy Markdown

Review: 1 finding.

Posted 1 new actionable inline comment in a single review. Existing findings were not duplicated.

github run

From my prompt:

> This branch implements plans/git-blueprints.md. There's a problem, though, with the way merges are handled, which applies not just to blueprint updates but also "update from mainline" (pre-existing, before this branch). The changes being pulled from the blueprint / mainline are delivered as a "changes" message, meaning they need to be encoded as a pile of operational transforms. That requires performing a diff, which is done character-by-character and can take quite a while. It's also somewhat difficult for the agent to examine what changed.
>
> I think we should develop an alternative where the initial merge is actually committed, and the chat log only indicates that the pinned commit was updated to this new commit. The new commit, of course, does not become mainline until it is accepted (and if the user/agent mkaes further changes first, e.g. fixing conflicts, that creates another new commit on top of the merge commit, and that is what actually becomes mainline).
>
> Please read the code and develop a plan for this. Consider whether there might be overlap with the way worktrees work (they also have a notion of commits that aren't "accepeted" yet), but only combine concepts if it makes things simpler.

Followed by a bit of discussion.
This is needed in order to compute blueprint exports later. Earlier in this branch we assumed that any merge was merging a blueprint release, but that will change soon.

This is part 2, commit 1 of plans/git-blueprints.md
A "changes" message may introduce a merge, which replaces the code content of that gadget so far with a new commit (previous OTs are no longer relevant).

This is commit 2 of plans/git-blueprints.md.
updateChatFromMainline() now creates a commit from the merged state and switches the chat's pinned commit for the gadget to it. The new commit does not land on mainline until the user clicks "accept" -- hopefully after fixing any conflicts.

This is part 2, commit 3 from plans/git-blueprints.md
This was the main point of part 2: no longer generating a bunch of OTs when applying a blueprint update. This means we can get rid of the splitting of changes and the `messageCount` hack.

This is part 2, commit 4 from plans/git-blueprints.md
We'll want this for the subsequent UI changes.

This is commit 5 of plans/git-blueprints.md.
This is part 2, commit 6 of plans/git-blueprints.md
This is part 2, commit 7 of plans/git-blueprints.md.
This is part 2, commit 8 of plans/git-blueprints.md.
@kentonv

kentonv commented Oct 5, 2026

Copy link
Copy Markdown
Member Author

Added a second round to this ("part 2" in the plan doc) to fix the way merges are done -- both for blueprint updates and when merging changes in a chat back to the workspace's mainline.

Previously we would encode the entire diff as OTs in a "changes" message, which could be very expensive to construct (it was a character-by-character diff) and required hacks to split it across multiple messages if the payload was too large for one row.

Part 2 of the PR makes it so we instead just introduce the changes in the form of a new commit, which subsequent OTs are based off of. Much cleaner.

I decided to add it to this PR rather than later because it allowed removing some complexity that had been added to the storage format earlier in the PR -- before anyone actually stored data in that format.

@ask-bonk

ask-bonk Bot commented Oct 5, 2026

Copy link
Copy Markdown

LGTM!

github run

@github-actions github-actions Bot deleted a comment from ask-bonk Bot Oct 5, 2026
@ask-bonk

ask-bonk Bot commented Oct 5, 2026

Copy link
Copy Markdown

LGTM!

github run

@github-actions

github-actions Bot commented Oct 5, 2026

Copy link
Copy Markdown

Eval results

Verdict: ⚪ Unchanged. No task moved beyond what 10 runs can tell apart from noise.

Task Score Δ score Fisher test Cache hits Avg min Avg steps
change-calendar 80% → 100% +20 pp p = 0.47 87%
0 pp
3.3 → 3.2 22.5 → 25.4
chess 90% 0 pp p = 1.00 97%
0 pp
8.9 → 9.0 59.6 → 68.4
incident-desk 90% → 100% +10 pp p = 1.00 94% → 95%
+1 pp
3.9 29.0 → 34.1
worker-logs 90% → 80% −10 pp p = 1.00 93% → 91%
−2 pp
3.9 → 2.9 26.7 → 20.4
Failed checks
Task Check Failed
change-calendar t2 plan-is-a-document-listing-exactly-the-weeks-windows 1/10 → 0/10
change-calendar t4 names-the-booked-windows-the-new-rules-reject 1/9 → 0/10
chess t1 agrees-with-the-oracle-on-the-hard-positions 1/10
chess t1 agrees-with-the-oracle-on-perft-positions 1/10 → 0/10
chess t1 agrees-with-the-oracle-through-random-games 1/10 → 0/10
incident-desk t1 opens-acknowledges-and-resolves-in-order 1/10 → 0/10
incident-desk t1 simultaneous-opens-of-one-id-admit-exactly-one 1/10 → 0/10
worker-logs t1 hourly-buckets-cover-every-hour-including-empty-ones 0/10 → 2/10
worker-logs t1 ranges-are-half-open-and-filter-by-worker 0/10 → 2/10
worker-logs t1 ingests-resets-and-summarises-per-worker 0/10 → 1/10
worker-logs t3 latency-rises-match-the-reference 1/10 → 0/8
worker-logs t3 latency-rises-count-late-events 1/10 → 0/8

Run · trajectories and raw results

@github-actions github-actions Bot deleted a comment from ask-bonk Bot Oct 5, 2026
@ask-bonk

ask-bonk Bot commented Oct 5, 2026

Copy link
Copy Markdown

🔬 Eval runs review

Performance

Across 10 runs per task, pass rates were calendar 80% → 100%, chess 90% → 90%, incident desk 90% → 100%, and worker logs 90% → 80%; comparison.json labels these changes unchanged, not beyond noise. Chess remained the largest expense, averaging about $0.053 → $0.066 and 59.6 → 68.4 model steps, with PGN implementation and exact-text editing retries driving detours; worker logs fell from about $0.023 → $0.019 and 26.7 → 20.4 steps, partly because two candidate runs stopped after turn 1. Cache hits stayed broadly similar, and chess’s significant reduction in cache breaks does not demonstrate fewer misses; the PR’s new merge-conflict guidance also prompted unnecessary conflict searches in freshly built gadgets.

⚪ VERDICT: NO REGRESSION FROM THIS PR

Neither the candidate’s generated-code failures nor its cache and cost changes establish a beyond-noise regression caused by the blueprint-format or merge changes.

Triage

Failure modes

  • Events lost before hourly reporting · worker-logs 2/10 · model error · this PR: no — In trial 2, turn 1’s writeFile(server.js) validated route but omitted it from normalizeEvent’s return, so ingestion violated the NOT NULL constraint and subsequent queries were empty. In trial 9, the same turn’s server write grouped timestamps using division without explicit flooring; the final executeCode smoke test returned zero for an hour containing an event, but the agent still claimed success. The task supplied the necessary event and bucket contracts; neither defect follows from the diff.
  • Rook capture preserves castling rights · chess 1/10 · model error · this PR: no — Trial 6, turn 1’s writeFile(server.js) compared the captured piece with castling-right letters (K/Q/k/q) rather than rook pieces (R/r). After gxh1=N, move() returned FEN retaining K; later smoke tests covered promotion and castling separately, not their interaction. Main also failed one chess run, but with broader legal-move errors.

Tool errors

  • editFile: Validation failed · change-calendar 1 → 4, chess 3 → 6, incident-desk 5 → 5, worker-logs 5 → 4 · model error — Calls omitted required fields or invented parameter names. Candidate calendar trial 2 repeated .replacement despite the error naming replacement; main chess trial 1 omitted filename and then corrected it.
  • editFile: No matching text was found in the file · chess 10 → 16, incident-desk 2 → 4, worker-logs 1 → 0 · model error — Agents quoted text that differed from the file, especially backslash escaping during PGN edits. Candidate chess trial 2, turn 2 issued six mismatching edits together, then used grep to recover; main chess trial 2 similarly recovered with corrected escaping.
  • readFile: File does not exist · incident-desk 2 → 6, worker-logs 4 → 1 · model error — Agents tried reading server.js or client.js immediately after creating an empty gadget. Candidate incident-desk trial 3 and worker-logs trial 2 then created the files with writeFile; main incident-desk trial 9 made the same unnecessary reads.
  • editFile: Multiple matches were found · change-calendar 0 → 1, chess 3 → 0, incident-desk 2 → 3, worker-logs 1 → 0 · model error — The schema explicitly requires exactly one match, but agents supplied repeated helper calls or SELECT fragments. Candidate calendar trial 7, turn 1 recovered by adding surrounding context; incident-desk trial 7, turn 3 similarly expanded its SELECT match.
  • executeCode: Failed to start Worker · chess 2 → 1 · model error — Main trials 1 and 10 supplied bare numeric source (24 and 3); candidate trial 7, turn 3 supplied 44. Each lacked the required default export and succeeded after retrying with an exported function, rather than exposing a Workshop startup defect.

Prompt cache

  • chess · cache hits 96.55% → 97.28% · breaks 0.87% → 0.49% · this PR: unclear — Break-rate p = 0.023, but mean steps increased from 59.6 to 68.4. Both trial-1 transcripts lose most history cache reads at the start of turn 2, after gadget creation changes the system prompt’s gadget/file listing: main reads 6,948 tokens and writes 16,492; candidate reads 7,174 and writes 16,225. Both reuse the accumulated history at turn 3. The candidate’s additional 226 initial prompt tokens match the new conflict guidance, but no inspected step demonstrates fewer misses; additional cached steps can lower the break rate.

What to do

  • In packages/workshop-backend/src/agent.ts, scope the new merge-conflict guidance explicitly to an actual merge or reported conflict, so agents need not scan freshly created gadgets before finishing. This addresses observed extra work, not a demonstrated regression.
  • Optional: add a brief correctness-check reminder to the gadget-writing guidance in packages/workshop-backend/src/agent.ts: exercise ingestion and a nonempty hourly bucket, inspect returned values, and test interacting chess special moves. The observed failures are model errors, not defects this PR must fix.

github run

@Maximo-Guk

Copy link
Copy Markdown
Member

🤖 comment ( feel free to refute! )

  1. P2 File/directory conflicts abort blueprint updates
    Location: packages/workshop-backend/src/overseer.ts:3445–3450
    If a gadget locally modifies file lib while an upstream release replaces it with lib/helper.js, the flattened merge result contains both paths. Writing that result throws conflicting file paths at: lib/helper.js before creating the proposal chat, so neither the user nor the agent can resolve the conflict through the update workflow.
    Reproduced both an upstream file-to-directory refactor and concurrent additions. These conflicts need a representable resolution state before writing the proposal commit.

Comment thread packages/workshop-backend/src/agent.ts Outdated
This is all stuff every model already knows, and we already inform the agent in context when merge conflicts actually exist.
@kentonv

kentonv commented Oct 5, 2026

Copy link
Copy Markdown
Member Author

@Maximo-Guk Known pre-existing issue: OTs don't detect when a name has been created both as a file and a directory; an error is raised only when you try to commit. Worth fixing eventually but not in this PR I think.

Comment thread packages/workshop-backend/src/agent.ts
@ask-bonk

ask-bonk Bot commented Oct 5, 2026

Copy link
Copy Markdown

Review: 1 finding.

Posted 1 new actionable inline comment in a single review. Existing findings were not duplicated.

github run

@kentonv
kentonv merged commit dcedc16 into main Oct 5, 2026
17 of 18 checks passed
@kentonv
kentonv deleted the kenton/git-blueprints branch October 5, 2026 17:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

kernel Changes to the Workshop kernel workshop/frontend Changes to the Workshop frontend workshop/shared Changes to shared Workshop APIs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants