Approval Policy
The approval policy is the conditional auto-approval layer for governed gates. It sits between the gate evaluator and the state machine, providing a middle ground between two extremes:
| Mode | Behavior |
|---|---|
requires_human_approval: true (no policy) | Always pauses for human approval |
--auto-approve on agentxchain run | Bypasses ALL approval gates |
| Approval policy | Auto-approves specific gates when evidence conditions are met |
In generated governed projects, "full-auto" is not a separate protocol mode. It is a project-policy posture: routine gates are marked credentialed: false, the QA ship gate also requires verification pass evidence, and approval_policy contains explicit rules for the routine boundaries that may close without an operator.
If you need repo-local automation after a human-approved gate succeeds, that is a different feature. See Gate Actions. Approval policy decides whether a human pause is required; gate actions decide what runs after approval.
For lights-out operation, operators need conditional approval — rules that auto-approve low-risk transitions when structural predicates pass, while still requiring human approval for high-risk transitions like publishing to npm, deploying to production, or any other external credentialed action.
Config shape
The approval_policy is an optional top-level section in agentxchain.json:
{
"approval_policy": {
"phase_transitions": {
"default": "require_human",
"rules": [
{
"from_phase": "planning",
"to_phase": "implementation",
"action": "auto_approve",
"when": {
"gate_passed": true,
"credentialed_gate": false
}
},
{
"from_phase": "qa",
"to_phase": "release",
"action": "require_human"
}
]
},
"run_completion": {
"action": "auto_approve",
"when": {
"gate_passed": true,
"all_phases_visited": true,
"credentialed_gate": false
}
}
}
}
Routine gates opt into policy eligibility with credentialed: false:
{
"gates": {
"qa_ship_verdict": {
"requires_files": [
".planning/acceptance-matrix.md",
".planning/ship-verdict.md",
".planning/RELEASE_NOTES.md"
],
"requires_human_approval": true,
"requires_verification_pass": true,
"credentialed": false
},
"publish_release": {
"requires_human_approval": true,
"credentialed": true
}
}
}
How it works
The approval policy only evaluates when a gate returns awaiting_human_approval. It cannot override gate failures — it can only relax the human-approval requirement when all structural predicates (files, verification, ownership) have already passed.
Gate evaluator returns 'awaiting_human_approval'
→ Approval policy evaluates rules
→ 'auto_approve': gate advances automatically
→ 'require_human': gate pauses for human approval
This means:
- Gate predicates are ALWAYS evaluated first
- Approval policy can only relax, never override failures
- The gate evaluator itself remains pure and unchanged
Phase transition rules
phase_transitions.default
Fallback action when no rule matches. One of "require_human" (default) or "auto_approve".
phase_transitions.rules
Ordered list of rules. First match wins.
| Field | Type | Required | Description |
|---|---|---|---|
from_phase | string | No | Source phase. Omit to match any source. |
to_phase | string | No | Target phase. Omit to match any target. |
action | string | Yes | "auto_approve" or "require_human" |
when | object | No | Additional conditions. All must be true for auto-approval. |
Conditions (when)
| Condition | Type | Description |
|---|---|---|
gate_passed | boolean | Gate structural predicates must have passed. In practice this is defense-in-depth, because the policy only evaluates after the gate already returned awaiting_human_approval. |
roles_participated | string[] | These role IDs must have at least one accepted turn in the current phase. The evaluator sees history after the just-accepted turn is appended, so the current accepted turn counts. |
credentialed_gate | false | Requires the current gate to be non-credentialed. true is rejected because credentialed gates are hard-stopped before rule matching. |
Run completion
Controls auto-approval for the final run completion gate.
| Field | Type | Required | Description |
|---|---|---|---|
action | string | Yes | "auto_approve" or "require_human" |
when | object | No | Conditions for auto-approval. |
Run completion conditions
| Condition | Type | Description |
|---|---|---|
gate_passed | boolean | Completion gate must have passed. This is also defense-in-depth for the same reason as phase transitions. |
all_phases_visited | boolean | Every phase declared in routing must have been active at least once. This is strict: if you declare an optional phase in routing and never visit it, completion will still require human approval. |
credentialed_gate | false | Requires the completion gate to be non-credentialed. |
Invariants
- Only applies to
requires_human_approvalgates. Gates without that flag already auto-advance — the policy does not affect them. - Credentialed gates hard-stop. A gate with
credentialed: truealways requires human approval. A catch-allauto_approverule cannot override it. --auto-approveoverrides policy entirely. The CLI flag is an operator override, not a project policy. It approves everything regardless of policy rules and is not equivalent to full-auto policy defaults.- Absent policy = today's behavior. If
approval_policyis not configured, allrequires_human_approvalgates pause for human approval. - Pure evaluation. Policy decisions are deterministic:
(gateResult, state, config) → "auto_approve" | "require_human". - Auditable. Every auto-approval decision is recorded in the decision ledger with
type: "approval_policy", the matched rule, and the gate context.
Examples
Auto-approve everything except release
{
"approval_policy": {
"phase_transitions": {
"default": "auto_approve",
"rules": [
{
"from_phase": "qa",
"to_phase": "release",
"action": "require_human"
}
]
},
"run_completion": {
"action": "require_human"
}
}
}
Auto-approve only after QA participation
{
"approval_policy": {
"phase_transitions": {
"rules": [
{
"from_phase": "qa",
"action": "auto_approve",
"when": {
"gate_passed": true,
"roles_participated": ["qa_engineer"]
}
}
]
}
}
}
Full-auto policy posture with safety conditions
{
"gates": {
"planning_signoff": {
"requires_human_approval": true,
"credentialed": false
},
"qa_ship_verdict": {
"requires_human_approval": true,
"requires_verification_pass": true,
"credentialed": false
},
"publish_release": {
"requires_human_approval": true,
"credentialed": true
}
},
"approval_policy": {
"phase_transitions": {
"default": "require_human",
"rules": [
{
"from_phase": "planning",
"to_phase": "implementation",
"action": "auto_approve",
"when": {
"gate_passed": true,
"credentialed_gate": false
}
}
]
},
"run_completion": {
"action": "auto_approve",
"when": {
"gate_passed": true,
"all_phases_visited": true,
"credentialed_gate": false
}
}
}
}
This is the tester-shaped routine case: QA records 38/38 acceptance criteria as passing, smoke evidence exits 0, qa_ship_verdict is non-credentialed, and the run completion gate closes by policy. If the same gate is changed to credentialed: true, the run pauses and agentxchain approve-completion --dry-run shows the pending human approval boundary.
Relationship to other mechanisms
| Mechanism | Scope | Approval policy interaction |
|---|---|---|
| Policies | Turn acceptance rules | Independent — policies evaluate on acceptance, approval policy evaluates on gate transitions |
| Gates | Artifact and approval checks | Approval policy sits downstream — only evaluates after gate returns awaiting_human_approval |
| Gate Actions | Post-approval repo-local automation | Adjacent but separate — gate actions run from human approval commands; gates with release/deploy actions should be credentialed: true so policy cannot bypass them |
| Hooks | External commands/endpoints | Independent — hooks fire on lifecycle events, not on gate approval decisions |
--auto-approve | CLI flag | Overrides approval policy — approves everything |