Skip to content

Migrating from 0.7.x to 0.7.9

LazyBridge 0.7.9 is the simplification release. The framework had no real users yet, so we ship breaking changes without deprecation paths or shims. Net public surface change: −1 in lazybridge.__all__ (50 → 49), plus 5 deleted Agent.from_* class methods. No new public concept.

The single LLM-friendliness lever is consistency: one canonical form per concept, errors always raise, no opt-in modes.

If you wrote code against 0.7.x, this page is the deletion-by-deletion codemod you need to run.


TL;DR — what changed

Removed Replacement One-liner
Agent.from_model("X") Agent("X") string-positional shortcut
Agent.from_engine(e) Agent(engine=e) canonical ctor
Agent.from_chain(a, b) Agent.chain(a, b) non-trivial factory kept; alias gone
Agent.from_plan(*steps, store=…) Agent(engine=Plan(*steps, store=…)) canonical ctor
Agent.from_parallel(*agents) Agent.parallel(*agents) non-trivial factory kept; alias gone
AgentRuntimeConfig / ResilienceConfig / ObservabilityConfig flat kwargs on Agent(...) dict-spread for fleet defaults
_UNSET precedence game flat kwargs only each kwarg has a real default
Tool(fn, mode="auto", schema_llm=…, allow_llm_schema=True) Tool(fn) (default mode="signature") or explicit mode="hybrid"/mode="llm" no graceful-fallback ladder
_ParallelAgent (returns list[Envelope]) ParallelAgent (returns one folded Envelope) run_branches(task) for per-branch list
wrap_tool(...) private _wrap_tool use Tool.wrap(...) instead
tool_choice="parallel" tool_choice="auto" parallel is automatic, not a config
Old doc/ directory gone 1.2 MB of stale fragments removed

Plus 9 silent-fallback paths converted to explicit errors — from_step("typo") raises PlanRuntimeError, unknown provider strings raise ValueError, MCP non-object schemas raise ValueError, Envelope.text() on unsupported payloads raises TypeError, etc.


Detailed migrations

1. Factory aliases → canonical ctor

# 0.7.x
agent = Agent.from_model("claude-opus-4-7", tools=[search])

# 0.7.9
agent = Agent("claude-opus-4-7", tools=[search])
# or, more explicit:
agent = Agent(engine=LLMEngine("claude-opus-4-7"), tools=[search])
# 0.7.x
pipeline = Agent.from_plan(
    Step("research"),
    Step("write"),
    store=Store(db="run.sqlite"),
    checkpoint_key="research",
    resume=True,
)

# 0.7.9 — Plan kwargs live on Plan, Agent kwargs on Agent
pipeline = Agent(
    engine=Plan(
        Step("research"),
        Step("write"),
        store=Store(db="run.sqlite"),
        checkpoint_key="research",
        resume=True,
    ),
    name="research_pipeline",   # required when engine is non-LLM (T7)
)
# 0.7.x — alias of Agent.chain
agent = Agent.from_chain(researcher, writer)

# 0.7.9 — only the real factory remains
agent = Agent.chain(researcher, writer)

Agent.from_provider("anthropic", tier="top") is kept (it's the only Agent.from_* that does non-trivial work — resolves a tier alias to the provider's current model).

2. Config objects → flat kwargs

# 0.7.x
agent = Agent(
    engine=LLMEngine("claude-opus-4-7"),
    resilience=ResilienceConfig(timeout=60, max_retries=5, cache=True),
    observability=ObservabilityConfig(session=session, name="agent-A"),
)

# 0.7.9 — flat kwargs (no precedence game, no _UNSET sentinel)
agent = Agent(
    engine=LLMEngine("claude-opus-4-7"),
    timeout=60,
    max_retries=5,
    cache=True,
    session=session,
    name="agent-A",
)

For fleet defaults, use a Python dict spread:

PROD_DEFAULTS = dict(
    timeout=60,
    max_retries=5,
    max_output_retries=2,
    cache=True,
    verbose=False,
    session=session,
)

# Same end-user value as a config object, no precedence game,
# better introspectability, fewer concepts to learn.
research = Agent(**PROD_DEFAULTS, engine=LLMEngine("claude-opus-4-7"), name="research")
write    = Agent(**PROD_DEFAULTS, engine=LLMEngine("claude-opus-4-7"), name="write")

CacheConfig is kept — it carries real semantic value (enabled / ttl), consumed inside LLMEngine as the canonical cache representation. Pass cache=CacheConfig(ttl="1h") for the longer Anthropic TTL.

3. Tool mode="auto" → explicit mode

# 0.7.x
search = Tool.wrap(search_web, name="search")  # default mode="auto"
# silently degrades signature → hybrid → llm depending on schema_llm

# 0.7.9 — both ``Tool.wrap()`` and ``Tool()`` default to mode="signature"
search = Tool.wrap(search_web, name="search")
# Explicit hybrid / llm enrichment now requires opt-in:
enriched = Tool.wrap(
    search_web,
    name="search",
    mode="hybrid",
    schema_llm=engine,
)

Passing mode="auto" raises ValueError at construction.

4. _ParallelAgentParallelAgent

# 0.7.x
fan = Agent.parallel(researcher_us, researcher_eu, researcher_asia)
results = fan("Same task for everyone")   # → list[Envelope]
for r in results:
    print(r.text())
# 0.7.9 — single Envelope (matches the framework invariant)
fan = Agent.parallel(researcher_us, researcher_eu, researcher_asia)
env = fan("Same task for everyone")       # → Envelope (joined)
print(env.text())                          # labelled-text join across branches

# For typed per-branch access:
import asyncio
branches = asyncio.run(fan.run_branches("Same task for everyone"))  # → list[Envelope]
for r in branches:
    print(r.text())

The wrapper Envelope's metadata.nested_* rolls every branch's cost up; the first non-None branch error propagates through .error.

5. wrap_toolTool.wrap()

# 0.7.x
from lazybridge.tools import wrap_tool
t = wrap_tool(some_callable_or_agent)

# 0.7.9 — wrap_tool is private (_wrap_tool); use Tool.wrap() factory
from lazybridge import Tool
t = Tool.wrap(some_callable_or_agent, name="...")  # name= required for callables

The module-level :func:lazybridge.tool (lowercase) is a thin backwards-compat alias for :meth:Tool.wrap and continues to work indefinitely; new code should reach for Tool.wrap so the factory sits next to the constructor on the class.

6. Silent fallbacks → explicit errors

0.7.x behaviour 0.7.9 behaviour
from_step("typo") warns + uses start envelope raises PlanRuntimeError with typo-aware "Did you mean?"
LLMEngine("unknown-model-xyz") warns + routes to Anthropic raises ValueError listing known providers
MCP server emits non-object inputSchema raises ValueError (was silently {})
Envelope.text() on custom class raises TypeError (was str(payload) fallback)
Memory(summarizer_timeout=2.0) warns at construction (timeout almost always fires)
BaseProvider._resolve_model empty raises ValueError (was empty string)

To opt back into the legacy 0.7 silent-default for unknown models (rarely needed):

LLMEngine.set_default_provider("anthropic")  # ⚠ 0.7-style fallback

7. Agent(name=...) required for non-LLM engines

# 0.7.x — silently named "agent"; collided when used as a tool
pipeline = Agent(engine=Plan(Step("research"), Step("write")))

# 0.7.9 — must pass an explicit name
pipeline = Agent(
    engine=Plan(Step("research"), Step("write")),
    name="research_pipeline",
)

The engine factories (supervisor_agent, human_agent, Agent.chain) supply sensible defaults so you don't need to retype name= for them.

8. Agent(model=..., engine=non-LLM) raises

# 0.7.x — model= silently ignored
agent = Agent(model="claude-opus-4-7", engine=Plan(Step("x")))

# 0.7.9 — raises ValueError; pass model on the engine itself
agent = Agent(engine=Plan(Step("x")), name="agent")
# (model is irrelevant to a Plan engine)

New in 0.7.9 — adopt these

The release deleted more than it added, but several new surfaces are worth picking up on the way through:

Symbol Purpose Reference
lazybridge.matrix.provider_capabilities() / native_tool_support() Declarative provider-capability aggregator (ClassVars → typed dict). Read-only; safe for docs / introspection / capability-aware error messages. reference/providers.md
lazybridge.PROVIDER_ALIASES Snapshot of the 12 model-string → provider routing aliases. Read it instead of reaching into _PROVIDER_ALIASES. reference/providers.md
LLMEngine.provider_aliases() Fresh-copy classmethod variant of PROVIDER_ALIASES. reference/providers.md
lazybridge.store.encryption.EncryptedStoreAdapter At-rest Fernet encryption wrapper for Store. Optional install: pip install 'lazybridge[encryption]'. Supports MultiFernet key rotation. reference/state.md
BaseProvider capability ClassVars supports_streaming / supports_structured_output / supports_thinking + supported_native_tools. Subclasses override when a backend doesn't. reference/providers.md
Standard error-message format Every PlanCompileError / PlanRuntimeError / UnsupportedFeatureError carries a four-part body (what failed → what was passed → what's expected → concrete fix snippet). LLM assistants should read the bottom line verbatim. for-llms/error-recovery.md
Session.emit warn-once dedup Per (exporter, exception class) pair, with a suppressed-count summary. No more log floods when an exporter goes flaky. reference/session.md
OTel gen_ai.agent.nesting_level span attribute Distinguishes root agent spans from nested sub-agent spans for dashboarding. guides/advanced/otel.md
examples/llm_assistant/ Six runnable, MockAgent-backed examples covering the canonical patterns. examples/llm_assistant/
docs/for-llms/codegen-contract.md + lazybridge/llms.json LLM-targeted Always/Never rules + machine-readable canonical-form contract. for-llms/codegen-contract.md

What's identical

  • Agent("model") string-positional shortcut works as before.
  • Agent(engine=LLMEngine(...)) canonical ctor unchanged.
  • Agent.chain / Agent.parallel / Agent.from_provider kept.
  • Tool.wrap(fn, name=...) factory unchanged surface; default mode now matches the explicit Tool() ctor (both mode="signature").
  • All sentinels (from_prev, from_start, from_step, from_parallel, from_parallel_all, from_memory, from_agent) unchanged.
  • All ext modules (lazybridge.ext.hil, .mcp, .planners, .evals, .viz, .gateway) unchanged.
  • CacheConfig kept (carries real semantic value).
  • Tool and tool both still public; Tool.wrap() is the canonical construction path.

Mechanical migration

LazyBridge does not ship a turnkey codemod for the 0.7 → 0.7.9 migration; the surface that changed is small enough that the patterns in the TL;DR table above are usually the fastest path.

For ad-hoc migration, pytest against the 0.7 test you're porting will surface every breakage point one at a time — the new error messages tell you exactly what to change at each call site.