Skip to content

fix(core): keep partial Code Mode catalog updates proportionate - #57090

Open
reynold-hu wants to merge 1 commit into
anomalyco:devfrom
reynold-hu:codemode-partial-delta
Open

reynold-hu wants to merge 1 commit into
anomalyco:devfrom
reynold-hu:codemode-partial-delta

Conversation

@reynold-hu

@reynold-hu reynold-hu commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Issue for this PR

Closes #55529

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

When the Code Mode catalog is partial (the inline budget hides some tools), update fell back to the full catalog as soon as a shown entry changed. Browser attach/detach or MCP reconnect changes those entries, so normal desktop use kept re-injecting the whole ~9–14k-character catalog as a fake user turn.

The partial branch now emits a short notice: "The Code Mode tool catalog has changed. Re-run search(...) before relying on previous results.", plus the existing namespace added/removed/changed details. It never repeats the full catalog from this branch. The complete-catalog delta behavior is unchanged.

Debouncing the attach/detach flap itself is left out so semantic changes still surface immediately; this change makes each notice proportional to the change.

How did you verify your code works?

  • New test in packages/core/test/codemode/instructions.test.ts: a 60-tool partial catalog; changing a shown entry returns a <200-character notice containing the re-run line, without ## Available tools or tool listings. On the parent the same update returns the full catalog.
  • bun test test/codemode/instructions.test.ts: 4 pass, 0 fail.
  • packages/core typecheck passes.

Screenshots / recordings

No UI change.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

Added by the opencode bot (AI-generated).

Related: #55389, #54408

@github-actions

Copy link
Copy Markdown
Contributor

The following comment was made by an LLM, it may be inaccurate:

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fix works for the scenario in #55529. I ran the issue's reproduction script (github 46 / nas-ops 35 / opencode 8 tools, adding and removing a 45-tool browser namespace) against the real catalog code. On the base, attach and detach each send the full catalog (8,811 and 8,912 characters). With this PR they send 154 and 184 characters: the re-run line plus the namespace added/removed line. The new test fails on the base and passes here. instructions.test.ts passes (4/4) and the packages/core typecheck passes.

Public API: unchanged. This only changes the text of the Code Mode instruction update.

One point that isn't blocking, noted inline: when a shown tool's own listing changes, the model only gets "catalog changed, re-run search", with no detail.

Related: #55389, #54408

// mean the full inventory changed enough to repeat it. Point the model at search instead.
const parts = [
entriesChanged
? "The Code Mode tool catalog has changed. Re-run `search(...)` before relying on previous results."

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dropping diff.added and diff.removed here makes sense, because shown entries come and go whenever the inline budget is redistributed. diff.changed is different. It means the same path is shown before and after, but its signature or description changed. Budget redistribution doesn't produce that; it's a real change to a tool the model still sees in its earlier catalog. When the count stays the same (for example, an MCP server reconnects with an updated schema), this branch now sends only the generic "re-run search" line, and the old signature stays in context. The complete-catalog branch lists these lines. Listing diff.changed here as well ("Changed tool listings supersede the previously listed ones: …") would cost little and keep the notice accurate.

@Neko-Yukari

Copy link
Copy Markdown

Posting numbers from the duplicate (#60129) as suggested, since they point the same way and go a bit further.

What we changed. CodeModeInstructions.update() falls back to render(current) whenever a shown listing changes while the catalog is partial. CodeModeCatalog.snapshot() now keeps the full inventory (path + rendered line) with a fingerprint, and select() derives the inline sample from it — so a rotated inline sample is no longer a catalog change. update() then diffs snapshots and emits only added/changed/removed listings and namespaces, restating everything only when the delta would be longer than a full render.

Measured on dev 726cf0963. Live opencode serve, 8 MCP servers (context7, enhanced-terminal, exa, gh_grep, graphify_hybrid, magpie-image, playwright, windows), disconnect all, then one connect per cycle ×6, one real step after each. Counting only the core/codemode notice text from the session DB (the same notices also carry core/mcp-guidance, split out):

dev patched
codemode notice chars 17,924 358 (−98.0%)
incl. shared MCP guidance 22,165 4,599 (−79.3%)

packages/core codemode tests: 21 pass on dev, 31 pass with the patch; typecheck:tsc and bun run check clean.

Caveat on this scenario: on 726cf0963 an MCP disconnect does not shrink an already-created session's catalog while a connect does, so only the growth case is measured live.

Branch: codemode-delta-catalog on Neko-Yukari/opencode at 2208ed9e. Happy to fold this into this PR's shape if you prefer the smaller "short notice" approach over real deltas.

Neko-Yukari added a commit to Neko-Yukari/opencode that referenced this pull request Oct 31, 2026
…anges

Folds in the short notice from anomalyco#57090: when a partial catalog would restate
more than 1,000 characters of added or changed listings, the delta names the
affected namespaces and asks the model to re-run search(...) instead. Smaller
deltas still restate the lines, since one listing costs less than the search
step it saves, and complete catalogs always restate because search is not
offered there.

Co-authored-by: Reynold Hu <[email protected]>
@Neko-Yukari

Copy link
Copy Markdown

Numbers from folding this shape into the fuller delta design from #60129, so the two can be compared side by side.

What the merged behaviour is. A complete catalog always restates the added or changed listings, because search(...) is not offered there. A partial catalog keeps restating while the added and changed listings together stay under 1,000 characters — one listing costs less than the search step it saves — and past that it emits the namespace-level notice from this PR instead:

Tool listings changed in \bulk`. Re-run `search(...)` before relying on previous results.`

Measured on a 60-tool partial catalog (tools.bulk.toolN, signatures ~160 chars):

change text injected
whole catalog block, for reference 8,877 chars
one listing changed 307 chars (restated)
all 60 listings changed 130 chars (notice)

So the notice path costs roughly 1.5% of the block it replaces, and the single-listing path stays small enough that the model does not need an extra round trip.

Checks. packages/core test/codemode 34 pass, test/instructions 17 pass, typecheck:tsc clean. The new test covers both sides of the 1,000-character boundary, so a change that crosses into the notice path cannot silently flip back.

Where it lives. Neko-Yukari/opencode branch codemode-delta-catalog at 7c8bd7169, on top of 726cf0963, with the commit crediting you as co-author. The bulk of the branch is the snapshot/delta work; this PR's notice is the fallback for large churn inside a partial catalog.

Question. If maintainers would rather have the notice shape for every partial-catalog change and no restating at all, that is a two-line change in incremental() — the threshold is a single constant. Happy to reshape this branch that way, or to fold it into this PR if you would rather carry one diff.

@opencodejr flagging so triage sees this; if it belongs on #55529 instead, point me there and I will repost.

@reynold-hu

Copy link
Copy Markdown
Contributor Author

Thanks for the numbers. The threshold shape is the right call: restating under 1000 chars keeps the common single-change case cheap without an extra search round trip, and the notice covers bulk churn at about 1.5 percent of the block. I would like to fold it into this PR as one diff. Happy for you to fold your branch in, co-author credit stays of course.

This branch has not been deployed

No deployments
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.

codemode: "Instructions updated" re-injects the full tool catalog on every tool-tree change (browser attach/detach, MCP reconnect), polluting context

2 participants