Changelog

Release notes

Every published release of the framework, the API server and CLI, and the TypeScript client. Versions and dates match PyPI and npm.

  1. Core framework

    v0.9.2

    Breaking changes

    Adds a native Anthropic provider with prompt caching, server tools and batches, token counting, an OpenAI batch helper, and several tool-node and background-task fixes. One breaking routing change.

    Breaking

    • anthropic/ and claude/ are now recognised model prefixes. Previously they were unknown prefixes that fell through to the OpenAI provider, which is how Claude behind an OpenAI-compatible proxy worked. Agent(model="anthropic/claude-3") now selects the native Anthropic provider and strips the prefix. Migration: pass provider="openai" explicitly to keep routing through an OpenAI-compatible endpoint, for example Agent(model="anthropic/claude-3", provider="openai", base_url=...).

    Added

    • Native Anthropic provider. model="claude-opus-5" (or "anthropic/...", "claude/...") now builds a real Anthropic client instead of constructing an AsyncOpenAI that failed at request time. It covers non-streaming and streaming, tool calling, multimodal input, reasoning, usage accounting and retry/fallback. Install with the anthropic, anthropic-vertex or anthropic-bedrock extra.
      • Three backends, selected with anthropic_backend: None (direct Claude API), "vertex" (AsyncAnthropicVertex) and "bedrock" (AsyncAnthropicBedrockMantle). Bedrock model ids keep their anthropic. prefix.
      • max_tokens is required by the API and is defaulted automatically (16000 non-streaming, 64000 streaming).
      • temperature, top_p and top_k are stripped per model for models that reject them with a 400.
      • reasoning_config={"effort": ...} maps to thinking={"type": "adaptive"} plus output_config.effort. budget_tokens is never emitted, because it returns a 400 on current Claude models.
      • A trailing assistant turn is dropped, since prefill returns a 400 on current models. This protects the injected context_summary.
      • A policy refusal is surfaced as a message with metadata["refusal"] instead of being treated as a transient failure that burns the model fallback list.
    • Agent.count_tokens(messages, tools) counts a request’s input tokens before sending it, using the provider’s own endpoint. For Anthropic it uses the exact payload the real call would send (system prompt, tool schemas, merged tool results). For Google it counts the converted contents only, so the system instruction and tool schemas are not included.
    • Anthropic prompt caching. anthropic_cache=True (or a dict such as {"type": "ephemeral", "ttl": "1h"}) places cache_control breakpoints at the end of the stable request prefix: the last tool and the last system block. It is skipped when the caller placed their own breakpoints. Verify hits with usage.cache_read_input_tokens; a prefix under about 1024 tokens silently does not cache.
    • Anthropic server tools. web_search_tool(), web_fetch_tool() and code_execution_tool() build correctly dated definitions. Server-tool result blocks are captured into metadata["server_tool_results"], and a server_tool_use block is recorded without being added to tools_calls, so the graph does not re-run work Anthropic already did. A server-tool error is surfaced as error_code, and stop_reason: "pause_turn" is flagged as metadata["pause_turn"].
    • AnthropicBatch and OpenAIBatch (tenxgraph.core.llm) for batch APIs, with a shared interface: build, submit, poll and collect. Results are keyed by custom_id, because batch results arrive in any order. BatchResult is exported alongside them.
    • call_llm supports Anthropic, so SummaryContextManager, the evaluation judge and UserSimulator work with Claude models.

    Changed

    • Tool-result serialization has explicit rules for datetime, UUID, Decimal, Enum, set and bytes values. Decimal renders as a string so money values keep their precision.
    • When a nested-object tool parameter fails validation and the model sent it as a JSON string, the string is decoded and validated as a fallback. Validation failures now return a readable message naming the tool and parameter.

    Fixed

    • use_vertex_ai=True hijacked Claude models. The flag short-circuited provider detection to "google", so Agent(model="claude-opus-5", use_vertex_ai=True) built a Google GenAI client with a Claude model name. It now acts as a backend selector for Anthropic (equivalent to anthropic_backend="vertex"), and an explicit anthropic_backend still wins. Behavior for non-Claude models is unchanged.
    • call_llm sent unrecognised model prefixes to the SDK verbatim, so call_llm("gemini/gemini-2.5-flash", ...) passed the full string as the model name. It now uses resolve_provider_and_model.
    • The Anthropic request builder could send an empty messages list when the context summary was the only turn. The lone trailing assistant turn is now sent as a user turn instead of being discarded.
    • AnthropicBatch skipped the trailing-assistant guard that the live request path applies, so the same input produced two different bodies. Both paths now agree.
    • A missing required tool argument failed the whole graph run (NODE_001). It is now a failed tool result that the model can correct, and on-error callbacks see the model’s raw arguments.
    • Tool results serialized Path values with backslashes on Windows. Paths, and resource uri values built from them, now use POSIX separators.
    • wait_for_all() returned before task cleanup callbacks ran, leaving finished tasks and their metadata in the manager. It now yields once so the callbacks run first.
  2. API server and CLI

    v0.5.1

    Reworks the CLI around a full-screen, animated terminal experience with new dev, audit, demo and config commands, guided prompts and machine-readable output modes.

    Added

    • agentflow dev, a goal-oriented local development command (config, host, port, --reload/--no-reload, --open/--no-open). api and play remain available.
    • agentflow audit runs six read-only checks: Python interpreter, installed 10xscale-agentflow-cli, installed 10xgraph, whether the installed core still exposes the evaluation API that agentflow eval imports, whether 10xgraph.json exists with a valid agent key, and whether the default port is free. It exits 1 on any failure and 0 otherwise (warnings do not fail the run), so it works as a CI gate.
    • agentflow demo previews the animation, timeline and progress states without touching project state (--style all|typing|network|init|build|eval).
    • agentflow config list|get|set|unset|path|validate manages cross-platform user preferences stored as JSON in the per-user config directory. output.format, output.color and output.progress are read at startup as defaults, and explicit flags still win.
    • Persistent full-screen surface on interactive terminals, with a pinned header and footer status bar and the command output scrolling between them. Opt out with --no-fullscreen or AGENTFLOW_NO_FULLSCREEN=1.
    • Animated command intros, live step timelines and determinate progress. Timelines are wired into play, dev, api, init, build, test and audit, and agentflow eval shows a progress bar with a running pass/fail tally.
    • Shared guided-prompt layer. Every interactive question uses one themed service with a clean Ctrl+C exit and a single non-interactive policy. agentflow skills now uses an arrow-key list where space toggles and enter confirms, and agentflow init explains each option inline.
    • Root options --format, --json, --color, --no-color, --progress, --animation/--no-animation, --fullscreen/--no-fullscreen, --cwd, --yes, --non-interactive, --debug and -V/--version.
    • Adaptive rendering with TTY and CI detection, plain and JSONL modes, NO_COLOR support, an ASCII fallback and quiet mode.
    • Reproducible agentflow init --non-interactive recipes and --dry-run previews.
    • Staged startup feedback, a pre-flight port check and connected-playground completion output for agentflow play and agentflow dev.
    • Stable CLI error codes and dependency recovery suggestions.

    Changed

    • Command implementations load lazily, so a broken optional feature no longer prevents root help, version, completion or unrelated commands from starting.
    • CLI logging uses one invocation-wide handler, so quiet and verbose levels apply consistently without duplicate records.
    • Project configuration discovery walks parent directories from the current working directory.
    • agentflow init no longer prints one line per scaffolded file. Files stream through the active timeline row instead.
    • The agentflow init template configures JWT with the bare "jwt" string, which is the form 10xgraph.json accepts for the built-in method.
    • The error for dynamic tool setup in production or multi-tenant mode now names which condition tripped (MODE or a configured auth backend) and points to CompiledGraph.attach_remote_tools() as the static alternative.

    Fixed

    • The full-screen session no longer erases the command’s output. Each line used to overwrite the last, and releasing the screen discarded everything drawn on it.
    • A terminal that cannot host a prompt no longer crashes the command. Under MSYS or Cygwin shells on Windows, stdin.isatty() is true but prompt-toolkit cannot attach, which surfaced as AF-INTERNAL-001. Such terminals are now treated as non-interactive.
    • Pinned chrome is repainted after each prompt, and one Rich Console is reused per stream so background output no longer collides with spinners and progress bars.
    • A console that reports itself as a terminal but refuses the alternate buffer (legacy Windows console) now aborts the frame before writing anything.
  3. Core framework

    v0.9.1

    Small fix release. The synchronous tool listing on ToolNode now includes remote tools, and AudioAgent is exported from tenxgraph.prebuilt.

    Added

    • AudioAgent is now exported from tenxgraph.prebuilt.

    Fixed

    • ToolNode.all_tools_sync() silently dropped remote tools. The async path (all_tools()) included them, so client-side tools were visible to the model in one path and invisible in the other. The sync path now returns the same set: local, MCP and remote tools.
    • UserSimulator now sets the per-simulation thread_id at the top level of the run config (it previously nested it under configurable), so each simulation gets its own thread.
    • The CompiledGraph.attach_remote_tools() docstring now documents the expected schema: OpenAI function-calling format, with the tool name read from function.name.
  4. TypeScript client

    v0.4.0

    Breaking changes

    Fixes tool parameter typing so all-optional tools typecheck, widens thread ID types, and changes WebSocket auth precedence so auth now wins over authToken when both are set.

    Breaking

    • WebSocket auth: auth now takes precedence over authToken. resolveBearerToken() previously checked authToken first and only fell back to auth. It now resolves auth first, matching buildHeaders() on the HTTP path, and returns null when auth is a non-bearer scheme instead of falling back to authToken. This affects wsStream() and realtime(), and only if you set both auth and authToken.
      • auth: { type: 'bearer', token: 'b' } with authToken: 'tok' now resolves to 'b' (it was 'tok').
      • auth of type basic or header with authToken now resolves to null (it was 'tok'). In a browser, basic auth then sends no credential on the socket. Header auth sends none in any runtime, because openWebSocket() never forwards a custom header name.
      • Migration: pass the socket credential as bearer with auth: { type: 'bearer', token }, or drop auth and keep authToken.

    Added

    • normalizeToolParameters(parameters?), exported from tools.ts, applies the JSON Schema defaults (type: 'object', properties: {}, required: []) to a partial tool schema.
    • A version-compatibility table in the README mapping client versions to 10xscale-agentflow-cli and 10xgraph versions.

    Changed

    • The three thread-state endpoints, threadState(), updateThreadState() and clearThreadState(), now accept string | number for threadId, matching the other thread methods. Existing calls passing a number keep compiling.
    • Tool registrations with no parameters now serialize to { type: 'object', properties: {}, required: [] } instead of a bare {}.

    Fixed

    • ToolParameter.required was mandatory, so all-optional tools did not typecheck (issue #12). required and properties are now optional, and ToolParameter accepts arbitrary JSON Schema keywords such as additionalProperties and $defs. The wire format is unchanged: client.setup() and ToolExecutor.all_tools() fill in the omitted keywords.
    • agent.ts broke type resolution under moduleResolution: nodenext. Its ./message import was the only extensionless relative import in src/, so node16 and nodenext consumers got TS2834. It now imports ./message.js.
  5. Core framework

    v0.9.0

    Breaking changes

    Production-hardening release covering optimistic concurrency, an idempotent tool ledger, real timeouts and cancellation, per-user isolation and new observability hooks. Includes three breaking changes.

    Breaking

    • injectq is pinned to >=0.4.0,<0.5. It is pre-1.0, so a 0.5 release could break the API. Without an upper bound it would have been picked up automatically and broken fresh installs.
    • The default user_id is now "anonymous" (it was "test-user-id"). With per-user isolation enabled, a run with no user_id previously filed itself under a placeholder that looked like a real account, which pooled every unauthenticated run into one identity.
    • A conditional edge whose condition raises now fails the run (GraphError, GRAPH_ROUTING_001). Previously the exception was swallowed and the graph fell through to the first static edge or END.

    Added

    • Optimistic concurrency control on durable state. states carries a version column with UNIQUE (thread_id, version), and writes take a per-thread row lock and compare-and-swap. A write based on a stale version raises the new StaleStateError (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. Entries are keyed by (thread_id, origin_message_id:tool_call_id), because a tool_call_id alone is not unique across turns.
    • Per-step durable checkpointing (durable_checkpoint_every_step, default on), so a crash replays one node rather than the whole run.
    • Node and tool timeouts (node_timeout, tool_timeout) that cancel the work, and stop requests now cancel a running node. Previously stop was only polled between nodes.
    • Real schema migrations, with a stepwise, idempotent runner guarded by pg_advisory_xact_lock so concurrent workers cannot race the DDL.
    • Per-user isolation in the checkpointer (enforce_user_isolation, default on) across state, messages and threads, plus global thread-ownership resolution. Owner-only isolation also covers the in-memory and SQLite checkpointers.
    • tenxgraph.core.authz, a module for authorization contracts and scopes, and user ID scoping in BaseStore and QdrantStore so stores honor authorization policies.
    • File ownership. Uploads record an owner, and 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 memory ran out.
    • OpenTelemetry metrics via metrics.setup_otel_metrics(), with counters and histograms on node and tool execution and outcome dimensions.
    • Structured, correlated logging via logging.setup_structured_logging(). Every record carries run_id, thread_id and node.
    • agentflow build --k8s generates a Kubernetes manifest whose termination grace period is long enough that a rolling deploy does not kill in-flight runs.

    Changed

    • Durable storage migrates to schema v3 in place on first connect.

    Fixed

    • Lost updates on concurrent writes to one thread (see the compare-and-swap above). Reads were also non-deterministic, because ORDER BY created_at DESC had 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 when it has one.
    • One failing tool orphaned its siblings, and malformed tool arguments raised JSONDecodeError through the whole node.
    • Retries on non-retryable errors. Status classification matched "500" as a substring, so max_tokens must be <= 500 was treated as a server error.
    • Cross-tenant reads and deletes of state, messages, threads and files.
    • Blocking urllib.urlopen inside async def in 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.

    Security

    • Rate limit bypass. The bucket key came from the leftmost X-Forwarded-For entry, 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.
    • Cross-tenant reads and deletes of state, messages, threads and files are closed off (see Fixed).
  6. API server and CLI

    v0.5.0

    Breaking changes

    Adds route protection, ownership and role-based authorization, observability and eval-report endpoints, and fixes a broken wheel that dropped the init templates. Production now refuses wildcard CORS with credentials.

    Breaking

    • Production refuses to start with wildcard CORS and credentials enabled. With MODE=production, ORIGINS='*' combined with credentials now raises InsecureCorsConfigError at startup. Set explicit ORIGINS, or set CORS_ALLOW_CREDENTIALS=false.
    • The authorization backend now defaults by run mode. When authorization is not set in 10xgraph.json, production now uses ownership (owner-only thread access) and development uses allow_all, instead of no backend being loaded.

    Added

    • Route guard. The server refuses to boot if any non-public route lacks a RequirePermission dependency, so a forgotten guard becomes a deploy-time error instead of an open endpoint.
    • Authorization backends. OwnershipAuthorizationBackend for owner-only isolation and RoleBasedAuthorizationBackend, which maps user roles to scopes. RequirePermission enforces the required "<resource>:<action>" scope. The authorization key accepts "ownership", "allow_all" or a "module:attribute" path to a custom backend.
    • File ownership. Media uploads record the uploader as owner, and other users cannot read the file.
    • Observability. A declarative observability block in 10xgraph.json (Logfire and LangSmith), telemetry recording for graph runs, and the /v1/graph/tools and /v1/observability/{thread_id} endpoints for tool listings and run traces.
    • Eval report endpoints /v1/evals/runs and /v1/evals/runs/{run_id} for reading local evaluation reports. These are treated as a development surface.
    • GraphInfoSchema reports is_realtime so clients can tell when a graph is a live agent.
    • py.typed marker, so type information reaches consumers (PEP 561).
    • A --integration pytest flag gates tests that need real Redis or Postgres.
    • mypy configuration and CI step, CodeQL scanning, Dependabot, and community health files (CONTRIBUTING.md, SECURITY.md, CODE_OF_CONDUCT.md, issue forms and a pull request template).

    Changed

    • Every runtime dependency now has a lower bound, and risky ones have an upper cap, including 10xgraph>=0.9.0,<2.0, fastapi>=0.116,<1.0 and pydantic>=2.13,<3. Environments that resolved older versions alongside this package may resolve differently.
    • The optional extras snowflakekit, redis and jwt gained version bounds.
    • CI runs on pushes to main and covers Python 3.12 and 3.13. The release workflow now depends on a passing test job.
    • The Documentation project URL points at the published docs site.
    • a2a.py and a2ui.py were removed from the wheel. Both were fully commented out and never mounted.

    Fixed

    • Scaffolding templates were missing from the wheel. The package-data globs dropped templates/dev/.env.example, templates/prod/.env.example, templates/prod/.python-version and templates/prod/pyproject.toml, so agentflow init failed for installs from PyPI. Packaging now ships the package tree wholesale.
    • agentflow version reported unknown when installed from a wheel. It now reads installed distribution metadata and also reports the core 10xgraph version.
    • The prod template shipped .pre-commot-config.yaml (typo), so pre-commit found no config in scaffolded projects. It is now .pre-commit-config.yaml.

    Security

    • Rate-limit bypass through X-Forwarded-For. The client IP was taken from the leftmost entry, which the caller controls, so a new value per request landed in a fresh bucket. The IP is now counted from the right using trusted_proxy_hops (default 1).
    • Cross-user access to threads and media is covered by the ownership checks above.
  7. TypeScript client

    v0.3.0

    Adds graphTools() and observability() endpoints and fixes packaging, sourcemap, typing and Node 18 issues. Dev tooling moves to vitest 3 and vite 7, clearing all reported vulnerabilities.

    Added

    • client.graphTools() lists the tools exposed by the graph’s tool nodes, grouped by node and tagged with their source (local, mcp or remote).
    • client.observability(threadId, runId?) returns the reconstructed trace (spans, events, cost) for a thread, defaulting to the latest run.
    • ESLint 9 (flat config) and Prettier, with lint, format, typecheck and check scripts.
    • CI covering lint, Prettier, tsc --noEmit, tests on Node 18, 20 and 22, and a packaging job that smoke tests the CJS and ESM entry points from a clean install. The release workflow is gated on CI, and CodeQL scanning and Dependabot were added.

    Changed

    • Upgraded vitest 1.x to 3.x and vite 5.x to 7.x, which cleared all 11 reported vulnerabilities. npm audit now reports zero.
    • tsconfig.json type-checks tests/ as well as src/, which surfaced and fixed six type errors in the test suite.
    • Twelve @ts-ignore comments became @ts-expect-error, and all of them turned out to be suppressing nothing, so they were removed.
    • Coverage thresholds were raised and pinned as a ratchet (72% lines and statements, 82% branches, 76% functions).

    Fixed

    • npm publish would have failed. Scoped packages default to restricted, so publishConfig.access: "public" was added, along with provenance: true.
    • uploadFile() threw on Node 18. File only became a global in Node 20, and the upload path did an unguarded file instanceof File, so every call failed with ReferenceError: File is not defined, including calls passing a plain Blob. The check is now guarded.
    • The build was not cross-platform. npm run build ended in cp -r dist-types/* dist/, which does not exist on Windows. tsc now emits declarations straight into dist/ through tsconfig.build.json.
    • Shipped sourcemaps did not resolve. The tarball contained 41 .map files but no sources. src/ is now included in files.
    • NodeJS.Timeout leaked into the public types, forcing browser-only consumers to install @types/node. forgetMemories now uses ReturnType<typeof setTimeout>, and Error.captureStackTrace is accessed structurally.
  8. API server and CLI

    v0.4.0

    Adds realtime audio sessions over WebSocket with connection limits and rate limiting, a global confeval.py for agentflow eval, and requires core 0.8.0 or newer.

    Added

    • Realtime audio sessions over WebSocket at /v1/graph/live, alongside the existing streaming socket at /v1/graph/ws. GraphService configures the realtime session.
    • WebSocket connection limits. A new websocket block in 10xgraph.json accepts max_connections, a per-process cap on concurrent WebSocket connections (null or 0 means unlimited).
    • WebSocket handshakes share the REST rate-limit bucket. Client key derivation is now shared between the HTTP rate-limit middleware and the WebSocket connection guard.
    • Global confeval.py discovery for agentflow eval. The nearest global confeval.py is found and used for criteria, and reports show where each case’s configuration came from.
    • Agent-skill reference docs for realtime audio agents.

    Changed

    • The minimum core dependency is now 10xgraph>=0.8.0.
    • Logging configuration and log sanitization were updated.

    Fixed

    • WebSocket error handling was improved, and GraphService now uses a consistent thread ID type for WebSocket sessions.
  9. Core framework

    v0.8.0

    Introduces the realtime audio-to-audio subsystem, with LiveAgent, AudioAgent and a Gemini Live client, plus an LLM circuit breaker, default request timeouts and secret redaction in logs.

    Added

    • Realtime (audio-to-audio) subsystem in tenxgraph.core.realtime. It includes provider-neutral contracts (RealtimeConfig, RealtimeClient and typed events such as AudioDeltaEvent, ToolCallEvent and TurnCompleteEvent), an upstream LiveInputQueue, and a GeminiLiveClient. Provider SDK imports are lazy.
    • LiveAgent and AudioAgent. LiveAgent runs the duplex session loop, dispatches tool calls through the existing ToolNode, persists finished transcripts as messages and reconnects on go_away or a dropped socket. CompiledGraph.arealtime is the entry point. AudioAgent builds and compiles a single realtime audio agent graph.
    • realtime optional extra (pip install "10xgraph[realtime]"), which pulls in google-genai.
    • LLM circuit breaker. RetryConfig gains circuit_breaker_enabled (default False), circuit_breaker_threshold (default 5) and circuit_breaker_reset_timeout (default 30.0 seconds). A failing (provider, model) pair is skipped and the call moves to the next fallback until the cooldown ends.
    • Default LLM request timeout of 600 seconds, applied to client construction for Google GenAI and OpenAI-style clients. Override it with the AGENTFLOW_LLM_TIMEOUT environment variable or set_default_llm_timeout(); read it with get_default_llm_timeout().
    • Secret redaction in logs. mask_secrets() masks common API key formats, Bearer tokens, key=value secrets and signed-URL credential parameters. This is best-effort, not a guarantee.
    • CompiledGraph can be used as an async context manager (async with graph:), which runs aclose() on exit. Calling aclose() more than once is a no-op.
    • A py.typed marker, so type information reaches consumers.

    Changed

    • Token usage is now calculated in the graph invoke and stream handlers.
    • ConsolePublisher accepts a logger.
    • Provider detection and model resolution in Agent was reworked around resolve_provider_and_model.