Migrating from 0.1 → 0.2
Loom 0.2.0 is mostly additive but ships two breaking changes. This guide shows what to change and why.
TL;DR
- agent = Agent("You are helpful.")
+ agent = Agent("You are helpful.", model="claude-opus-4-7")- with pytest.raises(ValueError):
- Agent("hi", model="bad-spec")
+ from loomflow.core.errors import ConfigError
+ with pytest.raises(ConfigError):
+ Agent("hi", model="bad-spec")That’s it for breaking changes. Everything else in 0.2.0 is new features and polish.
Breaking change 1: model= is required
Agent(...) used to silently default to EchoModel when model= was
omitted. Users got "Echo: ..." output and assumed they were talking
to an LLM. v0.2.0 fails fast with a helpful error:
ConfigError: Agent() requires a `model` argument. Pass one of:
model='claude-opus-4-7' (Anthropic, needs ANTHROPIC_API_KEY)
model='gpt-4o' (OpenAI, needs OPENAI_API_KEY)
model='echo' (zero-key fake — text echoes the prompt; for dev/tests)
model='mistral-large' (LiteLLM, also: command-, bedrock/, vertex_ai/, ollama/, ...)
model=AnthropicModel(...) or any Model-protocol instance for full control.Migration
| If you want… | Pass |
|---|---|
| To talk to Claude (most readers’ real use case) | model="claude-opus-4-7" |
| To talk to GPT | model="gpt-4o" |
| To talk to Mistral / Cohere / Bedrock / etc. | model="mistral-large" (etc.) |
| The zero-key fake for tests / dev | model="echo" |
| Full control over construction | model=AnthropicModel("claude-opus-4-7", ...) |
Why we did it. Default behaviour that produces plausible-looking but wrong output is the worst kind of footgun. If users want the echo behaviour they have to opt in explicitly. Easier to debug, easier to read, no silent fallback.
Breaking change 2: Resolver errors → ConfigError
_resolve_model("totally-unknown-spec") used to raise ValueError.
Now it raises ConfigError (from loomflow.core.errors), matching
the rest of the configuration-error vocabulary.
- with pytest.raises(ValueError, match="unknown model spec"):
- Agent("hi", model="bad-spec")
+ from loomflow.core.errors import ConfigError
+ with pytest.raises(ConfigError, match="unknown model spec"):
+ Agent("hi", model="bad-spec")If you were catching the resolver error in production code:
try:
agent = Agent(prompt, model=user_input_spec)
- except ValueError:
+ except ConfigError:
...What’s new in 0.2.0 (non-breaking)
Provider coverage
LiteLLMModel. Single adapter for ~100 providers via the LiteLLM
SDK. The string resolver dispatches the common prefixes:
Agent("...", model="mistral-large") # → LiteLLMModel
Agent("...", model="command-r-plus") # → LiteLLMModel
Agent("...", model="bedrock/anthropic.claude-3-sonnet-20240229-v1:0")
Agent("...", model="vertex_ai/gemini-pro")
Agent("...", model="ollama/llama3") # local Ollama
Agent("...", model="groq/llama-3.1-70b")
Agent("...", model="litellm/claude-3-haiku") # explicit opt-inNew embedders: VoyageEmbedder, CohereEmbedder. Same shape as
OpenAIEmbedder.
Polish
Agent.__repr__()for dev-time inspection.RunResult.total_tokensandRunResult.durationproperties.agent.consolidate() -> intreturns the count of new facts extracted (wasNone).tools=my_fn. Pass a single callable orTooldirectly without list-wrapping. Lists still work.
New plugin API
agent.add_tool(fn). Register a tool after construction.agent.remove_tool(name). Unregister by name.agent.tools_list(). List registered tool names.- Public introspection properties:
agent.model,agent.memory,agent.runtime,agent.tool_host,agent.budget,agent.permissions. agent.recall(query, kind=, limit=). Convenience wrapper aroundagent.memory.recall(...).
Background work
ConsolidationWorker(memory, interval_seconds=60). Long-running
anyio task that periodically calls memory.consolidate(). Surfaces
new fact counts via on_consolidated(count) callback; consolidator
failures via on_error(exc) so a transient hiccup doesn’t kill the
worker. Doubles as an async context manager:
async with ConsolidationWorker(memory, interval_seconds=60):
await main() # worker runs in background
# Worker is cancelled when the block exits.Common pitfall: stale install shadowing the editable
If you have a previous pip install loomflow in your env, an
editable install (pip install -e .) of 0.2.0 may not take precedence
because pip’s regular install sometimes wins on the import path:
$ python -c "import loomflow; print(loomflow.__version__)"
0.1.0 # ← stale; expected 0.2.0Fix:
pip uninstall loomflow -y
pip install -e '.[dev,...]'Verify with:
python -c "import loomflow; print(loomflow.__version__, loomflow.__file__)"
# Should print: 0.2.0 /your/checkout/path/loomflow/__init__.pyThis is a Python packaging quirk, not a Loom bug. But worth
flagging since it produces confusing AttributeError: ... has no attribute 'total_tokens' failures when 0.1’s RunResult is imported
instead of 0.2’s.