Multi-Agent Planning

Approve a MAP plan step (HITL parity with WCP)

Plan-scoped approval endpoint. Returns the same ApprovalResponse shape as the WCP endpoint (/api/v1/workflows/{id}/steps/{step_id}/approve) plus a plan_id field — see ADR-046 (HITL response parity).

A plan step is held only in MAP confirm / step mode, backed by a WCP workflow: the handler delegates to the WCP service and projects the full retry_context, approver metadata, and policies_matched. A plan no workflow backs has no paused step, and answers 404. (The in-memory pause the multi-agent routes used before v11.1.0 is retired, #4249.)

post/api/v1/plans/{planId}/steps/{stepId}/approve

Request

Path parameters

planIdstring required

MAP plan ID

stepIdstring required

Step ID awaiting approval

Request body

approved_bystring

Identity approving the step (overrides X-User-ID header)

commentstring

Audit justification. Required on WCP-backed plans (min 10 chars); if shorter, the handler auto-fills a generated audit message.

Response

Step approved successfully

workflow_idstring

Underlying WCP workflow identifier

plan_idstring

MAP plan id — present on MAP-plane responses. Omitted on WCP-plane responses (WCP has no plan concept).

step_idstring

Step that was approved or rejected

status'pending' | 'approved' | 'rejected' | 'expired'

Flat string alias of approval_status. Both fields always carry the same value — status is convenient for loggers, dashboards, and clients that prefer a simple string; approval_status is the typed source of truth. First-class on both the WCP and MAP response shapes so existing clients reading either field keep working without branching.

decision'allow' | 'block' | 'require_approval'

Post-approval decision. Approved require_approval steps resolve to allow; rejected to block. require_approval on an approve/reject response means the step is still pending.

reasonstring

Decision reason text. Approved / rejected responses prefix the original policy reason with Approved: or Rejected:.

approval_status'pending' | 'approved' | 'rejected' | 'expired'

Terminal approval status after the mutation landed. expired is an auto-timeout (Evaluation-tier) — a terminal not-approved state that blocks the step, kept distinct from a human rejected.

approval_idstring uuid

HITL queue entry UUID of the hold this decision acted on: the step's pending hold, else its newest (hold 1 is UUID v5 over workflow_id + ":" + step_id; hold n >= 2, a step held again after an earlier hold was decided, is UUID v5 over workflow_id + ":" + step_id + "#" + n). Matches the queue row written by the WCP HITL adapter. With no hold for the step (a step-gate row outside its hold ids is not a hold) it is the hold-1 id; empty on the legacy in-memory MAP flow, and omitted when the queue could not be read.

approved_bystring

Identity (X-User-ID, typically email) that approved the step

approved_atstring date-time

Timestamp when the approval was persisted

rejected_bystring

Identity that rejected the step (rejection path only)

rejected_atstring date-time

Timestamp when the rejection was persisted

messagestring

Human-readable status summary

Example response

{
  "workflow_id": "wf_41231a72-9b0c-4f5e-8a13-6d2e0c7b4f91",
  "plan_id": "plan-42",
  "step_id": "step-2",
  "status": "approved",
  "decision": "allow",
  "reason": "Approved: High-value transfer requires oversight",
  "approval_status": "approved",
  "approval_id": "318a270f-7b42-5c56-a191-8dbd1bf2e1e4",
  "approved_by": "fraud.analyst@banking.example",
  "rejected_by": "fraud.analyst@banking.example",
  "retry_context": {
    "gate_count": 2,
    "completion_count": 1,
    "prior_completion_status": "completed",
    "prior_output_available": true,
    "last_decision": "allow",
    "idempotency_key": "payment:wire:invoice-7721"
  },
  "message": "Step approved"
}

Changes

No recorded changes to this operation across all 1 revision of this API.