Actspan Docs Quickstart
DocsConcepts

Connect steps, route inputs, and pause for a human response.

On this page

Workflows

Purpose: stitch agents together, define node order/deps, and choose final output.

Start Here

For local commands, configure the workspace environment with ACTSPAN_PORTAL_DISABLED=1 in its .actspan.env. Run from that workspace directory and keep the selected provider credentials configured.

Use a workflow when multiple actions, prompts, agents, or nested workflows must execute in a declared order and pass outputs between steps.

cd /path/to/actspan-examples/openai/data_enrichment
actspan graph workspace.yaml data_enrichment
actspan run workspace.yaml data_enrichment \
  --input-text "Analyze NVIDIA (NVDA)"

Expected for this linear downloaded example: research precedes summarization and the workflow returns its configured final output. Inspect the actual step outcomes; other workflows can branch, skip nodes, pause or resume.

Review a portal-reported approval

When a native CLI workflow reports an approval pause to a matching control API, Actspan publishes a bounded review of the saved checkpoint. The portal shows the recorded research, available source links, and reviewer decision together. Metadata-only synchronization omits the research content. Sources shown are recorded links; displaying a link does not verify the underlying facts.

An authenticated human with the configured workspace approver role or workflow:approve permission can approve or reject. The server records that identity. A local --actor label or a developer service account cannot supply reviewer authority. Managed portal approval actions use the same reviewer check.

For an already reported pause with a supported configuration binding, publish its saved review without rerunning research:

actspan continue --thread-id <thread-id> --publish-review --format json

After the reviewer decides in the portal, continue from the original workspace:

actspan continue --thread-id <thread-id> --portal-decision --format json

The portal records the decision; this command executes the saved continuation and links its runtime record to the original paused run. Changed saved research, source, policy, variables, environment, or conflicting decisions block continuation on this path. POSIX thread locking prevents concurrent local continuations; completed claims cannot be reused for another execution.

Use an explicit condition after the approval node to choose what rejection does. Approval nodes record either decision; they do not implicitly cancel all downstream steps. The data-enrichment demonstration routes rejection to a terminal handoff and skips summarization.

Local workflows with portal reporting disabled retain local approval labels. SDK checkpoint methods reject continuation of CLI-owned portal threads: use the original CLI so the shared execution history is retained. Thread state stays at its originating runtime; this is not remote execution of the CLI workspace or independent attestation of every external call.

Configuration changes while approval is pending

Starting with core 0.1.2, native CLI and SDK approvals bind to the effective workspace configuration, including agent permissions, prompts, provider/model selection, workflow rules, declared local tool source and provider lock. A change returns APPROVAL_CONFIGURATION_CHANGED before approval consumption or tool execution. Start a new run and request a fresh review for the changed behavior.

Pending approvals created by earlier releases lack this binding and return APPROVAL_CONFIGURATION_UNBOUND. Start a new run instead of editing saved state to reuse an old decision. Credential-value rotation and YAML comments/map order do not invalidate a binding; behavior-bearing values and declared dependency bytes do. Saved workflow inputs remain protected separately. Published 0.2.0 has a package-resolution exception; its imported helpers are not bound. Explicit dependency manifests are a source-only addition pending a separately qualified release.

This check protects the mediated continuation. It does not attest arbitrary Python imports, remote tool implementation or a caller who rewrites local state.

Supported authoring forms

The public 0.2.0 data-enrichment download uses mapping-form workflows with steps. Keep that supplied form for its walkthrough. steps is normalized into nodes; explicit nodes is useful when showing conditions and checkpoints. Both forms are accepted. Choose one form per workflow rather than combining them; the syntax below is a partial reference, not a standalone starter.

Syntax

workflows:
  - name: <workflow_name>
    id: <optional_id>
    description: "..."
    tags: [prod, routing]
    metadata: { owner: "eng" }
    tool_policy:
      allow: [lookup_customer_profile, search_policy_kb]
      deny: [local_shell]
    nodes:
      - id: <node_id>
        kind: agent | prompt | tool | condition | wait_for_user | approval | handoff
        ref: <ref_name>              # for kind=agent/prompt/tool
        deps: [<node_ids>]           # optional
        allow_skipped_inputs: true   # optional explicit fan-in / join behavior
        tool_policy:
          allow: [search_policy_kb]
          approval_required: true
        inputs:
          input_text: "${INPUT_TEXT}"
      - id: route
        kind: condition
        deps: [triage_node]
        expr: "${triage_node.output_parsed.has_all_details} == true"
        then: launch_node
        else: get_data_node
      - id: wait_customer
        kind: wait_for_user
        deps: [launch_node]
        prompt: "Please share your order number."
        inputs:
          case_id: "${launch_node.output_parsed.case_id}"
      - id: manager_gate
        kind: approval
        deps: [launch_node]
        prompt: "Approve refund over threshold?"
        reason: "${launch_node.output_parsed.policy_reason}"
      - id: human_handoff
        kind: handoff
        deps: [manager_gate]
        prompt: "Escalate to senior support."
        reason: "specialist_review"
    output:
      from: "${some_node.output}"
      format: json | text

Field notes:

  • name (required): workflow identifier.
  • id (optional): stable identifier separate from name.
  • tags, metadata: optional annotations.
  • nodes: DAG of steps; deps gate execution order.
  • kind:
    • agent/prompt/tool: runs the referenced definition.
    • condition: evaluates expr and runs only then or else, skipping the other.
    • wait_for_user: pauses the workflow in a resumable waiting_for_user state and persists thread state.
    • approval: pauses the workflow in pending_approval until a human approves or rejects the checkpoint.
    • handoff: pauses the workflow in pending_handoff until a human marks the handoff complete or terminal.
  • Agent nodes with memory: policy use persisted thread transcript as bounded conversation context and update thread_state.memory_snapshot.by_agent.
  • inputs: templated values referencing workflow input or previous node outputs.
  • allow_skipped_inputs: explicit fan-in contract. If true, the node may execute even when one or more upstream branch dependencies were skipped.
  • tool_policy: runtime tool governance for agent, prompt, or tool nodes and optional workflow-wide defaults.
    • allow: permitted tool names. If a node defines allow, it overrides the workflow-level allow.
    • deny: denied tool names. Node and workflow deny lists are merged.
    • approval_required: if true, the node may only execute tools after an approved checkpoint exists in the thread audit trail.
  • prompt: optional checkpoint message emitted in the pause payload.
  • reason: optional checkpoint reason metadata for approval and handoff nodes.
  • edges: optional explicit graph; otherwise deps/expressions imply order.
  • output.from: expression or node output reference that becomes the workflow result; format hint (text/json), optional json_schema (future validation hook).
  • steps: supported readable authoring form, normalized into the node graph.

Execution model (current engine behavior):

  • DAG resolution: node dependencies are topologically ordered; execution fails loud if a node output cannot be parsed/validated.
  • State per node: each node’s result is stored and addressable via nodes.<id>.output (single) or nodes.<id>.outputs (collected map results).
  • Branch semantics: a condition selects one branch root and recursively marks the non-selected subtree as skipped. Skipped nodes stay addressable in context with output: null, skipped: true, and telemetry.status: skipped. String interpolation of skipped outputs resolves to an empty string, while structured/path lookups resolve to null. Use allow_skipped_inputs: true on explicit join nodes that should fan in partially skipped branches.
  • Parallel where possible: independent branches and mapped items (map.over) run concurrently; dependent nodes run sequentially (e.g., A→B→C means C waits for B).
  • Output formatting: output: { from: <node>.output, format: json } standardizes the final result; per-node validation/repair is applied so bad outputs do not silently propagate.

Execution examples:

  • Linear (A→B→C): b declares deps: [a], c declares deps: [b]; they run in order. Use inputs: { foo: "${nodes.a.output}" } to pass data forward.
  • Branch + fan-in: b and c both depend on a; they run in parallel. A downstream d with deps: [b, c] waits for both and can read nodes.b.output and nodes.c.output.
  • Fan-out + collect: with map.over: "${INPUT_LIST}", each item is processed in parallel and collected in nodes.<id>.outputs; a later node can consume that array to summarize or aggregate.
  • Pause + resume seam: wait_for_user, approval, and handoff store a thread_id, return a checkpoint payload, and stop execution so a later human action can continue the conversation. Preferred CLI shape: actspan workflow invoke <workspace> <workflow> --input-text ... for the first turn, then actspan workflow invoke --thread-id ... --input-text ... for resumed turns. Approval actions use actspan workflow approve|reject --thread-id ..., and handoff actions use actspan workflow handoff --thread-id ... --resolution complete|terminal. For an interactive shell over the same runtime, use actspan workflow chat <workspace> <workflow>. Follow the public approval guide for decision and continuation commands, or the planning example for a user-response pause.

Approval must route the decision

An approval records a decision. Put a condition after it; rejection must take a terminal branch, and the protected tool must require an approved checkpoint. This excerpt requires the upstream assess node, named agents, tool implementation and policy from the complete refund example; it is not a complete workspace on its own.

nodes:
  - id: manager_gate
    kind: approval
    deps: [assess]
    prompt: Approve the quoted synthetic refund of ${assess.output_parsed.amount_usd} USD?
    inputs:
      assessment: ${assess.output}
  - id: choose_decision
    kind: condition
    deps: [manager_gate]
    expr: '${manager_gate.output_parsed.decision} == "approve"'
    then: reviewed_payment
    else: rejected
  - id: reviewed_payment
    kind: agent
    ref: reviewed_refund_payer
    deps: [choose_decision]
    tool_policy:
      allow: [issue_reviewed_refund]
      approval_required: [issue_reviewed_refund]
    inputs:
      input_text: ${assess.output}
  - id: rejected
    kind: agent
    ref: review_declined_reply
    deps: [choose_decision]
    inputs:
      input_text: ${assess.output}
    tool_policy:
      allow: []
      deny: [issue_auto_refund, issue_reviewed_refund]

The referenced rejection-reply agent in that example has no payment tools. Inspect the ledger to establish absence of a write. Follow the refund example for its fully wired terminal-result branch, quoted amount and receipt checks.

Examples

Rewrite + classify (linear):

workflows:
  - name: rewrite_and_classify
    nodes:
      - id: rewrite_node
        kind: agent
        ref: rewrite_agent
        inputs: { input_text: "${INPUT_TEXT}" }
      - id: classify_node
        kind: agent
        ref: classify_agent
        deps: [rewrite_node]
        inputs: { input_text: "${rewrite_node.output_parsed.rewritten}" }
    output: { from: classify_node.output, format: json }

Route based on triage (condition):

nodes:
  - id: triage_node
    kind: agent
    ref: triage
    inputs: { input_text: "${INPUT_TEXT}" }
  - id: route
    kind: condition
    deps: [triage_node]
    expr: "${triage_node.output_parsed.has_all_details} == true"
    then: launch_node
    else: get_data_node
  - id: launch_node
    kind: agent
    ref: launch_helper
    deps: [route]
    inputs: { triage_summary: "${triage_node.output_parsed.summary}" }
  - id: get_data_node
    kind: agent
    ref: get_data
    deps: [route]
    inputs:
      triage_summary: "${triage_node.output_parsed.summary}"
      missing_fields: "${triage_node.output_parsed.missing_fields}"
  - id: final_message
    kind: agent
    ref: final_message_agent
    deps: [launch_node, get_data_node]
    allow_skipped_inputs: true
    inputs:
      input_text: |
        launch=${launch_node.output}
        get_data=${get_data_node.output}
output: { from: final_message.output, format: text }

Prompt + agent + tool node:

workflows:
  - name: prompt_then_tool
    nodes:
      - id: ask
        kind: prompt
        ref: rewrite_prompt
        inputs: { input_text: "${INPUT_TEXT}" }
      - id: compute
        kind: tool
        ref: math_tool         # tool defined under top-level tools
        deps: [ask]
        inputs: { a: 7, b: 6 }
      - id: format
        kind: agent
        ref: format_agent
        deps: [compute]
        inputs: { input_text: "${compute.output}" }
    output: { from: format.output, format: json }

Validation

  • actspan validate workspace.yaml
  • actspan preview workspace.yaml --format json to see plans and cost estimates.

Common errors

  • Unknown ref or deps.
  • Output reference points to a non-existent node or field.
  • Condition expr references missing paths.

How it fits together (Prompts vs Agents vs Workflows)

  • Prompts: Define a reusable provider-operation template (messages, model params, response_format, tools). They are the reusable request shapes.
  • Agents: Wrap prompts (structured) or instructions (assistant) with runtime behavior (tools, runtime limits, memory). Agents are the runnable units that workflows call.
  • Workflows: Orchestrate multiple nodes (agents/prompts/tools/conditions) into a DAG, handle branching, and pick the final output. This is the pipeline layer that stitches calls together.