Migrating from 0.16 → 0.17
Read this if you are upgrading an app from Loomflow 0.16. In 0.17 every subsystem was checked against its reference (provider SDKs and docs, the MCP / A2A / OpenTelemetry specs, the papers behind the architectures, the Agent Skills spec) and each defect found was fixed. Most of that needs nothing from you, but some defaults changed. This page lists the changes that can make existing code behave differently, and what to do about each. The changelog has everything else.
Models
effort=reaches every current Claude model. Claude 5.x, Fable, Opus 4 / 4.1 and Haiku 4.5 used to drop it with a warning; they now think at the level you set, and you pay for the thinking tokens. Each model gets the levels it accepts (minimalbecomeslowon Claude), and an effort string that isn’t one of the six levels raisesValueError. See Reasoning effort.- Retries.
RetryPolicyuses full jitter by default: each delay is drawn from 0 up to the backoff. PassRetryPolicy(jitter=0.1)for the old ±10%. The SDK clients loomflow builds no longer retry on their own, an OpenAIinsufficient_quota429 is no longer retried, and aRetry-Afterlonger thanmax_delay_sends the retries instead of sleeping. See RetryPolicy. - Claude structured output uses native JSON outputs on Claude 4.5
and later, so a run with
output_schema=can call its tools, where it used to be forced straight to the answer. See Providers. - Gateway ids are priced (0.17.1).
anthropic/claude-haiku-5.5,openai/gpt-5-miniand the like used to cost $0, somax_cost_usdbudgets never tripped. They are now priced as the vendor’s model, and from 0.17.2 get its capabilities too. See Gateways.
Permissions and hooks
Mode.ACCEPT_EDITSauto-approves only the built-inwriteandedittools. Every other destructive tool,bashincluded, now asks. Name your own editing tools withStandardPermissions(edit_tools=[...]). See Permissions.- An approval handler approves only with
True, an action name ("allow", …) or anApprovalDecision. Anything else, such as"deny"or{"approved": False}, used to be read withbool()and approved; it now denies. - A pre-tool hook that times out or raises denies the call. A
timed-out hook used to count as allow, and a raising one crashed the
run.
HookRegistry(timeout_denies=False)restores the old timeout behaviour. See Hooks.
MCP
- A tool name that two servers share is exposed as
server__tool(wasserver.tool), because providers reject.in tool names. The dotted name still resolves. A name only one server has stays bare. See MCP.
Durable runs
- A finished run’s steps never replay into a later run on the same
session. A run that raised still replays when you run it again, and
Agent.resumestill replays the last run. See Runs and epochs. - Journals are JSON.
SqliteRuntimeandPostgresRuntimeskip rows pickled by earlier releases, so those steps run again, unless you passserializer="pickle". Only do that for a store no untrusted party can write. - Journals and checkpoints are kept per user. Runs without a
user_idstill read what earlier versions wrote. A run with auser_idwon’t replay journal entries or resume checkpoints that earlier versions wrote for its session.Agent.list_checkpointstakesuser_id=. - Signals.
wait_for_signal(..., timeout_s=)raisesTimeoutError. Undelivered signals are kept for at most 10,000 sessions and an hour (max_pending_signal_sessions,pending_signal_ttl_sonInProcRuntimeandJournaledRuntime).
Sandboxes
FilesystemSandboxresolves relative paths against its first root (base=), not the process’s working directory.OSSandboxgives the tool a minimal environment, not the parent’s, where your API keys live. PassOSSandbox(env={...})for the variables a tool needs. On macOS, Seatbelt now confines reads too: the tool sees only the system libraries, the Python installation, its own package, the roots and a private temp dir. See Sandboxes.
Telemetry and serving
- OpenTelemetry spans follow the GenAI conventions:
chat {model},execute_tool {tool}andinvoke_agent, with the oldloom.*name in theloom.span.nameattribute. Dashboards keyed on OTel span names need updating; the in-memory, console and JSONL sinks keep theloom.*names. Metrics aregen_ai.client.*and no longer carrysession_id,turnsoruser_id. See Semantic conventions. - The serve
/streamendpoint sends a failure after the stream started asevent: stream_error(it reusederror). See Serving.
Budgets
max_wall_clockandper_user_max_wall_clockcount time spent running, not time since the budget was created. Code that callsallows_step/consumeitself wraps its work inbudget.running(). See Budget caps.
Architectures and workflows
- Debates vote on each debater’s
Final answer:line, andparse_scorereads “7/10” and “score: 7” as 0.7. The Supervisor’sforward_messageends the turn. - Reflexion lessons stay with their user. A run recalls its own
lessons plus any tagged
user_id="*". Untagged lessons written by earlier versions are no longer recalled. See Reflexion. - Workflows check their edges when the run starts: an edge, route
or default naming a node that was never added raises
ValueError. A workflow stops at an Agent node that comes back interrupted, withWorkflowResult.interruptedset. See Workflow. A2AClientspeaks A2A v1.0 and falls back to v0.3. Passprotocol_version="0.3"to pin the old protocol. See A2A.
Config, skills and evals
Agent.from_config/from_dictreject unknown keys withConfigError, naming the closest known key.- Skill names and descriptions are validated against the Agent
Skills spec: no leading or trailing hyphen or
--in a name, no XML tags in a description. A badSKILL.mdraisesSkillError. - An eval metric that raises fails the gate: the case records it in
CaseResult.metric_errors, andpassed/assert_thresholdsfail. - The unused
dbosandtemporalextras are gone. Drop them from your install line.
Errors
- Errors from
agent.stream()andWorkflow.parallelare raised as-is, not wrapped in anExceptionGroup. See Errors.
0.17.2: waiting for background work
Agent.aclose() waits for the agent’s background work (the
auto_extract fact extraction that runs after run() returns) and
returns how many tasks were still running. Call it before reading the
facts a run just produced, or at shutdown. See
aclose.
0.17.3: regression fixes
Upgrade if you hit any of these on 0.17.0 to 0.17.2; no code changes are needed.
agent.memory.forget()didn’t stop a running background extraction, which could write the erased facts back. It now does.- Under
SubprocessSandboxorOSSandbox, the workspace and living-plan tools failed on every call, and so did a skill’stools.pytools. Both work now; see Sandboxes. - A Seatbelt child started in the parent’s working directory and failed to start when the profile couldn’t read it. It now starts in its first root, so relative paths in a tool resolve there.
- An interrupted Agent branch of
Workflow.parallelreachedmergeas an internal object. It now stops the run withWorkflowResult.interruptedset; see Parallelism. - A role model given as an OpenAI or DeepSeek id
(
Reflexion(evaluator_model="gpt-4.1-mini")) failed when its key was only in the agent’sTuning(secrets=...). It now takes the key from there; see Secrets. - ReWOO’s default planner prompt told the model to reference steps as
{En}, which the executor didn’t read, so the step got that literal text. The prompt now says{{En}}, and{En}is read too.