The Event Pipeline
How CAO detects agent state and delivers messages at the right time — without polling.
The Event Pipeline
The fundamentals course introduced the Event Bus as the glue between CAO's components. Now let's look at what actually flows through it — and the five specialized services that make message delivery feel instant.
Think of it as a newsroom. Reporters (FIFOs) feed raw stories into a wire service (EventBus), editors (StatusMonitor) decide what is breaking news, and the dispatch desk (InboxService) routes the right story to the right person at the right time.
Reads raw bytes from named FIFOs at ~/.aws/cli-agent-orchestrator/fifos/{id}.fifo. Coalesces rapid-fire chunks within a 50ms window before publishing.
Async pub/sub backbone. Wildcard topic matching (terminal.*.output), bounded queues (1024 default), drop-and-log on overflow.
Maintains an 8KB rolling buffer per terminal. Runs provider-specific pattern matching to detect IDLE, PROCESSING, COMPLETED, WAITING_USER_ANSWER, or ERROR. Uses debounced edge detection with a sticky latch.
Routes the right message at the right time. When a terminal reaches IDLE or COMPLETED, delivers one pending message from its inbox.
Subscribes to terminal.*.output alongside everyone else. Batches writes to per-terminal log files so you always have a full record.
Watch a Chunk Travel
Follow a single piece of terminal output as it flows through every stage of the pipeline. Notice how the EventBus appears twice -- once routing raw output, once routing the derived status change. This two-phase publish is the key to decoupling detection from delivery.
The EventBus carries two kinds of topics: terminal.{id}.output for raw data, and terminal.{id}.status for derived state changes. StatusMonitor subscribes to the first and publishes the second. InboxService only subscribes to the second -- it never sees raw output.
The Sticky Latch
StatusMonitor uses debounced edge detection -- it does not re-evaluate status on every single chunk. Instead it waits for output to settle, then checks. And once it determines a terminal is IDLE, it applies a "sticky latch":
Once an agent reaches IDLE, the StatusMonitor latches that status. It will not drop to PROCESSING just because the TUI redraws a progress bar or refreshes the screen. Any new input sent to the terminal unlocks the latch and allows a transition back to PROCESSING — that includes handoff, assign, inbox delivery, and cao session send.
A TUI redraw would momentarily look like new output, causing StatusMonitor to flip the terminal to PROCESSING. InboxService would then hold messages hostage until the next IDLE detection until the next genuine idle detection.
Here is the core loop, simplified. The real code adds debouncing, buffer rotation, and the latch logic, but the publish/subscribe pattern remains the same:
# services/status_monitor.py (simplified)
queue = bus.subscribe("terminal.*.output") # wildcard!
async for topic, data in queue:
terminal_id = topic.split(".")[1]
buffer[terminal_id] += data
status = provider.get_status(buffer[terminal_id])
if status != last_status[terminal_id]:
bus.publish(f"terminal.{terminal_id}.status", {"status": status})
Workflows
Declarative YAML pipelines that turn multi-step agent tasks into repeatable, validated, resumable sequences.
Why Workflows?
The supervisor pattern with assign() is powerful — it lets a creative agent decompose
problems on the fly, delegate to workers in parallel, and adapt to unexpected results.
But sometimes you do not want creativity. You want a fixed pipeline that runs the
same steps, in the same order, every time.
Workflows give you exactly that: a declarative YAML spec that defines each step, its inputs, its outputs, and how data flows between them. No improvisation, no skipped steps, no variation between runs.
Supervisor + assign()
Flexible and creative. The supervisor decides what to do next based on results. Great for novel, open-ended tasks.
Workflows
Deterministic and repeatable. Steps are fixed in advance. Inputs are typed (string, int, bool, path) and step outputs can declare a JSON Schema. Runs are journaled and resumable.
Anatomy of a Workflow
A workflow spec is a flat YAML file with top-level metadata and a list of steps.
Specs live in ~/.aws/cli-agent-orchestrator/workflows/.
name: code-review-pipeline description: Automated PR review pipeline inputs: pr_url: type: string required: true steps: - id: fetch-diff provider: claude_code agent: developer prompt: "Fetch the diff from {{workflow.inputs.pr_url}} and summarize" output_schema: type: object properties: summary: {type: string} - id: review-security provider: claude_code agent: reviewer prompt: "Review for security issues: {{steps.fetch-diff.output.summary}}" - id: review-quality provider: kiro_cli agent: reviewer prompt: "Review for code quality: {{steps.fetch-diff.output.summary}}"
name: — workflow identifier used to run itdescription: — human-readable summaryinputs: — dictionary of typed parameterstype and required fieldssteps: — sequential list of steps to executeid: — unique step identifier used to reference outputsprovider: — which AI CLI runs this step (required)agent: — agent profile for this stepprompt: — message sent to the agent; supports {{}} interpolationoutput_schema: — required if later steps reference this step's output; the agent returns it via workflow_returnid: gives it a unique name for later referenceprovider: can differ per step (here claude_code){{steps.fetch-diff.output.summary}} — references a field from a previous step's output (full path required: {{steps.STEP_ID.output.FIELD}})agent profile but different provider{{workflow.inputs.NAME}} for inputs, {{steps.STEP_ID.output.FIELD}} for step outputsRunning a Workflow
From authoring to monitoring — the lifecycle of a workflow run:
cao workflow validate my-pipeline.yaml — catches grammar errors, duplicate step ids, and constraint violations (max 100 steps, 256KB limit) before you run anything. Template references are resolved at run time, so validate will not catch a typo in {{steps.…}}.
cao workflow run code-review-pipeline --input pr_url=https://... — launches the workflow with the required inputs. This blocks until the run finishes, then prints the run id and per-step results. Pass --run-id to pre-assign an id you can poll from another shell.
Each step launches an agent via run_agent_step (same substrate as handoff). Steps run one at a time, each with a 600-second timeout and up to 3 retries.
Step outputs are captured and available to later steps via {{steps.STEP_ID.output.FIELD}} template expressions (the full path is required: {{workflow.inputs.NAME}} for inputs, {{steps.STEP_ID.output.FIELD}} for step outputs). Outputs are validated against JSON Schema if defined.
cao workflow status RUN_ID — see which step is currently executing, which have completed, and the overall run state.
CAO journals every step completion. If a run is interrupted — process killed, machine reboots, network drops — cao workflow resume RUN_ID keeps every completed step and re-runs the interrupted step and anything after it on a fresh terminal.
Check Your Understanding
1. When would you choose a workflow over a supervisor with assign()?
Skills & Install
Reusable behavior modules that agents load on demand, and the install system that wires everything together.
Skills
A skill is a folder containing a SKILL.md file with structured instructions
that an agent can load at runtime. Skills live in ~/.aws/cli-agent-orchestrator/skills/
and are managed via the CLI: cao skills add, cao skills remove, cao skills list.
When an agent needs a skill, it calls the load_skill(name) MCP tool.
For Kiro CLI, skills are delivered natively via skill:// resources in the agent JSON.
Kiro CLI and OpenCode CLI discover skills natively; other providers load them on demand through the MCP server.
What's in a Skill
A folder with a SKILL.md file containing step-by-step instructions, constraints, and examples. The agent receives this content verbatim when it loads the skill.
How Agents Discover Them
Agents call load_skill(name) at runtime. The MCP server resolves the name to the skill folder, reads SKILL.md, and returns its content directly into the agent's context.
Built-in Skills
CAO ships 11 built-in skills, including supervisor and worker protocols, memory usage, workflow authoring, session management, and agent routing. These cover the most common multi-agent patterns out of the box.
cao skills add ./my-custom-skill
Copies the skill folder into the skills directory, making it available to all agents immediately.
cao skills remove my-custom-skill
Removes the skill from the skills directory. Running agents that already loaded it are unaffected.
cao skills list
Shows all installed skills with their names and descriptions.
Installing Agents
The cao install command takes a profile name, local path, or URL and sets it up
for your provider automatically. No manual JSON editing or config wrangling required.
cao install code_supervisor — Resolves a built-in profile and writes a context file to agent-context/. Also accepts local paths (./my-agent.md) or URLs from an allowlisted host such as raw.githubusercontent.com.
For Kiro CLI, CAO writes the agent JSON with skill:// resources. For Claude Code and Codex, it writes a context file (no provider-specific config needed). You never touch provider configs directly.
cao env
cao env set ANTHROPIC_API_KEY sk-... — Stores the variable in ~/.aws/cli-agent-orchestrator/.env. Profiles reference them with ${VAR} syntax.
cao profile list — Confirms the profile is registered and lists the name, source, and description. Use cao profile show <name> for the provider, role, and tool access.
# Install from built-in registry cao install code_supervisor # Install from a local file cao install ./my-agent.md # Install from URL with env var cao install https://raw.githubusercontent.com/org/repo/main/agent.md \ --env TEAM=platform # Manage environment variables cao env set ANTHROPIC_API_KEY sk-... cao env list cao env unset ANTHROPIC_API_KEY
A profile says WHAT the agent is — its role, provider, and system prompt. Skills say HOW it should behave in specific situations. A single profile can reference multiple skills, and the same skill can be shared across many profiles. This separation keeps agent definitions clean and behavior reusable.
Automation & Ops
Run agents on a schedule, orchestrate them from the outside, and build zero-human-in-the-loop pipelines.
Scheduled Flows
Scheduled Flows let you run agent tasks on a cron-like schedule without any human trigger. Define a flow as a Markdown file with YAML frontmatter, register it with the CLI, and CAO's background daemon handles the rest.
---
name: nightly-review
schedule: "0 2 * * *"
agent_profile: code_supervisor
provider: claude_code
---
Review all PRs opened today and
summarize findings.
The server's background daemon checks every 60 seconds for flows whose
cron expression matches. Sessions are auto-named cao-flow-{name}.
CLI commands: cao schedule add FILE, cao schedule list,
cao schedule run NAME, cao schedule enable/disable NAME,
cao schedule remove NAME.
Flows can include a script step that runs first and outputs JSON. If it
returns {"execute": false}, the flow is skipped for that cycle — useful for
conditional execution based on external state.
The Ops MCP Server
CAO ships two MCP servers with very different purposes. Understanding which is which is essential for building integrations.
cao-mcp-server
Runs INSIDE agent sessions.
Provides tools like handoff, assign, and send_message so agents can orchestrate each other during a workflow.
cao-ops-mcp-server
Runs OUTSIDE — for external control.
Lets CI/CD pipelines, meta-agents, or scripts manage CAO itself: launch sessions, send messages, read output, shut things down.
Ops server tools:
list_profiles— discover available agent profilesinstall_profile— install a profile by name or URLlaunch_session— start a new CAO sessionsend_session_message— deliver a message to a running sessionget_terminal_status— check if a terminal is idle, processing, etc.get_terminal_output— read recent terminal outputread_session_output— read a terminal's captured output by session nameget_profile_details— get detailed information about a specific profilelist_sessions— list all active CAO sessionsget_session_info— get detailed info about a specific sessionshutdown_session— gracefully stop a session
Fully Autonomous Operation
When an agent hits a permission prompt or asks for clarification, it enters
WAITING_USER_ANSWER status. Normally a human would respond. But the
answer_user_prompt tool lets another agent respond programmatically.
A supervisor (or another internal agent via the cao-mcp-server) calls
answer_user_prompt(terminal_id, answer) to unblock the waiting worker — no
human needed. Note: answer_user_prompt is available only through the internal
cao-mcp-server, not the ops server. External scripts can send input to a waiting terminal via POST /terminals/{id}/input — the same generic endpoint used for all terminal input.
Combine all three pieces for fully autonomous pipelines: Scheduled Flows trigger work on a cron, the Ops MCP Server lets external systems monitor and control sessions, and answer_user_prompt resolves any agent-to-human blocks automatically. The result: agents that run 24/7 without anyone watching.
Check Your Understanding
Which MCP server would a CI/CD pipeline use to launch a CAO session?