Posture Extension
The full normative specification is at spec/hushspec-posture.md.
Overview
The Posture extension adds a declarative state machine for capability and budget management. An agent starts in an initial state and transitions between states based on triggers (violations, timeouts, approvals, budget exhaustion). Each state declares available capabilities and optional budget limits.
Posture is declared under extensions.posture in a HushSpec document. When a conformant engine supports the posture extension, posture state is evaluated alongside core rules. The active state's capabilities narrow the set of permitted actions, and budget limits impose hard ceilings on cumulative operation counts within a session.
Schema
extensions:
posture:
initial: <state_name> # REQUIRED. Must reference a key in states.
states:
<state_name>:
description: <string> # OPTIONAL. Human-readable description.
capabilities: # OPTIONAL. Capabilities available in this state.
- <capability>
budgets: # OPTIONAL. Budget limits for this state.
<budget_key>: <integer>
transitions:
- from: <state_name|"*"> # REQUIRED. Source state ("*" matches any).
to: <state_name> # REQUIRED. Target state (must not be "*").
on: <trigger> # REQUIRED. What causes the transition.
after: <duration> # REQUIRED when on is "timeout".
Field Reference
Top-Level Fields
| Field | Type | Required | Description |
|---|---|---|---|
initial | string | REQUIRED | Name of the starting state. Must reference a key in states. |
states | object | REQUIRED | Map of state names to state objects. Must contain at least one state. |
transitions | array | REQUIRED | Array of transition objects defining state-machine edges. |
State Object Fields
| Field | Type | Required | Description |
|---|---|---|---|
description | string | OPTIONAL | Human-readable description of this state. |
capabilities | array of string | OPTIONAL | Capability identifiers available in this state. If absent or empty, no capability restriction is applied by posture for this state. |
budgets | object | OPTIONAL | Budget limits keyed by budget key. Values must be non-negative integers. |
Transition Object Fields
| Field | Type | Required | Description |
|---|---|---|---|
from | string | REQUIRED | Source state name, or "*" to match any state. |
to | string | REQUIRED | Target state name. Must not be "*". |
on | string | REQUIRED | Trigger that causes this transition. |
after | string | CONDITIONAL | Duration string (e.g. "30s", "5m", "1h"). Required when on is "timeout". |
Standard Capabilities
Capabilities declare what categories of action an agent may perform in a given state. When the current state's capabilities array is non-empty, only actions corresponding to a listed capability are permitted.
| Capability | Description |
|---|---|
file_access | Read and navigate filesystem paths. |
file_write | Write, create, or modify files. |
egress | Make outbound network requests. |
shell | Execute shell commands. |
tool_call | Invoke tools or MCP endpoints. |
patch | Apply patches or diffs to files. |
custom | Engine-defined custom capability. |
Engines may support additional capability identifiers beyond this standard set. Conformant validators should produce warnings (not errors) for unrecognized capabilities, ensuring forward compatibility.
Capability Narrowing
Posture capabilities narrow the base policy - they can restrict it, never widen it. When the posture extension is active:
- If the current state lists capabilities, an action is permitted only if it matches a listed capability and is allowed by core rules.
- If the current state has an empty
capabilities: []array, no operations are permitted through the posture extension, regardless of what core rules allow. - If
capabilitiesis absent (not specified), no capability restriction is applied by posture for that state - the core rules alone govern.
This means a state cannot grant access to action types that the base policy denies. Posture only removes options from the set the base policy allows.
Standard Budget Keys
Budget limits impose hard ceilings on cumulative operation counts within a session. Budget values must be non-negative integers.
| Budget Key | Description |
|---|---|
file_writes | Maximum number of file write operations. |
egress_calls | Maximum number of outbound network requests. |
shell_commands | Maximum number of shell command executions. |
tool_calls | Maximum number of tool/MCP invocations. |
patches | Maximum number of patch applications. |
custom_calls | Maximum number of engine-defined custom operations. |
Budget Enforcement
When a budget key reaches its limit, the corresponding action type is denied. The enforcement rules are:
- Hard ceiling. Once a budget counter reaches the declared limit, every subsequent request for that action type is denied for the remainder of the session (or until the agent transitions to a state with a higher or absent budget for that key).
- A value of
0means the operation is never permitted in this state. - Session-scoped. Budget counters are scoped to the session by default. Engines may support alternative scoping (per-state, per-window) as engine-specific extensions, but must document this behavior.
- Transition trigger. Engines may trigger a
budget_exhaustedtransition when any budget is fully consumed, enabling automatic state changes (e.g., from normal mode to restricted mode). - Negative values are invalid. Validators must reject documents containing negative budget values.
Standard Triggers
| Trigger | Description |
|---|---|
user_approval | The user or operator explicitly approves a state change. |
user_denial | The user or operator explicitly denies a pending action or confirmation. |
critical_violation | A core rule evaluation produces a deny for a critical-severity finding. |
any_violation | Any core rule evaluation produces a deny. |
timeout | A duration has elapsed since entering the current state. Requires the after field. |
budget_exhausted | Any budget in the current state has reached its limit. |
pattern_match | A content pattern match occurs (engine-specific semantics). |
Trigger Semantics
timeouttransitions must include anafterfield specifying the duration. The value is a string in the format<number><unit>where unit is one of:s(seconds),m(minutes),h(hours),d(days). Examples:"30s","5m","1h","7d".from: "*"matches any source state, allowing global transitions (e.g., a critical violation from any state transitions to lockdown).tomust not be"*". Wildcard targets are not permitted because the target state must be deterministic.
Transition Priority
When multiple transitions match the same trigger from the same source state, the engine selects the most specific from match. A named state takes priority over "*". If two transitions have equal specificity, the first transition in document order wins.
Examples
Basic 2-State Machine (Restricted to Approved)
A minimal posture that starts restricted and requires user approval to unlock full capabilities.
extensions:
posture:
initial: "restricted"
states:
restricted:
description: "Read-only until approved"
capabilities:
- file_access
- tool_call
budgets:
tool_calls: 20
approved:
description: "Full access after approval"
capabilities:
- file_access
- file_write
- egress
- shell
- tool_call
- patch
budgets:
file_writes: 200
egress_calls: 50
tool_calls: 500
transitions:
- from: "restricted"
to: "approved"
on: user_approval
- from: "approved"
to: "restricted"
on: any_violation
3-State Progressive Lockdown
Normal operation with progressive restriction: any violation drops to restricted mode, a critical violation from any state locks down entirely.
extensions:
posture:
initial: "standard"
states:
standard:
description: "Normal operating mode"
capabilities:
- file_access
- file_write
- egress
- tool_call
budgets:
file_writes: 100
egress_calls: 50
tool_calls: 200
restricted:
description: "Limited mode after violation"
capabilities:
- file_access
- tool_call
budgets:
tool_calls: 10
locked:
description: "No operations permitted"
capabilities: []
transitions:
- from: "standard"
to: "restricted"
on: any_violation
- from: "*"
to: "locked"
on: critical_violation
- from: "restricted"
to: "standard"
on: user_approval
- from: "standard"
to: "restricted"
on: timeout
after: "1h"
- from: "standard"
to: "restricted"
on: budget_exhausted
Budget-Driven Transitions
This pattern uses budget exhaustion as the primary transition mechanism. The agent starts with generous limits and progressively tightens as budgets run out.
extensions:
posture:
initial: "generous"
states:
generous:
description: "High limits for initial work"
capabilities:
- file_access
- file_write
- egress
- shell
- tool_call
budgets:
file_writes: 50
shell_commands: 30
egress_calls: 20
conservative:
description: "Tighter limits after initial budget spent"
capabilities:
- file_access
- file_write
- tool_call
budgets:
file_writes: 10
tool_calls: 50
read_only:
description: "Read-only when all budgets spent"
capabilities:
- file_access
transitions:
- from: "generous"
to: "conservative"
on: budget_exhausted
- from: "conservative"
to: "read_only"
on: budget_exhausted
- from: "*"
to: "read_only"
on: critical_violation
Design Patterns
Approval Gate
Start in a restricted state and require explicit user approval before granting elevated capabilities. Useful for high-risk environments where the agent should prove intent before gaining write access.
initial: "pending"
states:
pending:
capabilities: [file_access]
active:
capabilities: [file_access, file_write, tool_call, egress]
transitions:
- from: "pending"
to: "active"
on: user_approval
- from: "active"
to: "pending"
on: user_denial
Timeout Demotion
Automatically restrict capabilities after a period of inactivity or elapsed time. Ensures long-running sessions do not maintain elevated privileges indefinitely.
initial: "active"
states:
active:
capabilities: [file_access, file_write, egress, tool_call]
demoted:
capabilities: [file_access]
transitions:
- from: "active"
to: "demoted"
on: timeout
after: "30m"
- from: "demoted"
to: "active"
on: user_approval
Violation Ratchet
Use the wildcard from: "*" to ensure critical violations always reach lockdown, regardless of the current state. Less severe violations cause incremental restriction.
transitions:
- from: "standard"
to: "restricted"
on: any_violation
- from: "*"
to: "locked"
on: critical_violation
Because named-state transitions take priority over "*", and critical_violation is a different trigger from any_violation, both transitions can coexist without conflict.
Merge Rules
When a child document extends a base document containing posture configuration, the following merge rules apply under deep_merge strategy:
| Element | Merge Behavior |
|---|---|
| States | Child states override base states by name. If a child defines a state with the same name as a base state, the child's state object entirely replaces the base's. Base states not redefined in the child are preserved. |
| Transitions | Child transitions fully replace base transitions. If the child defines a transitions array, the base's array is discarded entirely. If the child omits transitions, the base's transitions are preserved. |
| Initial | If the child defines initial, it overrides the base's value. Otherwise the base value is preserved. |
Under replace strategy, the child's posture object entirely replaces the base's.