Releases and changelog
AgentFlow ships as three independently versioned packages. A release of one does not imply a release of the others, so check the version of the package you actually installed.
| Package | Installed with | Canonical changelog |
|---|---|---|
10xscale-agentflow (core) | pip install 10xscale-agentflow | CHANGELOG.md |
10xscale-agentflow-cli (API server + CLI) | pip install 10xscale-agentflow-cli | CHANGELOG.md |
@10xscale/agentflow-client (TypeScript) | npm install @10xscale/agentflow-client | Releases |
Check what you are running:
agentflow version # CLI version and the core version it resolves
pip show 10xscale-agentflow
npm list @10xscale/agentflow-client
Versioning policy
All three packages follow Semantic Versioning and the Keep a Changelog format.
From 1.0.0 on, the core public API is stable:
- A public API is never removed without a deprecation cycle. It is marked
deprecated in one release, emits a
DeprecationWarningnaming its replacement, keeps working for at least one subsequent minor release, and is only then removed in a major release. - Moved modules keep a back-compat shim at the old import path for at least one minor release. Import paths do not break silently.
- Breaking changes appear under a
### Breakingheading in the release notes, with the migration step spelled out.
What counts as public API:
| Package | Public surface |
|---|---|
| Core | Anything importable from agentflow.* that is not prefixed with _ |
| CLI | Names exported from agentflow_cli, CLI commands and flags, HTTP routes, and agentflow.json keys |
| TypeScript client | Everything exported from src/index.ts, plus the package entry points (main, module, types, exports) |
Scaffolding emitted by agentflow init (anything under
agentflow_cli/cli/templates/) is generated content, not API. It can change in
any release.
Core: 1.0.0
First stable release. The public API is now covered by the deprecation policy
above and the package is classified Development Status :: 5 - Production/Stable.
This is the production-hardening round on top of 0.8.0: durable state gains
optimistic concurrency and an idempotent tool ledger, execution is bounded by
real timeouts and cancellation, and per-user isolation is enforced across the
storage layer.
Upgrading from 0.8 or 0.9? Read the 1.0 upgrade guide first.
Breaking
injectqis pinned to>=0.4.0,<0.5. It is pre-1.0, so an unbounded dependency could pick up a breaking0.5and break fresh installs.- The default
user_idis now"anonymous"(was"test-user-id"). With per-user isolation enabled, the old placeholder silently pooled every unauthenticated run into one identity that looked like a real account. - A conditional edge whose condition raises now fails the run with a
GraphError(GRAPH_ROUTING_001). Previously the exception was swallowed and the graph fell through to the first static edge orEND, silently taking a path nobody chose. - Production refuses to start with wildcard CORS and credentials enabled. Set
explicit
ORIGINS, orCORS_ALLOW_CREDENTIALS=false.
Added
- Optimistic concurrency control on durable state.
statescarries aversioncolumn withUNIQUE (thread_id, version); writes take a per-thread row lock and compare-and-swap. A write based on a stale version raisesStaleStateError(HTTP 409 at the API) instead of silently discarding another run's update. - Durable tool-execution ledger (
tool_executions, schema v3). A node replayed after a crash no longer re-fires tool calls that already completed. Keyed by(thread_id, origin_message_id:tool_call_id), because atool_call_idalone is not unique across turns. - Per-step durable checkpointing (
durable_checkpoint_every_step, on by default), so a crash replays one node rather than the whole run. - Node and tool timeouts (
node_timeout,tool_timeout) that actually cancel the work, plus stop-cancels-a-running-node. Stop was previously only polled between nodes, so a hang inside one was unreachable. - Real schema migrations with a stepwise, idempotent runner and a
pg_advisory_xact_lock, so concurrent workers cannot race the DDL. - Per-user isolation in the checkpointer (
enforce_user_isolation, on by default) across state, messages, and threads. - File ownership. Uploads record an owner; reads by another user return 404.
- Backpressure on background tasks (
max_pending_tasks, default 1000). A slow or dead publisher sink previously grew an unbounded task set until OOM. - OpenTelemetry metrics via
agentflow.utils.metrics.setup_otel_metrics(): counters and histograms on node and tool execution, with outcome dimensions. - Structured, correlated logging via
agentflow.utils.logging.setup_structured_logging(). Every record carriesrun_id,thread_id, andnode, so one run can be grepped out of a busy server. agentflow build --k8sgenerates a Kubernetes manifest whose termination grace period is long enough that a rolling deploy does not kill in-flight runs. See deploy on Kubernetes.
Fixed
- Lost updates on concurrent writes to one thread. Reads were also
non-deterministic:
ORDER BY created_at DESCwith no tiebreak. - The realtime cache could be moved backwards, wedging a thread until its TTL expired. Cache writes are now an atomic version-guarded compare-and-set, and a lost version check invalidates the cache so the thread self-heals.
- Parallel tools clobbering each other's state. Each tool now runs on its own branch copy, merged back field-by-field against a baseline, using a field's reducer where it has one.
- One failing tool orphaned its siblings (
gatherwithoutreturn_exceptions), and malformed tool arguments raisedJSONDecodeErrorthrough the whole node. - Retries on non-retryable errors. Status classification matched
"500"as a substring, somax_tokens must be <= 500was treated as a server error. - Cross-tenant reads and deletes of state, messages, threads, and files.
- Rate limit bypass. The bucket key came from the leftmost
X-Forwarded-Forentry, which the caller controls, so a new value per request meant a new bucket and no limit at all. Proxy hops are now counted from the right. - Blocking
urllib.urlopeninsideasync defin the cloud media store stalled the event loop for every concurrent run in the process. - Connection-pool and Qdrant-collection cold-start races (double creation).
- Schema-version failures were swallowed instead of raised.
CLI and API server
Recent work on 10xscale-agentflow-cli:
Added
py.typedmarker, so type information reaches consumers (PEP 561).--integrationpytest flag gating tests that need real Redis and Postgres, so a defaultpytestrun requires no external services.- mypy configuration and a mypy step in CI, plus a CodeQL static-analysis workflow and Dependabot for pip and GitHub Actions.
Changed
- Every runtime dependency now has a lower bound, and pre-1.0 or
major-version-risky dependencies have an upper cap (
pydantic>=2.13,<3,fastapi>=0.116,<1.0,10xscale-agentflow>=0.9.0,<2.0). Previously all runtime dependencies were unpinned, so a major release of any of them could break installs without warning. - The release workflow depends on a passing test job; it no longer builds and publishes untested code.
- CI covers both advertised Python versions (3.12 and 3.13) rather than 3.13 alone.
Fixed
- Scaffolding templates were missing from the wheel, so
agentflow initfailed for anyone installing from PyPI. The packaging globs droppedtemplates/dev/.env.example,templates/prod/.env.example,templates/prod/.python-version, andtemplates/prod/pyproject.toml. agentflow versionreportedunknownwhen installed from a wheel. It now resolves from installed distribution metadata and also reports the core version.- The
prodtemplate shipped.pre-commot-config.yaml(typo), sopre-commitfound no config in scaffolded projects.
Removed
- The commented-out
a2a.pyanda2ui.pyrouters, which were never mounted but shipped in the wheel as dead code. Agent-to-Agent serving is not currently implemented; see the roadmap.
TypeScript client: 0.3.0
Fixed
uploadFile()threw on Node 18.Fileonly became a global in Node 20 and the upload path did an unguardedfile instanceof File, so every call failed withReferenceError: File is not defined, including calls passing a plainBlob. The package declaresengines.node >= 18, so this broke file upload on supported runtimes.npm publishwould have failed. Scoped packages default torestricted;publishConfig.access: "public"andprovenance: trueare now set.- The build was not cross-platform. It ended in
cp -r dist-types/* dist/, which does not exist on Windows.tscnow emits declarations straight intodist/.
Getting notified
- Watch releases on GitHub for release announcements.
- Security releases are announced through GitHub Security Advisories. See the security policy.