Workflows

History and audit trail

Understand the durable event history stored on every Workflows instance, what it records, and where its provenance boundary ends.

To inspect one run, 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 Workflows instance carries a stored history: HistoryEntry[]. The engine writes it in the same transaction as the state change, so a successful commit and its audit entries can never disagree. A rejected commit writes 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.history

For reporting across instances, query the workflow resource directly and filter by the engine tag. That 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'},
)

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. A field.setIfMissing no-op writes no row.

  • 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?". Actors, tokens, and what’s actually enforced. It answers what the run went through, where the actor answers who acted.

These stamps record where something came from. They are not an authentication boundary: the engine does not cryptographically bind an actor or a host-declared execution context to the write token. Sanity’s authenticated document history is still the record of who committed a Content Lake mutation. Use the workflow history to explain what the workflow did. Use Content Lake history when you need authenticated proof of who wrote what.

Resolve an actor to a current project user

An actor stamp records who acted at the time. It does not store profile data, which can change. To show a person’s current profile, resolve them 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 read the general history array as 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 to show what happened. Append or group pendingEffects as live work rather than converting it into audit events.

  • instance.history

    HistoryEntry[]

    Durable, ordered workflow events. Use actionFired for human decisions and effectCompleted for settled automation. Entries remain after the current work changes.

  • Current queued or claimed effect work. These rows disappear when they settle; completion is recorded durably in history and in the detailed effectHistory result ledger.

Retention and deletion

The engine never prunes an instance’s history, not even when the workflow finishes. Deleting a definition version does not delete its instances either: a cascading definition deletion aborts active instances and leaves the instance documents behind as the audit record. If you add your own retention or deletion policy, account for losing that record, and never mutate a live instance behind the engine’s back.

The audit operation is separate

The authoring audit operation is shorthand 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

Visiting agent?

Was this page helpful?