Blog · Guide · 29 October 2026

AEGIS workflow states and transitions, step by step

Also in: Español, Português, 日本語, Deutsch, Français, 한국어, 简体中文, Italiano, हिन्दी

Disclosure: 100monkeys.ai builds AEGIS, so read this guide as a maker's account. It was drafted by one of our AI agents from the public AEGIS documentation, chiefly the Workflow Manifest Reference and the CLI Reference, and reviewed by the company. Every YAML line and command below is copied from those pages; none was run for this guide. Where the docs leave a question open, the last section says so.

AEGIS by 100monkeys.ai, in beta, runs a workflow as a finite state machine written in one YAML file. The reference calls it "a durable, restartable sequence" that coordinates agents, containers, system commands and human approval gates. This guide writes one state at a time and shows how the engine chooses the next.

Step 1. Write the header

apiVersion: 100monkeys.ai/v1
kind: Workflow

metadata:
  name: dev-pipeline
  version: "1.0.0"

apiVersion must be exactly 100monkeys.ai/v1 and kind exactly Workflow. name follows ^[a-z0-9][a-z0-9-]{0,62}$, and the pair of name and version must be unique: overwriting an existing version needs the force parameter. metadata.input_schema is optional; when it is present, the caller's input is validated before the run starts, and a payload that fails is "rejected with HTTP 422; no workflow execution is created."

Step 2. Name the first state and the limits

Under spec, initial_state is required and must exist in spec.states. Two limits stop a loop that never ends. spec.max_total_transitions defaults to 50, with a ceiling of 100, after which "the workflow terminates". Each state's max_state_visits defaults to 5, with a ceiling of 20. Each state also has a timeout, 300s by default. spec.context holds constants every state reads as {{workflow.context.KEY}}, such as an iteration limit or a threshold.

Step 3. Pick a kind for each state

Every state names a kind. The reference lists eight:

Step 4. Write transitions in the order you want them tried

Transitions are "evaluated top-to-bottom. The first matching transition is taken." Each one names a target state and, optionally, a condition. This System state from the reference's own example branches on the exit code:

    EXECUTE:
      kind: System
      command: python main.py
      env:
        CODE: "{{GENERATE.output}}"
        PYTHONPATH: /workspace
      transitions:
        - condition: exit_code_zero
          target: VALIDATE
        - condition: exit_code_non_zero
          target: REFINE
          feedback: "Execution failed: {{EXECUTE.output.stderr}}"

The conditions each kind can use: on_success and on_failure for most kinds; exit_code_zero, exit_code_non_zero and exit_code with a value for System and ContainerRun; input_equals, input_equals_yes and input_equals_no for Human; consensus, all_approved and any_rejected for ParallelAgents; custom with a Handlebars expression for any kind; and always, or no condition at all, as the unconditional fallback. The reference's advice: "Always include an unconditional fallback as the last transition."

Step 5. Branch on a score

    VALIDATE:
      kind: Agent
      agent: judge-v1
      transitions:
        - condition: score_above
          threshold: 0.95
          target: COMPLETE
        - condition: score_below
          threshold: 0.95
          target: REFINE
          feedback: "{{VALIDATE.output.reasoning}}"

Both comparisons are strict, so a score of exactly 0.95 takes neither transition above. The score is read from the state's score, else final_score, else consensus.score. A state that carries no score matches neither, and if nothing else matches the run fails with State "<name>" carries no score for its score transitions. score_between takes inclusive min and max for an Agent state.

Step 6. Carry what happened into the next state

A transition's feedback string becomes {{state.feedback}} in the state it leads to, which is how a failure message reaches the agent that retries. Each finished state is written to the Blackboard under its name, so later states read {{EXECUTE.output.stdout}} or {{VALIDATE.score}}. Keep two namespaces apart: {{input.KEY}} is what the caller passed and "is immutable", while {{blackboard.KEY}} grows as the run goes. A template that names a key no state has written yet renders a visible placeholder, ERROR: missing key with the state's name, rather than an empty string.

Step 7. Loop with a guard, and end the run

    REFINE:
      kind: System
      command: update_context
      env:
        ITERATION: "{{blackboard.iteration_number + 1}}"
        ERRORS: "{{state.feedback}}"
      transitions:
        - condition: custom
          expression: "{{blackboard.iteration_number < workflow.context.max_iterations}}"
          target: GENERATE
        - condition: custom
          expression: "{{blackboard.iteration_number >= workflow.context.max_iterations}}"
          target: FAILED

    COMPLETE:
      kind: System
      command: finalize
      transitions: []

A state with transitions: [] is terminal: "When reached, the workflow execution completes with status completed." Give the loop its own guard on the Blackboard, as above, as well as relying on max_state_visits.

Step 8. Validate, deploy, run and watch the transitions

aegis workflow validate ./pipeline.yaml
aegis workflow deploy ./pipeline.yaml
aegis workflow run dev-pipeline --input '{"task": "..."}'
aegis workflow logs <execution_id> --transitions

validate checks the manifest locally; deploy registers it and fails on an existing name and version unless you add --force; logs --transitions shows "state entries/exits only", which is the quickest way to see which branch each state took.

What the docs leave open

Next

For the Human state in depth, with what the reviewer sees and what silence means, read How to give an orchestrator an approval step. AEGIS installs with the command printed on 100monkeys.ai:

curl -fsSL https://get.100monkeys.ai | bash

Install AEGIS from 100monkeys.ai