Upgrading to 2.0¶
kneo-agent2.0.0 shipped 2026-06-03 (on PyPI). This guide covers the breaking changes from 1.5.x;pip install --upgrade kneo-agentmoves you to 2.0.0.
A short, practical "what breaks and how to adapt" walkthrough for users on
1.5.x. The full list is in CHANGELOG.md; this page
covers only the parts that affect existing code. 2.0 is a major —
behaviour-breaking changes are allowed and bundled here so you adapt once.
TL;DR¶
- 2.0 is breaking. Unlike every 1.x release,
pip install --upgrademay change behaviour without a code change. Read the breaking changes below before upgrading. - Most users are affected by at most one or two: the
RetryMiddlewaretool-retry default flip (Bridge only), theRunConfigpartial-override + validation changes, the OpenAI Agents Adapter call interface, and the workflow.stream()honesty fix. - Nothing was removed from the top-level namespace — these are behaviour changes, not surface removals. The public-API name snapshot is unchanged except where noted.
- Read this page first if you: rely on
RetryMiddlewareretrying tool calls; pass a partialRunConfigper run; settemperature/max_iterationsviawith_defaults/skill defaults; pass a custom runner to the OpenAI Agents Adapter; or call.stream()on a Concurrent / Handoff / GroupChat / graph (or non-streamable Sequential)Workflow.
Breaking changes¶
1. RetryMiddleware no longer retries tool calls by default¶
RetryMiddleware(retry_tool_calls=...) now defaults to False (was
True). Non-idempotent tools — payments, writes, sends — are no longer
re-executed on a transient failure unless you opt in. 1.3.0 shipped a docstring
.. warning:: flagging this; 2.0 makes the flip. No deprecation warning is
emitted — this is a clean major-version flip.
Scope: RetryMiddleware is a Bridge-runtime feature (a documented no-op
under the Adapter / Native paths), so this only changes behaviour for Bridge
agents that use it.
Migration — if you want tools retried (your tools are idempotent, or you accept re-execution), opt back in explicitly:
from kneo_agent.middleware import RetryMiddleware
# 1.x default behaviour, now explicit:
RetryMiddleware(max_attempts=3, retry_tool_calls=True)
Model-call retries are unchanged (retry_model_calls still defaults to True).
2. A partial RunConfig override no longer clobbers agent defaults¶
In 1.x, a per-run RunConfig replaced max_iterations, temperature, and
tools wholesale — even when you left them at RunConfig's own defaults.
Passing RunConfig(system_prompt="…") silently reset max_iterations to 10,
temperature to 0.7, and tools to [], discarding whatever the agent was
built with.
In 2.0, a partial override changes only the fields you actually set; unspecified fields keep the agent / skill default.
agent = (
AgentBuilder()
.use_runtime(my_runtime)
.with_defaults(max_iterations=25, temperature=0.2)
.build()
)
# 1.x: max_iterations silently snaps back to 10, temperature to 0.7
# 2.0: max_iterations stays 25, temperature stays 0.2 — only system_prompt changes
await agent.run("…", run_config=RunConfig(system_prompt="Be terse."))
Migration — if you were relying on a partial override to reset fields to
RunConfig's defaults, restate them explicitly:
If you already restate every field in every override (the defensive 1.x pattern), nothing changes for you.
3. RunConfig validation now applies to merged agent / skill defaults¶
Coupled to change 2: in 1.x, RunConfig.__post_init__ validated values only at
direct construction, so a bad value supplied through AgentBuilder.with_defaults(...)
or a skill-file defaults block bypassed validation, and a non-numeric value
raised TypeError rather than the ValueError the changelog promised. 2.0
revalidates the resolved config, so:
- An out-of-range agent / skill default (e.g.
max_iterations=0,temperature=-1) now raisesValueErrorwhen the run config is built, instead of silently taking effect. - A non-numeric value raises
ValueError(wasTypeError). temperaturevalidation is stricter:bool(True/False) and non-finite values (NaN/inf) are now rejected withValueError(1.x accepted them —True >= 0andnan < 0/inf < 0all passed the old< 0check).
Migration — fix any out-of-range defaults in your with_defaults(...)
calls and skill files; if you catch this error, catch ValueError.
4. OpenAI Agents Adapter call interface; ADK & LangChain are pass-through¶
The OpenAI Agents Adapter's call interface now matches the real
openai-agents Runner: it calls Runner.run(starting_agent=…) and
Runner.run_streamed(…).stream_events() (1.x used an invented
runner.run(agent=…) / runner.stream() shape) and reads object- or
dict-shaped results.
Migration — if you passed a custom runner to
AdapterAgentFactory.for_openai(...) that implemented the old
run(agent=…) / stream() shape, update it to run(starting_agent=…) /
run_streamed(…).stream_events().
Note — adapter fidelity (2.0–2.1 vs 2.2.0). In 2.0–2.1 the
OpenAIAgentsAdapter/LangChainAdapterwere demonstration adapters, validated against SDK-shaped fakes and not fully wired to the live SDKs. Resolved in 2.2.0 (additive, no signature change): the OpenAI adapter builds a realagents.Agentand reads tool metadata fromitem.raw_item, and the LangChain adapter handles both a LangGraphcreate_agentgraph and a legacyAgentExecutor. The native runtime (NativeRuntimeFactory.for_openai(...)) remains a fine choice for the OpenAI path. See the 2.2.0 release notes.
The Google ADK and LangChain Adapters are pass-through: the
wrapped runner / executor owns its own configuration, and RunConfig
run-level fields are not injected per call. (An earlier 2.0 draft had the
LangChain adapter forward max_iterations — reverted: the pinned langchain 1.x
has no AgentExecutor, so that was a no-op there and clobbered a user-set
value on a no-override run.) temperature is uninjected on every adapter.
5. Workflow .stream() is honest about what can stream¶
Several orchestration workflows exposed a .stream() method whose only
behaviour was to run the whole workflow to completion and emit the result as a
single chunk — while supports_streaming() correctly returned False and
Agent.stream() raised StreamingNotSupportedError. That split was
contradictory: a direct .stream() call "worked" (degenerately) while the
agent-level path refused.
In 2.0, .stream() raises StreamingNotSupportedError on workflows that
cannot incrementally stream — ConcurrentWorkflow, HandoffWorkflow,
GroupChatWorkflow, and the graph Workflow — matching their
supports_streaming() == False. SequentialWorkflow still streams genuinely
when its participants can; but when a participant can't stream, its
.stream() now raises StreamingNotSupportedError instead of the 1.x
NotImplementedError (the new type does not subclass NotImplementedError,
so update any except NotImplementedError around it).
Migration — check supports_streaming() before calling .stream(), or use
.run() for the non-streaming workflows:
if workflow.supports_streaming():
async for chunk in workflow.stream(messages, config):
...
else:
result = await workflow.run(messages, config)
If you need the old run-to-completion-then-emit behaviour, call .run() and
yield the result yourself.
What didn't change¶
- The top-level
kneo_agent.__all__name set — 2.0 changes behaviour, not the exported surface (the public-API snapshot test is unchanged save for any explicitly noted entry). Names you import keep importing. SequentialWorkflowstreaming — unchanged.temperatureon the OpenAI Agents adapter — still not injected (now consistent across all adapters by design).- The Bridge / Native execution paths'
RunConfighandling, model-call retry defaults, MCP knobs, the middleware bundle's other defaults, and the provider version floors. - The factory advisory
UserWarnings (BridgeAgentFactory.for_openai/for_google_adk) remain supported advisories — not promoted to deprecations. Both entry points keep working.
CHANGELOG and policy¶
Full diff: CHANGELOG.md. Public-API stability guarantees
and the deprecation policy: api_stability.md. The
per-item reasoning and the scope-freeze decisions behind this guide live in
../releases/archive_TODO-2.0.md.