Skip to content

fix(mcp): bound plan context reads and derive lane freshness once - #2297

Merged
ScriptedAlchemy merged 2 commits into
masterfrom
fleet/beta55-plan-context-freshness
Sep 27, 2026
Merged

ScriptedAlchemy merged 2 commits into
masterfrom
fleet/beta55-plan-context-freshness

Conversation

@ScriptedAlchemy

@ScriptedAlchemy ScriptedAlchemy commented Sep 27, 2026 •

Copy link
Copy Markdown
Owner

Two findings from running the released 1.0.0-beta.55 against a ~200k-symbol index. They share the context read path (compute_context in handlers/graph/search.rs), so they land together.

(a) Plan-mode context timed out

Rebased onto #2296, which moved compute_context into handlers/graph/context.rs; the change lands there.

Cause. verified_plan_context computed test coverage with test_annotated_logical_files(None, …): a whole-generation census that paged every symbol (symbols_page) and fanned out over every annotation edge (edges_among), and only then filtered to the anchors' callers. On this repository that alone exceeded the 10 s project route deadline.

Change. Coverage comes from the anchors' two-hop caller files: named test files directly, inline-annotated files through the scoped test_annotated_logical_files(&paths, …) (per-file catalog index, the same path affected/diff_context/pr_context already use). The unscoped census had no other production caller, so the Option arm is removed. tracedecay_context now reports the verified graph read's RequestCostReceiptV1, so its reads are observable (tracedecay_cost: trailer).

Journey (this repo as corpus, isolated profile, one 12 GB-capped daemon). Same query, tracedecay tool context --args '{"task":"run_update_command","mode":"<m>","lexical_anchors":["run_update_command"],"format":"json"}':

Before, released beta.55 binary (release profile):

explore wall=1.23s   plan wall=9.82s
explore wall=0.87s   plan wall=10.18s  Error: project route error (timed_out): The admitted request timed out
explore wall=0.85s   plan wall=10.18s  Error: project route error (timed_out): The admitted request timed out

After, this branch (perf profile + hotpath, slower than release):

explore wall=1.42s   plan wall=1.15s  tracedecay_cost: wall_us=907172 graph_sealed_reads=25 graph_staging_reads=0 adjacency_queries=10 adjacency_rows=33
explore wall=1.16s   plan wall=1.14s
explore wall=1.12s   plan wall=1.19s
plan → {"extension_points": [], "test_files": ["crates/tracedecay-cli/src/main.rs", "crates/tracedecay/tests/mcp_suite/context_lexical_anchor_eval_test.rs"]}

Hotpath (after, 3 plan calls): mcp.graph.plan_context avg 2.11 ms, usecases.graph.verified.test_annotated_files avg 1.44 ms, mcp.graph.context.total avg 953 ms (search-dominated, same as explore). The release build has no hotpath lane; the before number is the wall time above.

Test. plan_context_reads_only_the_anchor_neighborhood (mcp_suite): a fixture with 40 unrelated files carrying inline #[test]s; plan mode must report test_files == ["src/lib.rs"] and a cost receipt with adjacency_rows < 40.

  • Fails with the fix reverted: assertion left == right failed: one cost trailer: [...] left: 0 right: 1 (no receipt; the census path is unmetered).
  • Passes with the fix.

(c) Search freshness contradicted itself

Cause. The executor marks lanes stale whenever the query gate served a generation it could not prove current itself, while the verdict comes from the scheduler's worktree reading. The two disagreed in one envelope.

Change. lanes_under_scheduler_freshness restates the executor's lane coverage under the scheduler reading: when the scheduler proves the served generation current (fresh, no rebuild, latest == served), a lane stale against that generation is complete (a partial lane drops its stale generation). search_freshness and the new function share one predicate, scheduler_proves_current. A newer sealed generation keeps the lanes stale.

Test. search_lanes_answer_to_the_freshness_the_verdict_reports (tracedecay lib).

  • Fails with the fix reverted: "summary": "state=fresh rebuild_in_flight=false served_generation=generation.mcp-verified-graph-fixture.1 latest_generation=generation.mcp-verified-graph-fixture.1 stale_lanes=exact,graph", "state": "possibly_stale".
  • Passes: {"freshness":{"state":"fresh"}} with exact/graph complete; with a newer latest generation, stale_lanes == ["exact","graph"].

Verification

  • cargo test -p tracedecay-graph-query -p tracedecay-mcp --lib: graph-query 23 passed, mcp 390 passed, 0 failed
  • cargo test -p tracedecay --features test-transport,test-helpers --lib -- graph_search_dispatch search_graph_independence: 12 passed, 0 failed
  • cargo test -p tracedecay --features test-transport,test-helpers --test mcp_suite -- typed_evidence_trailers context_behavior context_related search_behavior: 15 passed, 2 failed. The two failures are typed_callees_carry_their_read_cost_on_the_envelope_and_the_trailer and typed_callers_carry_their_read_cost, red on master (test(mcp): read-cost pins red after single-row code edges (#2277) #2291), not touched here.
  • cargo clippy -p tracedecay-graph-query -p tracedecay-mcp -p tracedecay --all-targets -D warnings, with and without test-transport,test-helpers: clean.
  • cargo fmt --all -- --check: clean. pnpm run contracts:check (dashboard): contracts up to date.

Plan-mode context attributed test coverage through a whole-generation
test-annotation census: it paged every symbol and fanned out over every
annotation edge before looking at the anchors' callers. On this
repository's ~200k-symbol index that took the read past its 10 s project
route deadline (`project route error (timed_out)`). Coverage now comes
from the anchors' two-hop caller files alone: named test files directly,
inline-annotated files through the per-file catalog index. The unscoped
census had no other production caller and is removed. Context reads now
carry the verified graph read's cost receipt.

A search or context response could say `state=fresh
rebuild_in_flight=false` and still mark lanes stale: the query gate that
served the lanes and the scheduler reading behind the verdict were two
freshness authorities. Lane states are now restated under the scheduler
reading, so a generation the scheduler proves current reads complete in
every lane, and a newer sealed generation keeps them stale.
@changeset-bot

changeset-bot Bot commented Sep 27, 2026 •

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: cfb5a2e

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 27, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-27T06:47:15.509817Z b46bd7e PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b46bd7e76c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +115 to +116
CodeIndexLaneStatusV1::Stale { generation } if generation == served_generation => {
*lane = CodeIndexLaneStatusV1::Complete;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve lane-local stale outcomes

Do not convert every matching Stale lane to Complete: CodeIndexSearchCoverageV1::from_fallback_lane_coverage uses this variant both for a whole-generation fallback and when an authenticated retriever independently returns PublicRetrieverStatus::Stale. In the latter case, a scheduler report that the worktree generation is current does not make that lane's evidence current; this rewrite can turn an empty/stale exact, lexical, or graph lane into complete and make the overall response report fresh. The conversion needs provenance that the status came specifically from served_stale, or it must preserve lane-local stale statuses.

AGENTS.md reference: AGENTS.md:L9-L12

Useful? React with 👍 / 👎.

@ScriptedAlchemy
ScriptedAlchemy merged commit e891e6c into master Sep 27, 2026
1 check passed
@ScriptedAlchemy
ScriptedAlchemy deleted the fleet/beta55-plan-context-freshness branch September 27, 2026 12:10
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