dgenio / dgenio/contextweaver

Make ambiguous routes machine-actionable: structured disambiguation options on RouteResult

Open
#641 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

ai complexity:complex developer-experience priority: medium product
Dominant language
Python
Stars
9
Forks
17
Avg merge
21h 36m
Merged PRs (30d)
22

Description

Summary

When the router flags ambiguity, populate a structured disambiguation field on RouteResult — a small list of options, each with the candidate tool_id, a short label, and the distinguishing signal — alongside the existing prose clarifying_question, so agent loops can render choice UIs, auto-answer from conversation context, or re-route on a user's pick without parsing prose.

Why this matters

Ambiguity handling is where deterministic routing meets the agent UX. Today the router's answer to "it's ambiguous" is a human-readable sentence; every consumer that wants to act on the ambiguity (present two buttons, ask the model to pick, re-route with the choice) must reverse-engineer structure out of prose. A typed option list turns the router's most interactive moment into a first-class API — and gives the gateway's tool_browse a structured payload clients can render.

Current evidence

  • src/contextweaver/routing/router.py:120clarifying_question: str | None is the only disambiguation output on RouteResult; routing/filters.py:93 (suggest_clarifying_question) builds the prose from the candidates it already has in structured form — the structure is computed and then discarded.
  • RouteResult.is_ambiguous and the confidence-gap mechanism (router.py:457) decide when; nothing represents what the choices are in machine-readable form.
  • Related (all distinct layers): closed #226 added explanation() — human-readable rationale rendering; newly filed #515 emits provider constrained-selection schemas for the route step — the model-facing selection contract; #500 routes low-confidence results to an optional reranker. None gives the caller a typed disambiguation payload; this issue should cross-reference all three.

External context

Structured "did you mean" options are the standard interactive-disambiguation contract in search and assistant UIs; MCP clients can render structured content but not parse free prose reliably.

Proposed implementation

  1. Add a small frozen dataclass (e.g. DisambiguationOption: tool_id, label, distinction — the per-option signal suggest_clarifying_question already derives, such as the differing namespace/verb/tags) in routing/filters.py or a sibling module.
  2. Populate RouteResult.disambiguation: list[DisambiguationOption] (default empty) whenever is_ambiguous is true, from the same inputs as the prose question; keep clarifying_question generated from the structured options so the two can never disagree.
  3. Serde: per-class to_dict/from_dict following the repo's split convention; regenerate published JSON Schemas (schemas/, _schema_gen.py drift gate).
  4. Surface through the gateway: include the options in the tool_browse ambiguous-response payload per docs/gateway_spec.md, coordinating shape with #515's constrained-selection work.
  5. Re-route ergonomics: document the loop (route(query, context_hints=[chosen]) or equivalent) so a user's pick feeds back deterministically.

AI-agent execution notes

  • Inspect first: routing/filters.py (suggest_clarifying_question internals — the option data already exists there), routing/router.py RouteResult + serde, schemas/ generation, docs/gateway_spec.md browse response shape, tests in tests/test_router*.py covering ambiguity.
  • Determinism: option ordering must follow the canonical tie-break (score, then ID); labels/distinctions derive only from catalog data (they enter prompts — respect the sanitization work in #480).
  • Preserve: existing clarifying_question text byte-identical for current fixtures (generate prose from structure carefully, or keep the existing generator and assert consistency in tests).
  • Edge cases: ambiguity among >2 candidates (cap options at top-k gap cluster); empty distinction signals (fall back to namespace); serialization round-trip with empty list default.

Acceptance criteria

  • Ambiguous routes carry ≥2 structured options with non-empty tool_id/label; non-ambiguous routes carry an empty list.
  • Prose clarifying_question and structured options are provably consistent (test asserts every option's tool referenced).
  • Round-trip serde + regenerated schemas pass the drift gates; gateway browse payload documented and tested.
  • Existing ambiguity fixtures produce byte-identical prose output.

Test plan

Unit tests on option derivation per ambiguity fixture; serde round-trips; gateway wire-shape test; determinism test (repeated routes → identical options); full make ci including schema drift gates.

Documentation plan

docs/tool_router.md ambiguity section; gateway spec browse payload; cookbook recipe for the choice-then-re-route loop; CHANGELOG.

Migration and compatibility notes

Additive field with empty default — not expected to require migration. Schema version bump per the repo's schema-governance process.

Risks and tradeoffs

Slight RouteResult surface growth ahead of the 1.0 API manifest (#518) — coordinate timing; distinction-label quality depends on catalog metadata richness (degrade gracefully to namespace/kind). Alternative — leaving prose only — keeps every downstream consumer writing its own fragile parser.

Suggested labels

product, developer-experience, ai

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with routing/filters.py and its suggest_clarifying_question internals, then read RouteResult and serde in routing/router.py. Run the ambiguity tests in tests/test_router*.py and inspect schema generation plus docs/gateway_spec.md for the browse response shape. Done means deterministic structured options, compatible prose, serde and schema gates, gateway coverage, and documented re-routing behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
backend-api-design, tooling
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.