Evaluation insights
Explain condition outcomes and field proposals from a Workflows evaluation.
Early access
Workflows is in early access, built in public. Read How early access works before you rely on it.
Workflows evaluates a running workflow instance to answer what the current actor can see and do now. The result is a WorkflowEvaluation: a read-only projection of the current stage, activities, transitions, editable fields, and action verdicts.
A direct engine integration receives this projection from engine.evaluate({instanceId}). A custom App SDK interface receives the same projection as session.evaluation from the reactive session. The session recomputes it when the workflow instance, referenced content, guards, or field previews change.
Authored semantics appear unchanged on WorkflowEvaluation.semantics, currentStage.semantics, and each activity and action evaluation. They are advisory metadata, separate from verdicts and condition insights.
Insights are explanations nested inside an evaluation. A condition insight explains why one condition is satisfied, unsatisfied, or unevaluable. WorkflowEvaluation.fieldInsights groups those explanations by the fields that the conditions read.
Insights answer two questions in a custom interface or diagnostic. Why is this action, requirement, transition, or field in the state it is in? And which condition outcomes would change if a field held a proposed value? You do not need insights to write conditions.
Insights describe the engine’s current advisory evaluation. They do not enforce access or replace Content Lake permissions.
Explain a waiting transition
Suppose a custom workflow screen must explain why its publish transition is waiting and what field changes would affect that decision. Pass the current WorkflowEvaluation to a helper that reads both the transition insight and the field proposals:
import type {ConditionOutcome, WorkflowEvaluation} from '@sanity/workflow-engine'
interface TransitionChange {
field: string
value: unknown
before: ConditionOutcome
after: ConditionOutcome
}
interface TransitionExplanation {
outcome: ConditionOutcome
blockedBy: string[]
changes: TransitionChange[]
}
export function explainTransition(
evaluation: WorkflowEvaluation,
transitionName: string,
): TransitionExplanation {
const evaluated = evaluation.currentStage.transitions.find(
({transition}) => transition.name === transitionName,
)
if (!evaluated) throw new Error(`Unknown transition: ${transitionName}`)
const changes = evaluation.fieldInsights.flatMap((field) =>
field.proposals.flatMap((proposal) =>
proposal.consequences
.filter(
({site}) => site.kind === 'transition' && site.transition === transitionName,
)
.map(({before, after}) => ({
field: field.field,
value: proposal.assign.value,
before,
after,
})),
),
)
return {
outcome: evaluated.insight.outcome,
blockedBy: evaluated.insight.blockedBy.map(({atom}) => atom.groq),
changes,
}
}Call explainTransition(session.evaluation, 'to-publishing'). The returned blockedBy list explains why the transition is waiting. Each changes entry names a proposed field value and the transition outcome before and after that value. These are verified condition consequences, not a prediction of document writes or external effects.
The outcome has three possible values:
- satisfied: the condition is true.
- unsatisfied: the condition is false.
- unevaluable: GROQ returned null because the condition could not be decided from the current values.
Where condition insights appear
Evaluated nodes expose the insight beside the decision it explains:
StartRequirementEvaluation.insight
ConditionInsight | undefined
Explains one declared start requirement. Present on each entry of
StartEvaluation.requirements; absent for non-GROQ requirement kinds.ActionEvaluation.insight
ConditionInsight | undefined
Explains an action
filterwhen declared.ActionEvaluation.whenInsight
ConditionInsight | undefined
Explains the
whentrigger for a cascade-fired action.ActivityEvaluation.filterInsight
ConditionInsight | undefined
Explains an activity
filterwhen declared.ActivityEvaluation.requirementInsights
Record<string, ConditionInsight> | undefined
Explains each authored activity requirement. Required-reference readability checks appear in unmetRequirements with names such as field:source and have no condition insight.
TransitionEvaluation.insight
ConditionInsight
Explains the transition’s
whencondition. Every transition evaluation includes it.EditableFieldEvaluation.insight
ConditionInsight | undefined
Explains a field’s effective editable predicate when it is conditional.
Explain missing referenced content
A workflow can be unable to advance because a selected document cannot be read. This includes a required content reference, or an optional reference needed by an unmet runtime requirement or transition condition. See Fields for requiredness and repair.
WorkflowEvaluation.missingDocuments identifies absent targets in active field scopes. Each MissingDocument carries the field target, document reference, and declared title when available. blockingMissingDocuments selects those that prevent current progress. Use it when explaining a blocked workflow; absence alone does not make every action unavailable.
When present, each entry's availability field carries the result of a completed check:
deleted
DocumentAvailability
The availability check confirms absence across published, draft, and release representations.
unreadable
DocumentAvailability
Access is denied, or the availability check failed or is unsupported. Do not describe this result as deletion.
outside-perspective
DocumentAvailability
A representation exists, but it is absent from the content snapshot evaluated for this workflow.
An evaluation built from a supplied snapshot can omit availability. Treat that as missing evidence. evaluateFromSnapshot accepts a documentAvailability reader when your integration can provide the completed check.
Use the action's complete verdict when enabling a control. Required-reference checks appear in unmetRequirements under field:<name>, without entries in requirementInsights. A satisfied transition condition also does not override a missing required reference.
Read field insights
WorkflowEvaluation.fieldInsights provides the field-centric view of the same current-stage conditions. Use it to show where a field matters and which concrete assignments the engine verified would change an outcome.
A field can take part in several conditions without producing a useful proposal. A proposal appears only when a blocking atom points to one concrete value, and re-evaluating with that value confirms it changes at least one of the sites involved.
field
string
The rendered
$fieldsentry name.reads
ConditionRead[]
Every distinct read of that field across current-stage condition sites.
involvedIn
InsightSite[]
The actions, action triggers, activity filters, requirements, transitions, or editable fields whose conditions read it.
proposals
FieldProposal[]
Verified assignments and the condition sites whose outcomes they change. Empty when no single concrete assignment can be proposed.
Condition insight reference
analysis
ConditionAnalysis
The parsed condition, its atom tree, and the scope reads found in it.
outcome
'satisfied' | 'unsatisfied' | 'unevaluable'
The engine’s verdict for the complete condition.
atoms
AtomInsight[]
Every atomic predicate with its outcome, pivotal flag, and any derivable requirement.
frontier
ConditionClause | undefined
The remaining unmet clause tree. Absent when the condition is satisfied.
blockedBy
AtomInsight[]
A flat, source-ordered list of the unmet frontier. Empty when the condition is satisfied.
AtomInsight
atom
ConditionAtom
The atomic predicate. Use
atom.groqfor display.outcome
ConditionOutcome
The atom’s current verdict.
pivotal
boolean
True when satisfying this atom alone, while other atoms keep their current outcomes, would satisfy the condition.
requirement
AtomRequirement | undefined
A machine-readable requirement when the atom can be solved from one scope read and a literal value.
Exports and utilities
The insight types and utilities shown on this page are exported from @sanity/workflow-engine. Import only the shapes and functions your integration uses:
import type {
AtomInsight,
AtomRequirement,
ConditionInsight,
ConditionOutcome,
ConditionRead,
FieldInsight,
FieldProposal,
InsightSite,
ScopeAssignment,
SiteConsequence,
} from '@sanity/workflow-engine'
import {
analyzeCondition,
explainCondition,
whatIfCondition,
} from '@sanity/workflow-engine'Engine and reactive consumers usually read the insights already present on WorkflowEvaluation. Use these utilities only when you need to analyze or evaluate a condition on its own. The package’s TypeScript declarations document every parameter.
analyzeCondition(): ConditionAnalysis
Parses a condition and returns its clause tree, atoms, scope reads, and whether it reads the dataset.
explainCondition(): Promise<ConditionInsight>
Evaluates one condition against a supplied dataset and parameter scope, then returns its outcome and atom-level explanation.
whatIfCondition(): Promise<WhatIfOutcome>
Re-evaluates a condition with one scope value replaced and reports the outcome before and after the assignment.
Next steps
Use The reactive session to keep the evaluation current in a custom interface.
See Build a workflow interface with the App SDK for a practical example that renders blocked transition atoms.
See Engine for the complete WorkflowEvaluation projection.
Visiting agent?
Workflows includes an MCP server for inspecting, operating, authoring, validating, and deploying workflows. Ask your human to set up the MCP server.