agent-approval-protoco…
Use for approval_request, COMMAND_SET, approval identifiers, fingerprints, and progress data
Use when classifying any operation before executing it, or deciding whether user approval is required
$ npx -y skills add metraton/gaia --skill security-tiers --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
/security-tiersContext preview
The summary Claude sees to decide when to auto-load this skill.
Use when classifying any operation before executing it, or deciding whether user approval is required
name: security-tiers description: Use when classifying any operation before executing it, or deciding whether user approval is required
Classification applies to each exact atomic command. After read-only investigation, one or more predictable T3 commands may be requested plan-first with `gaia approvals request-set` -- a single predictable T3 command uses the same verb proactively, not only the reactive path that begins after PreToolUse returns `[T3_BLOCKED]`. Never discover a set by attempting a compound shell command. Group only one bounded goal with exact known order and coherent risk, rollback, and verification. Those last two are supplied on the request itself -- `--rollback` and `--verification`, alongside `--rationale` and one `--command` per item -- and are sealed at mint, then rendered verbatim as the `ROLLBACK` and `VERIFICATION` fields of the surface the user consents against; requesting without them asks for consent while the surface states they are absent. Do not group speculative or output-dependent steps. Consent grouping is not execution atomicity: execution remains one command per call, ordered and fail-fast.
security-tiers classifies every operation into four tiers so an agent knows whether it can run freely or must request the user's consent.
| Tier | What it is | Approval? | Example verbs | |------|------------|:---:|---------------| | **T0** | Read-only; observes state, changes nothing | No | get, list, describe, show, logs, status | | **T1** | Local validation; no remote calls, no state | No | validate, lint, fmt, check | | **T2** | Simulation / dry-run; may read remote, never writes | No | plan, diff, --dry-run, template | | **T3** | State-mutating; creates, updates, or destroys | **Yes** | apply, create, delete, push, deploy |
**A fifth verdict exists OUTSIDE this ladder: the categorical denial** -- blocked commands, the `.claude`/hooks tree targeted through *Bash* (`hooks/modules/security/protected_path_guard.py::check`), the DB-write guards, and a file write into a git working tree whose author is the SHELL (`hooks/modules/security/shell_write_guard.py::check`). It is not a tier, it is not approvable, and no grant lifts it.
That last one is categorical for a reason worth stating, because it looks at first glance like it should be approvable: **it withholds no capability.** A grant is scoped to a TOOL AND A PATH, never to an effect, so consenting to a shell writer would be consenting to a channel rather than to a change -- and the change itself is already permitted, byte for byte, through `Write`/`Edit`. What the guard refuses is the channel that breaks ATTRIBUTION: a redirect, `tee`, `sed -i` or `dd of=` presents the gate a command string instead of the destination, so the boundary is evaluated against the wrong object and the write lands with no tool call naming the file. Signing a permission for a channel that exists precisely to leave no trace buys nothing. The rule needs BOTH halves to fire -- shell-authored AND resolving under a git tree -- which is why `/dev/null`, `/tmp` and `~/.gaia/scratch` still pass: they fall out of the rule, not off an allow-list. A consequence to know before it surprises you: in-tree `sed -i` is therefore NO LONGER the T3 ask-dialog that `_classify_sed` would route it to; the guard returns first, with no `approval_id`. Outside a working tree that same `sed -i` still reaches the ordinary approvable path.
The counter-argument was weighed and rejected, and is recorded because it is real and will resurface: `Write`/`Edit` force a file's contents through the agent's context and from there into the transcript, while `sed -i` transforms without anyone reading. For a file holding sensitive material the shell is better for confidentiality and worse for traceability. It was rejected because in these repositories secrets do not live in versioned files -- they live in Secret Manager, and the GitOps repo carries a check that refuses plaintext secrets at commit. A file with sensitive material sitting inside a git tree is the underlying problem, not the way it gets edited. If a concrete case ever appears, it reopens with that case in hand. **A protected path targeted through *Write*/*Edit* is a different verdict: T3-approvable**, not categorical -- a `FILE_WRITE` grant lifts it (`hooks/modules/security/approval_grants.py::check_approval_grant_for_file`); see "The `.claude` rule" below for the exact split. **The operative read for any denial: one carrying an `approval_id` has a consent path; one with none is categorical -- never report a categorical denial as T3**, since T3 names a consent request and the request they answer cannot change the outcome.
`git commit` and `git add` are **not** T3 -- they are local-only operations (they touch the working tree and local refs, never remote state), so they classify as safe by elimination. Only `git push` mutates remote state and is T3. This matches `GIT_LOCAL_SAFE_SUBCOMMANDS` in `mutative_verbs.py`, where `commit` and `add` are listed as local-safe.
**T3 gates a direction, not a category of verb.** An operation needs consent because it moves the system toward *more* capability (it grants) or *less* recoverability (it destroys). An operation that only moves the other way -- that *reduces* capability already granted -- does not need consent, because the worst it can do is take back power that was given. So within Gaia's own consent layer, `gaia approvals revoke|reject|reject-all|clean` are **not** T3: they only revoke or discard grants Gaia itself issued, never reaching outside the local approval store. The asymmetry is deliberate -- `gaia approvals approve` *grants* capability without passing through the user's consent surface at all, so it stays T3. This is anchored to the `gaia approvals` group in `CONSENT_REDUCING_SUBCOMMAND_EXCEPTIONS` (`mutative_verbs.py`), not generalized to every CLI's "revoke" -- a cloud IAM revoke is a
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