Skip to content

propose: hints-v2 — extend hints to resolve and to fuzzy-strategy neighbors signals#146

Merged
HumanBean17 merged 1 commit into
masterfrom
propose/hints-v2
May 16, 2026
Merged

propose: hints-v2 — extend hints to resolve and to fuzzy-strategy neighbors signals#146
HumanBean17 merged 1 commit into
masterfrom
propose/hints-v2

Conversation

@HumanBean17
Copy link
Copy Markdown
Owner

@HumanBean17 HumanBean17 commented May 16, 2026

Builds on the landed v1 hints surface (#120 / #144). Two narrow extensions, motivated by a real-world agent trace where the agent struggled after neighbors([method_id], 'out', ['DECLARES_CLIENT']) returned three brownfield-layer-C routes with no road signs, and after resolve(...) returned status: none with guidance only in prose message.

Scope:

  • Add hints: list[str] to ResolveOutput; emit on status: none (branched by hint_kind) and status: many (candidate count). status: one emits nothing. Plumbing contract for identifier / hint_kind is locked at §3.1.1 / Decision §7.14 — missing plumbing suppresses the hint rather than rendering a degraded template.
  • Add a single edge-attribute-driven neighbors rule: when any returned edge's attrs.strategy falls in a locked FUZZY_STRATEGY_SET (layer_c_source, layer_b_fqn, phantom, chained_receiver, overload_ambiguous, implicit_super), emit a meta-tier hint pointing the agent at attrs.strategy per row.

Out of scope (locked decisions, §5):

  • Per-result follow-up hints on neighbors (v1 §7.15 frame stays binding).
  • Confidence-threshold templates (strategy is categorical and avoids calibration debt).
  • Search-quality hints beyond v1's structural rule (trace didn't surface a search gap).
  • Truncated-vs-exact disambiguation on resolve many (would need a new field on ResolveOutput).

19 use cases (§4, including UC16b for cap truncation and UC16c for missing plumbing). 16 locked decisions (§7). 2 PRs (§6). Wire-up snippets in Appendix A.

Marking as draft for review.

Copy link
Copy Markdown
Owner Author

@HumanBean17 HumanBean17 left a comment

Choose a reason for hiding this comment

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

Propose review — HINTS-V2

Verdict: Direction is sound and trace-motivated. ~85% landable as-is. Fix contract gaps (v1 inheritance, terminology, purity story) and tighten FUZZY_STRATEGY_SET policy before implementation. Fine to keep as draft until those are addressed; add plans/PLAN-HINTS-V2.md before PR-A lands.


What works

  • Trace-driven scope — Two narrow gaps (resolve had no hints; neighbors had no signal on brownfield/fallback edges) extend v1 (#144) without reopening Shape 2 or per-row hints.
  • v1 frame respected — Meta-tier, cap discipline, no per-row neighbors hints, validation success=False carve-out (§7.15).
  • Strategy-over-confidence — Sensible; attrs.strategy is already categorical on DECLARES_CLIENT, EXPOSES, HTTP_CALLS, etc.
  • UC table — UC16b/16c and awkward-case notes (UC8/UC10 collapse) show implementer-level thinking.
  • 2-PR split — Resolve-first, neighbors-second; hints-only, no reindex.
  • Appendix A — Wire-up matches current mcp_hints.py style.

Must fix in propose before merge

1. UC6/UC7 and §6 PR-B say "routes" for DECLARES_CLIENT

DECLARES_CLIENT targets Client nodes, not Route. §6 PR-B ("layer_c_source route and annotation route") will send implementers to the wrong rel. Rename to clients in UC6/UC7, or switch the motivating case to EXPOSES if the trace was actually about routes.

2. v1 ≤120-char rule not inherited

v1 §7.6: rendered hints ≤120 chars; overlong templates drop. TPL_RESOLVE_NONE_TRY_SEARCH embeds the full identifier — long FQNs can exceed the cap. v2 has no decision, test, or truncation policy. Lock one: truncate identifier, cap in finalize_hint_list, or drop the hint when over 120.

3. Purity story contradicts v1 §7.18

v1 locked pagination hints on echoed output fields (FindOutput.limit), not call-site kwargs. v2 plumbs identifier / hint_kind via side payload (fine — same as find's hint_payload), but §2.6 ("pure function of the output object") and §7.10 oversell it. Either echo resolved_identifier (+ optional hint_kind) on ResolveOutput, or reword §2.6/§7.10 to "payload dict at call site per §3.1.1" and cite the find precedent.

4. §7.14 wording

Appendix treats hint_kind=None as symbol branch. §7.14 ("If either is missing or empty") wrongly implies both are required. Fix to: only identifier must be non-empty; hint_kind may be None.

5. §3.2 table vs Appendix A

Table mentions "identifier parses as method + path" / "service-qualified target" but Appendix only branches on hint_kind. Delete those triggers from the table or implement them.

6. Route/client templates are not concrete road signs

filter={path_prefix: …} violates v1 §2.1/§2.7 (one concrete call, no ellipsis). Derive a real filter fragment from the identifier, or lock a fixed template with an explicit substitution rule.


Policy / ontology

7. FUZZY_STRATEGY_SET placement — Repo convention puts closed vocabularies in java_ontology.py. The set mixes declaration strategies (layer_c_source, layer_b_fqn) and CALLS strategies (phantom, chained_receiver, …). Document that values come from graph builder strategy columns; consider VALID_FUZZY_EDGE_STRATEGIES (or equivalent) so hints don't invent strings.

8. layer_b_ann vs layer_b_fqn — Both are brownfield layers in build_ast_graph.py. Why is layer_b_fqn fuzzy but layer_b_ann "reliable"? Needs a one-sentence policy or the set will look arbitrary on the next layer change.

9. resolve none duplicates existing message_resolve_build_output already sets message to "use search(query=...)". v2's search hint repeats that for symbol/hint_kind=None. Pick: hints only for route/client, generic message + specific hints, or "hints must not repeat message verbatim" in §7.

10. Wildcard identifiersresolve_v2 returns status: none for */? via empty matches; v2 would still emit try search(query='…') with the wildcard. Add a UC or explicit no-hint rule if intentional.


Nits (non-blocking)

  • Add plans/PLAN-HINTS-V2.md before implementation (repo culture).
  • README update when code lands.
  • Extend generate_hints Literal with "resolve" in §6 PR-A.
  • UC8 chained_receiver on a DECLARES_CLIENT-only call is odd — consider a CALLS-focused UC for clarity.

Suggested checklist before "ready for implementation"

  1. Fix UC6/UC7/§6 PR-B: clients not routes (or change edge type).
  2. Add v1 §7.6 120-char decision + test.
  3. Resolve purity: echo on ResolveOutput or honest payload contract.
  4. Fix §7.14 (identifier only).
  5. Align §3.2 with Appendix.
  6. Concrete route/client templates.
  7. One paragraph on layer_b_ann vs layer_b_fqn.
  8. Wildcard + duplicate-message policy.
  9. Ontology/builder alignment note for FUZZY_STRATEGY_SET.

Happy to re-review after a propose revision pass.

@HumanBean17
Copy link
Copy Markdown
Owner Author

Thanks for the thorough review — all 10 substantive items and the 4 nits made the propose tighter. Applied in a0835b6 (force-push, amended onto the same propose commit). No pushback on this round.

Substantive items

  1. §3.1 / Decision §7.18 — resolved_identifier echo for purity. Added resolved_identifier: str | None to ResolveOutput (set on every success=True response, None on validation failure). generate_hints now reads payload["resolved_identifier"] rather than payload["identifier"]. This restores v1 §7.18's discipline that hint generation is a pure function of the output object alone — no call-kwarg leaks. The wire-up code, the §3.2 trigger table, the plumbing note, and Appendix A now all use the corrected field name.

  2. §3.1.1 — plumbing contract spelled out. New subsection enumerates the four payload fields resolve_v2 is required to populate (status, resolved_identifier, hint_kind, candidates) and the optional fifth/sixth (seeds — see Add Cursor rules and agent settings for CLI agents #3 below). Says explicitly that hint_kind is allowed to be None and defaults to the symbol branch.

  3. §3.1.2 — concrete seeds for route/client hints. New subsection. The call site computes path_prefix_seed / target_service_seed using the parsers resolve_v2 already uses (_resolve_parse_route_method_path, _resolve_parse_microservice_route) and plumbs them into the payload. If the relevant seed is None, the route/client hint is suppressed — no placeholder ellipsis ever renders. Parser logic stays in mcp_v2.py; generate_hints consumes the seed string verbatim.

  4. §3.2 — concrete substitution in template table. The route/client triggers now show the rendered shape filter={path_prefix: '{seed}'} instead of . Appendix A's TPL_RESOLVE_NONE_TRY_FIND_ROUTE / TPL_RESOLVE_NONE_TRY_FIND_CLIENT templates were also rewritten to use {seed} substitution, not ellipsis. This matches Decision §7.17.

  5. §3.2 — FUZZY_STRATEGY_SET moved to java_ontology.py. Closed vocabularies belong with the ontology, not in tool-surface modules. Appendix A now shows from .java_ontology import FUZZY_STRATEGY_SET rather than defining the set inline. The CI classification invariant (issue enforce strategy-classification invariant between brownfield pipeline and mcp_hints.py #147) scans against the ontology, which is the right anchor for the check.

  6. layer_b_ann vs layer_b_fqn rationale. New paragraph at end of §3.2 explains why both are "layer B" but only _fqn is fuzzy: _ann keys on explicit annotation code (evidence present); _fqn keys on FQN-pattern guesses (no evidence beyond identifier shape). _ROUTE_LAYER_RANK ranks _fqn as the lowest-rank fallback (rank 4). Captured as Decision §7.20.

  7. UC table — DECLARES_CLIENT targets clients, not routes. UC6 and UC7 corrected to say "3 clients" with attrs.strategy from the brownfield client pipeline. The trace case is faithfully reproduced. UC8 moved to the CALLS edge (where phantom / chained_receiver actually live) so the strategy literal matches the edge family.

  8. UC table — coverage of overflow, wildcard, no-seed, and many-truncated cases.

    • UC2b: long FQN (~70 chars) where rendered hint exceeds 120 chars → hints: [] per Decision §7.18.
    • UC2c: identifier contains *hints: [] per Decision §7.21.
    • UC3b: hint_kind='route' but parser yields no path_prefix_seedhints: [] per Decision §7.17.
    • UC4 (rewritten) + UC4b: client none with and without target_service_seed.
    • UC16b: status: many truncated at _RESOLVE_CANDIDATE_CAP = 10 — hint says "10 candidates" with no "+" suffix, ambiguity acknowledged in Risk §8 and §5 carve-out.
    • UC16c: plumbing-bug case (missing resolved_identifier on a status: none) → hints: [], tests catch.
  9. §7 Decisions — eight new locked decisions (§7.14–§7.22). All non-obvious choices from the review now have anchored locks:

    • §7.14: resolve hint payload plumbing required at call site; only resolved_identifier is required-and-checked (hint_kind may be None).
    • §7.15: status: none from validation rejection is out of scope; v2 hints fire only after _resolve_validate_identifier passes.
    • §7.16: candidate-cap truncation is not surfaced; separate propose if a trace shows it's needed.
    • §7.17: route/client none hints render concrete filter fragments derived from the identifier; no ellipsis placeholders; suppress when seed is missing.
    • §7.18: templates obey v1 §7.6's 120-char rule on the rendered string; drop-on-overflow, no truncation of seed/identifier.
    • §7.19: FUZZY_STRATEGY_SET lives in java_ontology.py.
    • §7.20: layer_b_ann primary, layer_b_fqn fuzzy (rationale above).
    • §7.21: wildcards (* / ?) in resolved_identifier suppress the resolve hint; the prose message carries the redirect to search.
    • §7.22: resolve none hint does not duplicate message verbatim — message is generic English, hint embeds the concrete resolved_identifier; two channels by design.
  10. Risk §8 — deduplication and updated mitigation. The old "resolve_none try search hint duplicates prose message" row was a duplicate of the longer row above it. Dropped the duplicate; kept the richer row and tightened the mitigation language to point at Decision §7.22.

Nits

  • TL;DR cardinals. Decisions count rose from 16 → 22 with the §7.14–§7.22 additions; UC count rose from 17 → 22 with the sub-letters (UC2b/2c/3b/4b/16b/16c). The TL;DR doesn't state either count inline so no edit needed there. Migration PR count is still 2 — matches §6.

  • Wire-up code reads resolved_identifier, not identifier. Fixed in Appendix A wire-up snippet at the same time as substantive item AST by Opus #1.

  • Wire-up code now enforces wildcard suppression and 120-char drop-on-overflow. Both Decisions §7.21 and §7.18 are visible as inline code paths in Appendix A (_RESOLVE_WILDCARDS, _RESOLVE_HINT_MAX_CHARS).

  • Wire-up code now handles route/client seeds. The if hint_kind == "route": / elif "client": branches read the seeds from payload and suppress when the seed is empty. Matches §3.1.2.

Branch propose/hints-v2 is at a0835b6 (force-pushed). The propose still answers what + why only — no file paths, no test names, no function signatures beyond the public surface schema.

Copy link
Copy Markdown
Owner Author

@HumanBean17 HumanBean17 left a comment

Choose a reason for hiding this comment

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

Second-pass review — after a0835b6

Verdict: Amendments land round-1 feedback well. Ready for implementation after a short consistency pass (copy drift, not design). Approve the propose once the items below are fixed.


Round-1 checklist

Item Status
UC6/UC7 clients not routes Fixed in §4; TL;DR still wrong
120-char rule Fixed (§7.18, UC2b, Appendix A)
resolved_identifier echo Good; §3.1 / §3.1.1 prose overclaims full output-only purity
§7.14 hint_kind=None Fixed
§3.2 dead parse triggers Fixed (seed-based)
Concrete route/client templates Fixed (§3.1.2, {seed})
FUZZY_STRATEGY_SET in ontology Fixed (§7.19)
layer_b_ann vs layer_b_fqn Fixed (§7.20)
Duplicate message / wildcards Fixed (§7.21–22, UC2c)
UC8 on CALLS Fixed

The reply comment on this thread accurately describes what landed. No pushback needed on substance.


Remaining nits (~10 min in-doc)

  1. TL;DR line 11 — still says DECLARES_CLIENT returned 3 routes; §4 UC6/UC7 correctly say clients. Align the TL;DR.

  2. §6 PR-B test summary (line 197) — still "layer_c_source route and annotation route" for a DECLARES_CLIENT round-trip. Should be clients (one layer_c_source, one annotation on DECLARES_CLIENT edges).

  3. §3.1.1 (lines 58–63) — opens with "all sourced from output fields" then lists hint_kind as call-site plumbed. Rephrase to the honest find_v2 hybrid: output fields + hint_kind / seeds in payload per §3.1.2.

  4. §3.1 line 54"pure function of the output object alone" is still too strong while hint_kind and seeds are payload-only. Soften to match find_v2, or echo hint_kind on ResolveOutput (optional polish).

  5. §7.6 vs §7.19 — §7.6 says CI classifies against mcp_hints.py; §7.19 says ontology. Pick ontology (correct per your amendment) and align §7.6.

  6. §2.7 — still says fuzzy enum lives in mcp_hints.py; update to java_ontology.py.

  7. UC16c — row text still says identifier missing; should say resolved_identifier.

  8. §6 PR-A test summary — add UC2b/2c/3b/4b/16b/16c (or "all resolve UC rows in §4"); current list is stale.


Non-blocking

  • plans/PLAN-HINTS-V2.md before PR-A — still fine to defer.
  • Echoing hint_kind on ResolveOutput would make purity prose literally true; not required for v2.

Once 1–8 are addressed, this propose is good to implement. Happy to do a final skim after one more push.

@HumanBean17
Copy link
Copy Markdown
Owner Author

All 8 nits applied in 94d917f (force-push, amended onto the same propose commit). Pure copy-drift cleanup — no design changes.

  1. TL;DR line 11: now says "3 clients with strategy: layer_c_source" — aligned with §4 UC6/UC7.

  2. §6 PR-B test summary: round-trip now describes "one layer_c_source client and one annotation client on DECLARES_CLIENT edges" — matches the trace shape.

  3. §3.1.1: rewritten as "a hybrid of output-field echoes and request-context plumbing, mirroring how find_v2 already plumbs kind and filter." status / resolved_identifier / candidates are explicitly tagged as echoes; hint_kind is tagged as request-context plumbing (not on ResolveOutput). The optional seeds in §3.1.2 are called out as plumbed the same way.

  4. §3.1 line 54: softened "pure function of the output object alone" → "pure function of its payload (mirroring find_v2's hybrid: most fields echo from the output object; hint_kind and the optional seeds in §3.1.2 are request-context plumbed by the call site, the same way find_v2 plumbs kind and filter)." Honest about the hybrid. Picked the prose-soften option, not the hint_kind-on-ResolveOutput option — keeping ResolveOutput minimal is worth more than literal output-only purity here.

    For consistency I also tightened Principle §2.6 ("Hints stay pure") and Decision §7.10 to use the same "payload (echoed output fields + request-context plumbing)" phrasing — they had the same overclaim.

  5. §7.6 vs §7.19: Decision §7.6 now reads "…not classified in the ontology (Decision §7.19 places the set in java_ontology.py)." Single canonical home; no contradiction with §7.19.

  6. §2.7: rewritten to "The fuzzy-strategy enum lives in java_ontology.py (Decision §7.19); resolve-template literals live in mcp_hints.py." Each set lives where it should.

  7. UC16c: row text now reads resolved_identifier (not identifier) — matches the renamed field everywhere else.

  8. §6 PR-A test summary: now enumerates every resolve UC row in §4 — UC1, UC2, UC2b, UC2c, UC3, UC3b, UC4, UC4b, UC5, UC16, UC16b, UC16c — plus the round-trip explicitly mentions the seed-suppression, wildcard-suppression, and 120-char-overflow paths produce hints: [].

Branch propose/hints-v2 is at 94d917f. Ready for a final skim.

@HumanBean17 HumanBean17 merged commit de755c5 into master May 16, 2026
1 check passed
@HumanBean17 HumanBean17 deleted the propose/hints-v2 branch May 23, 2026 16:21
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