How to Share Data Between Steps in Probe Using Step Outputs: A Complete Guide

Probe enables data sharing between steps through step outputs, where steps declare an id and an outputs map to expose values that subsequent steps can access via the {{outputs.<id>.<name>}} expression syntax.

Probe is a lightweight workflow automation tool designed for HTTP-based testing and orchestration. When building multi-step workflows, you need to share data between steps in Probe using step outputs—such as authentication tokens, session IDs, or dynamic values extracted from API responses.

Understanding Step Outputs in Probe

Probe's output system consists of three core components working together: the Step definition, the Outputs registry, and the expression engine.

The Core Architecture

The Step struct in step.go defines how outputs are declared:

  • Step.Outputs (lines 29-30): A map where keys are output names and values are expressions that extract data from the step's response
  • Step.saveOutputs (lines 41-67): Evaluates each output expression using the step's context and writes results to the global JobContext.Outputs registry
  • Outputs struct (lines 9-34 in outputs.go): Central store providing Set, Get, GetAll, and GetAllWithFlat methods for managing step-based and flat outputs

How Data Flows Between Steps

When a step executes, Probe builds a data pipeline:

  1. Step A finishes execution and calls saveOutputs(), which evaluates expressions like res.body.access_token and stores results via Outputs.Set()
  2. Step B begins execution via Step.SetCtx() (lines 20-34), which loads all previously saved outputs into the step's evaluation context using j.Outputs.GetAll()
  3. The expression engine (expr.go) resolves templates like {{outputs.login.token}} against this combined context

Defining Step Outputs in YAML

To share data between steps in Probe using step outputs, you must explicitly declare which values each step produces.

Basic Output Declaration

Every producing step requires an id field and an outputs map:

- name: Authenticate with API
  id: login                    # Required identifier for referencing

  uses: http
  with:
    post: /auth/login
    body:
      username: admin
      password: secret
  outputs:
    token: res.body.access_token
    expiry: res.body.expires_in

The id field creates the namespace (login) under which these outputs are stored. Without an id, subsequent steps cannot reference the data.

Extracting Values from HTTP Responses

Probe evaluates output expressions against the step's response context. Common extraction patterns include:

  • res.body.<field> for JSON response bodies
  • res.headers.<name> for header values
  • res.code for HTTP status codes
- name: Create Resource
  id: creator
  uses: http
  with:
    post: /api/resources
    body:
      name: test-resource
  outputs:
    resource_id: res.body.id
    created_at: res.body.timestamp

Consuming Step Outputs in Subsequent Steps

Once a step defines outputs, any subsequent step in the same job—or in dependent jobs—can access those values using Probe's expression syntax.

Referencing Outputs with Expression Syntax

Use the {{outputs.<step_id>.<output_name>}} template syntax to retrieve values:

- name: Fetch Protected Data
  uses: http
  with:
    get: /api/protected/data
    headers:
      Authorization: "Bearer {{outputs.login.token}}"
  test: res.code == 200

Probe's expression engine resolves this template at runtime by looking up login in the outputs registry and retrieving the token field.

Flat Access Shortcuts

When no naming conflicts exist, Probe creates flat shortcuts that allow direct access without the step ID prefix:

- name: Quick Token Access
  uses: http
  with:
    get: /api/verify
    headers:
      Authorization: "Bearer {{outputs.token}}"

According to the source code in outputs.go (lines 43-52), the Set method automatically creates these flat entries when the output name doesn't collide with existing keys. If two steps both output a field named token, the flat shortcut is omitted to prevent ambiguity, and you must use the scoped syntax {{outputs.step1.token}} or {{outputs.step2.token}}.

Cross-Job Data Sharing with Needs

Probe extends step outputs across job boundaries using the needs dependency system. When a job declares needs, Probe ensures the dependent job waits for completion and receives access to all outputs from the required jobs.

jobs:
- name: Authentication Job
  id: auth
  steps:
  - name: Login
    id: login
    uses: http
    with:
      post: /auth
    outputs:
      token: res.body.access_token

- name: Data Processing
  needs: [auth]              # Waits for auth job to complete

  steps:
  - name: Fetch Data
    uses: http
    with:
      get: /api/data
      headers:
        Authorization: "Bearer {{outputs.login.token}}"

The workflow.go file orchestrates this execution order, ensuring that the auth job's saveOutputs calls complete before the Data Processing job begins building its step contexts via SetCtx.

Conflict Handling and Best Practices

Output Name Collisions

The Outputs.Set method in outputs.go (lines 27-36) implements conflict detection. If a step's id collides with an existing flat output name, the method returns an error and retains only the step-scoped map, preventing accidental data overwrites.

To avoid collisions:

  • Use descriptive, unique step IDs like auth_login rather than generic names like step1
  • Namespace your outputs when multiple steps produce similar data (e.g., user_id vs admin_id)

Best Practices for Naming

When you share data between steps in Probe using step outputs, follow these conventions:

  • Use snake_case for step IDs: The id field becomes a namespace in expressions, so fetch_user_data is more readable than fetchUserData in YAML templates.
  • Document output schemas: Since Probe evaluates expressions dynamically, document what fields each step outputs (e.g., token, expires_at) in workflow comments.
  • Validate outputs with tests: Use the test field to ensure outputs exist before downstream steps consume them:
outputs:
  token: res.body.token
test: outputs.token != ""   # Fails step if extraction fails

Summary

  • Declare outputs by adding an id to your step and defining an outputs map with extraction expressions.
  • Access values in subsequent steps using the {{outputs.<step_id>.<output_name>}} expression syntax.
  • Use flat shortcuts like {{outputs.token}} when no naming conflicts exist, but prefer scoped access for clarity.
  • Share across jobs by declaring needs dependencies, which ensure output availability before dependent jobs execute.
  • Avoid collisions by using descriptive step IDs, as the Outputs.Set method in outputs.go prevents overwrites but may omit flat shortcuts when conflicts occur.

Frequently Asked Questions

What is the syntax for accessing step outputs in Probe?

Use the template expression {{outputs.<step_id>.<output_name>}} to access values. For example, if a step with id: login defines an output named token, reference it as {{outputs.login.token}}. If no naming conflicts exist, you can also use the flat syntax {{outputs.token}} for direct access.

Can I share data between different jobs in Probe?

Yes. Declare a needs dependency in the consuming job to ensure the producing job completes first. Once the dependency is satisfied, you can reference outputs from any step in the required job using the same {{outputs.<step_id>.<output_name>}} syntax, as the workflow.go orchestrator ensures outputs are persisted before dependent jobs begin.

What happens if two steps define outputs with the same name?

Probe handles this through conflict detection in outputs.go. If two steps output fields with identical names, the system retains both step-scoped values (accessible via {{outputs.step1.name}} and {{outputs.step2.name}}), but omits the flat shortcut ({{outputs.name}}) to prevent ambiguity. If a step ID collides with a flat output name, Outputs.Set returns an error and preserves only the step-scoped map.

How does Probe handle output extraction from HTTP responses?

Probe evaluates output expressions against the step's response context after execution completes. In the outputs map, write expressions like res.body.access_token for JSON fields, res.headers.location for headers, or res.code for status codes. The Step.saveOutputs method (lines 41-67 in step.go) evaluates these expressions using the step's context and stores the results in the global Outputs registry, making them available to subsequent steps via the expression engine.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →