Actions & plans API
An ExecutionPlan is what the model proposes; the deterministic executor is what disposes it. This page documents reading proposed plans with the trust verdict on every action, approving or vetoing a plan, listing disposed actions, and undoing a reversible one. The model never executes anything through this API, and neither do you: every side effect passes through the executor's policy gate, single-flight lock, and idempotency dedup.
Shared conventions, including authentication, pagination, the Idempotency-Key header, and the error envelope, are defined in the API overview. Error codes are catalogued in Errors.
The plan object
A plan wraps the kernel's ExecutionPlan, its optional reasoning and its actions[], with API metadata and the verdict the trust policy produced for each action. Action fields match PlannedAction in packages/kernel/src/trust.ts: connector, tool, args, optional value, entity_key, and idempotency_key.
Unique identifier, prefixed pl_.
Always execution_plan.
The operator that proposed the plan.
The event that triggered the run, or null for a scheduled run.
One of proposed, executed, vetoed, or expired. A plan whose actions all clear the policy without needing escalation executes without waiting; a plan holding an action that requires human approval sits in proposed until approved, vetoed, or expired.
The model's stated reasoning for the plan, verbatim. Optional in the kernel contract, so it may be null.
The proposed actions, in order. Each carries an action id (prefix act_), the PlannedAction fields, and a verdict.
The trust evaluation: decision (ALLOW, ALERT, or BLOCK), tier (the trust tier of the tool, 0–3), and rule, the guardrail that matched, or null when the block came from the default-closed fallthrough.
RFC 3339 timestamp of the proposal.
When the executor finished disposing the plan. null while proposed.
Plans awaiting approval expire after 72 hours by default and move to expired. Nothing in an expired plan ever runs.
Verdicts a plan's actions can carry, and the disposition DEDUP that appears once an action has been disposed:
{
"id": "pl_7c1a",
"object": "execution_plan",
"operator_id": "op_8f2a1c",
"event_id": "ev_3a91c7",
"status": "proposed",
"reasoning": "SO-10884 is paid, unfulfilled, and two days from its promise with no carrier scan. Hold it and tell the owner.",
"actions": [
{
"id": "act_91d0",
"connector": "cn_7d2f4a",
"tool": "order.hold",
"args": { "order": "SO-10884", "reason": "will miss promised ship date" },
"value": 184.5,
"entity_key": "order:SO-10884",
"idempotency_key": "order-risk:SO-10884:hold",
"verdict": { "decision": "ALLOW", "tier": 1, "rule": "tool:order.hold max_value:500" }
},
{
"id": "act_91d1",
"connector": "cn_7d2f4a",
"tool": "order.notify",
"args": { "order": "SO-10884", "to": "owner" },
"entity_key": "order:SO-10884",
"idempotency_key": "order-risk:SO-10884:notify",
"verdict": { "decision": "ALERT", "tier": 2, "rule": "tool:order.notify" }
}
],
"proposed_at": "2026-07-02T15:02:09Z",
"disposed_at": null,
"expires_at": "2026-07-05T15:02:09Z"
}
List plans
plans:readReturns plans for the tenant, newest first, cursor-paginated. Filter to status=proposed to build an approval queue.
One of proposed, executed, vetoed, or expired.
Only plans proposed by this operator.
Only plans holding an action with this entity_key.
Only plans proposed at or after this RFC 3339 timestamp.
Page size, 1–100. Defaults to 20.
Pagination cursor from a previous response's next_cursor.
curl "https://api.fibric.io/v1/plans?status=proposed&operator_id=op_8f2a1c" \
-H "Authorization: Bearer $FIBRIC_KEY"
{
"object": "list",
"data": [
{
"id": "pl_7c1a",
"object": "execution_plan",
"operator_id": "op_8f2a1c",
"event_id": "ev_3a91c7",
"status": "proposed",
"action_count": 2,
"proposed_at": "2026-07-02T15:02:09Z",
"expires_at": "2026-07-05T15:02:09Z"
}
],
"has_more": false,
"next_cursor": null
}
List responses summarize each plan with action_count; retrieve a single plan for full actions and verdicts.
Retrieve a plan
plans:readReturns the full plan object, including the model's reasoning and the verdict on every action. Inspect this before approving; the verdicts you see are the verdicts the executor will enforce.
The plan id, for example pl_7c1a.
curl https://api.fibric.io/v1/plans/pl_7c1a \
-H "Authorization: Bearer $FIBRIC_KEY"
Returns the plan object. A cross-tenant id reads as 404 not_found.
Approve a plan
plans:approveThe human escalation path: asks the executor to run a plan that is waiting in proposed. Approval is a request to run, not a bypass. The executor re-evaluates every action against the guardrails at execution time, enforces single-flight per entity_key, dedupes on each idempotency_key, and writes a receipt per action. An action whose verdict is BLOCK stays blocked with your approval on the record.
The plan to approve. Must be in proposed, otherwise 409 state_conflict.
Identifier of the human or service approving, for example an email address. Recorded on every resulting receipt. Defaults to the API key's label.
Optional subset of action ids (act_…) to run. Actions not listed are skipped and recorded as skipped. Omit to run the whole plan.
Optional free-text note recorded with the approval.
curl -X POST https://api.fibric.io/v1/plans/pl_7c1a/approve \
-H "Authorization: Bearer $FIBRIC_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: approve-pl_7c1a" \
-d '{ "approver": "ops@acme.com" }'
{
"id": "pl_7c1a",
"object": "execution_plan",
"status": "executed",
"approver": "ops@acme.com",
"results": [
{ "action_id": "act_91d0", "disposition": "ALLOW", "ok": true, "receipt_id": "rc_5b21" },
{ "action_id": "act_91d1", "disposition": "ALERT", "ok": true, "receipt_id": "rc_5b22" }
],
"disposed_at": "2026-07-02T15:04:41Z"
}
Error cases:
| Status | Code | When |
|---|---|---|
404 | not_found | No plan with this id exists for the tenant. |
409 | state_conflict | The plan is already executed, vetoed, or expired. |
409 | entity_locked | Another plan holds the single-flight lock on an entity this plan touches. See below. |
409 | idempotency_conflict | The Idempotency-Key header was reused with a different body. |
Veto a plan
plans:approveRefuses a proposed plan. Nothing in a vetoed plan ever runs, and the veto, with who and why, is written to the audit trail. The operator sees the veto as feedback on its next run over the same entity.
The plan to veto. Must be in proposed, otherwise 409 state_conflict.
Why the plan was refused. Recorded verbatim.
curl -X POST https://api.fibric.io/v1/plans/pl_7c1a/veto \
-H "Authorization: Bearer $FIBRIC_KEY" \
-H "Content-Type: application/json" \
-d '{ "reason": "customer already contacted; hold would double-message" }'
{
"id": "pl_7c1a",
"object": "execution_plan",
"status": "vetoed",
"vetoed_by": "ops@acme.com",
"veto_reason": "customer already contacted; hold would double-message",
"disposed_at": "2026-07-02T15:06:12Z"
}
The action object
Once a plan is disposed, each of its actions becomes an addressable record of what the executor did with it. The disposition is the kernel's ActionDisposition: a trust decision, or DEDUP when the idempotency key had already been applied.
Unique identifier, prefixed act_. Stable from proposal through disposition.
Always action.
The plan this action belonged to.
The PlannedAction fields, unchanged from the proposal.
ALLOW, ALERT, BLOCK, or DEDUP.
Whether the disposition completed without error. A BLOCK is ok: false with error: "blocked by trust policy"; a connector failure is ok: false with the upstream error.
Error detail when ok is false.
The immutable receipt written for this action, including refusals.
true when the tool declares a compensating inverse and the action applied successfully.
true after a successful undo.
RFC 3339 timestamp of the disposition.
List actions
actions:readReturns disposed actions for the tenant, newest first, cursor-paginated.
Only actions from this plan.
Only actions with this entity_key.
Filter by ALLOW, ALERT, BLOCK, or DEDUP.
Only actions disposed at or after this RFC 3339 timestamp.
Page size, 1–100. Defaults to 20.
Pagination cursor from a previous response's next_cursor.
curl "https://api.fibric.io/v1/actions?entity=order:SO-10884&disposition=ALLOW" \
-H "Authorization: Bearer $FIBRIC_KEY"
{
"object": "list",
"data": [
{
"id": "act_91d0",
"object": "action",
"plan_id": "pl_7c1a",
"connector": "cn_7d2f4a",
"tool": "order.hold",
"entity_key": "order:SO-10884",
"idempotency_key": "order-risk:SO-10884:hold",
"disposition": "ALLOW",
"ok": true,
"receipt_id": "rc_5b21",
"undoable": true,
"undone": false,
"disposed_at": "2026-07-02T15:04:41Z"
}
],
"has_more": false,
"next_cursor": null
}
Undo an action
actions:writeRuns the tool's declared compensating inverse, for example releasing a hold that order.hold placed. The undo is itself an action: it takes the same single-flight lock on the entity_key, carries its own idempotency key derived from the original, and writes its own receipt. The original action's record is never altered; undone flips to true and both receipts stand.
The action to undo. Must be undoable: true and not already undone.
Why the action is being reversed. Recorded on the undo receipt.
curl -X POST https://api.fibric.io/v1/actions/act_91d0/undo \
-H "Authorization: Bearer $FIBRIC_KEY" \
-H "Content-Type: application/json" \
-d '{ "reason": "shipment confirmed; releasing the hold" }'
{
"id": "act_91d0",
"object": "action",
"undone": true,
"undo_action_id": "act_9a44",
"undo_receipt_id": "rc_5c07",
"undone_at": "2026-07-02T17:40:02Z"
}
Error cases:
| Status | Code | When |
|---|---|---|
404 | not_found | No action with this id exists for the tenant. |
409 | action_not_undoable | The tool declares no inverse, the action did not apply, or it was already undone. |
409 | entity_locked | The entity's single-flight lock is held. Retry after Retry-After. |
Single-flight and idempotency semantics
Two kernel primitives shape every write on this page. Both are visible in packages/kernel/src/executor.ts and explained in Single-flight & idempotency.
- Single-flight per entity. The executor serializes side effects per
entity_key. Inside the executor this is a wait, not a failure; at the API boundary, a request that would need a lock held by longer-running work returns409with codeentity_lockedand aRetry-Afterheader. Retry the same request unchanged; when the lock is free, it disposes normally. - Idempotency per side effect. Every side-effecting action carries an
idempotency_key. A key the executor has already applied disposes asDEDUP: acknowledged, skipped, receipted, never applied twice. This is the property that makes a runaway flood, such as the 657-message incident that shaped the kernel, structurally impossible.
Approval never overrides a BLOCK. The trust policy fails closed: a side-effecting action with no matching guardrail is refused, and the refusal is receipted with the rule, or the absence of one, that produced it. To let an action through, change the operator's guardrails in the Operators API and let the operator re-propose.