Summary
Make the MCP surface self-explanatory to agents. Today the tools return correct data and have solid docstrings, but an agent has to infer what a result means and what to do next. We can close that gap so an LLM driving quack always knows (a) how to use what we just returned, (b) what each field means, and (c) where to look when behavior surprises it.
Motivation
Observed while seeding quack on a real workspace (~1,100 files) and driving it purely over MCP:
search hits came back with tiers: ["structural"] and never semantic. This turned out to be correct — embeddings were simply never built (quack embed was never run) — but nothing in the result tells the agent that. An agent can't distinguish "semantic tier found nothing" from "semantic tier isn't available." Surfacing that state would prevent wasted reasoning.
- After
describe() calls, the agent has to already know it must call reindex() before search/sql reflect the change. The describe return hints at it, but the model->catalog data-flow (.index.yaml is authoritative, DuckDB catalog is derived) is not discoverable from within a session.
search returns hits and folders as separate lists, but the payload doesn't explain why they're separate or when folders is populated (the where/which-folder routing).
Proposed changes (MCP surface only — no ranking/indexer changes)
- Situational
next_steps in tool results. One concise, outcome-aware line per result:
search with hits -> how to read a hit (get_file), what empty tiers/related_via mean, and that an absent semantic tier means embeddings aren't built (run quack embed).
search with zero hits -> broaden terms, or reindex() if the space was just annotated.
get_file truncated -> how to request more via char_limit.
describe / reindex -> confirm what changed and that search/sql now reflect it.
- Richer return-shape semantics in docstrings. Document what field values mean to an agent:
tiers (structural = name/tag/description match, fts = body text, semantic = embeddings), related_via / neighbour hits, stale (file changed after its description was written), and when folders is populated.
- An
explain() (about) MCP tool returning a concise architecture briefing on demand: the .index.yaml -> reindex -> DuckDB-catalog -> tools data-flow, what's authoritative vs derived (so an agent understands why describe needs a reindex), the tier model, and the catalog schema. Keep the always-on server instructions lean and add a one-line pointer to explain() so it's discoverable without inflating every session's token cost.
Non-goals
- No changes to search ranking, fusion (RRF), the indexer, or the catalog schema.
- Behavior-preserving for the CLI.
Acceptance
- New
next_steps field present and outcome-appropriate on search, get_file, describe, reindex.
explain() returns the architecture/data-flow doc; server instructions point to it.
- Existing 107 tests stay green; new tests assert the guidance fields and
explain() output.
Summary
Make the MCP surface self-explanatory to agents. Today the tools return correct data and have solid docstrings, but an agent has to infer what a result means and what to do next. We can close that gap so an LLM driving quack always knows (a) how to use what we just returned, (b) what each field means, and (c) where to look when behavior surprises it.
Motivation
Observed while seeding quack on a real workspace (~1,100 files) and driving it purely over MCP:
searchhits came back withtiers: ["structural"]and neversemantic. This turned out to be correct — embeddings were simply never built (quack embedwas never run) — but nothing in the result tells the agent that. An agent can't distinguish "semantic tier found nothing" from "semantic tier isn't available." Surfacing that state would prevent wasted reasoning.describe()calls, the agent has to already know it must callreindex()beforesearch/sqlreflect the change. Thedescribereturn hints at it, but the model->catalog data-flow (.index.yamlis authoritative, DuckDB catalog is derived) is not discoverable from within a session.searchreturnshitsandfoldersas separate lists, but the payload doesn't explain why they're separate or whenfoldersis populated (the where/which-folder routing).Proposed changes (MCP surface only — no ranking/indexer changes)
next_stepsin tool results. One concise, outcome-aware line per result:searchwith hits -> how to read a hit (get_file), what emptytiers/related_viamean, and that an absentsemantictier means embeddings aren't built (runquack embed).searchwith zero hits -> broaden terms, orreindex()if the space was just annotated.get_filetruncated -> how to request more viachar_limit.describe/reindex-> confirm what changed and that search/sql now reflect it.tiers(structural= name/tag/description match,fts= body text,semantic= embeddings),related_via/ neighbour hits,stale(file changed after its description was written), and whenfoldersis populated.explain()(about) MCP tool returning a concise architecture briefing on demand: the.index.yaml->reindex-> DuckDB-catalog -> tools data-flow, what's authoritative vs derived (so an agent understands why describe needs a reindex), the tier model, and the catalog schema. Keep the always-on serverinstructionslean and add a one-line pointer toexplain()so it's discoverable without inflating every session's token cost.Non-goals
Acceptance
next_stepsfield present and outcome-appropriate onsearch,get_file,describe,reindex.explain()returns the architecture/data-flow doc; serverinstructionspoint to it.explain()output.