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

yaml
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

FieldTypeRequiredDescription
initialstringREQUIREDName of the starting state. Must reference a key in states.
statesobjectREQUIREDMap of state names to state objects. Must contain at least one state.
transitionsarrayREQUIREDArray of transition objects defining state-machine edges.

State Object Fields

FieldTypeRequiredDescription
descriptionstringOPTIONALHuman-readable description of this state.
capabilitiesarray of stringOPTIONALCapability identifiers available in this state. If absent or empty, no capability restriction is applied by posture for this state.
budgetsobjectOPTIONALBudget limits keyed by budget key. Values must be non-negative integers.

Transition Object Fields

FieldTypeRequiredDescription
fromstringREQUIREDSource state name, or "*" to match any state.
tostringREQUIREDTarget state name. Must not be "*".
onstringREQUIREDTrigger that causes this transition.
afterstringCONDITIONALDuration 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.

CapabilityDescription
file_accessRead and navigate filesystem paths.
file_writeWrite, create, or modify files.
egressMake outbound network requests.
shellExecute shell commands.
tool_callInvoke tools or MCP endpoints.
patchApply patches or diffs to files.
customEngine-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 capabilities is 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 KeyDescription
file_writesMaximum number of file write operations.
egress_callsMaximum number of outbound network requests.
shell_commandsMaximum number of shell command executions.
tool_callsMaximum number of tool/MCP invocations.
patchesMaximum number of patch applications.
custom_callsMaximum 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 0 means 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_exhausted transition 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

TriggerDescription
user_approvalThe user or operator explicitly approves a state change.
user_denialThe user or operator explicitly denies a pending action or confirmation.
critical_violationA core rule evaluation produces a deny for a critical-severity finding.
any_violationAny core rule evaluation produces a deny.
timeoutA duration has elapsed since entering the current state. Requires the after field.
budget_exhaustedAny budget in the current state has reached its limit.
pattern_matchA content pattern match occurs (engine-specific semantics).

Trigger Semantics

  • timeout transitions must include an after field 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).
  • to must 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.

yaml
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.

yaml
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.

yaml
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.

yaml
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.

yaml
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.

yaml
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:

ElementMerge Behavior
StatesChild 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.
TransitionsChild 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.
InitialIf 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.