gather: deterministic context-pack tool (aft_gather) - #152
Conversation
There was a problem hiding this comment.
4 issues found across 7 files
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
f8e7fa9 to
cf09b9b
Compare
|
Follow-up on the two maintainability notes from the Greptile summary (they weren't separate review threads, so noting here) — both addressed in
Matched/rendered text is byte-identical; 23/23 gather tests green. |
1d068c9 to
d2e15e6
Compare
5364eac to
f42c5ec
Compare
f42c5ec to
66e05e2
Compare
66e05e2 to
d20f276
Compare
|
Rebased onto current The rename covers the advertised tool name and the agent-facing error strings only. The wire command stays Rebase conflicts were confined to Verified: Still one commit. Two notes on the full plugin suite, both pre-existing on |
b06cd3b to
a4b6aae
Compare
|
Opened #192 to ask the design question separately from the diff: whether you want one-call context packs in aft at all, and in what shape. Answer there and I'll rework this to match — or close it if the answer is no. Current state here: green on |
a4b6aae to
500f1a9
Compare
|
Rebased onto v0.49.3 ( CI: 13 green, one red — Scenario 1's
Same job, same assertions, on the two nearest green runs:
So the same code path passes on the parent commit and on the previous revision of this branch. The delta between them is a 90s wall clock on a cold Windows runner, not the diff — nothing in this change is reachable from bridge spawn or plugin boot. I can't re-run it — Separately: #192 asks whether you want this capability at all, which is the more useful question than another rebase. |
500f1a9 to
9bb5af3
Compare
|
Rebased onto
The four new commits didn't touch any file this branch touches, so the rebase was clean and only the governed manifests needed regenerating for the new base. |
a486e48 to
c72bcfd
Compare
5eb5870 to
6b50f10
Compare
6b50f10 to
6755cb9
Compare
Normalize path separators before comparison so absolute and relative spellings of the same file dedupe on Windows. Path::is_absolute() is false for rooted-no-drive paths there, and strip_prefix emits backslashes, so the same symbol previously rendered twice in one pack. Backslash is a legal filename character on Unix, so a file literally named 'a\b.rs' now normalizes to 'a/b.rs' and renders a not-found stub. Accepted: matches normalize_path_for_compare, fails gracefully.
6755cb9 to
4707e57
Compare
|
Green on Since the last update this branch picked up The flag was inert on one path. The coverage gap that allowed it is closed. Restored, it passes with 20 neighbors including the test callers. Red → green → red, all three outcomes observed. A Windows-only test bug. Two red rounds along the way were runner flakes, both cleared on the next sha with no code change touching them — Full gate locally: Still the open question from #192, and it is yours rather than mine: whether one-call context packs belong in aft at all. There is now a second person asking for them in that thread with an independently-arrived-at use case. |
gather: deterministic context-pack tool (aft_gather)
What
One new tool —
aft_gather— assembles a bounded "context pack" (ranked, deduped, budgeted verbatim code evidence) in a single call. It replaces the multi-turnsearch → outline → zoom → callgraphread chain an agent otherwise runs to build context around a question or a symbol.Two modes (mutually exclusive):
question: "how does X work?"— seeds fromhandle_semantic_search(same pipeline asaft_search, all lanes/fallbacks)symbol+filePath— seeds from the callgraph (impactdepth-1 callers +call_treedepth-1 callees)Seeds expand one hop through the callgraph, dedupe by canonicalized (file, symbol) with seeds winning, and render via
render_symbol_within_budgetuntil a hard line budget (default 400, cap 800) is spent. Everything past the cut appears as one-line stubs under## Beyond budget (zoom to expand)— nothing is silently dropped.Why
Agents burn serial turns assembling context: search, then outline the hits, then zoom the symbols, then chase callers. Each turn round-trips through the model. A pack returns evidence (verbatim bodies with file:line headers), not conclusions — the agent reasons over it directly, ready to attach to a subagent dispatch.
Measured on a real config repo (3 questions, one call each vs. the manual chain):
used=283/400. Manual baseline: 5-6 tool calls.budget=200.Independently reproduced on the aft codebase itself:
question: "how does bash output compression dispatch pick a compressor"→seeds=15, used=226/400, one pack assembling the full dispatch chain (compress→ gate →compress_with_registry_exit_code20-compressor array →Compressortrait → install path → subc mirror) that otherwise takes a 4–5-call search→zoom chain.Honest degradation
The pack never lies about its own quality:
file:line (no containing symbol)stubs and flags the header withdegraded=semantic-index-building (partial results — retry when index ready)— detected via the response'ssemantic_statusfield, cleared as soon as one real seed resolves. No blocking or retry inside the tool.{file, line_text, line}, no symbol name) resolve to their containing symbol by line containment — definitions, call sites, and comment hits all upgrade to the enclosing symbol. Hits with no containing symbol stay visible as stubs.(N unresolved external calls omitted)) instead of drowning the stub list; unresolved seeds and callers are never suppressed.Implementation
crates/aft/src/commands/gather.rs— Rust-side composition: callshandle_semantic_search/impact_result/call_tree_result/render_symbol_within_budgetdirectly (shared&AppContext, no bridge round-trips, no parallel reimplementation of search).main.rsdispatch arm,subc_translate.rsmapping, TS factorypackages/opencode-plugin/src/tools/gather.ts+ registration (same tier asaft_callgraph— depends on the callgraph store).protocol.rsResponsedoc-comment):success:false+codefor un-performable calls (e.g.invalid_requeston a bad mode combo),success:truewith a visible degraded/stub pack for partial results — never a bare empty success.Tests
22 unit tests in
gather.rs, including red-checked regressions (each confirmed to fail against pre-fix code): mid-codepoint truncation panic, duplicate-symbol line-anchored resolution, abs/rel path dedupe, containing-symbol resolution via a realTreeSitterProvider, callee-only stub suppression driven through the productionbuild_packpath, and degradation-flag presence/absence/mixed cases.Limitations (deliberate scope)
aft_callgraph.Need help on this PR? Tag
/codesmithwith what you need. Autofix is disabled.Summary by cubic
Adds
aft_gather_context, a one-call, deterministic context-pack builder that returns ranked, deduped, verbatim code within a fixed line budget, replacing the multi-step search→outline→zoom→callgraph chain. Exposed asgatherin Rust andaft_gather_contextin@opencodeon the “all” surface.New Features
question(semantic seeds) orsymbol+path(impact callers + call-tree callees); 1-hop expansion with(file, symbol)dedupe and seeds winning.degraded=semantic-index-buildingandneighbors=skipped(callgraph-unavailable).includeTestsflag to optionally include test-file neighbors (default off).@opencodewith XOR schema validation andpath→filePathmapping; transport-invariance test added; gated to the “all” surface.Bug Fixes
/and renders a clean “file not found” stub.Written for commit 4707e57. Summary will update on new commits.
Greptile Summary
This PR introduces
aft_gather— a new single-call context-pack builder that replaces the multi-turn search→outline→zoom→callgraph chain agents currently run to assemble code evidence. It seeds from semantic search (question mode) or callgraph impact/call-tree (symbol mode), expands one hop, deduplicates by canonicalized(file, symbol), and renders verbatim bodies within a hard line budget.gather.rs, 1665 lines): seeds, dedup, per-symbol budget balancing, grep-fallback resolution to containing symbol, honest degradation flags, and 22 unit tests including regressions for mid-codepoint truncation, budget accounting, and callee suppression.gather.ts): exposes user-facingpathparameter and maps it to internalfilePathbefore calling the bridge; transport-invariant test added.main.rs,translate_gatherinsubc_translate.rs, manifest lane (HeavyInit) and execution mode (Pure) consistent withcallgraphandsearch.Confidence Score: 4/5
pathalongsidefilePath.path→filePathbefore calling the bridge, the Rust handler readsfilePath, and the transport-invariant test validates this. The mismatch is in translate_gather, which is only exercised when gather is invoked directly through the subc bridge with schema-documented parameter names — in that path,has_file_pathstays false and symbol mode returns a confusing validation error instead of running. The integration test avoids this code path by sendingfilePathdirectly.filePathbut the schema (and any direct subc caller) sendspath.Important Files Changed
filePathfrom the agent args, but the subc schema (generated from gather.ts) advertisespath. Any caller using the subc bridge path with the schema-documented parameter will fail to activate symbol mode. The TS execute path maps path→filePath before calling the bridge, so the plugin path is unaffected.path, maps it tofilePathin execute() before calling the bridge. Mode validation mirrors Rust-side logic. Transport invariant test added and passes.pathnotfilePath. Well-structured.pathas the symbol-mode file parameter. Matches the TS tool definition but mismatches translate_gather which readsfilePath.Flowchart
%%{init: {'theme': 'neutral'}}%% flowchart TD A[Agent calls aft_gather_context] --> B{Mode?} B -->|question| C[handle_semantic_search\ntop_k=15] B -->|symbol + path| D[impact_result\ncallers depth-1] C --> E[Parse results\nresolve grep-fallback hits\nto containing symbol] E --> F{seeds empty?} F -->|yes, stubs only| G[build_pack\ndegraded=true] F -->|no| H[collect_callgraph_neighbors\ncallers + callees per seed] D --> I[collect_callees_for_seed\ncall_tree depth-1] I --> J[neighbors list] H --> K[build_pack] J --> K K --> L[Dedupe by file+name\nseeds win] L --> M[Per-symbol budget\nrendering loop] M --> N{lines_used + section + 1 > budget?} N -->|fits| O[Render symbol body\ncount lines] N -->|over budget| P[Add to stubs list] O --> Q{render error?} Q -->|unresolved callee| R[Suppress\nunresolved_count++] Q -->|seed/caller error| P O --> S[Accumulate body] S --> T[Build header last\nused=lines_used+1] T --> U[Append Beyond budget section] U --> V[Response.success text=pack]Reviews (26): Last reviewed commit: "gather: deterministic context-pack tool ..." | Re-trigger Greptile