Skip to content

Replace string hints with enriched hints_structured #211

@HumanBean17

Description

@HumanBean17

Summary

Drop the hints: list[str] field from all five MCP tool outputs and add a reason: str field to StructuredHint instead. This consolidates two parallel hint systems into one, reducing surface area and maintenance burden while preserving all information.

Motivation

The hints_structured field was added in #209 as a machine-parseable companion to the original string hints. Both are generated in mcp_hints.py, consumed by the same LLM clients, and produce largely the same information in different formats. This is exactly the kind of redundant surface area the project principles warn against:

  • "Don't widen the public surface 'just in case'"
  • "Breaking changes are always allowed"

String hints carry advisory context (e.g. "no match — try search(query='Foo')") that hints_structured currently loses when actionable=false. Rather than maintain two fields, we can capture that context directly in structured hints.

Example

Actionable hint (describe finds no match)

Before (two fields):

{
    "results": [],
    "hints": [
        "no match — try search(query='BankAccount') for ranked fuzzy lookup",
        "try find(role='service') to browse by role"
    ],
    "hints_structured": [
        {"tool": "search", "args": {"query": "BankAccount"}, "actionable": True},
        {"tool": "find", "args": {"role": "service"}, "actionable": True}
    ]
}

After (single field):

{
    "results": [],
    "hints_structured": [
        {
            "tool": "search",
            "args": {"query": "BankAccount"},
            "actionable": True,
            "reason": "no match — try ranked fuzzy lookup"
        },
        {
            "tool": "find",
            "args": {"role": "service"},
            "actionable": True,
            "reason": "browse by role to discover related symbols"
        }
    ]
}

Non-actionable hint (advisory)

Before:

{
    "hints": ["results look weak — narrow the query or try find(role=…)"],
    "hints_structured": [
        {"tool": "find", "args": {}, "actionable": False}
    ]
}

After:

{
    "hints_structured": [
        {
            "tool": "find",
            "args": {"role": "service"},
            "actionable": False,
            "reason": "results look weak — narrow the query or try find with a role filter"
        }
    ]
}

Proposed change

Remove:

  • hints: list[str] from all five output models in mcp_v2.py
  • String hint generation logic in mcp_hints.py
  • MCP_HINTS_FIELD_DESCRIPTION constant
  • Parity test (test_structured_hints_parity_with_string_hints)

Add:

  • reason: str field to StructuredHint — carries the advisory text (e.g. "no match for this symbol", "results look weak — narrow the query")
  • Example: StructuredHint(tool="search", args={"query": "Foo"}, actionable=True, reason="no match — try ranked fuzzy lookup")

Update:

  • MCP_HINTS_STRUCTURED_FIELD_DESCRIPTION to reflect it's now the sole hint mechanism
  • All hint templates in mcp_hints.py to emit reason alongside tool/args
  • Docs (README.md, docs/AGENT-GUIDE.md, AGENTS.md) to remove references to hints field
  • Tests to assert on reason content instead of string hints

Scope

  • mcp_hints.py — generation logic
  • mcp_v2.py — output models and tool handlers
  • tests/test_mcp_hints.py — test updates
  • README.md, docs/AGENT-GUIDE.md — doc updates
  • propose/completed/HINTS-ROAD-SIGNS-PROPOSE.md, propose/completed/HINTS-STRUCTURED.md — historical reference only, no changes needed

No reindex required — this is an output-only change, no graph or index schema impact.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions