Skip to main content
When a governance policy requires human oversight, Forge creates an approval request and pauses execution until a human resolves it. This is the hold disposition — the third possible outcome of the governance pipeline, alongside allow and block. Approvals bridge the gap between full autonomy and full manual control. They let agents execute freely for routine work while ensuring that high-stakes, high-risk, or policy-sensitive actions get human review before proceeding.

⚡ How Approvals Are Triggered

Approvals enter the system through two paths:
Policy-Triggered

Dispatch policy with action: require_approval matches, or approval policy trigger condition matches. Returns hold.

Workflow-Triggered

Step configured with hitl flag. Requires human approval regardless of policy evaluation.

When an approval is created, the workflow run’s pendingApprovals counter is incremented and its operatorStatus is recalculated. The run remains in a paused state until all pending approvals are resolved.

🔄 Approval States

Every approval follows a simple state machine:

APPROVAL STATE MACHINE

Pending

Awaiting human decision. Execution is paused.

human approves
human rejects
7 days elapsed
Approved

Execution resumes.

Rejected

Step fails. Workflow handles.

Auto-Rejected

7 days stale. System rejects.

RESOLUTION CHANNELS

BurgundySlackAPI

ESCALATION TIMELINE

Created
1h: Normal

First reminder

24h: Elevated

Urgency up

72h: Critical

Urgent reminder

7d: Auto-Reject

System rejects stale

StateDescriptionTerminal
pendingWaiting for human decision. Execution is paused.No
approvedHuman approved. The workflow interpreter is notified to resume.Yes
rejectedHuman rejected. The step is marked as failed.Yes

📡 Resolution Channels

Approvals can be resolved through three channels:
ChannelHow It WorksPermission Required
Operator InterfaceOperators resolve approvals from the approvals queue in a connected operator interface (e.g., the Burgundy dashboard). This is the primary resolution channel.governance:approve
SlackWhen a Slack channel is configured, approval notifications are sent to Slack. Operators can resolve directly from Slack.governance:approve
APIApprovals can be resolved programmatically through the platform API. Used for automated approval workflows or external integrations.governance:approve
The resolvedVia field on each approval records which channel was used, providing a complete audit trail of how governance decisions were made.

🔄 Approval Lifecycle

1

Creation

When the governance pipeline returns a hold disposition, an approval record is created with:
  • runId and stepId identifying the blocked execution
  • summary describing what requires approval
  • source indicating whether the trigger was workflow (explicit HITL flag) or policy (policy evaluation)
  • workflowName and vendorName for context
A notification is emitted for downstream consumers (Slack, dashboard alerts).
2

Waiting

While pending, the approval is queryable via the Platform API and visible in operator interfaces. The approval record surfaces how long the approval has been waiting, the blocked workflow run and its current status, the governance source, and a downstream effect label explaining the impact on the run.
3

Escalating Reminders

A cron job runs every 15 minutes to check for pending approvals and send escalating reminders:
AgeUrgencyBehavior
1 hournormalFirst reminder notification
24 hourselevatedElevated urgency reminder
72 hourscriticalCritical urgency reminder
Reminders are debounced: a new reminder is not sent within 1 hour of the previous one, and a reminder at a given tier is not re-sent until the approval ages into a higher tier.
4

Resolution

An operator resolves the approval with a decision (approved or rejected), their identity, and an optional reason. Resolution triggers:
  1. The approval record is updated with status, resolver, timestamp, and resolution channel
  2. The workflow run’s pendingApprovals counter is decremented
  3. The run’s operatorStatus is recalculated
  4. A workflow resume event is fired via reliable delivery (outbox pattern with retry) to unblock the interpreter
5

Auto-Rejection

A daily cron job scans for pending approvals older than 7 days and auto-rejects them. Auto-rejection sets status to rejected with resolver system:auto_reject, fires the interpreter resume event, and logs an audit event with the approval age.
The workflow resume event uses reliable delivery (outbox pattern with 3x retry). This prevents a failure mode where the approval is resolved in the database but the workflow never receives the signal — a scenario that previously caused workflows to remain stuck forever despite resolved approvals.

🏷️ Special Approval Types

Beyond standard workflow step approvals, Forge uses the approval system for several governance use cases:
When an agent’s lifecycle stage transition requires approval (e.g., activating a new agent), a lifecycle approval is created with a step ID in the format lifecycle:{agentId}:{targetStage}.When approved, the agent’s lifecycle stage is transitioned automatically. When rejected, the transition is recorded in the audit trail without changing the agent’s state.
When a deployment policy requires approval, a deployment approval is created with a step ID in the format deployment:{deploymentId}.Resolution transitions the deployment to active (approved) or inactive (rejected).
When organizational hierarchy changes require approval (creating new agent positions), a position creation approval is created. Pending position data is stored in a separate pendingPositionCreations table.Approval creates the position. Rejection cleans up the pending record. Auto-rejection after 7 days also cleans up.

BurgundyApprovals Queue

The platform provides a dedicated approvals queue that surfaces pending approvals with rich context. This data is accessible through the Platform API and operator interfaces. See the Burgundy Dashboard Guide for the operator experience.
  • Blocked Run Summary — The workflow run being blocked, its status, and operator status
  • Governance Source — Which policy, gate, or trigger caused the hold, with the policy name and trigger details
  • Explanation — A headline and rationale snippet derived from the latest governance decision
  • Downstream Effect — A label explaining the impact (e.g., “Run progress is blocked until this approval is resolved”)
  • Age — How long the approval has been pending
  • Action Links — Direct links to the workflow run detail page and audit packet
The queue also shows recently resolved approvals (last 12 hours) for operational awareness.

📋 Approval Data Model

FieldTypeDescription
runIdID (optional)The workflow run requiring approval
stepIdstringThe step or lifecycle identifier
summarystringHuman-readable description of what needs approval
statuspending / approved / rejectedCurrent state
sourceworkflow / policyWhat triggered the approval
requestedAttimestampWhen the approval was created
resolvedAttimestamp (optional)When the approval was resolved
resolvedBystring (optional)Who resolved the approval
decisionstring (optional)The resolution decision
reasonstring (optional)Reason provided by the resolver
resolvedViaburgundy / slack / apiResolution channel
slackChannelstring (optional)Slack channel for notifications
reminderSentAttimestamp (optional)Last reminder timestamp
For audit and compliance purposes, every approval lifecycle event (creation, reminder, resolution, auto-rejection) is recorded in the auditEvents table with full context including the resolver identity, resolution channel, and any associated governance decision.

In Burgundy: Resolve approvals in the approval queue.