Agent.as_tool¶
Wrap an agent as a Tool that another agent can call. The default
shape is the same as passing the agent directly into tools=[...];
the explicit form unlocks two extras: a different surface name than
the agent's own name=, and a verify= judge-and-retry loop around
every call.
Signature¶
agent.as_tool(
name=None, # surface tool name; defaults to agent.name
description=None, # LLM-facing description; defaults to agent.description
*,
verify=None, # Agent or callable[[str], Any] — judge-and-retry gate
max_verify=3, # max attempts when verify=...
) -> Tool
The returned Tool has the schema (task: str) -> Envelope (the
framework sets returns_envelope=True automatically so engines roll
up cost / token / latency metadata correctly through the call).
The implicit form — Agent(tools=[other_agent]) — is canonical for
the no-rename, no-verify case. See
Canonical vs sugar for the
exhaustive comparison.
Synopsis¶
as_tool does three things, in order of how often you'll reach for
them:
- Wraps an agent so it satisfies the
Toolcontract. The wrapped tool'snamebecomes the LLM-facing tool name; the wrapped tool'sdescriptionis what the model reads when deciding whether to call it. - Optionally renames. If you pass
name="alias", that's what the LLM sees; the source agent's ownname=is unchanged. The alias is also whatfrom_agent("alias")reads from theStore(see Store). - Optionally adds a
verify=judge. Every invocation runs up tomax_verifytimes; after each run the judge sees the output and replies"approved"(case-insensitive) or"rejected: <reason>". On rejection the judge's feedback is threaded into the next attempt's task. This is the "Option B" placement — judge sits at the tool-call boundary, not around the agent's own engine.
The implicit form (tools=[other_agent]) covers (1). When you need
(2) or (3), construct explicitly with as_tool(...).
When to use it¶
- You want a different surface name than
agent.name=. The agent's ownname=is the authoritative key forStorewrites, graph nodes, and cost rollup. If you want the LLM to see a different name (e.g.researchinstead ofsenior_researcher_v2), passname="research"toas_tool. - You want a different LLM-facing description. Override
description=to give the model a more focused or context-specific cue without changing the source agent. - You want a judge-gated call.
verify=judgeruns the judge after every call, retrying up tomax_verifytimes with the judge's feedback. Use this for high-stakes outputs where the parent agent should not see "first try, possibly wrong" results. - You want to expose
Agent.parallel(...)as a single tool. TheParallelAgentreturned byAgent.parallel(...)already returns ONE Envelope from__call__/runsince 0.7.9; itsas_tool()just delegates so the outer agent reads the same labelled-text join the runner produces directly.
When NOT to use it¶
- For the simple "agent A calls agent B" case. Just pass the
agent:
Agent(tools=[other_agent])is canonical and saves the noise. The framework wraps it the same way internally. - When the wrapped agent has structured output. The default
schema is
(task: str) -> str. If the parent needs a typed payload from the child, orchestrate withPlanandStep(target=child, output=Model)instead —Planpreserves typed envelopes between steps;as_toolflattens them. - As a substitute for
verify=on the parent agent.Agent(verify=judge)wraps the parent's whole run; this is the right choice when the policy applies to every output. The per-toolverify=is for when only specific sub-tools need gating.
Example¶
from lazybridge import Agent, LLMEngine
def search(query: str) -> str:
"""Search the web for ``query`` and return the top three hits."""
return "..."
researcher = Agent(
engine=LLMEngine("gemini-3-flash-preview"),
tools=[search],
name="senior_researcher_v2",
)
judge = Agent(
engine=LLMEngine(
"claude-opus-4-7",
system='Respond "approved" or "rejected: <reason>".',
),
name="judge",
)
# 1) Implicit — pass the agent directly. Tool name = "senior_researcher_v2".
orchestrator = Agent(
engine=LLMEngine("gemini-3-flash-preview"),
tools=[researcher],
)
# 2) Explicit alias — tool name "research", source agent name unchanged.
orchestrator = Agent(
engine=LLMEngine("gemini-3-flash-preview"),
tools=[
researcher.as_tool(
name="research",
description="Find three high-quality sources for a topic.",
),
],
)
# 3) Verified call — judge gates every research invocation, up to two attempts.
orchestrator = Agent(
engine=LLMEngine("gemini-3-flash-preview"),
tools=[
researcher.as_tool(
name="research",
verify=judge,
max_verify=2,
),
],
)
result = orchestrator("write a paragraph on bee population trends")
print(result.text())
# 4) Parallel fan-out as a single tool — wrapper Envelope already carries the join.
fan_out = Agent.parallel(researcher_us, researcher_eu, researcher_asia)
orchestrator = Agent(
engine=LLMEngine("gemini-3-flash-preview"),
tools=[fan_out.as_tool(name="multi_region_research")],
)
Pitfalls¶
- Default schema is
(task: str) -> str. The wrapped agent'soutput=Modelis not preserved in the tool's signature — the LLM sees a string contract regardless. For typed downstream consumption use aPlanstep withStep(output=Model)instead. max_verifyis the upper bound, not the target. A judge that's too strict can fail every attempt, costingmax_verifyfull agent runs per call. Pick a defensible verdict prompt and keepmax_verify=2or3unless you have a reason.- Verify retries cost tokens on both the child and the judge.
Each attempt is a full child-agent run plus a judge run. Use
verify=only on calls that genuinely warrant the cost. - The alias is the Store key.
as_tool(name="research")makes__agent_output__:researchthe auto-write key — not__agent_output__:senior_researcher_v2.from_agent("research")reads it back. Keep aliases stable across runs that share a Store. - Long nested chains share one Session. Pass
session=sesson the outer agent only — inner agents (whether wrapped viaas_toolor passed directly) inherit it.usage_summary()aggregates cost across the whole tree. - Pre-existing
as_tool()callsites are still supported.tools=[agent.as_tool("name")]andtools=[agent]are equivalent when the agent already hasname="name". Don't rewrite working code mechanically; prefer the implicit form for new code, and reach foras_toolwhen you actually want one of its three jobs.
See also¶
- Chain — when you want a fixed sequential pipeline rather than an LLM-directed dispatch.
- Parallel —
Agent.parallel(...).as_tool()folds scripted fan-out into a single tool. - Tool — the surface every
as_tool()call produces. - Guides → Full → verify= (Phase 3) — verify around the parent agent's whole run versus per-tool gating.
- Canonical vs sugar —
the rules for when to call
as_toolexplicitly versus relying on the implicittools=[agent]form.