> ## Documentation Index
> Fetch the complete documentation index at: https://docs.checkfu.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Automations

> Run scheduled or event-triggered jobs with versioned graphs, explicit authority, and recoverable node work.

An **Automation** starts a configured job from a schedule, an event, or a
manual request. An **AutomationGraph** describes that job: its trigger, the
nodes that do work, and the dependencies between them. An **AutomationRun**
records one execution; each firing retains its original source and identity.

For example, ask a support Agent to summarize the queue every weekday at 09:00
UTC. Select an Agent with authorized access to the queue, an Environment, and
a service Principal. Connect the schedule to the Agent node, save the graph,
then plan and apply that exact revision. Each occurrence starts its own job.
The summary remains in its Session; sending it elsewhere requires a separately
authorized delivery action.

```text theme={"theme":{"light":"github-light","dark":"github-dark"}}
weekday schedule
  → one Automation firing
  → Agent node starts its child Session
  → the Session's Run produces a summary
  → inspect the retained result
```

The schedule starts the job. The Agent node delegates the work to Sessions;
it does not run the Agent inside a generic workflow retry. A Check can judge
an input or result, while a Boolean condition routes on known values. Neither
a passing Check nor a chosen branch grants permission for the next action.

Start with [One Agent](#one-agent), use
[provider triggers](/guides/trigger-agents-from-providers) for incoming events,
and read [Sessions and Runs](/concepts/sessions-and-runs) for Agent execution.

<Warning>
  The Checkfu CLI and TypeScript SDK are private-alpha artifacts with no self-service public installation channel.
  Part of this flow is behind a rollout fence; a deployment that has not enabled it refuses the request rather than running it.
  Confirm [capability status](/getting-started/status) before making availability part of your application's contract.
</Warning>

## The authority split

| Resource | What it owns | What it does not own |
| - | - | - |
| `AutomationGraph` | Authored composition, semantic revisions, plan/apply, managed bindings, and drift | Trigger timing, Agent execution, approval decisions, Connection credentials, Memory curation, or a second event log |
| `Automation` | Durable trigger configuration, active or paused state, readiness projection, and firing authority | The Session transcript or the runtime authority of a specialized child resource |
| `AutomationRun` | One execution record, including lifecycle, Usage, error, and causal firing coordinate | Agent conversation state |
| `Agent` and `AgentVersion` | The Agent configuration and its immutable published version | A particular Automation firing |
| `Session` | The interaction and ordered history for Agent work | Recurrence or provider-side webhook registration |
| `Run` | One request in that Session, including its tool rounds, waits, and recovery | The whole Automation execution |
| `Outcome` | A bounded revise-until-done loop inside one Session | A graph edge, post-run audit, or permission grant |
| `Connection` and governance resources | Credential custody, access, policy, and human approval | Composition topology |
| `CurationRun` and `MemoryStore` | Memory curation and durable Memory state | A generic graph step engine |

A graph is a secret-free authoring document. Planning and applying it compiles
and reconciles ordinary Checkfu resources; firing does not reread the mutable
editor document. The visual builder is an authoring surface, not a runtime
dependency.

Automation owns graph and firing coordination through one selected driver.
Cloudflare Workflow coordination, where qualified, preserves the original node
execution; Sessions and their Cloudflare Agents or native Harness owners execute
Agent turns. A workflow retry must not replay an entire turn or start a parallel
driver for the same firing.

## Direct Automations and graph-managed Automations

The direct Automation APIs remain first-class. Use them when your application already owns the desired configuration and wants to create, patch, pause, resume, fire, inspect, or delete an Automation directly.

Use an AutomationGraph when a person or product surface wants to author composition visually or as a graph document. The graph lifecycle is explicit:

1. Create or replace a secret-free semantic document.
2. Plan one exact revision against live Workspace state.
3. Review resource dispositions, pins, blockers, and required human inputs.
4. Apply the reviewed plan through ordinary resource operations.
5. Inspect the durable application journal and drift projection.

Publishing work through this lifecycle applies a reviewed, saved graph
revision. The plan records the published Agent versions it pins. An admitted
firing retains its program, node inputs, and selected versions through waits
and recovery; editing or applying a later revision does not rewrite that work.
An immutable version identifies the original configuration, not lasting
permission to execute it.

A resource has at most one active authoring owner. A graph never silently adopts a directly managed Automation, and a direct writer must not race a graph-managed resource. Ownership transfer, when supported, is explicit and fenced. References do not claim ownership.

<Warning>
  Do not run an API writer and an AutomationGraph reconciler against the same managed fields. There is no last-writer-wins authoring mode.
</Warning>

## Exporting a saved graph

In Console, **Export JSON** downloads the saved graph's `document` and `layout`. Save pending edits before exporting. The file preserves the selected Environment, service Principal, resource references, nodes, edges and authored input bindings; it does not publish or run the graph. Applied resource associations are resolved again during plan and apply.

The export is an authoring artifact, not captured permission to create another graph. To use it with the graph create API or `checkfu graph create --input @file.json`, add the required `created_by_principal_id` for the current creating Principal to the request alongside `document` and `layout`. Creator attribution does not authorize the request; ordinary create authorization still applies. Review the selected bindings under current Workspace authority. Do not copy credentials or infer a new creator from the old graph.

This is a same-Workspace workflow round-trip. Moving it to another Environment or Workspace requires explicitly selecting valid local bindings. After creation, reopen the graph, plan its exact revision and review the plan before applying it. An imported document alone does not activate an Automation.

## Triggers, activation, and readiness

Landed Automation triggers are:

* `schedule`, with a cron expression and timezone;
* `schedule_once`, with one absolute date saved as canonical UTC;
* `webhook`, with a signed ingest secret, dedupe key, and optional admission filter;
* `subscription`, for an admitted watched-resource occurrence under a CollaborationSpace.

To start work from a messaging app, run your own gateway and call the Session API. See [Connect your own messaging app](/guides/connect-your-own-messaging-app).

For a webhook Automation, patching `ingest_secret` replaces the inbound signing secret with no overlap window. Requests that authenticate after the rotation must use the new secret; the old secret returns HTTP `401` with error type `auth.invalid_key`, including old-signed replays. Rotation does not clear delivery deduplication: an identical previously accepted occurrence signed with the current secret replays its original firing, while reusing its dedupe key with changed content conflicts. Rotating the secret does not retroactively cancel work already admitted. These inbound rules are separate from an outbound WebhookEndpoint receiver's secret cutover.

You can also call the manual fire operation. Manual fire uses the same durable firing path as a schedule or inbound event. It does not create a separate execution model.

A paused Automation must pass the server-owned readiness projection before `resume` can move it to active. Unknown required evidence blocks activation just as a known failure does. The landed projection checks the current target, referenced Agents, model routes used by Decisions and Checks, PermissionAssignments, webhook Connection and registration health, Outcome grader readiness, and retention compatibility.

An acting Agent node can carry a per-firing Session budget. In the inspector, enter a positive whole-number limit and choose what it counts; the default is model spend in micro-USD. The graph saves and exports the exact request and compiles it into the Automation target. The limit applies to each child Session and uses the existing `pause_session` breach action; it does not cancel a turn or grant spending authority. Clearing the field removes only that authored cap, while other applicable Budgets remain. A step Agent can carry the same request as a **Child Session limit** when it starts a new Session. Each newly created child, including a fan-out child or an authored retry that creates a new Session, has its own cap; this is not an aggregate workflow cap. Recovery of the original child retains its original cap, even if the graph is later edited. A cap on selected-Session continuation is refused; it cannot replace that Session’s existing Budget. Budgets on nonacting Agents and ordinary hands-to chains are refused rather than silently discarded. Native execution still requires the admission and runtime support described below.

Every Automation carries an explicit `environment_id`. Graph authoring requires that field too: Console asks you to select an Environment, and the pure graph compiler forwards `environmentId` into the applied Automation. Readiness checks that the Environment exists and is live. Native Session admission validates it before creating each child Session.

The `run_agent` launch projection preserves that same Environment through launch, recovery, prior-output reads, and child Outcome inspection. A successful graph plan or apply proves reconciliation; execution qualification requires the applicable runtime journey.

Graph authoring and native runtime support are separate. The current native launcher refuses Outcome and FlowReview inputs, and native admission refuses delivery-screened inputs until their owner adapters are installed. A projected completed Run also does not establish effect settlement for a successor step. These paths remain unavailable while preserving the original child identity; saving or applying their graphs does not qualify filtering, bounded revision, or scheduled learning execution.

Pausing an Automation prevents new firings. It does not delete prior AutomationRuns, Sessions, Outcomes, approval records, or audit evidence. Resuming re-enables the same durable Automation only when the implemented readiness checks pass.

Pause does not cancel an already admitted Session or a reconciliation pass that began while active. A new reconciliation pass that observes a paused Automation leaves an unfinished step firing pending, with its original history and effect identities. After resume passes readiness, the next reconciliation continues that firing under current authority; it does not create a replacement firing. Firing a paused Automation returns HTTP `409` with error type `validation.conflict`. Pause is not a substitute for cancelling a Session or Run.

## What happens when an Automation fires

For a direct Agent target or a supported `run_agent` step that starts a new
Session, the firing creates an Automation execution record and admits a child
Session with its selected AgentVersion and Environment. The initial Run does
the requested Agent work. Each record keeps its own identity:

* the Automation determines when the configured job may fire;
* the firing and AutomationRun identify this occurrence and its progress;
* the AgentVersion determines the child's configuration;
* the Session keeps that child's interaction history; and
* the Run carries the request through model calls, tool rounds, and waits.

An admitted child Session is not a completed node. A dependent node waits for
the original child's required result and effect settlement. Notifications and
a successful HTTP response do not, by themselves, establish either.

### Waits and cancellation

A timer, human approval, or unfinished child can leave the firing pending.
Resume the original wait with its retained inputs and current authority. An
approval records a decision; it does not execute an application handler or
prove that an external effect completed. Other authored branches may still be
running while one branch waits.

Pausing the Automation prevents new firings; it does not cancel admitted child
work. Use the supported Session or Run cancellation path for that work and
inspect its actual outcome. A cancellation request does not undo an external
effect or prove that all sibling work stopped. Do not create another firing to
replace a canceled or uncertain operation. See
[Recovery and authored retries](#recovery-and-authored-retries).

### Current authority

The trigger's credentials authorize event admission, not every later action.
The Automation's service Principal, selected accounts, resource permissions,
policy, any required approvals, audience, and Budgets still govern node work.
Recheck at the relevant effect and result-release boundaries, including after
waits and recovery. A recorded version, Check verdict, or successful Agent Run
cannot restore revoked access or authorize a wider audience.

See [Permissions](/concepts/tenancy-and-governance) and
[Billing](/concepts/billing-and-platform-supply) for those independent limits.

Scheduled learning composes ordinary nodes for authorized Session selection, Agent analysis, validation, permitted Memory updates or Skill proposals, and durable processed-history tracking.

## Bounded composition

Automation step programs support a bounded directed acyclic shape. The landed verbs include Agent work, Check evaluation, governed Connection actions, human approval, and durable wait. Fan-out, control-only joins, and `all_completed` joins that collect several named producers' retained output into one step are available only within the typed bounds enforced by the compiler. Cycles, ambiguous output merges, fail-open Decisions, and unsupported topologies are refused.

Dependencies and authored concurrency determine which nodes may proceed
together. Recovery retains the original nodes, item order, and concurrency
limits. A waiting node does not imply that every other branch has stopped.

Native Workflow execution currently refuses Agent nodes with `for_each`;
their original-node execution adapter is unavailable. The compiler's authored
item order and concurrency limits remain the contract for supported paths, but
authoring or applying such a node does not make it executable through Workflow.

Each node must compile to a supported operation owned by the relevant resource.
A saved node or successful plan does not install a missing execution adapter.
Use the specific limits below before depending on Outcome, selected-Session
continuation, delivery screening, or result bindings.

### One Agent

The simplest graph is a trigger connected to one Agent. It compiles and reconciles to an ordinary Automation using the graph's selected Environment. You can also author that Automation directly through the public API.

### Signed webhook ingest

Send a direct Automation event to `POST /v1/ingest/automations/{id}` with the supported `Checkfu-Version`, `Content-Type: application/json`, and `Checkfu-Signature`. This route authenticates with the Automation's ingest secret, rather than an API key. Sign the exact body bytes; the server does not normalize JSON before verification. Provider callbacks use their registered provider's signature scheme and callback route, rather than this direct-ingest secret.

Direct ingest has no deliberate old/new secret overlap window. Each request verifies against the current secret selected at its ingress lookup; after changing the secret, re-sign retries with the new one. A retired-secret signature is refused with `401 auth.invalid_key`. This verification rule does not promise atomic cancellation of requests already in flight. The signed timestamp must be within five minutes of server time in either direction; that freshness tolerance is separate from secret rotation.

Signature verification happens before replay lookup. Changing the secret does not create a new deduplication identity: an authenticated retry of the same retained event rejoins its original firing or filtered result. Keep the original event and authored deduplication key when re-signing a retry.

| HTTP result | Meaning |
| - | - |
| `201` | An admitted Agent Run, a steps firing, or a recorded skipped firing. Inspect the returned record; acceptance does not prove Agent completion or external delivery. |
| `200` | A retained `FilteredAutomationDelivery`: the admission filter stopped the event. This is an explainable result, not an HTTP failure. |
| `401 auth.invalid_key` | The direct-ingest signature is missing, invalid or expired. |
| `409 validation.conflict` | The current Automation or firing state conflicts with the request. |
| `413 validation.payload_too_large` | The body exceeds the 1 MiB ingest limit. |

A replay rejoins the existing firing or filtered result under its deduplication identity; `200` is not a generic replay status. Keep legitimate events distinct using the authored deduplication key. An uncertain external effect must retain its original execution identity rather than be replaced by a new event.

### One-time schedules

A one-time trigger selects one absolute occurrence. Set a graph Trigger node's `trigger` field, or a directly authored Automation's `trigger` field, to:

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{ "kind": "schedule_once", "at": "2026-12-01T17:00:00.000Z" }
```

The Console accepts a date with an explicit UTC offset and saves canonical UTC with millisecond precision. API, SDK, CLI graph documents and MCP use the same saved shape. A one-time trigger has no cron or timezone field. Recurring triggers retain `kind: "schedule"`, `cron` and `timezone`. Console and CLI inspection keep the selected date visible and show “no pending occurrence” when its scheduled cursor is empty. A combined workflow can still accept webhook events after its one-time occurrence is consumed.

Fresh creation and an explicitly changed date require a future occurrence. Pausing preserves a pending occurrence; resuming after its date makes that original occurrence eligible for catch-up. An Agent-target occurrence consumes its cursor when its native Session launch is confirmed; a steps occurrence consumes it when the program records a terminal result. A terminal Agent admission failure also consumes the occurrence. These receipts can consume the original cursor while paused; a launched Session completing later is a separate result. An Automation that was paused remains paused; resuming it does not rearm the consumed occurrence. Inspect the recorded result before selecting a new future date to rearm it. Renaming, changing metadata, reordering the two entry sources, pausing or resuming a consumed Automation does not schedule it again. Only selecting a different future date creates a new pending occurrence. Settlement of an older scheduled firing preserves the new date; its terminal failure must not pause that new occurrence. An authoring change racing with the terminal pause wins through the existing version precondition, while the original firing retains its failure receipt. Recovery retains the original occurrence and execution identity rather than dispatching a replacement for an uncertain external result. **Run now** is a separate manual firing and does not consume the scheduled occurrence.

The saved date is the earliest eligible dispatch time, rather than a guarantee of execution at that instant. One-time occurrences do not use recurring schedule jitter. If the scheduler is unavailable at the selected date, the retained pending occurrence remains eligible for catch-up when scheduling resumes; it does not become a new occurrence.

Deleting an Automation removes its pending occurrence. Delayed schedule commands for a deleted Automation are drained without retrying. Pausing stops new schedule admission; an already admitted occurrence can still settle.

A steps workflow can combine one recurring or one-time schedule entry with one webhook entry. Each keeps its own trigger input and deduplication semantics. Two scheduled entries, including a one-time entry paired with a recurring entry, are rejected during planning. This uses the existing durable scheduler and firing records.

### Classify once and route

In the builder, connect an incoming trigger to a Decision and select **Typed criteria**. Configure the evaluation questions, choose a model routing profile that supports evaluation, and set the rule for the first branch. Give the Decision explicit continue, otherwise, uncertain, and error destinations; a destination can finish without launching an Agent.

Hosted deployments register the TypeSafe evaluator (`jev-1.13.0`) for routing profiles that use your own TypeSafe API key. Registration does not enable it: typed evaluation runs only for a provider, model, and purpose (admission filter, Decision, ambient conversation, DeliveryScreen, Outcome, or node test) with a reviewed, unexpired qualification. Jev `jev-1.13.0` is qualified for all six purposes until 2026-12-24, from live results recorded 2026-09-25. Until yours is, readiness reports the route as blocked and names the purpose, a live evaluation fails with `evaluator_unqualified` without calling the provider, and you can still test rules offline or use a predicate or boolean Check instead.

For another rule over the same classification, add a later Decision, choose **Use an existing Check result**, and select the first Decision or an unconditional standalone typed Check action. Configure only the new rule. Every path to the later Decision must pass through the original evaluation. The compiler rejects paths that could read a result before it exists.

```text theme={"theme":{"light":"github-light","dark":"github-dark"}}
incoming event
  → classify and test “billing?”
      ├── continue → billing Agent
      └── otherwise → use that result to test “technical?”
          ├── continue → technical Agent
          └── otherwise → finish
```

An unconditional standalone typed Check action can precede a Decision that interprets its saved answers, including a definite negative or uncertain judgment. A failed Check action ends the workflow before the later Decision; to route evaluation errors, configure the Check directly on a Decision. Reusing a result does not grant permission for a later action.

Both rules consume one saved evaluation. A restart after evaluation reuses its result. Select **Include incoming event** on a routed Agent to pass it the original event with its instructions. Checks do not create an Agent Run or Computer.

Save the graph, use **Test rule offline** to inspect each rule with sample answers, then apply it. Offline testing uses the saved revision and makes no provider call. Uncertain answers and provider failures have separate branches; neither grants permission to launch an Agent or perform an action.

### Agent A, Decision, optional Agent B

A Decision can also judge one exact prior step output selected by an `output` edge. Predicate Decisions have continue and otherwise arms. Typed Check Decisions additionally require uncertain and error arms. Any arm may finish the program. The condition is a closed predicate set or a fail-closed Check.

Equality predicates compare text, booleans or finite JSON numbers without converting their types. In the inspector, choose **Value type** to author `false` or `0` as values; they differ from the text `"false"` or `"0"`. The same values survive Graph save and reopen through API, CLI, SDK and MCP. Text preserves whitespace and may be empty, while `contains` remains textual. Null is not an equality value. An unavailable bound field keeps its existing failure semantics; it is never converted to text.

```text theme={"theme":{"light":"github-light","dark":"github-dark"}}
schedule
  → Agent A Session
  → Decision over Agent A's retained output
      ├── continue → Agent B Session
      └── otherwise → finish
```

By default, when Agent B runs, its `run_agent` step creates its own Session. The AutomationRun joins the composition evidence, while each Session remains the execution truth for its Agent.

An Agent target can instead save `session_target: { mode: "continue_selected", session: "sess_…" }` to name an existing Session. The current native launcher refuses this continuation until its protected later-input adapter is installed, even when the selected Session matches. It does not fall back to creating a replacement Session. Saving or applying this target is authoring support, not execution readiness.

Each accepted later input must retain its own original firing and Run in the selected Session. Replaying a firing must recover that same input and Run; it must not reuse the Session's creation request, select the latest Run, or mint another input. A current-authorized edit from `start_new` to `continue_selected` does not transfer the earlier firing's authority. The new firing needs current input authority and the original selected resources; a missing, erased, expired or foreign association remains unavailable.

A `run_agent` step records `running` when its child is admitted; it does not complete merely because the Session exists. The workflow must observe that child's terminal result before admitting a dependent step. Child settlement now has a durable wake adapter that reads the retained original terminal result and reconciles the original firing; notifications alone do not authorize advancement. Successor admission still checks current authority, and duplicate or delayed wakes must preserve the original child identity. Scheduler reconciliation remains available for pending steps and timed waits. The scheduler-free customer journey remains unqualified until its acceptance case passes on the offered target; landed source is not a customer pass. Manually firing again is not the intended completion mechanism.

An Outcome attached to Agent B revises Agent B's work in Agent B's Session. It does not jump back to Agent A and does not turn the Decision into a cycle.

For criteria and feedback expressed as separate editable nodes, the [bounded worker/evaluator recipe](https://github.com/andyrewlee/checkfu/blob/main/apps/api/test/fixtures/automation-workflows/outcomes-bounded-review.json) uses worker → evaluator → Check, one explicit feedback revision, then a second evaluation. Only a conclusive negative first Check starts revision; uncertainty and errors terminate before it. Each Agent step owns a separate original child, rather than an intrinsic Outcome loop in one Session. Inspect the recorded Decision branch to distinguish acceptance, exhausted criteria and uncertainty; program completion alone is not acceptance. This authoring example remains unqualified for native execution, worker confinement and original-effect closure. A revision never authorizes repeating an uncertain external effect.

The compiler forwards the graph's selected Environment to the Automation, which uses it for both Agent launches. Readiness and Session admission still govern whether those launches can proceed.

### Connection action and human approval

A Connection step calls a named tool through the governed Connection path. Credentials remain in custody and never enter the graph document, model context, Session events, or sandbox.

Permission is evaluated before policy. The trust sequence remains:

```text theme={"theme":{"light":"github-light","dark":"github-dark"}}
PermissionAssignment → ActionPolicy → ActionApproval
```

A graph approval node compiles to the existing approval authority. The resulting ActionApproval is bound to the Automation execution and frozen action context. Approval does not create a Session, and the graph does not decide who may approve.

### Managed Tool action

A **Managed Tool** node selects an exact Checkfu-operated tool and release, its arguments, and a maximum customer charge. In an exported graph, use `kind: "step_managed_tool"`, `selection`, `arguments`, and `maximumChargeMicrousd`. The compiler preserves that selection in the Automation's `invoke_managed_tool` action; it does not choose a newer release. Optional `argumentBindings` use the same authored input bindings as other action nodes. Save, reopen and review the graph before applying it.

The node uses the Automation's selected service Principal. A saved release, spending ceiling or successful Check is not permission to send. Current tool and spend authority, policy, any required purchase approval, financial admission and the installed provider adapter still govern execution. An offering without the required adapter keeps the step pending rather than reporting a completed call. See [Managed Tools](/concepts/managed-tools) for release discovery and purchase scope; execution availability remains subject to the offered target's rollout fence.

For pre-send filtering, route a Decision's admitted branch to the Managed Tool node. Route rejected, uncertain and error results to their intended terminal or revision branches. The delivery node must have no alternate control path that bypasses the Decision. A bounded revision may revise the draft before delivery; it must not repeat an uncertain delivery.

Once an invocation is retained, recovery keeps the original firing, step and invocation. A pending approval or an unknown provider outcome does not authorize a replacement call. Replaying the same firing rejoins its retained invocation. Managed Tool nodes do not expose an authored semantic retry override. Result-to-Agent, Check or Decision bindings remain unavailable until the protected result-read adapter is installed; a completed invocation reference alone grants no access to its result.

### Memory curation

Dreaming is a scheduled learning workflow composed from ordinary nodes: schedule → select authorized Session history → Agent analysis → validate → permitted Memory update or Skill proposal → record processed history. Its graph must remain inspectable and editable, with explicit inputs, outputs, approvals and bounded revision. A template or editor shortcut may help author this graph.

A Memory write with `expected_sha256: null` is an explicit first creation, not an overwrite. The maintained `dreaming` recipe uses that form at `/history-improvement.md`. For later cohorts, the `dreaming-update` example adds a governed Memory read before analysis, passes the existing content into analysis and validation, and binds `/document/content_sha256` from that original read into the write's `expected_sha256`. A missing document or stale read fails or remains unavailable; it cannot fall back to creation. A concurrent change must pass the Memory owner's compare-and-set check before processed history advances. These recipe sources do not establish native runtime qualification.

The processed-history step names the query and the destination effects it requires. It advances only after those effects have completed; a failed or unresolved destination must leave the selected history pending. A confirmed Memory write can satisfy a required write effect. A completed Skill proposal confirms that the proposal was created, not that its instructions were adopted. Publishing or merging executable Skill changes still requires the Skill's policy and adoption path; a processed-history receipt does not grant that authority.

The **Observe Skill Proposal** node references one ancestral proposal step. Its structured result distinguishes `empty` (the original proposal step had no selected history), `pending` (no matching governed adoption has been established), and `evidence` (the proposal's exact adopted Skill ID and version). Branch on `/disposition` before selecting `/skill` or `/version`; those fields exist only for evidence. An unavailable lookup is an execution error, not a pending result. Observation is read-only and cannot satisfy a processed-history destination effect, merge instructions, or authorize later Skill use.

Its saved result stays attached to the original firing and proposal receipt across recovery: a later adoption cannot silently rewrite a recorded pending result, and a newer Skill head cannot replace recorded version evidence. Rechecking a proposal in a separately authored workflow does not revise the earlier receipt. Source admission is enabled; runtime and public author/save/reopen qualification for this candidate are still unrun.

A conclusive negative validation may route to a separate processed-history step with no required improvement effects. The learning example authors this as “processed without improvement”: the original query must still have completed, its selected evidence must remain valid, and current source authority must pass. It does not assert that a Memory write or Skill proposal occurred. The example's uncertain and error branches leave the original selection pending for diagnosis and recovery; ending the graph alone does not release that selection. Its positive branch still requires both destination effects before advancing the frontier.

## Editable workflow examples

The repository includes secret-free graph documents and layouts for [proactivity](https://github.com/andyrewlee/checkfu/blob/main/apps/api/test/fixtures/automation-workflows/proactivity.json), [screened output](https://github.com/andyrewlee/checkfu/blob/main/apps/api/test/fixtures/automation-workflows/filtering.json), [bounded Outcomes](https://github.com/andyrewlee/checkfu/blob/main/apps/api/test/fixtures/automation-workflows/outcomes.json), and [Session-history learning](https://github.com/andyrewlee/checkfu/blob/main/apps/api/test/fixtures/automation-workflows/dreaming.json). The [explicit proactive-delivery design](https://github.com/andyrewlee/checkfu/blob/main/apps/api/test/fixtures/automation-workflows/proactivity-delivery.json) evaluates the event’s goal, evidence and audience before admitting a private worker, then sends its output through a selected Connection tool. False, uncertainty and errors end before worker or delivery. Event audience and recipient values are authored inputs, not grants; current account, audience and final-send authorization remain required. It authors one Check and one child, with no revision or effect retry. Unknown delivery retains the original effect identity. A separate [bounded filtered-delivery design](https://github.com/andyrewlee/checkfu/blob/main/apps/api/test/fixtures/automation-workflows/filtering-delivery.json) uses draft, reviewer, typed quality branches, one revision, and selected Connection delivery. Its workers must lack external delivery authority; prompt instructions and a passed Check do not grant send permission. Replace their resource placeholders with selected same-Workspace IDs and current model profiles; follow the [binding and execution notes](https://github.com/andyrewlee/checkfu/blob/main/apps/api/test/fixtures/automation-workflows/README.md) before creating a graph.

These are authoring examples, not deployed or qualified workflows. The screened-output example does not select an external account or sender. The learning example proposes Skill instructions rather than adopting them; original history custody, permitted Memory publication and the processed frontier still require execution proof. Saving or exporting an example does not grant permission, publish it or start a paid call.

## A graph document example

Use the [private-alpha SDK](/reference/typescript-sdk) to save a document with a schedule, Agent A, a Decision over Agent A's output, optional Agent B, and an Outcome on Agent B. First select an Environment and a service Principal in the same Workspace, and create or retrieve the two Agents. Use their resource IDs rather than names. The authenticated author must be authorized to create the graph for that service Principal. The author’s setup credential is not the Automation’s execution identity. Revoking that credential blocks later requests authenticated with it; it does not by itself pause the Automation or revoke its admitted service Principal. Runtime admission and resource access still check the Automation’s current authority.

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
import type { BodyOf, Op } from "@checkfu/sdk"

type CreateGraphInput = BodyOf<Op<"automationGraphs.createAutomationGraph">>

const document: CreateGraphInput["document"] = {
  name: "triage-and-escalate",
  environment_id: environment.id,
  service_principal_id: servicePrincipal.id,
  nodes: [
    {
      kind: "trigger",
      id: "weekday",
      trigger: { kind: "schedule", cron: "0 9 * * 1-5", timezone: "UTC" },
    },
    {
      kind: "step_agent",
      id: "triage",
      agent: triageAgent.id,
      prompt: 'Review the queue and return JSON with an "escalate" boolean.',
    },
    {
      kind: "step_parse_json",
      id: "triage_json",
      inputBindings: { content: { sourceNodeId: "triage", pointer: "" } },
      outputSchema: {
        type: "object",
        properties: { escalate: { type: "boolean" } },
        required: ["escalate"],
        additionalProperties: false,
      },
    },
    {
      kind: "decision",
      id: "route",
      inputBindings: { escalate: { sourceNodeId: "triage_json", pointer: "/escalate" } },
      condition: {
        kind: "predicates",
        predicates: [{ kind: "equals", pointer: "/escalate", value: true }],
      },
    },
    {
      kind: "step_agent",
      id: "escalate",
      agent: escalationAgent.id,
      prompt: "unused when previous output is selected",
    },
    {
      kind: "outcome",
      id: "done",
      rubric: "The escalation names an owner, impact, and next action.",
      maxIterations: 3,
    },
  ],
  edges: [
    { kind: "starts", from: "weekday", to: "triage" },
    { kind: "then", from: "triage", to: "triage_json" },
    { kind: "output", from: "triage", to: "triage_json" },
    { kind: "then", from: "triage_json", to: "route" },
    { kind: "output", from: "triage_json", to: "route" },
    { kind: "continue", from: "route", to: "escalate" },
    { kind: "otherwise", from: "route", to: null },
    { kind: "output", from: "triage", to: "escalate" },
    { kind: "reviews", from: "escalate", to: "done" },
  ],
}

const createKey = crypto.randomUUID()
const graph = await checkfu.automationGraphs.create(
  { created_by_principal_id: author.id, document },
  { idempotencyKey: createKey },
)
```

The Parse JSON node validates the retained Agent text against its declared schema. The Decision reads the parsed Boolean through an explicit binding; returning JSON text alone does not put `escalate` at the root of a previous-output envelope. Invalid JSON or a missing Boolean fails the parsing step before the dependent Decision or Agent runs.

Keep `createKey` for retries of this unchanged request. Creating the graph saves authored intent; plan and apply its saved revision to create the ordinary Automation and related resources. The Automation runtime never asks this document what to do at firing time. The authored Environment remains part of each Agent step's launch authority.

## Provider events

A `WebhookSource` binds an Automation to a governed Connection and optional provider resource. A `ProviderWebhookRegistration` separately owns provider-side registration or polling lifecycle, health, repair, and revocation. Neither resource replaces Automation firing authority.

The provider trigger catalog reports what a catalog entry can do: automatic registration, polling, manual URL setup, or unsupported. Catalog visibility is not production qualification. Production provider-reaching catalog, create, inspect, reconcile, test, delete, and polling paths require the qualified IntegrationRuntime and refuse closed with typed unavailability when it cannot be reached. Stored ProviderWebhookRegistration get and list reads remain observable for diagnosis. The deterministic `fake` provider is test and scenario infrastructure only and is never selected by production composition.

See [Trigger Automations from providers](/guides/trigger-agents-from-providers) for the complete trust split.

## Delivery screening

An agent-target Automation can set `input.delivery_screen` with a model routing profile, optional tier, instructions, and a failure disposition. Each firing freezes this policy into its Session admission. Outbound messages from every Agent in that Session require a screen verdict before becoming readable; blocked messages retain a content digest and a `message.screened` verdict instead of the reply. The default failure disposition is `fail_closed`.

In the graph editor, configure delivery screening on an Agent node and connect it to an output. All delivering Agents must declare the same policy. Applying the graph puts that policy into Automation input. Editing the graph later affects future firings.

A Check followed by a Connection delivery step gates that delivery step only. It does not restrict tools available inside earlier draft or revision Agents. To prevent pre-filter delivery, those Agents must have an admitted capability configuration that excludes delivery through the selected Connection and any other code, browser or network capability that could reach the intended destination. Prompt instructions alone do not enforce this boundary. Governed File and directory reads can remain available; an empty authored tool list does not mean the native Agent has no tools. A quality verdict never grants permission to send.

Delivery-screened Session inputs remain unavailable on native paths without the screening adapter. Saving the explicit filtered-delivery graph also does not establish that its workers have the required capability restrictions or that the connected customer journey is qualified.

## Agent attempts and recovery

A step Agent can save a bounded retry policy, but saving that policy does not enable retries on a target whose original-child settlement adapter is unavailable. The current native Session path keeps a failed or interrupted child pending when its failure reason or effect and charge settlement is unresolved. It does not launch a replacement child, even after the configured retry delay. Inspect the original firing trace to distinguish this pending recovery from a settled failure.

Transport recovery rejoins the original child. An authored retry is a separate attempt and requires a retained, retryable failure, reconciled original effects and charges, and current input, approval and spend authority. Cancellation or an unknown external effect does not authorize that attempt. Outcome feedback instead revises within the same Session and its own iteration bound.

## Explicit worker and evaluator rounds

For an editable workflow with separate worker and reviewer Sessions, start an empty graph with **Worker and evaluator with one revision**. Select the Agents for **Produce work**, **Review work**, **Revise work** and **Review revision**. Supply an incoming event with `id`, `goal`, `rubric`, `evidence` and `audience`; the original criteria and selected audience are bound into each Agent step. The revision also receives the original work and the reviewer's feedback.

The reviewer returns JSON containing `verdict` and `feedback`. A Parse JSON node validates that result before deterministic Decisions route it. An `accepted` verdict ends the review. Only `revise` admits the one authored revision; `uncertain` and `rejected` end without revising. The final review ends on either branch, so it cannot request another revision. Invalid reviewer JSON fails parsing instead of admitting work.

These are explicit graph steps, each with its own child Session, rather than the intrinsic Outcome loop described below. An otherwise branch that ends normally does not turn the AutomationRun into a failed execution. Inspect the recorded review output and Decision branch to distinguish quality rejection from successful execution of the graph. Save and reopen the graph, then use **Export JSON** to inspect or edit the same document through the API, CLI, SDK or MCP before planning and applying it.

This template has no delivery nodes. Select Agents without external delivery capabilities: an instruction not to send is not an enforced tool restriction, and a review result does not grant permission. Adding a delivery action requires current action authority and a control path that passes the relevant filter.

The template's authoring and compiler source is landed. Native end-to-end execution of the bounded-review journey remains unqualified; the template does not enable unavailable Outcome execution or target capabilities.

## Outcome versus post-run review

Outcome is active control inside a Session. A `needs_revision` verdict starts another Run in that same Session until the rubric is satisfied or the iteration limit is reached.

Typed Outcome criteria require `failure: "fail_closed"`. A definite false verdict requests bounded revision with the authored feedback. Unresolved rule judgments and evaluator errors remain unavailable; they do not satisfy the Outcome or consume a revision. The compiler refuses `fail_open` Outcome criteria rather than silently changing that policy. If uncertain evaluation is retried with byte-identical evidence, the Session can terminate that evaluation as `interrupted` with reason `outcome.evidence_unchanged`, rather than grading the same evidence again. This interruption is not a negative quality verdict and does not authorize a replacement child or an authored semantic retry.

In the graph editor, select an Outcome node to edit its **Report declaration**. The JSON declaration uses the same `extraction` and `outputs` fields as the public graph contract, including each output's type and optional acceptance values. Choose **Apply report declaration** to store valid edits or **Remove report** to remove only the report; the rubric, criteria, feedback and iteration bound are preserved. A report beside typed criteria must use `json` extraction. This authoring control does not enable the currently unavailable native Outcome execution path.

FlowReview is passive post-settlement audit. It records criteria results over retained evidence and never resumes Agent work. Use Outcome for definition of done and FlowReview for after-the-fact inspection.

## Recovery and authored retries

Recovering a firing resumes its original step, child and effect identities.
It reads retained node progress before doing more work; it does not reload the
latest graph, select a newer child Run, or replay already completed nodes.
Transport recovery and an explicitly authored semantic retry are different
operations. A lost acknowledgement does not establish failure. For a connected tool call, an indeterminate result retains `status: null`; a non-repeat-safe call is not dispatched again. An authored call retry requires the provider's repeat-safety declaration or a stable provider idempotency key, and remains bounded by the configured attempt and wait limits. A known authorization refusal is terminal for that original effect.

An Agent retry is a separate semantic attempt, rather than transport recovery. A coarse failed Run, an interrupted Outcome without a retained reason, or a stored retry deadline does not prove that the original effects settled. Current step execution withholds a replacement child when that evidence is missing. Cancellation does not authorize a retry. Inspect the original child and effect records before requesting another operation; successful and unknown effects still belong to the original attempt.

A pending Memory write can revisit the same port request without selecting new Session history. The Memory adapter's original receipt and idempotency record establish whether a mutation already happened; the number of port calls does not. The processed-history cursor advances only after the required effect completes.

## Evidence to inspect

Debug each authority at its own boundary:

* read the Automation and its readiness or pause reason to understand implemented activation checks;
* read the AutomationRun to understand one execution's lifecycle, Usage, and error;
* read each Session event log to understand Agent execution and approvals encountered inside that Session;
* read the Outcome to understand revision attempts and the terminal verdict;
* read ActionApproval for the frozen human decision;
* read ProviderWebhookRegistration for provider lifecycle and health;
* read CurationRun, MemoryProposal, and MemoryStore for memory curation.

A single product screen may join these records, but it should not flatten them into one invented runtime resource.

For a step program, read `GET /v1/automation-firings/{id}/trace`. Its `steps.state`, `steps.result`, and `steps.outcomes` distinguish pending execution from a settled failure; the typed receipt collections show which effects were recorded. A known Skill authoring refusal records a failed proposal step with `skill_proposal_owner_refused`. A temporary proposal receipt-read outage leaves the firing pending and does not advance the processed-history cursor. Recovery inspects the original proposal identity before authoring, and a retained receipt can complete that firing without another author call. Replaying a settled refusal does not turn it into a transient retry. These outcomes describe execution, not permission to publish or adopt a Skill; adoption still changes executable instructions under current policy.

### Plan and step diagnostics

A rejected graph plan returns `kind: "rejected"` with an `errors` array. Each entry has a machine-readable `code`, an explanatory `detail`, and an optional `edge`. For example, `edge_endpoint_kind` means an authored relationship connects incompatible node kinds; correct that relationship before applying the graph. A rejected plan is a validation result, rather than evidence that an Agent ran.

A steps firing exposes its durable progress as `steps`, with a step id and outcome kind for each recorded transition. A `failed` outcome may include a content-free `reason`. For example, `session_input_denied` means the selected Session input was refused by current authority; the dependent child is not launched. Treat this as a known refusal, rather than retrying it as an unknown external result. `rejected` records a negative Check verdict, while `denied` records a human refusal. `running`, `waiting` and `awaiting_approval` describe unfinished work.

The reason field is an extensible machine-code string. Preserve unfamiliar codes when displaying diagnostics; use the outcome kind and the documented code meanings rather than parsing internal exception text.

## Operate Automations from the CLI

Inspect an original firing with `checkfu automation trace afire_... --json`. This retrieves its public trace once, including evidence completeness; it does not poll, resume, or retry execution. Without `--json`, the command summarizes the firing state, error, and evidence completeness. For a step program it also shows the persisted program state, unresolved or settled result, attempt count, and recorded step outcomes, including Decision branches and waits. Recorded outcomes are execution history; displaying them does not resume a wait or retry an effect.

For step programs, `automation run` and `automation runs` show the recorded state and failed step reasons. A pending program can already contain a failed branch while other branches remain active. A successful CLI request exits with code `0`; this does not mean the workflow completed successfully. Scripts should use `--json` and inspect the firing's `program.state`, `program.result`, and `program.steps`. Pending execution keeps `program.result` null. The commands report the returned receipt without polling or retrying the workflow.

`checkfu automation` covers the same operate loop as the Console's Automations page. Each command takes an `auto_` ID or an exact Automation name, and prints JSON with `--json`:

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
checkfu automation list                      # name, ID, status, trigger in words, last run
checkfu automation run "Triage issues" --event @sample-issue.json
checkfu automation runs "Triage issues" --limit 10
checkfu automation pause "Triage issues"
checkfu automation resume "Triage issues"
```

`run` calls `POST /v1/automations/{id}/fire`. The optional `--event` (inline JSON or `@file`) becomes the firing's payload, which is what `{{event}}` renders and what step conditions read. The firing's source stays `manual`, and webhook admission filters are skipped. For an Agent target, `run` prints the new Session and the `checkfu session follow` command to watch it. `runs` lists recent AutomationFiring receipts, newest first. `pause` and `resume` send the version the command just read as `expected_version`, so a concurrent change is refused instead of overwritten. Create and edit Automations with `checkfu graph` (AutomationGraph plan and apply).

Each CLI `run` chooses a fresh idempotency key by default. To recover a lost manual-fire response, choose an explicit key on the first request and repeat the same Automation, authenticated actor, event and key:

```sh theme={"theme":{"light":"github-light","dark":"github-dark"}}
checkfu automation run auto_... --event @sample-issue.json --idempotency-key triage-request-1
```

The repeated request rejoins the original durable firing; it may remain pending or unavailable until original recovery is possible. Changing the key creates another manual firing. Do not use a new firing to retry an external effect whose outcome is unknown. Inspect the original receipt with `checkfu automation runs auto_...`; authored answer revisions and semantic retries follow the workflow's own bounds and current authority.

## Test one Agent block

You can check one Agent block against a sample without applying the graph or firing the Automation. `POST /v1/automation-graphs/{id}/node-tests` with `mode: "prompt_preview"` renders the first message that block's Agent would receive for a saved `revision`. The result gives the rendered prompt, the `{{event…}}` references the sample does not contain (each renders as empty), the Agent's current version (`null` when it has none), and the graph's Environment. The preview is free: no Session starts, no model is called, and nothing is recorded.

Where the prompt comes from depends on the block's edges:

* `event_template`: the prompt is rendered against `event`, the sample trigger payload, with the same renderer and 64 KiB limit a firing uses. This covers a webhook or subscription Agent target, a hands-to entry Agent on a webhook, and a step Agent with **Include incoming event** (a step prompt that references no field gets `{{event}}` appended, the same as at runtime).
* `literal`: the prompt is sent as written. This covers a scheduled Agent target and a step Agent that takes no event.
* `previous_output`: the whole message is `previous_output`, a sample of the preceding Agent's output. This covers a hands-to successor, a Decision successor, and a step with an `output` edge.

A block that nothing fires is refused with `node_receives_no_prompt`. A missing sample is refused with `event_sample_required` or `previous_output_sample_required`. A rendered prompt over 64 KiB is refused with `rendered_prompt_too_large`, and so is one over 4,096 characters when the Agent target has a Definition of done.

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
checkfu graph test-node agph_... --revision 3 --node triage --mode prompt --event @sample-issue.json
checkfu graph test-node agph_... --revision 3 --node writer --mode prompt --previous-output @triage.txt
```

In the Console, an Agent block's inspector has a **Test this block** section. It uses the sample event saved for the graph, shows the rendered prompt, and offers **Run once**. After you confirm, Run once creates one ordinary Session through `POST /v1/sessions`, using the previewed prompt as the first message, the exact Agent version, and the graph's Environment. The Session runs as you, not as the graph's service Principal, and its usage counts against your Budgets. It is not an AutomationFiring and does not appear in the Automation's runs. Its title and metadata mark it as a test of that graph node, except in a ZDR Workspace, which does not keep them. The Automation-only configuration around the Agent (admission filter, delivery screen, Definition of done, retries and the per-firing Session cap) is not applied. Other Budgets applicable to your ordinary test Session still apply. To run the same test from the SDK or CLI, create a Session from the preview's `agent_id`, `agent_version`, `environment_id`, and `rendered_prompt`.

## Next steps

<CardGroup cols={2}>
  <Card title="Outcomes" icon="bullseye" href="/concepts/outcomes">
    Define bounded revise-until-done behavior inside a Session.
  </Card>

  <Card title="Provider triggers" icon="bolt" href="/guides/trigger-agents-from-providers">
    Register, inspect, repair, or relay provider events without weakening trust boundaries.
  </Card>

  <Card title="Receive Checkfu webhooks" icon="webhook" href="/guides/receive-webhooks">
    Consume outbound lifecycle notifications from Checkfu.
  </Card>

  <Card title="Action approvals" icon="user-check" href="/concepts/action-approvals">
    Handle the human decision authority used by governed actions.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.