Aegis Orchestrator
Guides

Building Workflows

Step-by-step guide to writing, deploying, and running multi-agent workflow FSMs with the Forge reference pattern.

Building Workflows

This guide walks through creating a multi-agent workflow using the AEGIS workflow YAML format. You will build a simplified version of the Forge reference pattern: a pipeline that takes a programming task through requirements analysis, implementation, and review.


Prerequisites

  • AEGIS daemon running with Temporal configured (see Getting Started)
  • At least three agents deployed: requirements-agent, coder-agent, reviewer-agent
  • Basic familiarity with Workflows

Before You Start: Check for Existing Workflows

If your node has the discovery service configured (enterprise feature), search for existing workflows that might already cover your use case before writing a new one:

// Tool Call: aegis.workflow.search
{ "query": "code review pipeline with human approval" }

This avoids creating duplicate workflows and helps you build on proven patterns. On nodes without discovery, use aegis workflow list to browse the registry manually.


Workflow Metadata: description

Every workflow manifest should include metadata.description.

The description field is a human-readable summary of what the workflow accomplishes. It is the primary input to the semantic embedding used by the discovery service — write it to capture intent and capability, not implementation steps.

metadata:
  name: my-pipeline
  version: "1.0.0"
  description: "Builds, tests, and deploys a Node.js service to Kubernetes on every merge."
  labels:
    category: deployment
    team: platform

Declaring structured inputs with input_schema

If your workflow accepts named runtime inputs, declare them in metadata.input_schema. This makes the contract between callers and the workflow explicit, and enables the orchestrator to validate input payloads before the workflow starts.

metadata:
  name: my-pipeline
  version: "1.0.0"
  input_schema:
    type: object
    properties:
      task:
        type: string
        description: Natural language description of the task to complete
      language:
        type: string
        enum: [python, typescript, rust]
    required:
      - task

Input fields are then available in state templates as {{input.task}}, {{input.language}}, etc. See the Workflow Manifest Reference for the full field specification.


Step 1: Write the Workflow Manifest

Create my-pipeline.yaml:

apiVersion: 100monkeys.ai/v1
kind: Workflow
metadata:
  name: dev-pipeline
  version: "1.0.0"
  description: "Requirements analysis, human approval, implementation, and code review pipeline."
  labels:
    category: development
    team: platform
  tags:
    - code-review
    - ci-cd
    - human-in-the-loop
    - software-development
spec:
  context:
    language: python
    test_framework: pytest

  initial_state: REQUIREMENTS

  states:

    # Stage 1: Analyze the task and produce a requirements document
    REQUIREMENTS:
      kind: Agent
      agent: requirements-agent
      timeout: 180s
      transitions:
        - condition: on_success
          target: APPROVE_REQUIREMENTS
        - condition: on_failure
          target: FAILED

    # Stage 2: Human approval gate before implementation begins
    APPROVE_REQUIREMENTS:
      kind: Human
      prompt: |
        Requirements:
        {{REQUIREMENTS.output}}

        Approve? (yes/no)
      timeout: 86400s   # Wait up to 24 hours for operator approval
      transitions:
        - condition: input_equals_yes
          target: IMPLEMENT
        - condition: input_equals_no
          target: FAILED
          feedback: "{{human.feedback}}"

    # Stage 3: Implement based on the approved requirements
    IMPLEMENT:
      kind: Agent
      agent: coder-agent
      timeout: 600s
      input: |
        Implement in {{workflow.context.language}} using {{workflow.context.test_framework}}.
        Requirements: {{REQUIREMENTS.output}}
      transitions:
        - condition: on_success
          target: REVIEW
        - condition: on_failure
          target: FAILED

    # Stage 4: Review the implementation
    REVIEW:
      kind: Agent
      agent: reviewer-agent
      timeout: 300s
      transitions:
        - condition: score_above
          threshold: 0.85
          target: DONE
        - condition: score_below
          threshold: 0.85
          target: FAILED
          feedback: "Review score too low ({{REVIEW.score}}): {{REVIEW.output.reasoning}}"

    DONE:
      kind: System
      command: "echo 'Pipeline complete. Output at /workspace/output.'"
      transitions: []

    FAILED:
      kind: System
      command: "echo 'Pipeline failed'"
      transitions: []

Step 2: Using Blackboard Templates

Agents in downstream states automatically receive earlier states' outputs via Handlebars templates in the input field. For example, the coder-agent's input already references {{REQUIREMENTS.output}} from Step 1.

At execution time, the rendered input is available via task.input in the runtime:

from aegis import AegisClient

client = AegisClient()
task = client.get_task()

# The rendered input is available directly
print(task.input)  # "Implement in python using pytest.\nRequirements: ..."

# You can also access the raw blackboard if needed
language = task.blackboard.get("language", "python")

For the full list of available template variables, see the Workflow Manifest Reference — Handlebars Template Variables.


Step 3: Deploy the Workflow

aegis workflow deploy ./my-pipeline.yaml --force

Use --force when you are re-deploying the same workflow name and version pair.

Expected output:

Deployed workflow "dev-pipeline" (id: wf-a1b2c3d4-0000-...)

List deployed workflows:

aegis workflow list

Step 4: Start a Workflow Execution

aegis workflow run dev-pipeline \
  --input '{"task": "Write a REST API for a simple to-do list with CRUD operations."}'

Expected output:

Workflow execution started: wfx-a1b2c3d4-1111-...

Watch execution logs:

aegis workflow logs wfx-a1b2c3d4-1111-... --follow --verbose

Use aegis workflow executions get wfx-a1b2c3d4-1111-... when you want a point-in-time view of the execution status, blackboard, and persisted metadata.

Example output:

Execution ID: wfx-a1b2c3d4-1111-...
Workflow:     dev-pipeline
Status:       running
Current State: APPROVE_REQUIREMENTS (WAITING FOR HUMAN SIGNAL)

State History:
  REQUIREMENTS          completed  (2026-02-23T10:00:01Z → 10:02:14Z)
  APPROVE_REQUIREMENTS  waiting    (2026-02-23T10:02:15Z → ...)

Step 5: Signal a Human State

The workflow is suspended at APPROVE_REQUIREMENTS. Resume it through the workflow CLI:

aegis workflow signal wfx-a1b2c3d4-1111-... --response approved

To reject with feedback:

aegis workflow signal wfx-a1b2c3d4-1111-... \
  --response "rejected: Requirements need more detail on error handling"

After approval, the workflow continues to the IMPLEMENT state automatically.


Step 6: Monitor to Completion

# Continue watching logs
aegis workflow logs wfx-a1b2c3d4-1111-... --follow --verbose

Using ParallelAgents States

Run multiple judges concurrently and gate on consensus. Add a AUDIT state that evaluates the implementation from multiple perspectives simultaneously:

AUDIT:
  kind: ParallelAgents
  agents:
    - agent: security-reviewer
      input: "Security audit: {{IMPLEMENT.output}}"
      weight: 2.0              # weighted higher
      timeout_seconds: 300
    - agent: performance-reviewer
      input: "Performance review: {{IMPLEMENT.output}}"
      weight: 1.0
      timeout_seconds: 180
    - agent: style-reviewer
      input: "Style review: {{IMPLEMENT.output}}"
      weight: 1.0
      timeout_seconds: 60
  consensus:
    strategy: weighted_average   # weighted_average | majority | unanimous | best_of_n
    threshold: 0.85
    min_agreement_confidence: 0.75
    min_judges_required: 2       # at least 2 of 3 must succeed
  timeout: 600s
  transitions:
    - condition: consensus
      threshold: 0.85
      agreement: 0.75
      target: DONE
    - condition: score_below
      threshold: 0.85
      target: FAILED
      feedback: |
        Audit failed (score: {{AUDIT.consensus.score}}, confidence: {{AUDIT.consensus.confidence}}):
        Security: {{AUDIT.agents.0.output}}
        Performance: {{AUDIT.agents.1.output}}
        Style: {{AUDIT.agents.2.output}}

All three judges run simultaneously. The state waits until all have completed (or timeout is hit), then evaluates the consensus block. See the Workflow Manifest Reference — kind: ParallelAgents for all consensus strategies.


Composing Workflows with Subworkflows

As your workflow library grows, you will find common patterns repeated across pipelines — data processing, deployment sequences, validation gates. The Subworkflow state kind lets you extract these into standalone workflows and invoke them from a parent.

Example: Parent Workflow Calling a Reusable Validation Pipeline

Suppose you have a reusable validation-pipeline workflow that runs security scanning, linting, and test execution. You can invoke it from any parent workflow:

Child workflow (validation-pipeline.yaml, deployed separately):

apiVersion: 100monkeys.ai/v1
kind: Workflow
metadata:
  name: validation-pipeline
  version: "1.0.0"
spec:
  initial_state: SECURITY_SCAN
  states:
    SECURITY_SCAN:
      kind: Agent
      agent: security-scanner
      intent: "{{intent}}"
      input: |
        Repository: {{input.repo_url}}
        Branch: {{input.branch}}
      timeout: 300s
      transitions:
        - condition: on_success
          target: LINT
        - condition: on_failure
          target: FAILED
    LINT:
      kind: ContainerRun
      image: "rust:1.75-alpine"
      command: ["cargo", "clippy", "--all-targets", "--", "-D", "warnings"]
      workdir: "/workspace"
      volumes:
        - name: workspace
          mount_path: /workspace
      transitions:
        - condition: exit_code_zero
          target: TEST
        - condition: exit_code_non_zero
          target: FAILED
    TEST:
      kind: ContainerRun
      image: "rust:1.75-alpine"
      command: ["cargo", "test", "--workspace"]
      workdir: "/workspace"
      volumes:
        - name: workspace
          mount_path: /workspace
      transitions:
        - condition: exit_code_zero
          target: DONE
        - condition: exit_code_non_zero
          target: FAILED
    DONE:
      kind: System
      command: "echo 'Validation passed'"
      transitions: []
    FAILED:
      kind: System
      command: "echo 'Validation failed'"
      transitions: []

Agent states have two distinct template fields: intent: carries the natural-language steering ("why") and input: carries the structured data ("what"). They are separate concerns — intent: resolves to {{intent}} inside the agent's prompt template, while input: resolves to {{input}}. When intent: is omitted on a state, {{intent}} in the agent's prompt falls back to the workflow-level caller intent.

Parent workflow that invokes the child:

apiVersion: 100monkeys.ai/v1
kind: Workflow
metadata:
  name: release-pipeline
  version: "1.0.0"
spec:
  initial_state: GENERATE_CODE
  states:
    GENERATE_CODE:
      kind: Agent
      agent: coder-v2
      input: "{{intent}}"
      timeout: 600s
      transitions:
        - condition: on_success
          target: VALIDATE
        - condition: on_failure
          target: FAILED

    VALIDATE:
      kind: Subworkflow
      workflow_id: validation-pipeline
      mode: blocking
      result_key: validation_result
      input: "Validate code generated for: {{intent}}"
      timeout: 1800s
      transitions:
        - condition: on_success
          target: DEPLOY
        - condition: on_failure
          target: FIX_CODE
          feedback: "Validation failed: {{VALIDATE.result}}"

    FIX_CODE:
      kind: Agent
      agent: coder-v2
      input: |
        Fix the code based on validation feedback:
        {{state.feedback}}
      timeout: 600s
      transitions:
        - condition: on_success
          target: VALIDATE
        - condition: on_failure
          target: FAILED

    DEPLOY:
      kind: ContainerRun
      image: "docker:24-cli"
      command: ["sh", "-c", "echo 'Deploying validated artifact'"]
      transitions:
        - condition: exit_code_zero
          target: DONE
        - condition: exit_code_non_zero
          target: FAILED

    DONE:
      kind: System
      command: "echo 'Release complete'"
      transitions: []

    FAILED:
      kind: System
      command: "echo 'Release failed'"
      transitions: []

The VALIDATE state starts the validation-pipeline child workflow in blocking mode. The parent waits for the child to complete and then stores the child's final Blackboard under the validation_result key. If the child fails, the parent routes to FIX_CODE with the child's output as feedback.

Fire-and-Forget for Background Tasks

Use fire_and_forget mode when you want to trigger a workflow without waiting for it to finish — for example, kicking off a notification pipeline or a cleanup job:

NOTIFY:
  kind: Subworkflow
  workflow_id: notification-pipeline
  mode: fire_and_forget
  input: |
    {"message": "Deployment complete", "channel": "ops"}
  transitions:
    - condition: on_success
      target: DONE

The parent continues immediately. The child's execution ID is available as {{NOTIFY.child_execution_id}} for later inspection.

For the complete field specification and validation rules, see the Workflow Manifest Reference — kind: Subworkflow.


Retry Loops and Visit Limits

When a workflow contains backward transitions -- for example, a validation state that routes back to a code-generation state on failure -- the FSM can enter an unbounded retry loop if the upstream state never produces acceptable output. Two fields prevent this:

  • max_state_visits (per state, default 5, ceiling 20): limits how many times the FSM can enter a given state. Once exceeded, the workflow terminates.
  • max_total_transitions (workflow level, default 50, ceiling 100): limits the total number of state-to-state transitions across the entire execution.

Set these values based on the expected retry depth of your workflow. Lower values are appropriate for pipelines where retries are unlikely to succeed after a few attempts; higher values suit workflows with legitimate multi-pass refinement.

spec:
  initial_state: GENERATE
  max_total_transitions: 30

  states:
    GENERATE:
      kind: Agent
      agent: coder-v1
      max_state_visits: 5
      input: |
        {{#if state.feedback}}
        Previous attempt failed: {{state.feedback}}
        Fix the code.
        {{else}}
        {{intent}}
        {{/if}}
      transitions:
        - condition: always
          target: VALIDATE

    VALIDATE:
      kind: Agent
      agent: reviewer-v1
      max_state_visits: 5
      transitions:
        - condition: score_above
          threshold: 0.85
          target: DONE
        - condition: score_below
          threshold: 0.85
          target: GENERATE
          feedback: "{{VALIDATE.output.reasoning}}"

    DONE:
      kind: System
      command: "echo 'Complete'"
      transitions: []

In this example, VALIDATE can route back to GENERATE up to 5 times. If either state hits its max_state_visits limit or the workflow exceeds 30 total transitions, execution terminates. These limits are independent of max_iterations, which controls the agent's inner refinement loop within a single state execution.


Temporal UI

Temporal provides a built-in workflow visibility UI that shows the durable state of every workflow execution. Access it at http://localhost:8233 when running the local dev stack.

The UI shows:

  • Current state of each execution
  • History of state transitions
  • Any pending timers (e.g., Human state timeouts)
  • Detailed error information for failed executions

This is the authoritative view for debugging stuck or failed workflow executions.


Workflow Scope

Every workflow has a scope field in its metadata that controls which callers can discover and invoke it:

ScopeWho Can Use It
tenantAll members of your organization (or the individual consumer user's per-user tenant)
globalAll tenants — reserved for platform builtins

Newly registered workflows default to tenant scope. You do not need to specify scope explicitly in the manifest.

Global scope is reserved for platform builtins such as builtin-intent-to-execution. Promoting a workflow's scope to global is an admin operation.

When referencing a workflow by name — for example in aegis workflow run <name> or an aegis.workflow.run tool call — the system resolves using narrowest-first order: tenant-scoped workflows first, then global builtins. A tenant-scoped workflow named my-pipeline will be found before a global workflow of the same name.


Workflow Manifest Reference

For the complete field specification including all StateKind options, TransitionRule operators, and Blackboard template variables, see the Workflow Manifest Reference. For current CLI vs API workflow operation coverage, see the CLI Capability Matrix.

On this page