Intentionally-omitted verbs should surface as explicit stubs, not silent absence #54
Labels
No labels
autonomy
async-consult
autonomy
epic
autonomy
headless
autonomy
live-collab
coherence-core
priority
P0
priority
P1
priority
P2
priority
P3
priority
P4
qa-fixture
role/ai
role/creator
role/design
role/director
role/engineer
role/exec
role/human
role/ops
role/qa
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
coilyco-flight-deck/mcp-beaver#54
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problem
When a verb is deliberately excluded from a generated tool surface, the agent sees nothing at all. Absence is indistinguishable between:
An agent cannot tell these apart, so it guesses — and the guesses are wrong in both directions.
Concrete evidence from one session
Working the forgejo MCP through a 23-issue filing batch, I got it wrong twice in opposite directions:
Inferred a restriction that was real but misattributed. Edit-comment is absent. I concluded "this MCP has no edit-comment verb", treated the two comments I had posted as immutable, and worked around it by posting a third superseding comment — noise that a normal edit would have avoided. The omission turns out to be deliberate, which is a fine decision, but nothing in the surface said so, so the workaround was built on a guess rather than on the actual reason.
Inferred a restriction that did not exist.
create_issuehas nolabelsparameter, so I twice told the user labelling was impossible and left 24 issues untagged.add_issue-labelandset_issue-labelwere exposed the whole time. Absence of the parameter got over-generalised into absence of the capability.Both errors have the same root: an agent reasoning from a hole in the tool list, with nothing in the surface to reason against.
Requested
Emit a stub for deliberately-omitted verbs. It appears in discovery, carries the reason in its description, and returns a structured refusal rather than doing anything. Sketch:
The important parts:
Scope question for you
Whether this belongs as a first-class guardfile construct (a
withheld/denynode that generates the stub) or as convention — an ordinary tool entry whose handler always refuses — is your call. The guardfile version is better if the intent is auditable policy; the convention version is cheaper and needs no generator change.Either way it wants a schema-level marker so a client can distinguish stubs from live tools without parsing prose in the description.
Related
labelshalf of the second failure above.server-infoshould be on by default — the disclosure argument for opt-in does not survive reading the payload #61Landed in
4632484as a guardfile construct rather than a convention, taking the scope question the issue left open.Chose the guardfile version because the intent is auditable policy and belongs beside the grants it explains. It rides beside
wraplike the other siblings, so opcore's frozen grammar and the umbra pin are untouched.All three of the important parts:
tools/listand inlint, so absence stops being the only signal.NOT AVAILABLE, then the authored reason, then the alternative, then a plain statement that calling it always fails and reaches no upstream.reasonis required: a stub that does not say why just restates the absence, louder.The schema-level marker you asked for.
_metacarriescoilyco.io/withheld: true, pluscoilyco.io/withheld/alternativewhen one is stated, so a client separates stubs from live tools without parsing prose. Calling one returns an error result with structured content:Error rather than a success carrying a refusal, so a client that only checks
isErrorcannot read it as having worked.One guard worth stating.
withholdnaming a tool the spec does mint is a build error. A stub shadowing a live grant would advertise a working capability as refused - worse than the silence, because it manufactures exactly the wrong inference rather than no inference.lint --methodsprintsWITHHELDfor stubs rather than the bare-the info tool gets, since both resolve no HTTP method for completely different reasons.Deny-by-absence is intact: a stub grants nothing, holds no credential, and reaches no upstream. It converts silence into a statement.
Your second failure -
create_issuehaving nolabels- is #53, and the runtime side of that turns out to need no change. Details there.