# Why your agent doesn't have to ask.

*31 July 2026 · the launch journal · manifest and transcript re-executed 5 August 2026*

The protocols got you to the door and then went quiet. MCP defines how tools are listed and called; A2A defines how agents address each other; neither defines the part your session actually stalls on — **selection and approval**. Which tool an agent picks, and whether the client fires the call without waking a human, is explicitly the client's problem. In practice every serious client resolves it the same way: calls annotated as read-only and honestly described clear the auto-approval line; anything else queues for a human.

Which means the integration bottleneck for an agent-facing tool is not latency or auth or docs. It is **annotation honesty**. A tool that under-declares (everything unmarked, "just approve it") trains the human to rubber-stamp. A tool that over-declares (a write hinted read-only) burns the only thing an agent vendor has — the client's willingness to trust hints at all. The line only works if the hints are true, provably, per verb.

## The manifest, and the rule behind it.

The epcis.dev tool manifest annotates on one rule: **`readOnlyHint: true` exactly where the verb is provably pure — no state written, no side effect beyond the response.** Ask the door yourself; the manifest below is its answer, pasted:

```
$ curl -sS -X POST https://epcis.dev/mcp -H 'content-type: application/json' \
    -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' \
  | jq -r '.result.tools[] | "\(.name)\t\(.annotations)"'

capture     {"readOnlyHint":false}
query       {"readOnlyHint":true}
get_event   {"readOnlyHint":true}
trace       {"readOnlyHint":true}
translate   {"readOnlyHint":true,"idempotentHint":true}
resolve     {"readOnlyHint":true}
subscribe   {"readOnlyHint":false}
```

six tools, 6 annotations: nothing here is merely undeclared. `readOnlyHint: true` on `query`, `get_event`, `translate`, `resolve`; `readOnlyHint: false` on `capture`, `subscribe`.

The pure set is pure by *construction*, not by promise. `translate` computes a document and [a fidelity report](/journal/translation-with-a-receipt/) and deliberately does **not** capture its own output; the description says so, because the description is part of the contract the client's selection logic reads. It is also the one tool here that is a function of its argument alone, so it is the only one that also claims `idempotentHint`. The read tools return Scope-filtered views and write nothing. `capture` appends to an append-only record — the one genuinely irreversible act on this surface — and declares `readOnlyHint: false` rather than staying silent, because a line only works if the write side draws it too. `subscribe` refuses today, and still declares `false`: a standing subscription is state, and a hint that would become a lie on the release that implements it is worse than no hint at all.

Complete descriptions plus honest hints: that pairing is what lets a client select correctly *and* approve automatically, with no human parsing tool names at 2 a.m.

## The transcript: first value, zero prompts, and the stop.

A `verifier`-class agent, deputized to check a partner's document sweep, in one session at [the MCP door](/journal/a-door-not-a-second-path/). Executed against `POST https://epcis.dev/mcp` on 5 August 2026 — the calls are real, the replies are the door's, abridged only by trimming the translated document body:

```
→ tools/list
← capture, query, get_event, translate, resolve, subscribe
                                            [no key sent, no prompt raised]

→ tools/call translate  { xml: object-event-1.2.xml }
← "isError": false
  "fidelityReport": { "roundTripClean": true, "lossyPaths": [],
      "notes": "Clean round-trip: every source value maps to a
                standard EPCIS 2.0 field." }
  "sourceSchemaVersion": "1.2",  "meteredEvents": 1
                                            [readOnlyHint:true — auto-approved]

→ tools/call trace  { epc: "urn:epc:id:sgtin:0614141.107346.2018" }
← "isError": true
  { "type": "epcisException:SecurityException", "status": 401,
    "detail": "/epcs/…107346.2018/events is a keyed door: send
               Authorization: Bearer <capture-scoped key>." }
                                            [read, but this origin keys its store]

→ tools/call capture  { document: {…} }
← "isError": true
  { "type": "epcisException:SecurityException", "status": 401,
    "detail": "/capture is a keyed door: send Authorization:
               Bearer <capture-scoped key>." }
                                            [readOnlyHint:false — the write line]
```

First value arrives on call two, with **no key, no account and no approval prompt**: `translate` is annotated pure, it is pure, and the client fires it inside already-granted authority. A ten-year-old 1.2 document comes back as EPCIS 2.0 JSON-LD with a clean round-trip receipt, and the agent never had to wake anyone.

Then the session stops, twice, and the two stops are different in kind — which is the whole point of annotating honestly. `capture` stops **at the declared boundary**: the annotation says `readOnlyHint: false`, the client knows before it calls that this is the irreversible one, and the refusal that comes back is the one the manifest predicted. `trace` is annotated read-only and *is* read-only; it stops for an unrelated reason — this origin keys its store, so reads of stored events need a capture-scoped key too. Both refusals are typed, both are [tool results with `isError: true` rather than transport errors](/journal/errors-agents-branch-on/), and neither is a surprise the agent has to interpret.

Run the same four calls yourself; the door is public and the transcript above is what it returns.

That stop is not friction; it is the product behaving. The record downstream of `capture` is evidence — [append-only, hash-identified](/journal/identity-is-computed/), carrying [an attested `who` distinct from the warrantor account](/journal/who-is-not-capturedby/). A write like that *should* ride on explicitly granted authority: a capture key, scoped at [/get-a-key/](/get-a-key/) — the flow asks whether an agent captures on your behalf precisely so the key that comes back matches the delegation you actually run.

## The payoff, stated as economics.

For the humans reading over your shoulder: the auto-approval line is where agent adoption lives or dies. Below it, an agent reaches first value — a validated corpus, a fidelity report, a verified trace — with **no meeting, no ticket, and no human in the loop**, minutes after `npx -y epcis.dev mcp` lands in a config block. Above it, every write is deliberate, keyed, and attributable. Tools that blur the line get quarantined by careful clients and rubber-stamped by careless ones; both outcomes are failures. Tools that hold the line get something better than engagement: they get *persisted* — an [AGENTS.md](/journal/errors-agents-branch-on/) block, an MCP entry that survives the session, a standing place in the free zone.

Honest hints, complete descriptions, pure verbs marked pure and writes marked writes. That is the entire mechanism by which your agent doesn't have to ask — and the reason that, when it finally does ask, the human should listen.

---

**Proof, not adjectives.** This build could not verify the gateway's test suite green, so no sentence on this page claims a passing run — the last verified run is recorded in the repository's own CI, not here. Live on this origin, no key: POST /translate, /validate, /hash. No conformance attestation has ever been issued. The dated ledger is /what-ships-today/.
