Expand description
The one place each shared tool description is written (#590). The one place each shared tool description is written.
roteiro:ignore-file — this file is prose about the tools, and two of them
are about intent debt: debt’s description has to name TODO/FIXME/HACK and
todo!()/unimplemented!() stubs, and debt_density’s has to name the prose
matches (“for now”, “deferred”, “tbd”) a reader might otherwise be surprised
by. Scanned, those descriptions register as three markers the repository does
not have. The same opt-out is on markers.rs, check_cli.rs and the
tool-choice fixture, for the same reason: a file that documents a scanner is
not a file that owes work.
§Why a module of string constants
Four tools are advertised on two surfaces: the served-chat registry in
roteiro, and this crate’s MCP server. The prose was written twice, and
nothing compared the copies — so they drifted. Measured when this module was
introduced: sandbox_status differed by 363 bytes, sandbox_clear by 197,
security_list by 55. A served model and an MCP client were told materially
different things about the same tool, including sandbox_clear, the one tool
on either surface that destroys anything.
A description is not decoration here. It carries the warnings that prevent the
likeliest misuses — that no-analyzer-on-record is not a clean repository,
that bytes.exclusive and not bytes.total is what clearing an image frees —
so one surface quietly holding an older draft is a real divergence.
§What #675 removed, and the rule it used
This prose is the dominant cost of every tooled turn: measured on the
15-tool surface before the trim, rto_serve::advertised_system_prompt rendered
14,586 bytes of which 12,354 — 85% — were these constants, and they reach a
local model a second time through its own chat template’s tool slot (#681), so
this is the one lever that pays on both paths.
What went was the prose that restates a guarantee something else already upholds, on the surface where it is upheld:
- The schema states it.
sandbox_clear’sdry_runsemantics and the consequence of naming neither scope are in itseverything/dry_runargument descriptions, which go out beside this text on both surfaces. Two tools also declared the absence of an argument (“Noprojectargument”, “Takeskeyand nothing else”) beside a rendered signature that already showed what they take — and one of those two had gone stale, becausecontexthad since gainedproject. - The code refuses it.
sandbox_clear’s three store-integrity refusals (a registered box, an unrecognised store entry, an index row pointing outside the root) arerto_exec::sandbox_store’s, each with its own test, and each reaches the caller as an error rather than as prose read beforehand. - The result body says it.
no-analyzer-on-recordcarriesrto_exec’sNO_RESULT_REASON, andcheck’snot-runcarriesnot_run_reasonwith noreportat all — read every time, where a description is read once. - The system turn says it.
search’s “read thesnippet, thenexplain” isrto_serve::advertised_system_prompt’s grounding rule, stated once for every tool.
What stayed is what nothing but the words upholds: look before you delete,
quote freed_bytes, escalate a complete: false retention, config_secrets
is not a secret scanner, a truncated findings page hides only the least severe.
Those were left even where they are long, because no measurement in this
repository can yet say what a model loses without them — see #675.
§Why the cut is 14% and not the 59% #675 hoped for
Because most of what the rule above marks cuttable is already pinned by a test that was written on purpose, and each pin encodes a decision this issue has no standing to reverse:
| phrase | pinned by | the decision behind it |
|---|---|---|
limit is 1-n … no unlimited setting | every_limit_tool_advertises_the_bound_it_enforces, both surfaces | #393: “a model reads the description even when it does not validate against the schema” |
It needs no limit / COUNTS, NEVER FINDINGS | security_status_advertises_no_bound_on_either_surface, security_status_states_why_it_needs_no_bound | #402: a schema that disagrees with the clamp |
DIFFERENT REQUESTS | the_mutating_tool_states_its_obligations_where_a_model_reads_them, and its served twin | ADR-0014 v1.6: the obligations that “do not survive living in a doc comment” |
THREE states … are ALWAYS present | security_status_description_says_what_ready_has_checked, both surfaces | #464: a host missing both assets and binary must not be two round trips |
carries NO report … rather than report zero findings | security_list_description_refuses_the_clean_reading | the never-run reading, refused where a model reads it |
Every one of those is duplication with a schema, a refusal in code, or a
result field — and every one was put there knowing that, on the stated
ground that a model reads this string and may act before it reads anything
else. So the honest reading of #675 is not “the prose is bloated” but “the
prose is the same fact stated in three places, and the repository has already
decided it wants it stated in all three.” Recovering those bytes is a
different piece of work from shortening: it means moving the shared statements
to the one-per-server places that already exist — crate::mcp’s instructions
and rto_serve’s system turn, where #599’s working-tree caveat already lives —
and re-pointing five tests at the new home. That is a design change, and it
needs the interpretation measurement #675 describes, because it trades “every
tool says it” for “the server says it once”.
§Why the MCP side still repeats the text
It should not have to, and this is the closest the framework allows. rmcp’s
#[tool(description = …)] is parsed by darling into a String, so it takes
a string literal and rejects a path: description = SANDBOX_STATUS fails
to compile with “Unexpected type path”. Nor can the served side simply read
the MCP surface, because serve does not imply mcp — a serve-only build has
no MCP module to ask.
So the constant here is the source, roteiro uses it directly, and the
literal mcp.rs is forced to carry is compared against it by
both_tool_surfaces_describe_a_tool_the_same_way. One authority, one
mechanically-checked copy — rather than two copies and a hope.
Ungated on purpose: a serve build without mcp needs these too.
Constants§
- CHECK
check.- CONFIG_
SECRETS config_secrets.- CONTEXT
context.- COUPLING
coupling.- DEBT
debt.- DEBT_
DENSITY debt_density.- EXPLAIN
explain.- LIST_
PROJECTS list_projects.- LIST_
TOOL_ CLASSES list_tool_classes— the index that keeps a withheld class discoverable.- PATH
path.- SANDBOX_
CLEAR sandbox_clear— delete cached images; the one tool that changes anything.- SANDBOX_
STATUS sandbox_status— what the machine-global sandbox image store holds.- SEARCH
search.- SECURITY_
LIST security_list— stored findings, with the run evidence behind them.- SECURITY_
STATUS security_status— readiness, in two separately scoped halves.
Functions§
- for_
tool - The description for
name, orNonefor a tool this module does not own.