Skip to content

yaab.multiagent

Multi-agent composition: Sequential, Parallel, Map, Loop, Swarm.

yaab.multiagent

Multi-agent orchestration patterns over the one runtime.

These workflow agents compose other agents and expose the same run / run_sync / as_tool surface as a plain :class:~yaab.agent.Agent, so they nest arbitrarily and drop into tools, graphs, and servers:

  • :class:SequentialAgent — run sub-agents in order, sharing one state;
  • :class:ParallelAgent — run sub-agents concurrently on the same input;
  • :class:MapAgent — fan one sub-agent across many inputs;
  • :class:LoopAgent — re-run a sub-agent until a condition or a cap;
  • :class:Swarm — autonomous hand-off between peer agents;
  • :class:RouterAgent — run exactly one of N branches (exclusive choice);
  • :class:~yaab.flow.Flow — explicit branching, cycles, fan-out/merge, and durable human pauses, when the control flow itself must be inspectable.

Every pattern shares one :class:~yaab.state.State object across all its children for a run, so a value written by one step is read by the next by key. A step can declare writes="key" to capture its (typed) output into that shared state; the next step reads it via {key} instruction injection or a tool. Usage is rolled up across all sub-agents so cost/token accounting stays whole.

Any unit in a pattern may be wrapped in a :class:~yaab.conditions.Step to add when= (an input guard — do I run?), stop= (an output guard — does the pattern stop?), or else_= (a fallback unit on skip or failure). The same three keywords behave identically on every pattern, because every pattern runs its children through one guarded-execution seam (:func:_run_guarded).

Branch dataclass

One guarded branch of a :class:~yaab.multiagent.RouterAgent.

when is the input-guard form of a :class:Condition (a Condition, a (input, ctx) -> bool callable, or an expression string over input/state/deps). Branches are evaluated in declared order; the first whose guard is true is the only agent run.

Step dataclass

The uniform carrier of conditional metadata for any unit.

Wraps an :class:~yaab.agent.Agent, a workflow agent, a tool, or a sub-agent with optional when= (input guard), stop= (output guard), else_= (fallback unit on skip or failure), writes= (capture the unit's output into shared state under a key), and a per-step timeout.

A bare unit in a list is implicitly Step(unit) with no guards, which is what keeps every existing plain [agent_a, agent_b] list working.

SequentialAgent

Bases: _WorkflowBase

Run sub-agents in sequence over one shared state.

Each child runs against the same :class:~yaab.state.State, so a child that declares writes="key" lands its output where the next child can read it (by {key} injection or a tool). pipe_output (default True) keeps the classic convenience of feeding the prior step's text as the next prompt.

Any child may be a :class:~yaab.conditions.Step with when= (skip the step), else_= (run a fallback instead), or writes=. The agent-level stop= is an output guard checked after each step; stop_when= / early_stop= are deprecated aliases that emit a DeprecationWarning.

ParallelAgent

Bases: _WorkflowBase

Run sub-agents concurrently on the same prompt over one shared state.

All branches see the same state object. Each branch that declares writes="key" lands its result under a key downstream steps read by name — two branches writing the same session-scoped key without a reducer is a declared :class:~yaab.state.StateConflictError, not a silent clobber. The returned output is a name -> result map.

A branch may be a :class:~yaab.conditions.Step with when=: its guard is evaluated against the shared input before scheduling, so an excluded branch never runs and is, by default, absent from the result map (the map stays a clean name -> output). Set include_skipped=True to add name -> RunResult(status="skipped") entries for a total map.

MapAgent

Bases: _WorkflowBase

Fan one sub-agent out across many inputs concurrently over one shared state.

Given a list of prompts (or a function that derives them from the incoming prompt), run the same agent on each in parallel and return the list of outputs. max_concurrency bounds simultaneous runs. The children share the run's state object and its session_id so they replay the same session.

Wrap the mapped agent in a :class:~yaab.conditions.Step with when= to filter inputs: each derived input whose guard is false is dropped (absent from the results), so when="len(input) > 0" is the per-input filter.

LoopAgent

Bases: _WorkflowBase

Re-run a sub-agent over one accumulating shared state until a stop condition.

Each iteration runs against the same :class:~yaab.state.State, so a tool that increments state["count"] accumulates across iterations. stop= is the output guard (state-aware: stop="state.score >= 0.9" reads what each iteration wrote); the loop also stops at max_iterations. until= is a deprecated alias of stop=. else_= runs when the cap is hit without stop= firing. pipe_output (default True) feeds the prior output as the next prompt for the common refine-in-place case.

SwarmState

Bases: BaseModel

Shared, mutable state threaded through a swarm via DI.

shared is kept for backward compatibility; under the unified model the swarm's structured state lives on the run's one :class:~yaab.state.State, and the handoff target is an ordinary run-local state write — not a fourth backing store.

Swarm

Bases: _WorkflowBase

Autonomous hand-off between peer agents (swarm) over one shared state.

Each member is augmented with handoff_to_<peer> tools. When an agent decides another is better suited, it calls the handoff tool; the swarm then continues the task with that agent. Every member runs against the same :class:~yaab.state.State, and the handoff target is recorded as a run-local state write (temp:__handoff__) rather than a magic attribute on a separate object. stop= is an output guard checked after each member runs (e.g. stop="len(state['temp:__handoff_log__']) > 4" caps a handoff cycle). Runs until no further hand-off (or a cap).

RouterAgent

Bases: _WorkflowBase

Exclusive-choice routing — run exactly one of N branches.

A peer of Sequential/Parallel/Map/Loop/Swarm that evaluates input guards in declared order and runs exactly one branch (or the default). The routing decision spends zero model calls — it is a plain-Python/expression picker, so it is deterministic and fully auditable. Because exactly one branch runs and its output is returned unmodified, there is no skip-cascade and no merge node.

on_no_match is "default" (run the default branch) or "error" (raise). A router with no matching branch and no default returns a result with status "skipped". writes= captures the chosen branch's output into shared state. Nests like every workflow agent and works as a tool.

from_picker classmethod

from_picker(name: str, picker: Callable[[Any, Any], str], to: dict[str, Any], *, default: Any = None, on_no_match: str = 'default', writes: str | None = None) -> RouterAgent

Build a router from a label-returning picker.

picker(input, ctx) -> label classifies the input; to maps each label to an agent. A picker may only return a key present in to — an unknown label raises immediately (a typo is a loud error, never a silent fall-through to the default). The str-returning picker is adapted to bool guards here, so each :class:Branch is always a boolean guard.