Skip to content

Migrating from 0.x to 1.0

This guide covers everything that changes between the final 0.24.x releases and selectools v1.0.0. It is short on purpose: 1.0 is a stability declaration, not a rewrite. If your code runs warning-free on 0.24.x with python -W error::DeprecationWarning, you are already 1.0-ready except for the items below.

For competitor migrations (LangChain, CrewAI, etc.) see Migration Guides.


What semver means at 1.0

From v1.0.0 onward, selectools follows semantic versioning over its stable surface, as defined in the Deprecation Policy:

  • Every public symbol carries a stability marker: @stable, @beta, or @deprecated.
  • @stable APIs are frozen. Breaking changes to any @stable symbol require v2.0.0.
  • @beta APIs are excluded from the semver guarantee. They may change in a minor release (1.1, 1.2, ...) without a deprecation cycle. The marker — not the version number — is the contract.
  • Deprecations continue to follow the two-minor-release window: anything deprecated in 1.X is not removed before 1.X+2.

Check a symbol's marker at runtime via obj.__stability__, or see Stability Markers.


Breaking change: AgentConfig.hooks removed

The dict-based hooks parameter — deprecated since the observer system shipped (see ADR-002) — is removed in 1.0. AgentConfig(hooks={...}) is no longer accepted. Migrate to AgentObserver (or AsyncAgentObserver for async handlers), which receives richer data: a stable run_id for the whole run, a call_id per tool invocation, the resolved system prompt, and structured results.

Before (0.x):

from selectools import Agent, AgentConfig

def log_tool(tool_name, tool_args):
    print(f"-> {tool_name}({tool_args})")

agent = Agent(
    [search_tool],
    config=AgentConfig(
        hooks={
            "on_tool_start": log_tool,
            "on_agent_end": lambda response, usage: print(usage),
        }
    ),
)

After (1.0):

from selectools import Agent, AgentConfig, AgentObserver

class LoggingObserver(AgentObserver):
    def on_tool_start(self, run_id, call_id, tool_name, tool_args):
        print(f"-> {tool_name}({tool_args})")

    def on_run_end(self, run_id, result):
        print(result.usage)

agent = Agent([search_tool], config=AgentConfig(observers=[LoggingObserver()]))

Hook → observer method mapping

0.x hook key 1.0 observer method Notes
on_agent_start on_run_start(run_id, messages, system_prompt) gains run_id + resolved system prompt
on_agent_end on_run_end(run_id, result) result is the full AgentResult (use result.usage)
on_iteration_start on_iteration_start(run_id, iteration, messages) gains run_id; iteration is 1-based
on_iteration_end on_iteration_end(run_id, iteration, response) gains run_id
on_tool_start on_tool_start(run_id, call_id, tool_name, tool_args) call_id matches the paired end/error event, even with parallel tools
on_tool_chunk on_tool_chunk(run_id, call_id, tool_name, chunk)
on_tool_end on_tool_end(run_id, call_id, tool_name, result, duration_ms) duration is now milliseconds
on_tool_error on_tool_error(run_id, call_id, tool_name, error, tool_args, duration_ms)
on_llm_start on_llm_start(run_id, messages, model, system_prompt)
on_llm_end on_llm_end(run_id, response, usage)
on_error on_error(run_id, error, context)

Override only the methods you need — all observer methods are optional no-ops on the base class. Ready-made observers: LoggingObserver, SimpleStepObserver, plus OTelObserver and LangfuseObserver in the [observe] extra.


Rename: Agent._clone_for_isolation()Agent.clone_for_isolation()

The agent-cloning method used for concurrency isolation (eval suites, A2A task handling, planning) is promoted to public API at 1.0 and loses its underscore prefix. If you called the private name directly:

clone = agent._clone_for_isolation()   # 0.x
clone = agent.clone_for_isolation()    # 1.0

Behavior is unchanged: the clone shares tools and provider configuration but gets isolated memory and fresh usage accounting.


__all__ additions (not breaking)

Symbols that were importable but missing from selectools.__all__ are now declared. This matters if you use from selectools import * or lint against the public API:

  • Pipeline family: Pipeline, Step, StepResult, step, branch, parallel, retry, cache_step, compose
  • Router provider: RouterProvider, RouterConfig (these two were previously importable only from selectools.providers; the top-level re-export is new)

Existing from selectools import Pipeline-style imports keep working as before; they are simply official now. from selectools import RouterProvider becomes available for the first time.


Python 3.9 support dropped

v1.0.0 raises the floor to Python 3.10 (requires-python = ">=3.10"), planned to land with the 1.0 tag itself. Python 3.9 reached upstream end-of-life in October 2025.

  • The last series supporting 3.9 is 0.24.x, which moves to security-fixes-only when 1.0 ships (see SECURITY.md).
  • If you are stuck on 3.9, pin selectools<1.0.

No other breaking changes

Pending final confirmation

The statement below is finalized by the pre-1.0 stability-marking sweep. Until the 1.0.0 tag is cut, treat this section as provisional.

Aside from the AgentConfig.hooks removal, the clone_for_isolation rename, and the Python 3.9 drop, no other breaking changes are planned for 1.0. Code that runs deprecation-warning-free on the latest 0.24.x release should run unmodified on 1.0 (on Python 3.10+).


Symbols promoted to stable at 1.0

The pre-1.0 marking sweep promotes proven @beta APIs (2+ releases, no breaking changes) to @stable:

Symbol Module 0.x marker 1.0 marker
AgentGraph selectools.orchestration @beta @stable
GraphState selectools.orchestration @beta @stable
MergePolicy selectools.orchestration @beta @stable
ContextMode selectools.orchestration @beta @stable
InterruptRequest selectools.orchestration @beta @stable
Scatter selectools.orchestration @beta @stable
CheckpointStore selectools.orchestration @beta stable (registry)
InMemoryCheckpointStore selectools.orchestration @beta @stable
FileCheckpointStore selectools.orchestration @beta @stable
SQLiteCheckpointStore selectools.orchestration @beta @stable
SupervisorAgent selectools.orchestration @beta @stable
PlanAndExecuteAgent selectools.patterns @beta @stable
ReflectiveAgent selectools.patterns @beta @stable
DebateAgent selectools.patterns @beta @stable
TeamLeadAgent selectools.patterns @beta @stable
trace_to_html selectools.trace @beta @stable
SimpleStepObserver selectools.observer @beta @stable
RedisSessionStore selectools.sessions @beta @stable
AzureOpenAIProvider selectools.providers @beta @stable

Companion types of the promoted APIs (result/config/event dataclasses, enums, and graph helper functions such as GraphResult, GraphEvent, GraphNode, PlanStep, ReflectiveResult, DebateResult, TeamLeadResult, SupervisorStrategy, CheckpointMetadata, goto, update, merge_states, trace_to_json) were previously unmarked and are now @stable alongside their parent APIs.

CheckpointStore is @runtime_checkable and therefore carries its marker in selectools.stability.STABILITY_REGISTRY instead of a class attribute (a class attribute would become a structural protocol member on Python 3.9-3.11 and break isinstance() for third-party stores).

Deliberately not promoted (still @beta at 1.0): the loop-detection family, LocalProvider (test stub), the Pipeline/compose family, everything shipped in v0.24/v0.25-dev (pending, results, a2a, AgentAPI, knowledge backends, unified memory, LiteLLMProvider/RouterProvider, sanitizers, planning, compression/HITL types), and the rag/mcp/embeddings/observe/evals (evaluators) surfaces.

Anything still @beta at 1.0 (for example, the selectools serve CLI surface) remains outside the semver guarantee until promoted in a later 1.x release.