/docs / workflows / activities

Workflow Activities

Activities are the units of work within workflows. Each activity is an autonomous LLM task with full tool access. Activities execute sequentially -- each activity's output becomes context for the next. This page covers activity structure, execution model, budgets, error handling, and a complete working example.

Activities are defined inside workflow bindings. See also Triggers for when workflows run, and Event System for inter-workflow communication.

Workflow Binding

Each entry in the workflows map binds activities to a trigger:

{
  "workflows": {
    "morning-briefing": {
      "trigger": { "type": "schedule", "cron": "0 7 * * *" },
      "description": "Daily morning briefing",
      "activities": [
        {
          "id": "gather",
          "intent": "Gather today's priorities from calendar and email",
          "skills": ["@nebo/skills/briefing-writer"],
          "steps": ["Check calendar for today", "Scan inbox for urgent items", "Compose briefing"],
          "token_budget": { "max": 4096 }
        },
        {
          "id": "deliver",
          "intent": "Send the briefing to the user",
          "steps": ["Format as concise bullet points", "Post to chat"],
          "token_budget": { "max": 1024 }
        }
      ],
      "budget": { "total_per_run": 6000 },
      "emit": "briefing.ready"
    }
  }
}

Binding Fields

FieldTypeRequiredDefaultDescription
refstringno--External workflow qualified name (@org/workflows/name@version). Optional when using inline activities.
triggerobjectyes--When this workflow runs (see Triggers)
descriptionstringno""Human-readable description
inputsmapno{}Default inputs passed to the workflow on trigger
activitiesarrayno[]Inline activity definitions. When present, the workflow runs inline -- no external ref needed.
budgetobjectno{}Token budget constraints (total_per_run)
emitstringno--Event name to emit on completion. By convention, prefix with the agent slug (e.g., executive-assistant.briefing.ready) to avoid cross-agent collisions.

Activities

Activities define the steps an agent takes when a workflow fires. Each activity is an autonomous LLM task with full tool access. Activities execute sequentially -- each activity's output becomes context for the next.

Activity Fields

FieldTypeRequiredDescription
idstringyesUnique activity ID within the binding
intentstringyesTask description -- what the LLM should accomplish
stepsstring[]noStep-by-step hints for the LLM
skillsstring[]noSkill references available to this activity (@org/skills/name)
mcpsstring[]noMCP server slugs available to this activity
modelstringnoModel override ("sonnet", "haiku", "opus")
token_budgetobjectnoPer-activity token limit: { "max": 4096 }
on_errorobjectnoError handling: { "retry": 1, "fallback": "skip" }
min_iterationsnumbernoForce LLM to continue even if it wants to stop early

How Activities Execute

The execution model for each activity follows a predictable sequence:

  1. System prompt is constructed from: execution rules, skills, available tools, intent, steps, inputs, and prior activity results
  2. Agentic loop runs (up to 50 iterations): LLM streams, tool calls, results, repeat
  3. Context chaining: Each activity's output is appended as [Activity '{id}' result]: {text} and passed to the next activity
  4. Branch termination: If an activity produces empty output (empty string), its branch stops -- no downstream activities execute. This follows n8n-style semantics where empty output signals "nothing to do."
  5. Completion: Activity ends when LLM produces text without tool calls (or budget exhausted)

Activity Results

Each completed activity records the following fields. Results are stored per run and available in system events and the workflow run API response.

FieldTypeDescription
started_attimestampWhen the activity began executing
completed_attimestampWhen the activity finished
input_tokensnumberToken usage for input (prompt)
output_tokensnumberToken usage for output (completion)
error_patternstringError pattern tracked for circuit breaker (if failed)
failure_reasonstringHuman-readable failure reason (if failed)

Built-in Tools

Two tools are always available inside activities, regardless of what skills or plugins are configured:

ToolPurpose
emitEmit an event into the EventBus. Other agents can subscribe to it.
exitStop the workflow early with a reason (clean termination, not failure).

Error Handling

Each activity can configure retry logic and fallback behavior via the on_error field:

FieldTypeDescription
on_error.retrynumberTimes to retry the activity before giving up (default: 0)
on_error.fallbackstringWhat to do after retries exhausted: skip, abort, notify_owner. Defaults to notify_owner.

Circuit Breaker

A built-in circuit breaker (CIRCUIT_BREAKER_THRESHOLD = 3) monitors consecutive failures. If 3 or more consecutive activities fail with the same error pattern, the workflow aborts automatically. The failure reason is stored per activity result, enabling post-mortem analysis. This prevents infinite retry loops on systematic errors (e.g., a misconfigured API key causing every activity to fail the same way).

Same-tool Loop Detection

If the LLM calls the same tool 3+ times in a row, a steering hint is injected to break the loop. This prevents runaway tool-call patterns that waste budget without making progress.

Cancellation

Workflows support graceful cancellation via a cancellation token. When cancelled:

  1. The currently in-progress activity is allowed to finish (no mid-activity abort)
  2. No subsequent activities are started
  3. The workflow run is marked as cancelled, not failed

Cancel a running workflow via POST /agents/{id}/workflows/{binding}/cancel.

Budget Math

When using inline activities with a budget.total_per_run, the sum of all activity token_budget.max values must not exceed total_per_run. This is validated at parse time -- a mismatch prevents the agent from loading.

Activity 1: gather  -> 4,096 tokens
Activity 2: deliver -> 1,024 tokens
                       -----
Sum:                   5,120 tokens
budget.total_per_run:  6,000 tokens  (>= sum, headroom for retries)

Both per-activity and global budgets are enforced independently at runtime.

Tip: Leave headroom between the sum of activity budgets and total_per_run to accommodate retries. In the example above, 880 tokens of headroom allow a retry of the smaller activity without exceeding the global budget.

Complete Example: Email Triage Pipeline

This example demonstrates a complete multi-workflow pipeline. A watcher monitors Gmail in real-time. For each email, a triage activity classifies it and emits events. Separate workflows handle customer service and sales inquiries independently.

{
  "workflows": {
    "email-watcher": {
      "trigger": {
        "type": "watch",
        "plugin": "gws",
        "event": "email.new",
        "command": "gmail +watch --format ndjson"
      },
      "description": "React to new emails in real-time",
      "activities": [
        {
          "id": "triage",
          "intent": "Classify the incoming email and route it",
          "steps": [
            "Read the email content from _watch_payload",
            "Classify as: customer-service, sales-inquiry, or ignore",
            "If not ignore, emit the appropriate event with email data"
          ],
          "token_budget": { "max": 2048 }
        }
      ],
      "budget": { "total_per_run": 2048 }
    },
    "handle-cs": {
      "trigger": { "type": "event", "sources": ["email.customer-service"] },
      "description": "Handle customer service emails",
      "activities": [
        {
          "id": "respond",
          "intent": "Draft a helpful response to the customer service email",
          "skills": ["@acme/skills/cs-templates"],
          "token_budget": { "max": 3000 }
        }
      ],
      "budget": { "total_per_run": 3000 }
    },
    "handle-sales": {
      "trigger": { "type": "event", "sources": ["email.sales-inquiry"] },
      "description": "Handle inbound sales inquiries",
      "activities": [
        {
          "id": "qualify",
          "intent": "Qualify the lead and add to CRM",
          "skills": ["@acme/skills/lead-qualification"],
          "token_budget": { "max": 3000 }
        }
      ],
      "budget": { "total_per_run": 3000 }
    }
  }
}

Read top to bottom: the watcher monitors Gmail. For each email, the triage activity classifies and emits an event. Each emission triggers the appropriate handler with the email data as inputs.