agent-approval-protoco…
Use for approval_request, COMMAND_SET, approval identifiers, fingerprints, and progress data
Use when presenting a returned APPROVAL_REQUEST for informed user consent
$ npx -y skills add metraton/gaia --skill orchestrator-present-approval --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
/orchestrator-present-approvalContext preview
The summary Claude sees to decide when to auto-load this skill.
Use when presenting a returned APPROVAL_REQUEST for informed user consent
name: orchestrator-present-approval description: Use when presenting a returned APPROVAL_REQUEST for informed user consent
Present trusted contract data exactly. Do not execute, derive, shorten, reorder, or silently expand it.
WHAT the user sees is produced Gaia-side from the sealed payload by one function -- `hooks/adapters/consent_presentation.py::render_native_text`, over that module's `VISIBLE_FIELDS` table -- and it comes out identical on every host. Its exact shape, field set, render order and absence semantics are stated in `template.md`. Show that text verbatim. Compose nothing, summarise nothing, reorder nothing, translate no label.
For an existing pending id, read both presentation pieces with `gaia approvals show <approval_id> --consent-surface`. The JSON `visible_text` field is that rendered surface byte-for-byte; print it unchanged. Use the JSON `approve_label` field unchanged for the approval control -- Gaia sources it from `render_approve_label`, so the presenter must not rebuild it. This read records no `SHOWN` event and makes no decision or grant.
A presentation is two pieces. Print the rendered surface verbatim as console text -- the MESSAGE. Then ask a binary decision -- the QUESTION -- carrying one line of operation, the command count, and the approval id, and nothing else. `template.md` states both shapes literally, together with the four rules that make the split safe: the id on both ends, adjacency with a reprint duty, the minimum the question carries, and a binary control set with no `always`.
That order is deliberate and the reason is narrow. When the surface travels *inside* a host's decision payload, whether the host's renderer displays every line of it is unverifiable from outside that host: nothing you can read back confirms the user saw the `ROLLBACK` line, or the eighth command's fingerprint, or anything past a truncation the renderer applied silently. Consent over a truncated surface is exactly the failure this whole protocol exists to prevent, and that exposure stands recorded as unmitigated -- closable only by real end-to-end work against each host, never by a claim made here. Printed as console text, completeness stops depending on a renderer nobody can inspect and starts depending on the user's own terminal.
The choice has a real cost, stated here rather than buried. The binding between what is shown and what is answered stops being CONTAINMENT and becomes ADJACENCY WITH VERIFIABLE IDENTITY ON BOTH SIDES. That is weaker -- containment could not fail to hold, adjacency can. What is bought for it is that truncation stops being an uninspectable property of someone else's renderer. Rule 2 in `template.md` -- reprint the surface if anything intervened before the question -- is what keeps that trade honest; without it the design is not a weaker binding, it is no binding at all.
The user's reply must be resolvable to the `approval_id`, because that identifier is what the hook layer matches against the pending row before any grant exists (`hooks/modules/security/approval_grants.py::extract_approval_id_from_label` feeding `hooks/modules/security/approval_grants.py::activate_db_pending_by_id`).
How the identifier gets there turns on one condition:
that produced it, that handle suffices. The id does not need to be in the control at all.
nothing but the text of the control the user selected -- then the id MUST travel in that text, because that text is the only thing that comes back.
For the second case, Gaia's resolver reads one form and only that form: the selected control's text must begin with the literal English word `Approve` and must end with the complete canonical id bracketed as `[P-<32 lowercase hex>]`. A translated verb, any paraphrase of `Approve`, a short display label, a raw nonce, or suffix text after the bracket all read as no identifier. Activation then resolves that exact id; it never scans for a matching prefix.
Print text, and offer two controls where the chosen one returns either its own text or a correlation handle back to the request. That is the entire requirement. No harness renders the seven fields, none parses the sealed payload, none needs to know the field set, and none needs a decision primitive richer than two controls -- the split moved all of that Gaia-side, which is the practical dividend of printing the surface as text.
Stated that way on purpose: this skill names no host. A harness either can do those two things or cannot, and that is decidable without per-host prose here. Host-specific instructions are the shape that went stale fastest -- the retired adapter documentation asserted a host's semantics against code that had already moved -- so what a particular host calls its controls belongs in that host's adapter, never in this skill.
If the reply resolves to no pending row, nothing activates. No grant is inserted, the ledger stays `PENDING`, and every retry of the blocked command re-blocks on the same `approval_id`. The outcome is indistinguishable from a decision never having been made -- while the user believes they consented. That is the incident this skill exists to prevent, and it is exactly as reachable on a single command as on a COMMAND_SET.
The residual risk has a fixed direction, which is the one reassurance available: resolution is the sole predicate, and nothing else reads the reply, so an unresolvable reply under-grants. No reply shape over-grants.
**A reply that resolves to nothing is a finding, not a no-op.** Silence is what makes this failure dangerous, so a reply the resolver c
Repo: metraton/gaia
Use for approval_request, COMMAND_SET, approval identifiers, fingerprints, and progress data
Use for the exact input/output schema and validation rules of agent_contract_handoff
Use when creating a new specialist agent for Gaia, or reviewing whether an existing agent follows the correct structure, tone, and component inventory
Use when the orchestrator must read, reconcile, route, and present an agent_contract_handoff
Use when writing, drafting, or publishing a blog article for metraton.github.io