Skip to content

Composition

Composing agents: chain, Agent.parallel, Plan, or tools=[...]?

Pick by who decides what runs when — pre-scripted by you, LLM-driven, or declared with compile-time validation.

Decision tree

Linear pipeline, output of step N becomes task of N+1?
    → Agent.chain(a, b, c)
      # sugar for Agent(engine=Plan(Step(target=a, name=a.name),
      #                              Step(target=b, name=b.name), …))

Run the same task on N agents concurrently, get the joined output?
    → Agent.parallel(a, b, c)
      # asyncio.gather over a/b/c — returns ONE Envelope whose
      # text() is the labelled-text join.  Call .run_branches(task)
      # (async) if you need typed per-branch list[Envelope].

Let the LLM decide which sub-agent(s) to call (and when, including
in parallel)?
    → Agent(engine=LLMEngine("…"), tools=[a, b, c])
      # the engine fans out automatically when the model emits
      # multiple tool calls in one turn — no config needed

Declared workflow with typed hand-offs, routing, parallel bands,
or crash-resume?
    → Agent(engine=Plan(Step("…", output=…), Step("…", routes={…}), …),
            tools=[…])

Quick reference

Who decides the flow? Use
You, linear and fixed Agent.chain(a, b, c)
You, deterministic fan-out → list Agent.parallel(a, b, c)
You, declared DAG with types / routing / resume Plan(Step(…), …)
The LLM (which to call, when, in parallel) Agent(tools=[a, b, c])

Notes

  • Agent.chain is sugar for a linear Plan. Use it for one-liner sequential handoffs; reach for the explicit Plan the moment you can see a router or a parallel band coming.
  • Agent.parallel returns ONE Envelope (since 0.7.9) whose payload is the labelled-text join of every branch. For typed per-branch access call parallel.run_branches(task) (async) → list[Envelope].
  • tools=[a, b, c] is the LLM-driven path. When the model emits multiple tool calls in one turn, LazyBridge dispatches them concurrently via asyncio.gather — automatic parallelism, no flag.
  • All four shapes compose recursively. A Plan step's target can be an agent built from Agent.chain or Agent.parallel; an Agent.parallel branch can itself contain a Plan. The unit at every level is the same Agent.

See also: scripted composition

  • Chain — sequential composition reference.
  • ParallelAgent.parallel semantics; ParallelAgent return type.
  • As tool — when to pass an agent in tools=[…] directly vs agent.as_tool("alias").
  • Plan — declared orchestration with compile-time DAG validation.
  • Parallelism decision — automatic vs declared.

See also: LLM-driven orchestrators

When the structure of the work is decided at runtime, two factory helpers in lazybridge.ext.planners ship a pre-built orchestrator agent:

  • orchestrator_agent(...) (alias make_planner) — DAG builder; the LLM composes a Plan step by step via builder tools, with compile-time validation.
  • blackboard_orchestrator_agent(...) (alias make_blackboard_planner) — flat to-do list; the LLM manages tasks via set_plan / get_plan / mark_done tools without DAG structure.

Both wrap an LLM with the planning toolkit. Use them when you want LLM-driven dispatch but don't want to author the planning prompt from scratch. See the Plan tool and Blackboard planner recipes.