ObjectStackObjectStack

Execution — Automation Protocol reference

Defines schemas for execution logging, error tracking, checkpointing, concurrency control, and scheduled execution persistence.

Automation Execution Protocol

Defines schemas for execution logging, error tracking, checkpointing, concurrency control, and scheduled execution persistence.

Industry alignment: Salesforce Flow Interviews, Temporal Workflow History, AWS Step Functions execution logs.

Source: packages/spec/src/automation/execution.zod.ts

TypeScript Usage

import { CheckpointSchema, ConcurrencyPolicySchema, ExecutionErrorSchema, ExecutionErrorSeverity, ExecutionLogSchema, ExecutionStatus, ExecutionStepLogSchema, ExecutionStepMetricsSchema, ExecutionStepSkipReasonSchema, FlowRunGateSummarySchema, FlowRunNodeSummarySchema, FlowRunSummarySchema } from '@objectstack/spec/automation';
import type { Checkpoint, ConcurrencyPolicy, ExecutionError, ExecutionErrorSeverity, ExecutionLog, ExecutionStatus, ExecutionStepLog, ExecutionStepMetrics, ExecutionStepSkipReason, FlowRunGateSummary, FlowRunNodeSummary, FlowRunSummary } from '@objectstack/spec/automation';

// Validate data
const result = CheckpointSchema.parse(data);

Checkpoint

Properties

PropertyTypeRequiredDescription
idstring✅Checkpoint ID
executionIdstring✅Parent execution ID
flowNamestring✅Flow machine name
currentNodeIdstring✅Node ID where execution is paused
variablesRecord<string, any>✅Flow variable state at checkpoint
completedNodeIdsstring[]✅List of node IDs already executed
createdAtstring✅Checkpoint creation timestamp
expiresAtstringoptionalCheckpoint expiration (auto-cleanup)
reasonEnum<'wait' | 'screen_input' | 'approval' | 'error' | 'manual_pause' | 'parallel_join' | 'boundary_event'>✅Why the execution was checkpointed

ConcurrencyPolicy

Properties

PropertyTypeRequiredDescription
maxConcurrentintegeroptional (default: 1)Maximum number of concurrent executions allowed
onConflictEnum<'queue' | 'reject' | 'cancel_existing'>optional (default: "queue")queue = enqueue for later, reject = fail immediately, cancel_existing = stop running instance
lockScopeEnum<'global' | 'per_record' | 'per_user'>optional (default: "global")Scope of the concurrency lock
queueTimeoutMsintegeroptionalMaximum time to wait in queue before timing out (ms)

ExecutionError

Properties

PropertyTypeRequiredDescription
idstring✅Error record ID
executionIdstring✅Parent execution ID
nodeIdstringoptionalNode where the error occurred
severityEnum<'warning' | 'error' | 'critical'>✅Error severity level
codestring✅Machine-readable error code
messagestring✅Human-readable error message
stackstringoptionalStack trace for debugging
contextRecord<string, any>optionalAdditional diagnostic context (input data, config snapshot)
timestampstring✅When the error occurred
retryablebooleanoptional (default: false)Whether this error can be retried
resolvedAtstringoptionalWhen the error was resolved (e.g., after successful retry)

ExecutionErrorSeverity

Allowed Values

  • warning
  • error
  • critical

ExecutionLog

Properties

PropertyTypeRequiredDescription
idstring✅Execution instance ID
flowNamestring✅Machine name of the executed flow
flowVersionintegeroptionalVersion of the flow that was executed
statusEnum<'pending' | 'running' | 'paused' | 'completed' | 'failed' | 'cancelled' | 'timed_out' | 'retrying' | 'refused'>✅Current execution status
refusalMessagestringoptionalRendered end node message when status is refused — the per-record text the flow refused with. Absent on every other status.
trigger{ type: string; recordId?: string; object?: string; userId?: string; … }✅What triggered this execution
steps{ nodeId: string; nodeType: string; nodeLabel?: string; status: Enum<'success' | 'failure' | 'skipped'>; … }[]✅Ordered list of executed steps
summary{ selected: integer; acted: integer; skipped: integer; unmeasured?: integer; … }optionalPer-run rollup: records selected / acted on, gate skips, per-node status
variablesRecord<string, any>optionalFinal state of flow variables
startedAtstring✅Execution start timestamp
completedAtstringoptionalExecution completion timestamp
durationMsintegeroptionalTotal execution duration in milliseconds
runAsEnum<'system' | 'user'>optionalExecution context identity
tenantIdstringoptionalTenant ID for multi-tenant isolation

Nested Shape: ExecutionLog.trigger

PropertyTypeRequiredDescription
typestring✅Trigger type (e.g., "record_change", "schedule", "api", "manual")
recordIdstringoptionalTriggering record ID
objectstringoptionalTriggering object name
userIdstringoptionalUser who triggered the execution
metadataRecord<string, any>optionalAdditional trigger context

Nested Shape: ExecutionLog.steps[number]

PropertyTypeRequiredDescription
nodeIdstring✅Node ID that was executed
nodeTypestring✅Node action type (e.g., "decision", "http")
nodeLabelstringoptionalHuman-readable node label
statusEnum<'success' | 'failure' | 'skipped'>✅Step execution result
startedAtstring✅When the step started
completedAtstringoptionalWhen the step completed
durationMsintegeroptionalStep execution duration in milliseconds
inputRecord<string, any>optionalInput data passed to the node
outputRecord<string, any>optionalOutput data produced by the node
error{ code: string; message: string; stack?: string }optionalError details if step failed
retryAttemptintegeroptionalRetry attempt number (0 = first try)
parentNodeIdstringoptionalEnclosing structured-region container node ID (loop/parallel/try_catch)
iterationintegeroptionalZero-based iteration of the enclosing loop, carried through any nesting — a step inside a parallel branch that is itself inside a loop body carries the loop's iteration here and its branch index on branch. A step inside a try / catch region that is itself inside a loop body carries the enclosing loop's iteration — a try/catch region has no index of its own — while regionKind stays try / catch.
branchintegeroptionalZero-based index of the enclosing parallel branch. Present only on a step inside a parallel branch; absent everywhere else. When the parallel node is itself inside a loop body, the loop iteration is reported through iteration, never here.
regionKindstringoptionalRegion kind the step ran in: loop-body | parallel-branch | try | catch. Stays try / catch for a step inside a try/catch region nested in a loop body; the loop is reported through iteration. For parallel-branch the branch index is reported through branch, and the enclosing loop iteration — when the parallel node sits inside a loop body — through iteration.
metrics{ selected?: integer; acted?: integer; unmeasuredEffect?: boolean; failures?: integer }optionalRecords this step selected / acted on — and, for a step that delegated to a child run (subflow, a map item), the failures that child contained — as reported by the node executor
skippedBy{ nodeId: string; edgeId?: string; label?: string }optionalThe gate that closed, when status is skipped

Nested Shape: ExecutionLog.summary

PropertyTypeRequiredDescription
selectedinteger✅Total records read by the run
actedinteger✅Total records written / effects dispatched by the run
skippedinteger✅Total node executions a closed gate prevented
unmeasuredintegeroptionalTotal executions that may have caused an effect the platform cannot count. Absent = not tracked (an older run), which is not the same as zero.
failedintegeroptionalTotal node executions that failed — a fold of nodes[].failures, INCLUDING what a delegating node (subflow / map) rolled up from a child run that COMPLETED while containing failures: this total answers "what did this run cause", subflows included, so a parent whose child lost rows does not read failed: 0. A child that FAILED — whether or not it also contained failures before it failed — counts once, as the delegating step's own failure, and its own failed stays on its row (unlike acted, which carries a failed child's writes). On a run that completed every one of them was contained (caught by a try_catch or routed down a fault edge) and the run went on. Absent = not tracked (an older run), which is not the same as zero.
nodes{ nodeId: string; nodeType: string; nodeLabel?: string; status: Enum<'success' | 'failure' | 'skipped'>; … }[]✅Per-node breakdown, in first-execution order
gates{ nodeId: string; targetNodeId: string; edgeId?: string; label?: string; … }[]✅Gates that closed during the run, most-skipped first
detailOmittedbooleanoptionalSet when persistence dropped nodes/gates to keep the stored row bounded — the totals are still exact. Declared so empty arrays are never mistaken for "nothing ran".

ExecutionStatus

Allowed Values

  • pending
  • running
  • paused
  • completed
  • failed
  • cancelled
  • timed_out
  • retrying
  • refused

ExecutionStepLog

Properties

PropertyTypeRequiredDescription
nodeIdstring✅Node ID that was executed
nodeTypestring✅Node action type (e.g., "decision", "http")
nodeLabelstringoptionalHuman-readable node label
statusEnum<'success' | 'failure' | 'skipped'>✅Step execution result
startedAtstring✅When the step started
completedAtstringoptionalWhen the step completed
durationMsintegeroptionalStep execution duration in milliseconds
inputRecord<string, any>optionalInput data passed to the node
outputRecord<string, any>optionalOutput data produced by the node
error{ code: string; message: string; stack?: string }optionalError details if step failed
retryAttemptintegeroptionalRetry attempt number (0 = first try)
parentNodeIdstringoptionalEnclosing structured-region container node ID (loop/parallel/try_catch)
iterationintegeroptionalZero-based iteration of the enclosing loop, carried through any nesting — a step inside a parallel branch that is itself inside a loop body carries the loop's iteration here and its branch index on branch. A step inside a try / catch region that is itself inside a loop body carries the enclosing loop's iteration — a try/catch region has no index of its own — while regionKind stays try / catch.
branchintegeroptionalZero-based index of the enclosing parallel branch. Present only on a step inside a parallel branch; absent everywhere else. When the parallel node is itself inside a loop body, the loop iteration is reported through iteration, never here.
regionKindstringoptionalRegion kind the step ran in: loop-body | parallel-branch | try | catch. Stays try / catch for a step inside a try/catch region nested in a loop body; the loop is reported through iteration. For parallel-branch the branch index is reported through branch, and the enclosing loop iteration — when the parallel node sits inside a loop body — through iteration.
metrics{ selected?: integer; acted?: integer; unmeasuredEffect?: boolean; failures?: integer }optionalRecords this step selected / acted on — and, for a step that delegated to a child run (subflow, a map item), the failures that child contained — as reported by the node executor
skippedBy{ nodeId: string; edgeId?: string; label?: string }optionalThe gate that closed, when status is skipped

Nested Shape: ExecutionStepLog.error

PropertyTypeRequiredDescription
codestring✅Error code
messagestring✅Error message
stackstringoptionalStack trace

Nested Shape: ExecutionStepLog.metrics

PropertyTypeRequiredDescription
selectedintegeroptionalRecords this node READ or matched (a get_record query, a lookup)
actedintegeroptionalRecords this node WROTE (created / updated / deleted) or effects it dispatched (notifications delivered)
unmeasuredEffectbooleanoptionalThis execution may have caused an effect the platform cannot count (an external write through a connector). NOT interchangeable with acted: 0 — it says the count is unknown, not that it is zero.
failuresintegeroptionalNode executions that failed inside a child run this execution delegated to and went on from — a subflow child or a map item whose run COMPLETED while containing failures: its summary.failed, rolled up so the parent answers "what did this run cause". Folds into this node's failures and so into the run-level failed. NOT this execution's own outcome: a step that failed is status: 'failure' and counts once through nodes[].failures, and a child that FAILED — whether or not it also contained failures before it failed — is exactly that step failure: its own failed, contained and fatal alike, stays on the child's run row and nothing rides up here (unlike acted, which does carry a failed child's writes). Absent = delegated nothing, or the child tracked no count, or the producer did not track it; never zero.

Nested Shape: ExecutionStepLog.skippedBy

PropertyTypeRequiredDescription
nodeIdstring✅Node whose out-edge did not open (the gate)
edgeIdstringoptionalEdge whose condition evaluated false
labelstringoptionalEdge label, when the flow names its branches

ExecutionStepMetrics

Properties

PropertyTypeRequiredDescription
selectedintegeroptionalRecords this node READ or matched (a get_record query, a lookup)
actedintegeroptionalRecords this node WROTE (created / updated / deleted) or effects it dispatched (notifications delivered)
unmeasuredEffectbooleanoptionalThis execution may have caused an effect the platform cannot count (an external write through a connector). NOT interchangeable with acted: 0 — it says the count is unknown, not that it is zero.
failuresintegeroptionalNode executions that failed inside a child run this execution delegated to and went on from — a subflow child or a map item whose run COMPLETED while containing failures: its summary.failed, rolled up so the parent answers "what did this run cause". Folds into this node's failures and so into the run-level failed. NOT this execution's own outcome: a step that failed is status: 'failure' and counts once through nodes[].failures, and a child that FAILED — whether or not it also contained failures before it failed — is exactly that step failure: its own failed, contained and fatal alike, stays on the child's run row and nothing rides up here (unlike acted, which does carry a failed child's writes). Absent = delegated nothing, or the child tracked no count, or the producer did not track it; never zero.

ExecutionStepSkipReason

Properties

PropertyTypeRequiredDescription
nodeIdstring✅Node whose out-edge did not open (the gate)
edgeIdstringoptionalEdge whose condition evaluated false
labelstringoptionalEdge label, when the flow names its branches

FlowRunGateSummary

Properties

PropertyTypeRequiredDescription
nodeIdstring✅Node whose out-edge did not open (the gate)
targetNodeIdstring✅Node the closed edge would have run
edgeIdstringoptionalEdge whose condition evaluated false
labelstringoptionalEdge label, when the flow names its branches
skippedinteger✅Times this gate evaluated false (once per loop iteration)

FlowRunNodeSummary

Properties

PropertyTypeRequiredDescription
nodeIdstring✅Node ID
nodeTypestring✅Node action type (e.g., "get_record", "decision")
nodeLabelstringoptionalHuman-readable node label
statusEnum<'success' | 'failure' | 'skipped'>✅Terminal status of the node across the run — failure if any execution failed, else success if any succeeded, else skipped. Judged on this node's OWN executions: a delegating node (subflow / map) whose child completed while containing failures reads success here with failures > 0
runsinteger✅Times the node executed (loop iterations and parallel branches each count)
failuresinteger✅Executions that failed — a failure a try_catch caught or a fault edge routed counts here too — plus what a delegating execution rolled up from a child run that COMPLETED (metrics.failures: the contained failures of a subflow child or a map item). On a delegating node this may therefore exceed runs and is no longer only this node's own failed executions; a child that FAILED adds only the step's own failure (unlike acted, which carries a failed child's writes too); the run-level failed is the sum of this across nodes
skippedinteger✅Times a closed gate kept this node from running at all
selectedintegeroptionalRecords read across every execution — omitted for a node that reads none
actedintegeroptionalRecords written / effects dispatched across every execution — omitted for a node that writes none
unmeasuredintegeroptionalExecutions that may have caused an effect the platform cannot count (see ExecutionStepMetrics.unmeasuredEffect)

FlowRunSummary

Properties

PropertyTypeRequiredDescription
selectedinteger✅Total records read by the run
actedinteger✅Total records written / effects dispatched by the run
skippedinteger✅Total node executions a closed gate prevented
unmeasuredintegeroptionalTotal executions that may have caused an effect the platform cannot count. Absent = not tracked (an older run), which is not the same as zero.
failedintegeroptionalTotal node executions that failed — a fold of nodes[].failures, INCLUDING what a delegating node (subflow / map) rolled up from a child run that COMPLETED while containing failures: this total answers "what did this run cause", subflows included, so a parent whose child lost rows does not read failed: 0. A child that FAILED — whether or not it also contained failures before it failed — counts once, as the delegating step's own failure, and its own failed stays on its row (unlike acted, which carries a failed child's writes). On a run that completed every one of them was contained (caught by a try_catch or routed down a fault edge) and the run went on. Absent = not tracked (an older run), which is not the same as zero.
nodes{ nodeId: string; nodeType: string; nodeLabel?: string; status: Enum<'success' | 'failure' | 'skipped'>; … }[]✅Per-node breakdown, in first-execution order
gates{ nodeId: string; targetNodeId: string; edgeId?: string; label?: string; … }[]✅Gates that closed during the run, most-skipped first
detailOmittedbooleanoptionalSet when persistence dropped nodes/gates to keep the stored row bounded — the totals are still exact. Declared so empty arrays are never mistaken for "nothing ran".

Nested Shape: FlowRunSummary.nodes[number]

PropertyTypeRequiredDescription
nodeIdstring✅Node ID
nodeTypestring✅Node action type (e.g., "get_record", "decision")
nodeLabelstringoptionalHuman-readable node label
statusEnum<'success' | 'failure' | 'skipped'>✅Terminal status of the node across the run — failure if any execution failed, else success if any succeeded, else skipped. Judged on this node's OWN executions: a delegating node (subflow / map) whose child completed while containing failures reads success here with failures > 0
runsinteger✅Times the node executed (loop iterations and parallel branches each count)
failuresinteger✅Executions that failed — a failure a try_catch caught or a fault edge routed counts here too — plus what a delegating execution rolled up from a child run that COMPLETED (metrics.failures: the contained failures of a subflow child or a map item). On a delegating node this may therefore exceed runs and is no longer only this node's own failed executions; a child that FAILED adds only the step's own failure (unlike acted, which carries a failed child's writes too); the run-level failed is the sum of this across nodes
skippedinteger✅Times a closed gate kept this node from running at all
selectedintegeroptionalRecords read across every execution — omitted for a node that reads none
actedintegeroptionalRecords written / effects dispatched across every execution — omitted for a node that writes none
unmeasuredintegeroptionalExecutions that may have caused an effect the platform cannot count (see ExecutionStepMetrics.unmeasuredEffect)

Nested Shape: FlowRunSummary.gates[number]

PropertyTypeRequiredDescription
nodeIdstring✅Node whose out-edge did not open (the gate)
targetNodeIdstring✅Node the closed edge would have run
edgeIdstringoptionalEdge whose condition evaluated false
labelstringoptionalEdge label, when the flow names its branches
skippedinteger✅Times this gate evaluated false (once per loop iteration)

On this page