History and audit trail
Understand the durable event history stored on every Editorial Workflows instance, what it records, and where its provenance boundary ends.
This page is for developers and operators who need to inspect what happened to one workflow run. If you have an instance ID, fetch the instance through the engine and read its typed history array. Use the CLI when inspecting a run interactively, and query the Content Lake directly only for reporting across many instances.
Every Editorial Workflows instance carries a durable history: HistoryEntry[]. The engine writes it in the same transaction as the state change, so a successful commit and its audit entries cannot diverge. Rejected commits write neither. Do not patch instance documents directly.
Get the history data
The normal programmatic path is engine.getInstance({instanceId}). It returns the complete WorkflowInstance; read instance.history and narrow each entry by _type.
import type {HistoryEntry} from '@sanity/workflow-engine'
const instance = await engine.getInstance({instanceId})
const history: HistoryEntry[] = instance.historyFor reporting across instances, query the workflow resource directly and filter by the engine tag. This is the bulk-read path; getInstance is the safer default for one known instance.
const rows = await workflowClient.fetch(
`*[_type == "sanity.workflow.instance" && tag == $tag]{
_id,
definition,
currentStage,
history
}`,
{tag: 'production'},
)getInstance({instanceId})(): Promise
Primary programmatic API. Read the returned instance.history array.
client.fetch(query, params)(): Promise<WorkflowInstance[]>
Use GROQ against the workflow resource for reporting across instances. Always scope the query by tag.
sanity-workflows show {instanceId} --include history(): CLI output
Print the instance and all history entries currently stored.
sanity-workflows tail {instanceId}(): stream
Stream history entries appended after the command starts.
What history records
Every entry has _key, _type, and at; every engine-stamped entry may also have executionContext. Event-specific fields are listed in the reference table below.
HistoryEntry is exported from @sanity/workflow-engine. It is a discriminated union: switch on entry._type and TypeScript narrows the available fields.
stageEntered
stage, fromStage?, transition?, via?, reason?, actor?
A stage becomes current.
stageExited
stage, toStage, transition?, via?, reason?, actor?
The instance leaves a stage.
transitionFired
fromStage, toStage, transition?, via?, reason?, actor?
A transition or administrative move changes stages.
activityStatusChanged
stage, activity, from, to, actor?
An activity changes status.
actionFired
stage, activity, action, actor?, driverKind?, triggered?
A caller or cascade fires an action.
opApplied
stage, activity?, action?, edit?, effect?, opType, target?, resolved?, actor?
An operation changes instance state.
effectQueued
effectKey, effect, origin
An action queues an effect.
effectCompleted
effectKey, effect, status, outputs?, detail?, actor?
An effect settles.
effectClaimReleased
effectKey, effect, claim, via, actor?
An expired effect claim is taken over or swept.
spawned
activity, instanceRef, rowKey?, actor?
A child workflow is created.
subworkflowAdopted
stage, activity, instanceRef, rowKey
A live child is rebound on stage re-entry.
subworkflowResolved
activity, instanceRef, status
A child reaches done or aborted.
subworkflowOrphaned
instanceRef, detail
A terminal child cannot match a parent registry row.
fieldQueryDiscarded
scope, field, detail
A query-sourced initial value fails its declared shape.
aborted
stage, reason?, actor?
The instance is hard-stopped.
For interactive inspection, run sanity-workflows show <instanceId> --include history. To stream entries as they are appended, run sanity-workflows tail <instanceId>.
Actor and execution context
Where an actor exists, the engine stamps the identity resolved from the committing client. An actionFired entry also distinguishes caller-fired work from an action fired automatically during a cascade. executionContext records the runtime and the host-declared {kind, id}, answering “through what did this run?” alongside the actor’s “who?”
These stamps are operational provenance, not an authentication boundary. The engine does not cryptographically bind an actor or host-declared execution context to the write token. Sanity’s authenticated document history remains the ground truth for who committed a Content Lake mutation. Use the workflow history to explain workflow semantics; use Content Lake history when authenticated mutation provenance matters.
Resolve an actor to a current project user
Actor stamps preserve durable provenance rather than mutable profile data. Resolve a person when presenting current profile information with engine.resolveActor({actor, projectId}) or resolveClientActor(client, {actor, projectId}). The result distinguishes resolved, missing, inaccessible, and not-person; agent and system actors are never sent to a project-user API. Studio and App SDK integrations expose native project-user directories for the same contract.
History and effect history are different
The instance’s history is the workflow event trail. effectHistory is the result ledger for settled effect runs: status, duration, outputs, and error detail. The two correlate through effectKey. Conditions read declared effect outputs through $effects; they do not use the general history array as mutable workflow state. See Effects and runtimes.
Build an application activity feed
Keep durable events and current work as separate lanes in an application feed. Render history in its stored order for what happened; append or group pendingEffects as live work rather than converting it into audit events.
instance.history
HistoryEntry[]
Durable, ordered workflow events. Use
actionFiredfor human decisions andeffectCompletedfor settled automation. Entries remain after the current work changes.instance.pendingEffects
PendingEffect[]
Current queued or claimed effect work. These rows disappear when they settle; completion is recorded durably in
historyand in the detailedeffectHistoryresult ledger.
Retention and deletion
The engine does not prune an instance’s history when the workflow finishes. Deleting a definition version also does not delete its instances: a cascading definition deletion aborts active instances and leaves those instance documents as the audit record. Any separate retention or deletion policy must account for the loss of that record and must not mutate live instances behind the engine.
The audit operation is separate
The authoring audit operation is convenience syntax for appending a timestamped, attributed row to a workflow field such as notes. It is business data defined by your workflow. It does not replace the engine-owned history trail. See Operations.
Next steps
- Engine: transaction semantics that keep state and history together.
- Definitions and instances: the durable instance document.
- Effects and runtimes: effect delivery and the separate result ledger.
- Operations: the state changes recorded in history.
Visiting agent?
Editorial Workflows includes an MCP server for inspecting, operating, authoring, validating, and deploying workflows. Ask your human to set up the MCP server.