LazyBridge ships several factory functions and classmethod shortcuts
that exist for ergonomic reasons. Each is sugar over a more explicit
canonical form built from the framework's primitives (Agent,
LLMEngine, Plan, Step, HumanEngine, Tool, …).
Knowing the canonical form behind every sugar is useful because:
It teaches the framework's actual mental model (Engine + Tools + State).
Not every sugar is a pure alias — some build extra structure or
return different types. This page calls those out so you know what
the sugar buys you and what (if anything) it costs.
Tutorials and code reviews should lead with the canonical form so
the engine choice is visible at the call site.
The shape used in every "Canonical" block below is the same one
examples/ uses: each constructor argument on its own line and
result = agent(task) on a separate line from the print.
Pure alias. The first positional argument is interpreted as a model string and threaded through to LLMEngine(...) internally. Hides which engine drives the agent at the call site.
Not pure sugar. Builds an LLMEngine whose model string is a tier alias (super_cheap / cheap / medium / expensive / top); each provider class maps the alias to its current lineup. Use when you want "freshest model in tier X" without pinning a date-stamped name.
No sugar — write the canonical form. Plan's kwargs (max_iterations,
store, checkpoint_key, resume, on_concurrent) live on Plan(...);
Agent's kwargs (tools=, session=, name=, …) live on Agent(...).
# Canonical Pattern A — Step.target is the agent itself, no tools= neededfromlazybridgeimportAgent,Plan,Steppipeline=Agent(engine=Plan(Step(target=researcher,name=researcher.name),Step(target=writer,name=writer.name),),name="chain",)# Canonical Pattern B — Step references by name, agents in tools=pipeline=Agent(engine=Plan(Step("research"),Step("write")),tools=[researcher,writer],)
Both Patterns are canonical. Pattern A is what Agent.chain produces
internally — no tools= needed because Plan dispatches Agent
targets via target.run() directly. Pattern B is more readable when
many agents share a single tool-map at the top level.
Sugar
Expands to
Differences
Agent.chain(researcher, writer)
Pattern A above, with name="chain"
Not a pure alias — it constructs the Plan + Step graph for you, but the result is structurally identical to canonical Pattern A. Targets are agents (not name strings); no tools= needed.
# Canonical (no Agent-shaped equivalent — this IS the canonical form)fromlazybridgeimportAgentmulti=Agent.parallel(researcher_a,researcher_b,researcher_c)env=multi("Same task for everyone")# -> Envelope (labelled-text join in .text())# For typed per-branch list[Envelope]: branches = await multi.run_branches(task)
Not sugar over Agent. Returns ParallelAgent, a sibling class whose __call__ produces ONE Envelope whose payload is the labelled-text join of every branch ([name]\n<output>) — same shape as Plan's from_parallel_all aggregator, with transitive cost rollup and first-error short-circuit. For typed per-branch access (list[Envelope]) call parallel.run_branches(task) (async). Use this when you want every branch unconditionally; to let the LLM decide which branches to invoke, use Agent(tools=[a, b, c]) instead; to run concurrent steps that aggregate via from_parallel_all, use a Plan parallel band (Step("a", parallel=True)).
Pure alias with a kwarg split: HIL-engine kwargs go to HumanEngine(...), remaining **agent_kwargs flow to Agent(...). Lives in lazybridge.ext.hil (not on Agent) so the core package doesn't have to import the ext-side engine.
# Canonical — explicit ``Tool.wrap()`` factory pins the LLM-visible name# even if the function is renamed, keeping tool-maps and plan# references stable across refactors. This is the form the framework# docstring (lazybridge/__init__.py) flags as canonical.fromlazybridgeimportToolagent=Agent(engine=LLMEngine("claude-haiku-4-5"),tools=[Tool.wrap(search_web,name="search_web")],)# Sugar — bare callable. Backward-compatible; auto-wrapped with# ``Tool(search_web, name=search_web.__name__)``. Convenient for# one-shot scripts; prefer the explicit form in production.agent=Agent(engine=LLMEngine("claude-haiku-4-5"),tools=[search_web],)# Advanced — direct ``Tool`` constructor when you need ``mode=`` /# ``strict=`` / ``schema_llm=`` / a custom name.fromlazybridgeimportToolsearch=Tool(search_web,name="search",description="Search the web for the query.",mode="signature",)agent=Agent(engine=LLMEngine("claude-haiku-4-5"),tools=[search])
Sugar / variant
Canonical
Differences
tools=[search_web] (bare callable)
Tool.wrap(search_web, name=search_web.__name__)
Backward-compatible auto-wrap. Build-time, when the agent constructs its tool-map. Convenient for one-shot scripts; refactor-fragile because the LLM-visible tool name is whatever __name__ happens to be — rename the function and every plan reference / tool-map key changes silently.
tool(search_web, name="search") (lowercase)
Tool.wrap(search_web, name="search")
Pure alias. The module-level lazybridge.tool is a thin shim that calls Tool.wrap — kept indefinitely so existing imports work. New code should reach for Tool.wrap so the factory sits next to the constructor on the class.
(no callable-introspection canonical — this IS the canonical for pre-built schemas)
Not sugar over Tool(callable, …). Used when the JSON Schema is already known (MCP servers, OpenAPI bridges, third-party tool registries). Bypasses the schema builder and sets _definition directly.
# Canonical — the agent's own name= becomes the tool nameresearcher=Agent(engine=LLMEngine("claude-haiku-4-5"),name="research",tools=[search],)orchestrator=Agent(engine=LLMEngine("claude-haiku-4-5"),tools=[researcher],# <-- pass the agent directly)
Sugar
Expands to
Differences
researcher.as_tool("deep_research")
A Tool whose func calls researcher.run and whose name is the alias
Not pure alias. Use when you want a different surface name than the agent's own name=, or when wrapping a duck-typed agent that doesn't have an explicit name=. Also takes verify= / max_verify= to wrap the call in a judge/retry loop — a feature tools=[researcher] does not expose.
Tool.wrap(researcher, name="deep_research")
Equivalent to researcher.as_tool("deep_research")
Pure alias of as_tool for agent-like inputs. Useful when you're building a tool list programmatically (single dispatcher for callables, agents, and Tools).
# Canonical (sync) — what every runnable example in examples/ usesresult=agent(task)print(result.text())
Form
When
agent(task) (sync)
Canonical entry point.__call__ detects whether an event loop is already running and either runs asyncio.run or schedules a coroutine with caller contextvars.
await agent.run(task)
When you're already inside an async def caller. Same semantics as agent(task), no event-loop detection.
async for chunk in agent.stream(task)
When you want incremental tokens / events instead of one final envelope.
# Canonical — the pool's route tool, named explicitly per local action spacefromlazybridgeimportAgent,AgentPool,LLMEngine,concludepool=AgentPool(max_depth=8)worker=Agent(name="worker",engine=LLMEngine("claude-haiku-4-5",max_tool_calls_per_turn=1),tools=[pool.as_tool("ask_pool"),conclude],)pool.register(worker,...)# register AFTER construction
pool.as_tool("ask_pool") is not sugar over tools=[agent]. The two
express different things: tools=[agent] is a static, one-way edge
(this agent may call that one), while pool.as_tool(...) exposes a whole
bounded local action space the agent can route within at runtime —
including cycles, which tools=[agent] cannot express. There is no
shorter canonical form; the explicit tool name is what scopes one local
world from another (see
Dynamic graph and
Pool chains).
Form
When
tools=[other_agent]
A fixed, one-way agent → agent call. No cycles, no runtime topology.
pool.as_tool("ask_pool")
A bounded local action space chosen at runtime; supports cycles, gateways, and conclude.
Tutorials, code reviews, the example you ship in the README
No. Canonical form makes the engine choice visible.
Internal one-liners when the engine choice is uninteresting (Agent(engine=Plan(...)) for a 3-step pipeline, human_agent(timeout=60) for a one-shot gate)
Yes.
Production code with structured config (the agent is built once, configured via runtime= / resilience= / observability=)
No. Canonical form composes more cleanly with config objects.
When you're using a tier alias (Agent.from_provider("anthropic", tier="top"))
Yes. This is the canonical way to pin a tier without a date-stamped model name.
Scripted fan-out (Agent.parallel(...))
Yes. This is the canonical form — there is no Agent-shaped equivalent.