faramesh approvals
List, inspect, approve, deny, and watch deferred tool calls. The CLI surface for human-in-the-loop governance.
faramesh approvals is the operator command set for resolving deferred tool calls. Actions that policy says require a human in the loop before they run. Every defer rule in your governance.fms produces approval records; this is how you (or a UI built on the same socket) handle them.
You'll use this all day if you're an operator. If you're an agent developer, you'll mostly call list and approve while iterating on policy.
Usage
faramesh approvals list [--agent ID] [--status pending|approved|denied|expired] [--json]
faramesh approvals pending # alias for "list --status pending"
faramesh approvals show <approval-id>
faramesh approvals approve <approval-id> [--reason TEXT]
faramesh approvals deny <approval-id> [--reason TEXT]
faramesh approvals watch [--agent ID]
faramesh approvals history [--agent ID] [--since DURATION]All subcommands talk to the running daemon. The daemon must be in READY state (see faramesh status).
Subcommands
list / pending
List approvals. Default is pending only:
$ faramesh approvals list
Approval Queue
──────────────
NOTE: 2 pending approval(s)
APPROVAL ID AGENT TOOL AGE CONTEXT
apr-9001 support-bot send_email 14m to=customer@example.com
apr-9002 support-bot stripe/refund 3m amount=$8000--json produces a stable schema for tooling:
{
"approvals": [
{
"id": "apr-9001",
"agent_id": "support-bot",
"tool": "send_email",
"args": { "to": "customer@example.com", "body": "[REDACTED]" },
"deferred_at": "2026-05-17T19:30:00Z",
"rule_ref": "governance.fms:14",
"context": "exceeds session cap"
}
]
}Filtering:
faramesh approvals list --agent support-bot
faramesh approvals list --status approved --since 24hshow
Detailed view of one approval:
$ faramesh approvals show apr-9001
Approval Detail
───────────────
Approval ID: apr-9001
Status: PENDING
Agent: support-bot (identity: spiffe://corp/support-bot)
Tool: send_email
Rule ref: governance.fms:14 (defer send_email if external)
Args:
to: customer@example.com
body: [REDACTED by redact send_email args:["body"]]
Deferred at: 2026-05-17T19:30:00Z
Pipeline state at decision time:
- identity verified
- rule#3 matched (defer)
- rate limit ok (3/100 per hour)
- budget ok ($82.50/$1000)
NEXT STEP: faramesh approvals approve apr-9001 --reason "...".The arguments shown are post-redaction. Exactly what's stored in the WAL.
approve
Resolve the approval as a permit:
faramesh approvals approve apr-9001 --reason "manual review passed; customer in EU"What happens:
- The daemon updates the approval record (status
APPROVED, with operator id and reason). - The next time the agent retries that exact same call, it permits once.
- A signed DPR is written linking the operator who approved.
- Any
alert { on = "approved" }blocks fire.
The approval is single-use by default. The agent gets one permit; subsequent identical calls re-defer (assuming the rule still says defer).
deny
Resolve as a deny:
faramesh approvals deny apr-9002 --reason "amount exceeds policy ceiling"The agent receives POLICY_DENY with your reason. Use this when the answer is a hard no rather than letting the approval expire.
watch
Stream pending approvals live (TTY-friendly):
faramesh approvals watch
[2026-05-17T19:30:00Z] apr-9001 support-bot send_email external recipient
[2026-05-17T19:33:14Z] apr-9002 support-bot stripe/refund amount=$8000
^CUseful in a side terminal during policy authoring or in an on-call shift.
history
Past approvals, including resolved and expired:
$ faramesh approvals history --since 7d
Approval History (7d, 14 records)
ID AGENT TOOL STATUS DURATION REASON
apr-8997 support-bot send_email APPROVED 3m "verified internal"
apr-8998 support-bot stripe/charge DENIED 1h "fraud pattern"
apr-8999 support-bot send_email EXPIRED 24h —EXPIRED means the approval timed out without a decision. Default expiry is 24 hours; configurable via runtime { approval_ttl = "12h" }.
Approvals in CI / scripts
Approvals are scriptable. The flow:
# 1. List pending
PENDING_JSON="$(faramesh approvals list --json --status pending)"
# 2. Iterate and decide programmatically
echo "$PENDING_JSON" | jq -c '.approvals[]' | while read approval; do
id=$(echo "$approval" | jq -r '.id')
tool=$(echo "$approval" | jq -r '.tool')
if [ "$tool" = "send_email" ]; then
faramesh approvals approve "$id" --reason "auto-approved by script"
fi
doneFor programmatic use over the SDK socket directly, the protocol is documented in Faramesh Cloud → approval webhook (the same shape used by the hosted approvals UI).
Approval lifecycle
PENDING ─► APPROVED ─► (agent retries → permit, single use)
│
├──────► DENIED ─► (agent retries → POLICY_DENY)
│
└──────► EXPIRED ─► (agent retries → POLICY_DEFER again, new approval)Common scenarios
"I want to approve everything from one agent for the next hour"
That's a standing grant, not an approval. See faramesh credential and standing grants. Approvals are per-call.
"The approval expired before I saw it"
Raise runtime { approval_ttl } or wire alerts to a channel you watch. The alert { on = "defer" notify = "slack://..." } block does this.
"I want a UI, not the CLI"
Faramesh Cloud provides a web UI on top of the same socket. Self-host or use the hosted control plane. The CLI and UI are interchangeable. You can resolve a CLI-listed approval from the UI and vice versa.
What's next
- Denial codes → POLICY_DEFER: the SDK side.
- Credentials → standing grants: bulk pre-authorization for trusted operators.
- Faramesh Cloud: the UI version of these commands.
faramesh explain approval: full evaluation context for one approval.