Repository navigation
feat(context): reuse code evidence with per-file revalidation - #2425
Conversation
A reuse proof now records each cited code file as Scope, path and file digest (with the index fingerprint), sharing MAX_REUSE_DEPENDENCIES with Atomic Memory. Validation re-reads only the cited files from the Scope's worktree and fails closed when one changed, disappeared or the repository is unavailable, so prepare can combine include_reuse with include_code. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PC_GATEWAY_INJECTION_CODE=on asks prepare for code evidence. When before_send withholds a replayed injection with context-refresh-required, for example because the agent edited a cited file, the executor drops that conversation's replay entry, prepares once more from the pre-injection request and checks again, instead of returning the withhold. A request refreshes at most once. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ompatibility Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| if request.include_reuse: | ||
| self._require_reuse_scope(scope) | ||
| if request.include_code: | ||
| raise ContextReuseError("context-reuse-code-unsupported") |
There was a problem hiding this comment.
P2: the machine-readable contract still says include_reuse rejects code.
Removing this guard is the point of the PR, but openapi/powercontext.yaml is not part of the change set and at this head it still states, on the include_reuse request field:
Return a versioned context and reuse envelope. Supported for single-Scope Atomic Memory with complete bounded dependencies; unsupported selected families, code, or references are rejected explicitly.
That sentence is false at 02bd987c: prepare_reuse/validate_reuse accept code dependencies (context_reuse.py:80, :194), application.py no longer raises context-reuse-code-unsupported, and the new prose in docs/{en,zh}/docs/operate/context-protection.md says the opposite ("The reuse profile supports a single Scope, selected Atomic Memory, and code evidence (include_code: true)" / 中文版第 46 行). Two files in the same PR series now describe the same flag incompatibly, and the contract one is the older, wrong one. context-reuse-code-unavailable and context-reuse-source-changed are also unlisted there, while the doc enumerates when each fires.
Consequences beyond wording:
openapi/powercontext.yamlis the source for the published API reference, so clients keep modellinginclude_reuse + include_codeas rejected and will not send the combination.- The file is also the digest input for the bundled Desktop contract:
desktop/src-tauri/src/transport/operations.jsoncontractSha256equalssha256(LF(openapi/powercontext.yaml))(verified: this branch's openapi hashes to the recorded value). A prose-only edit therefore moves that digest too, which is presumably why it was left alone; the churn is deferred, not avoided, and the next PR that touches openapi inherits the wrong sentence. - No check catches this:
make api-generate-check/js-api-generate-checkcompare generated code against the same file, so description drift is invisible.
Minimum fix: replace the code mention with what the proof now records per cited file ({path, file_sha256, fingerprint}, validated against the current worktree) and regenerate the Desktop contract digest in the same commit, or record in the PR why the contract stays stale until the next digest-moving change.
There was a problem hiding this comment.
Thanks, you're right that the contract and the docs now disagree. I left openapi/powercontext.yaml alone for the reason you guessed, and I should have said so in the PR.
Changing that description moves the normalized contract digest. That needs a new qualified Desktop profile in compatibility.json for the new digest: a wheel built at the PR commit and desktop/tests/real_server.py run against it in all four modes, as for sqlite-atomic-112db11e-v1. That run builds the Tauri server_probe example, which I can't build in this environment (no GTK/WebKit toolchain). Only updating operations.json/operations.ts would fail test_bundled_desktop_contract_has_a_qualified_business_profile.
So I've recorded in the PR description that the contract text stays stale until the next digest-moving change, with the exact replacement:
Return a versioned context and reuse envelope. Supported for a single Scope with complete bounded dependencies: selected Atomic Memory, and code evidence recorded per cited file as path, file digest and index fingerprint and validated against the current worktree (
context-reuse-source-changed,context-reuse-code-unavailable). Unsupported selected families or references are rejected explicitly.
If you'd rather have it in this PR, I'm happy to make the change, provided a maintainer can run the Desktop qualification for the resulting commit, or I can open an issue so the next contract change picks it up.
There was a problem hiding this comment.
Rechecked at 2d324ac48: both runtime findings from my review are fixed in the original real-Git/SQLite scenarios. This existing documentation gap remains: the include_reuse description in openapi/powercontext.yaml and the generated Python request model still says code is rejected, while the runtime now accepts it.
Documentation follow-up (non-blocking): please track the source-contract correction together with the required generated artifacts and Desktop qualification. The PR-body explanation records why it is deferred, but the published API guidance consumed by clients still needs that correction.
There was a problem hiding this comment.
Thanks for rechecking. Tracked in #2431, with the replacement text and the generation and Desktop qualification steps, so the next digest-moving contract change can include it.
There was a problem hiding this comment.
对照 #2431 的修复步骤做了一次核对,供后续跟进参考:该 issue 的 Steps to fix 已覆盖本次改动需要的全部四步——改 openapi/powercontext.yaml 的 include_reuse 描述、make api-generate / make js-api-generate / pnpm --dir desktop generate、为新契约摘要补 Desktop 资格记录与 desktop/VALIDATION.md 证据、make contract-test。也就是说契约冲突的修复路径是完整的,缺的只是"移动摘要"这个时机。
因此本线程保持打开直到 #2431 落地即可,不需要在本 PR 内先改描述:本 PR 现在只留下"文档与 openapi 说法相反"这一处不一致,源码侧(prepare 记录、check 复验、file_digests 读工作区而非索引)已在上一轮核对通过。
|
@jasper-yl Please keep this work on the |
hidb4ai
left a comment
There was a problem hiding this comment.
I reproduced two code-reuse contract failures at this head: ineligible Git paths remain reusable, and mixed provenance can exceed the shared dependency limit. Details inline.
| if _excluded(path, config): | ||
| digests[path] = None | ||
| continue | ||
| content, reason, _size, _mode = _read_relative(root_fd, path, limits.max_file_bytes, deadline) |
There was a problem hiding this comment.
[P2] Honor Git inventory eligibility when revalidating cited files
With the default include_untracked=False, prepare a proof citing tracked src/sample/budget.py, then run git rm --cached src/sample/budget.py, add that path to .gitignore, and rebuild the index. A fresh prepare omits the file, but validate_reuse still succeeds and enforcing before_send returns allow for the old code. _excluded checks configured patterns and content rules; this lookup never checks the tracked/untracked/ignore eligibility used by capture. Consequently a no-longer-indexable file remains reusable until expiry, contrary to the documented source_ineligible behavior. Recheck the current Git eligibility of the cited paths and invalidate their proofs when they become ineligible, while preserving reuse across unrelated file changes.
There was a problem hiding this comment.
Thanks, confirmed. Fixed in 2d324ac. worktree_digests now checks the cited paths against the same _inventory the capture uses (tracked files, plus untracked and not ignored ones when include_untracked is set), and refuses symlinks and submodules as the capture does. Your scenario (git rm --cached, add to .gitignore, rebuild) now fails with context-reuse-source-changed even though the bytes are unchanged; it is test_a_cited_file_that_leaves_the_git_inventory_stops_reuse against a real repository and index. Unrelated file changes still keep the proof valid.
| roots = _selected_roots(scope_id, build) | ||
| code_dependencies = _code_dependencies(scope_id, build) | ||
| if len(roots) + len(code_dependencies) > MAX_REUSE_DEPENDENCIES: | ||
| raise ContextReuseError("context-reuse-dependency-limit") |
There was a problem hiding this comment.
[P2] Count the complete Memory snapshot in the shared dependency limit
This counts selected Memory roots, but read_dependency_snapshot also includes their historical ancestors and Source records. In a real SQLite runtime, creating 31 Atomic Memories, merging them into one, and preparing that merged Memory with two cited code files issues and validates a proof containing 63 Memory dependency records plus 2 code records: 65 dependencies. This check sees only 1 + 2; validate_reuse repeats the same root-based check. Enforce the shared 64-dependency limit against the full Memory snapshot plus code dependencies at issuance and validation, so adding code cannot exceed the declared bound.
There was a problem hiding this comment.
Thanks, confirmed. Fixed in 2d324ac. The bound is now checked against the complete Atomic Memory snapshot returned by read_dependency_snapshot (ancestors and Sources included) plus the code files, both when the proof is issued and when it is validated. The early root-plus-code check remains as a cheap pre-check. test_the_full_memory_snapshot_and_code_share_the_dependency_bound reproduces one root whose snapshot has 63 records, plus two code files, and gets context-reuse-dependency-limit.
… full dependency set A cited file must still be in the current Git inventory (tracked, or untracked and not ignored when the repository includes untracked files), so a file that is untracked and ignored stops reuse even with unchanged bytes. The shared dependency bound now counts the complete Atomic Memory snapshot, including ancestors and Sources, plus code files, at issue and at validation. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@jasper-yl We have completed the benchmark and pushed the experiment branch to frf12/powercontext, commit We did not observe a benefit from code graph injection in this tested configuration. The fixed sample contains 66 SWE-bench Pro tasks (11 repositories × 6, seed 2425, not selected by historical solve results). Both arms used
The one-task solve difference does not establish a stable quality regression. Token figures are known subtotals, with missing usage observations. The branch includes the runner, fixed cohort, per-case results, experiment patches and recovery instructions. The full report documents the 64 original pairs plus two technical-recovery pairs, all retry costs and the grader-only network recovery. The tested integration used #2425 at Please review this branch, its configuration and the results, and confirm from your side whether they match the intended behavior. |
|
@frf12 thank you for the careful run and for publishing the branch and data. The configuration matches what #2425 is meant to test in isolation: code evidence through the gateway, with steering, memory, plugins and web search off. Your result agrees with our own data:
My hypothesis for the cache loss is the refresh path I added: when the agent edits a cited file, the next replay fails revalidation, the gateway prepares again, and the injected block in the turn's user message changes. Every byte after it, including the whole tool history, is then uncached. With 234 prepares in total, ON averages roughly two to three per arm against one per turn for OFF, which fits this. The logs would confirm it: count So I don't think #2425 should merge as it is. A code-injection design would at least have to keep the conversation's cached prefix intact: check freshness when a value is first injected, and keep replaying that exact value for the rest of the conversation, as the agent's own tool outputs already do, instead of rewriting it. Even then our data shows no gain over the instruction alone. I'd keep #2405 open only for that design question and leave the branch and your results as the reference. I don't think another run is warranted until a design is agreed. |
|
@jasper-yl the github ci action failed. |
hidb4ai
left a comment
There was a problem hiding this comment.
Re-reviewed unchanged head 2d324ac48 after reading the newly published benchmark and the author’s response. The two earlier correctness findings remain fixed.
Design note (non-blocking): I recommend holding the merge pending an agreed code-injection design and evidence of benefit. The 66-task report shows OFF 45/66 versus ON 44/66, with recorded non-cached input tokens up 72.71%. That one-task difference does not establish a quality regression, and the token figures are incomplete usage subtotals. The run used 02bd987c8 plus documented benchmark patches; the current head was not benchmarked. Refresh-induced prompt-cache loss remains a hypothesis requiring trace confirmation. These results do not currently support an efficiency claim for the feature.
CI is also incomplete: code-seekdb (3.14, 0) was canceled after exceeding the job’s 30-minute limit during migration tests, which failed the aggregate CI result. I found no evidence establishing that timeout as a defect introduced by this PR.
|
Mergeability update for head Please synchronize with the target branch, resolve the compatibility declaration against the final merged persistence inputs, and rerun the required checks before merging. This merge conflict appeared after the previous review. |
Resolve the conflict with Source locators (v1_5_0_004), code evidence reuse (oceanbase#2425), and the 79 other commits that landed since the previous merge. - `release/upgrade-support.json`: keep the rationale sentences added upstream for Topic Memory diagnostics, code evidence reuse, and the `pc_artifact_source_locators` relation, append the sentence about this branch's code-organization changes, and recompute the persistence digest after formatting. The generated table and index DDL for all 76 tables is identical to master on the SQLite and MySQL dialects. Follow the removal of the scheduler path into code that arrived with the merge: 15 `scheduler_path` arguments in six new tests (Atomic Memory reconciliation, temporal retention, and unrelated reconciliation, the MiniMax host, the Hermes conformance support, and the release 1.4.0 migrations) are dropped. The new `cli/minimax.py` is imported only inside the CLI package, so it stays where it is. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Which issue or RFC does this PR close?
Closes #2405. The design choices are the two described in this comment.
Rationale for this change
preparerefused to combineinclude_reusewithinclude_code, so the gateway, which only injects values that carry a reuse proof, could never inject code context. A proof for code needs its own dependency record, because code goes stale when repository files change, not when Artifact revisions change. Code evidence already carries what that record needs: the path, the file digest and the index fingerprint.What changes are included in this PR?
Core
context_reuse.py: a reuse proof now records one dependency per cited code file:{path, file_sha256, fingerprint}, deduplicated by path and sorted.context-reuse-cross-scope-unsupported).MAX_REUSE_DEPENDENCIES._current_code. It fails closed withcontext-reuse-source-changedwhen a cited file now has another digest, is missing, or would no longer be indexed. It fails withcontext-reuse-code-unavailablewhen the Scope has no usable code configuration or repository.codefield and cite no code, so they validate as before.code/capture.py:worktree_digestsreads only the listed files, with the same no-follow path walk, exclusion rules and content rules as a capture.CodeService.file_digestsandScopedCodeApplication.file_digestsexpose it.application.py: theinclude_reuse+include_coderejection is removed, and the Scope's code application is passed to proof issue and validation.checkalready reports a failed proof as acontext-refresh-requiredwithhold.context-protection.md(English and Chinese) describes the code dependency.release/upgrade-support.json:src/powercontext/builtin/codeis a managed persistence input, so the compatibility review is re-signed. The added sentence states that the change is a read-only lookup and leaves the index cache layout, manifest, graph rows, publication journal, SQL layout, migration resources and checksums unchanged.Gateway
PC_GATEWAY_INJECTION_CODE=onsetsinclude_codeon prepare. It is off by default, and the assembly staysmemory_only.Executor::run: whenbefore_sendwithholds a request that carried an injection, withcontext-refresh-required, the executor:ReplayCache::forget);A request refreshes at most once. A second refresh-required withhold, or one for a request without an injection, is returned as before. For code reuse this means one preparation per user message, plus one each time a cited source changes.
Docs:
deploy-server.md(English and Chinese) andgateway/README.md.Not changed: the API contract text. The
include_reusedescription inopenapi/powercontext.yamlstill says code is rejected. Editing it moves the normalized contract digest, and the bundled Desktop contract then needs a newly qualifiedcompatibility.jsonprofile: a wheel built at the change anddesktop/tests/real_server.pyrun in all four modes, which needs the Tauri toolchain. The description therefore stays stale until the next digest-moving change. The replacement text is in the review thread.Are there any user-facing changes?
preparewithinclude_reuseandinclude_codenow returns a proof instead ofcontext-reuse-code-unsupported.PC_GATEWAY_INJECTION_CODE.Deployments that change none of these settings behave as before.
How was this change tested?
Core, against a real git repository and code index (
tests/builtin/runtime/test_code_context_reuse.py):test_code_reuse_revalidates_cited_files_only:budget.pyvalidates;source-changed, and restoring its bytes makes it valid again;source-changed;code-unavailable.test_code_reuse_is_bound_to_its_scope.test_code_dependencies_stay_in_scope_and_within_the_bound: a cross-Scope origin, andMAX_REUSE_DEPENDENCIES + 1files.The existing reuse test asserted the old rejection; it now checks that without a code index the proof simply cites no code.
make check(lock, hooks, ruff, ty, upgrade policy) passes.pyteston the reuse, prepared-context, context-check and native-code suites: 182 passed, 3 skipped.Gateway:
cargo fmt,clippy -D warningsandcargo test: 136 passed.a_stale_reuse_proof_drops_the_replay_and_prepares_once_more: three steps of one tool loop. The replay that turns stale is withheld, prepared again and sent with the new value; the stale value is never sent, and the next step replays the new value.a_request_refreshes_at_most_once.refresh_required_without_an_injection_is_withheld_directly.code_evidence_is_requested_only_when_enabled.With the refresh branch disabled, the first two fail.
End to end with a Server and a gateway built from this branch, on a git repository bound to the Scope and indexed with
powercontext code index, and a scripted Responses upstream. The client was simulated withcurl: one conversation (X-PowerContext-Conversation-ID) whose user message asks about the indexed function, followed by tool continuations.prepare_contextcheck_contextbudget.pyAll four requests returned 200. The code-reuse benchmark and safety validation you offered in feat(context): let reuse proofs cover code evidence, so the gateway can inject code context #2405 are not part of this PR.
AI usage statement
Built with Claude Code (Claude Opus 5.5), which wrote the change, the tests and the docs, and ran the checks above.
🤖 Generated with Claude Code