Skip to content

Changelog

User-facing changes to symfonic-core. The format is based on Keep a Changelog and the project follows Semantic Versioning.

This is the curated public changelog — it covers changes relevant to users of the published package. Fine-grained internal release notes are maintained separately by the maintainers.

[Unreleased]

[11.0.0] - 2026-09-10

This is a published release and package metadata declares 11.0.0. It is a major version because three per-call arguments are retired and stream_typed is served by the kernel projection; both are listed below.

The compatibility facade and legacy implementations remain in the source. What changes is which body serves a turn on this line, not what the source contains. Adopters who depend on a retired argument stay on 9.12, the newest release that still honours it; the retirement is not backported.

Physical removal of the legacy implementations is a separate, explicitly authorized operation behind its own gate, and is not part of this release.

Added

  • Eval CI gates in the source repository. PRs run deterministic scaffold evals; nightly/manual jobs run the real-model catalog. Public publication requires live validation of the same wheel being uploaded. Maintainers must configure dedicated test services and require the offline check in branch protection; adopter projects do not automatically inherit these workflows.

  • Public kernel composition and a kernel-native scaffold. Compose memory, prompting, tools, delegation, human interaction, governance, extensions and telemetry through public entry points. Generated applications use that composition for their agent, UI/API and background memory work.

  • Durable memory and semantic retrieval. Automatic post-turn extraction can persist facts for recall across conversations in the same scope. GraphBackedHms supports a vector backend paired with an embedder; the scaffold explicitly reports graph-only operation when embeddings are not configured. Operational degradation is reported rather than presented as an empty successful extraction or retrieval.
  • QUICK, NIGHTLY and DEEP consolidation factories. QUICK supports turn cadence; the scaffold's scheduled and manual Deep Sleep paths use the capability runtime. Scope leases, commit-time fencing and a single declared transaction domain protect consolidation writes. Procedural memories have separate draft, human review, approval and tool-precondition enforcement steps; a discovered procedure is not automatically authorized.
  • Conversation and memory observability. Correlated model, tool and stage events, durable counters and memory-lifecycle details are available to the scaffold's admin views. The optional engineering observability stack adds Grafana, Prometheus and Tempo. Input/output content capture is opt-in and subject to redaction and access controls; metadata tracing does not imply that prompt or memory contents were recorded.
  • Reusable agent regression evals. symfonic.evals and generated scaffold suites provide deterministic fast/integration profiles and configured live runs, JSON/JUnit reports, capability applicability, and the Glass Harbor book fixture. Journeys cover recall, grounding, tools, delegation, isolation, injection, consolidation and observability. Restart, concurrency and bounded memory-work checks complement response assertions. Non-applicable scenarios are reported explicitly, not counted as passes.

Changed

  • Kernel-first onboarding and versioned documentation. The site separates historical 9.x documentation, built from v9.12.0, from 11.0, which it now serves at the root. Intent-tooling guidance starts with public kernel composition and labels the FrameworkConfig path as compatibility.
  • Examples use declared public surfaces. Migrated examples exercise the capabilities they teach; retained compatibility, historical-comparison and archive examples are classified explicitly. This is not physical legacy removal.

Fixed

  • A contributed extension policy is now consulted before the call it governs — check yours before upgrading. The decision machinery worked, but nothing on a turn asked it, so a policy could compose cleanly, report success, and never run. It now runs, and a deny prevents the tool call. If you contribute policies, a deployment that appeared to work may have been working because the policy was never consulted; verify what yours decides before you upgrade. An abstention — which is what a policy that raises becomes — denies, matching the behaviour that already existed elsewhere.
  • A pre-model stage is dispatched. A capability declaring one compiled into the plan, took its place in the compiled order, and ran in no pass. If you declared such a stage, it now executes, once per model round.
  • Credential hygiene distinguishes "nothing to examine" from "examined and clean". At ingress on a kernel turn it has no subject — the request carries no key/value bag and the tool calls do not exist yet — and it reported that as a clean pass. It now reports no-subject. This changes the evidence a turn records, not what is scrubbed. Ingress free text is still not scrubbed.
  • Python 3.11 can import the kernel's immutable mapping defaults.
  • The Anthropic extra now requires SDK <1.0, preserving the documented httpx.AsyncClient injection contract until an explicit SDK 1.x migration.
  • AWS scaffold imports and migrations no longer initialize memory-model SDK clients. Credential and region resolution occurs when a model is used; this does not replace missing AWS configuration with a mock response.

  • Governance refusals take precedence over repairs. Argument amendments reach the dispatched call, and tool preconditions inspect the final arguments. Credential hygiene scrubs matching credential keys before dispatch; it is not a general detector of secrets embedded in arbitrary text. Decisions retain declared rule attribution without exposing state values.

  • Extension tools bind callable implementations and argument schemas; delegation delivers child responses with scope and run correlation. Forced tool choice respects provider support instead of failing through an unadapted binding call.
  • Memory metadata round-trips preserve producer data while protecting reserved authorization fields. Retrieval respects scope visibility and retractions; consolidation preserves phase order and rolls back failed publication.
  • Documentation API discovery traverses the packaged eval fixtures, and the version selector remains usable on generated 404 pages.

Upgrade and validation notes

Capabilities must be composed explicitly; a FrameworkConfig flag alone does not configure the corresponding kernel capability. Pause-token compatibility depends on the redemption path. Bounded consolidation work does not promise bounded total database size or constant-cost vector search. Validate a newly generated project against your own providers and storage before rollout.

See the transition guide, migration guidance and agent regression evals.

[9.12.0] - 2026-08-08

Added

  • Prompt blocks — a pinned standing-context lane inside the cached prefix. FrameworkConfig.prompt_blocks declares named blocks (BOUNDARIES, IDENTITY, RULES, ENVIRONMENT, USER_PROFILE, …), each served by one source and rendered into the L0 + L1 cached region rather than the uncached MEMORY_CONTEXT placeholder. Resolution is one deterministic read per block — no query, no similarity score, no top-K truncation — so a pinned fact is never dropped on the turns that do not happen to mention it. The rendered bytes are memoised per scope under the blocks' own durable revisions, so an unchanged turn returns the same string and re-bills nothing. Adds no new cache breakpoints: blocks are text inside the existing L1 section.
  • Four tiers with a trust boundary. platform / operating are authored and render verbatim; profile / session are learned and render inside <untrusted-data> delimiters with per-fact provenance. The precedence rule (PLATFORM > OPERATING > PROFILE > SESSION) is stated once in the region header instead of being left to the model to infer from concatenation order.
  • Block source adapters: StaticBlockSource, FileBlockSource, ComputedBlockSource, the "memory" lane, and DatabaseBlockSource (rides the existing [postgres] extra). Capabilities — offline_safe, scope_aware, history, writability — are structural: validation asks isinstance(source, HistoryCapableBlockSource) rather than reading a self-reported flag, so an adapter for a store with no versioning cannot claim a capability it does not have.
  • examples/prompt_blocks — a runnable walkthrough of the taxonomy, the capability matrix, the rejected pairings, the rendered region and the cache accounting. A config literal, a temp file, a pure callable and an in-memory graph: no model, no network, no database. Run it with python -m examples.prompt_blocks.
  • docs/concepts/prompt-blocks.md — the concept guide: which block to use for what, who is allowed to write each one, the adapter capability matrix, the adapters deliberately not shipped and why (Consul KV and Vault KV v1 are read-only-only, not missing), and the cache rationale.

  • PromptBlockSpec.render_when — a host-supplied predicate, checked before the source is read, so a gated-off block costs no I/O. This is what lets an ONBOARDING block delete itself once its job is done: a permanent onboarding block is waste on turn 500 and keeps steering the agent. A predicate that raises fails the turn (BlockGateError) rather than being absorbed by on_source_failure, because a gate's failure leaves no trace in the rendered prompt at all.

  • EMBEDDING_BASE_URL / EMBEDDING_API_KEY — point embeddings at a self-hosted server without dragging the chat model with them. The OpenAI SDK reads OPENAI_BASE_URL for every client, so the two could not diverge before. No API key is required when EMBEDDING_BASE_URL is set: naming your own endpoint says api.openai.com is not the target, and self-hosted servers commonly take no auth.
  • examples/block_boundaries — where a block stops: what a delegated child inherits, what each on_source_failure policy does, and how render_when removes a block. Copyable with symfonic examples add, and runs with no model, network or database.
  • Guide: Standing-Context Blocks — choosing a source, the memory-lane prerequisite that fails at construction, failure policy, cache cost, and what a delegated child inherits.
  • Calling run() without a scope is supported, and it partitions rather than accepting or refusing wholesale. scope defaults to None on run() / stream(), so a CLI or a single-tenant deployment reaches this on the ordinary call shape. A block needs a scope only if it is not deployment-scoped, or its source declares scope_aware, or it carries a render_when gate — the gate is called with the scope, so without one there is no question to ask it. Everything else is deployment-global and renders normally: a StaticBlockSource ignores the scope by declaration, so there is nothing for a missing one to change. Blocks that genuinely are scope-keyed cannot be resolved, and each one's own on_source_failure decides what that means — fail_closed raises SecurityScopeError naming the blocks and the remedy, omit and last_known_good degrade as declared. The alternatives were both wrong in the same way: refusing every fail_closed block deletes an authored BOUNDARIES block from every single-tenant deployment, and omitting silently deletes it too, just without saying so.

Fixed

  • ask_user structured elicitation now works end to end. It was unreachable, and each defect hid the next: the tool was bound to the model but never entered tool_manifest, so the prompt's "use ONLY these exact names" list did not authorise it and no model called it (verified against five models); stream_typed recognised only the older interrupt shape, so the graph paused and the caller was told nothing; resume() lost its dependency container and had no interrupt interception, so a second question inside a resumed run parked silently; and a model emitting a question with fewer than two options killed the run outright.
  • An abandoned ask_user question no longer breaks the conversation. A user who never answers used to leave an unanswered tool call that every later turn replayed — rejected by providers that validate tool-call pairing — plus stale state that re-asked the dead question forever. Both are now repaired on the next turn without mutating the checkpoint, so a late answer still resumes.
  • A stale pause no longer strands the router. ask_user_pause_ttl_seconds now governs router staleness as well as pause-token validity, so a caller threading a stable run_id through run() for tracing still recovers. _interrupt_pending carries the same guard for experimental_interrupt users. The preset receives AgentConfig, not FrameworkConfig, so the new symfonic.core.config.AgentConfig.ask_user_pause_ttl_seconds field had to exist for the router guard to be reachable at all.
  • resume_interrupt() never resumed. It passed a state key nothing read, so the parked node's interrupt() re-fired instead of returning and the run could not advance. It now uses Command(resume=...), as resume() does.
  • A render_when predicate that raises now fails the turn at the engine. The gate refused correctly, and a blanket except Exception one layer up turned the refusal into "no blocks" — a declared policy discarded by the boundary meant to honour it.

Upgrade note. Threads already carrying an abandoned pause from before 9.12.0 have no minted_at stamp, so the age check cannot help them; the repair applies to new abandonment. The unanswered-tool-call stub text also changed, which re-bills the cached prefix once for any thread carrying an orphan.

Changed

  • Memory-hydration budgeting no longer calls tiktoken. It preferred an exact token count when tiktoken happened to be importable — which was never a decision: tiktoken is not a declared dependency, it arrives transitively with langchain-openai, so [openai] counted exactly while [anthropic] used a character estimate and the same configuration produced two different hydration caps. It also downloads its encoding on a cold cache, with no timeout, while holding a process-global lock, on a request path. Hydration now uses a character estimate everywhere (len // 3), so the cap is deterministic across installs and nothing on this path can reach the network. Expect a slightly different truncation point than 9.11 if you were on an OpenAI-family extra; this is a sanity ceiling on context size, not token accounting.
  • An expired pause token now raises code="gone" instead of "unauthorized". A timeout and a tampered token were indistinguishable; one deserves "the question expired, ask again" and the other a security alert. Forged tokens keep "unauthorized". Breaking if you match on that code.
  • DomainTemplate.onboarding_checklist documentation corrected. It reaches the model only inside the memory-extraction directive, as a recording instruction. Nothing tells the agent to ask for those fields, and core has no completion signal that would tell it to stop — if you want active onboarding, author that instruction yourself and give it a stop condition.

Security

  • No block grants a destructive verb, at any tier. AgentPermission is exactly read / append / replace / rewrite; clear and delete do not exist. The check runs at import against the literal itself, so widening it later fails loudly rather than quietly granting a delete. Removal happens only through the memory layer's soft-retract path.
  • One writer per block, and it is never the agent. No block-edit tool is registered on any construction path in this release, and core writes to no block source on any prompt or agent path. The only write verb shipped — WritableBlockSource.append_revision — is an operator-facing API the host application calls from its own admin surface; that system owns its authorization and its audit trail. The verb is append only: a restore is expressed as appending the restored content, so history stays strictly additive.
  • The child lockdown is live before the tools exist. symfonic.agent.subagents.lockdown strips prompt_block_self_edit from every delegated child config (including an explicitly-supplied SubAgentSpec.config, which is sanitised rather than trusted) and rejects a pre-built child carrying a block-edit tool. The guard matches the whole memory_block_* namespace, so a verb added later is caught rather than admitted.
  • Misconfigurations are construction-time errors, not runtime checks. A writable platform block, a per-tenant block on a deployment-global source, an operator_editable block on a source that cannot show its history, and an authored tier backed by the memory lane are all unrepresentable in a valid configuration — the process does not start.

Unchanged

  • prompt_blocks defaults to (), and that default is byte-identical. With no block declared, nothing is resolved, no fragment is spliced into any prompt region, and every prompt path renders exactly as it did before this release.

[9.11.0] - 2026-08-04

Added

  • promote_profile_corrections — user profile corrections are now written onto the tenant's own SOUL: node in the graph, scope-keyed, instead of into a shared config dict. Consolidation gains a profile_fields: frozenset[str] argument: the domain's soul_schema is read to learn which field names make up a profile, and is never written to. A domain declaring timezone now gets timezone corrections promoted — the previous implementation only ever handled a hardcoded name / role / personality / language.
  • ConsolidationReport.profile_updates, mirroring the existing soul_updates (which is unchanged, so no consumer's key set breaks).
  • examples/memory_lifecycle — a runnable walkthrough of the memory lifecycle: how an operational fact ages out, why identity never does, that retirement is reversible, and how a user correction reaches the profile. In-memory only; no model, no network, no database. Run it with python -m examples.memory_lifecycle.

Fixed

  • Profile corrections no longer corrupt the extraction schema. The previous apply_soul_corrections wrote instance values into DomainTemplate.soul_schema — a dict[str, str] of field names to their expected types, which is rendered to the model as Schema: {...} to shape extraction. {"name": "str"} became {"name": "Amiel"}, degrading every subsequent extraction for that domain.
  • A cross-tenant leak in the generated worker. The scaffold template built one soul_schema dict above its per-tenant consolidation loop and passed the same object to every tenant; in-place mutation meant one tenant's profile values accumulated into the next tenant's consolidation. Every project generated by symfonic init carried this. The new signature takes an immutable frozenset, so the leak is gone by construction rather than by remembering to copy.

Deprecated

  • SleepConsolidator.run(soul_schema=...), and the same argument on quick_nap() / nightly_nap(). Still accepted, and still functional: when supplied without profile_fields, the field set is derived from soul_schema.keys() and a DeprecationWarning is emitted. The dict is now read-only on every path — nothing mutates a caller's dictionary any more. Pass profile_fields=frozenset(...) instead.

[9.10.0] - 2026-08-04

Added

  • Semantic memory now expires. A new consolidation phase (9.2) soft-retracts SEMANTIC nodes whose durability marker says expired, whose transient TTL has elapsed, or whose valid_until timestamp has passed. Until now the SEMANTIC layer had no expiry path at all: cleanup_working_ttl only covers WORKING, decay_importance lowers importance but never removes anything, and retract_node fires only on an explicit user correction. A fact that quietly stopped being true — "the Slack integration is broken" — stayed in the agent's context forever. Expiry is entirely time-based; no model is ever asked whether a memory is stale, and the terminal action is a soft retraction that flows through the existing 7-day grace window, never a delete.
  • INCIDENT: label prefix. Nodes labelled INCIDENT: are treated as transient even without an explicit durability, so operational facts age out on their own. This is the first prefix convention that makes a node expire faster rather than slower — the deliberate opposite of SOUL: / AGENT_IDENTITY:, which are exempt from decay. A future valid_until extends an incident's life; nothing makes one permanent.
  • ConsolidationReport.nodes_expired reports how many nodes phase 9.2 retracted.

Fixed

  • A sub-agent no longer overwrites the tenant's agent identity. A delegated child inherits its parent's tenant scope but is built with a domain named after its own spec, so a child that ran before its parent for a fresh tenant would seed AGENT_IDENTITY: <child-name> onto the shared identity node — and because the seeder early-returns once any identity node exists, the parent then silently and permanently inherited its child's identity. Only a top-level run may now seed.
  • SOUL: nodes are excluded from LLM-judged semantic merge. They were eligible, so the judge could absorb SOUL: name into SOUL: role and stamp a supersede-retraction on the loser, corrupting the user profile. AGENT_IDENTITY was already excluded for exactly this reason.

Backward compatibility

Nothing changes at default settings. Phase 9.2 only acts on nodes that carry an explicit durability marker, a valid_until timestamp, or an INCIDENT: label; a node with none of these is untouched. Existing SEMANTIC data is unaffected — promotion into SEMANTIC already requires durability == "durable", so no stored fact starts expiring on upgrade.

[9.9.0] - 2026-07-19

Added

  • Kimi OAuth auto-refresh. KimiOAuthProvider.from_kimi_cli() reads the kimi-cli credential file and auto-refreshes the short-lived (~15 min) token on every request — local dev sessions no longer 401 mid-run or need a manual kimi refresh. The rotated refresh token is written back safely (file lock + atomic write) so your kimi-cli login stays valid; tokens are never logged. Explicit KIMI_OAUTH_ACCESS_TOKEN opts out (unchanged).

Changed

  • Clear, actionable auth-failure messages for all OAuth providers. An expired/revoked subscription token now names the exact remediation (kimi-cli login / codex login / claude login) via a clear error or a log-only WARNING on 401/403, instead of an opaque SDK error.

[9.8.0] - 2026-07-19

Subscription OAuth providers for local dev/test. Three providers now let a single developer drive the full pipeline against a real LLM using the OAuth token their CLI already stored — no metered API key. They are not a production path: none bill per tenant or refresh tokens, each replays a personal subscription token (which may be throttled/disallowed under the vendor's ToS), and each refuses to construct in a production-like environment. See the Subscription OAuth providers guide.

Added

  • KimiOAuthProvider (symfonic.core.kimi_oauth_provider) — dev/test-only provider that calls the Kimi/Moonshot OpenAI-compatible backend with a kimi.com subscription token instead of a metered MOONSHOT_API_KEY. Env-vars-only (KIMI_OAUTH_ACCESS_TOKEN, KIMI_OAUTH_BASE_URL, KIMI_OAUTH_PLATFORM); production guard identical to CodexOAuthProvider. Defaults to the kimi.com subscription/coding endpoint (https://api.kimi.com/coding/v1, model kimi-for-coding) — the only endpoint a subscription token authenticates against — so subscription tokens work without an override.
  • Live (live_llm) tests for all three OAuth providers — each sends a real SystemMessage + HumanMessage round-trip and is deselected by default.

Changed

  • AnthropicOAuthProvider is no longer deprecated. It is now a supported dev/test-only provider (same posture as CodexOAuthProvider): construction no longer emits a DeprecationWarning — instead it logs a one-time ToS caveat. It still auto-loads the Claude Code token from the Keychain / ~/.claude/.credentials.json and is never for production.

Fixed

  • CodexOAuthProvider now works through the normal agent path. The Codex Responses backend rejects role: "system" (400 System messages are not allowed), which crashed every SymfonicAgent run on the first call. The provider now rewrites outgoing system messages to the developer role on the Responses-API path. (Verified live against the Codex backend.)
  • symfonic init --llm-provider openrouter now points adopters at OPENROUTER_API_KEY instead of a generic LLM_API_KEY hint.
  • KimiProvider observability — Kimi spans now report gen_ai.system="openai" (was "unknown"), consistent with its DeepSeek sibling.

[9.3.0] - 2026-07-13

Lazy-tooling skill-resolution contract (issue #36). No API changes.

Changed

  • lazy_tooling validates procedural skills against your registered tools. A skill's tool identifier is its action_tool metadata (else content) and must be the exact name of a registered tool. A name that matches nothing is now dropped with a LazySkillResolutionWarning instead of leaking into the system prompt as a phantom tool. Correctly-named skills are unaffected; put descriptive text in steps / context, not in the identifier field.

[9.2.1] - 2026-07-13

Correctness patch from an external review of the 9.1/9.2 work. No API changes.

Fixed

  • Agent shutdown no longer blocks on unrelated agents. Background consolidation is now tracked and flushed per agent, so exiting one agent's async with never waits on another agent's pending work.
  • Provider-family extraction respects routing. The family-tuned extraction template is now selected from the resolved model config, so a router serving a different vendor than its default gets the right template.
  • Config facades validate overrides. FrameworkConfig.child(...) and AgentBuilder now re-validate the assembled config, enforcing field constraints and warning on unknown fields instead of silently keeping them.
  • Declarative sub-agent descriptions preserved. A SubAgentSpec with only description set now gives the child a matching domain description (was empty).

[9.2.0] - 2026-07-12

Facade minor: a fluent builder over the 9.1 construction surface and provider-family-aware memory extraction. Fully additive — no breaking changes.

Added

  • AgentBuilder — a fluent way to assemble an agent. Chain .provider().model().domain().tools().enable_hms().memory().sub_agent().build() instead of hand-wiring FrameworkConfig + SymfonicAgent. It's a thin facade over the same primitives (FrameworkConfig, SubAgentSpec, HMSFactory) — no new behaviour — and .memory(embeddings=…) builds an in-memory HMS for you.

Changed

  • HMS memory extraction adapts to the provider family. OpenAI-family and Google agents now render a more explicit extraction directive (a strict <GRAPH_OPERATIONS> JSON protocol) instead of the Anthropic-delimiter default, reducing silent memory loss on instruction-following models. Anthropic and unrecognised/custom providers are unchanged (wire-neutral). An explicit extraction_template_path still overrides the family default.

[9.1.0] - 2026-07-12

Construction-DX minor for sub-agent ergonomics and non-Claude memory reliability. Fully additive — no breaking changes.

Added

  • Declarative sub-agents — SubAgentSpec. Describe a child agent and let the parent build it: SymfonicAgent(sub_agents=[SubAgentSpec(name="researcher", description="deep research", tools=[...], temperature=0.2)]). The parent constructs the isolated child at wiring time, inheriting the parent's ModelProvider and every behaviour flag, but with its own fresh domain so its tool manifest auto-derives from its own tools. The pre-built SubAgent(agent=...) form remains as the escape hatch; mix both freely.
  • FrameworkConfig.child(parent, name=, description=, model_name=, temperature=, max_tokens=, **overrides) — derive a sub-agent config that inherits every parent flag and overrides only what you name. Stops per-child config drift.
  • Provider inheritance. A SubAgentSpec inherits the parent's provider by default (providers are stateless, so one instance is safe to share); pass provider= to override per child.
  • AgentResponse.delegated_to is now populated. The field (added empty in 9.0.1) is wired: a parent run records the sub-agent(s) it delegated to via run_agent and stamps them, in order, onto its final response.
  • FrameworkConfig.extraction_template_path — point HMS memory extraction at a custom directive template. The bundled template is Anthropic-delimiter oriented; non-Claude adopters (Qwen/DeepSeek/vLLM) can supply a JSON-only directive to stop silent memory loss when the model ignores the delimiter.

Changed

  • async with SymfonicAgent(...) now flushes background memory work. __aexit__ awaits flush_background_tasks() before releasing resources, so the idiomatic context-manager exit no longer silently drops fire-and-forget consolidation. aclose() stays a pure resource-release for the hot-reload path. A best-effort atexit guard emits a RuntimeWarning if a process exits with consolidation still pending — turning silent memory loss into a visible signal.

[9.0.1] - 2026-07-12

Agent-experience patch from autonomous-adopter feedback. No breaking changes.

Added

  • symfonic guide and a wheel-resident AGENTS.md — an offline quick reference (decision tree, canonical imports, a real quickstart, footguns) discoverable from an installed wheel, no repo needed. Plus llms.txt for online agents.
  • AgentResponse.delegated_to — the sub-agent(s) a parent delegated to (tuple[str, ...], empty = answered directly).

Changed

  • tool_manifest auto-derives silently from your registered tools (was a UserWarning); this closes the "tool registered but the LLM can't see it" footgun. Tools marked @symfonic_tool(visibility="hidden") are excluded; an explicit manifest still wins.
  • symfonic.core now points to symfonic.agent in its docstring, and the LazyToolingWarning carries an actionable Fix: line.
  • Sharing one ModelProvider across a parent and its sub-agents is safe and documented (providers hold no per-call state).

Fixed

  • Non-Claude memory-label corruption — labels are whitespace-collapsed and length-capped so a model that ignores the extraction format can't persist its whole response as a node label.

[9.0.0] - 2026-07-10

Changed (breaking)

  • Migrated to the langgraph 1.x / langchain-core 1.x line. Core now requires langgraph>=1.0,<2.0 and langchain-core>=1.0,<2.0; the 0.x line is no longer supported. Provider extras admit the langchain- 1.x releases (anthropic, openai, aws, google, ollama, chroma). A plain pip install -U symfonic-core pulls the aligned 1.x set. Step-by-step upgrade instructions:* Migrate 8.x to 9.x.

Fixed

  • The durable Mongo checkpointer is now installable. It shipped in 8.12.0 but pip install "symfonic-core[mongodb]" could not resolve — the Mongo adapter requires langgraph-checkpoint>=3 (i.e. langgraph>=1.0), which the old langgraph<1.0 core pin forbade. On the 1.x line the whole checkpoint cluster (postgres/sqlite/mongo) resolves consistently.
  • Tool errors no longer crash a run on langgraph 1.x. langgraph 1.x changed the tool-node default to re-raise non-validation tool exceptions; symfonic now restores the prior behavior where a raised tool error is fed back to the model as a recoverable message instead of aborting the graph.
  • Anthropic http_client transport injection works across the langchain-anthropic 1.x line (the seam's version gate now spans the 1.x releases, still guarded by its structural probe).

[8.12.0] - 2026-07-09

Added

  • Durable checkpointer for MongoDB. A Mongo graph backend now wires a durable MongoDBSaver (needs pip install "symfonic-core[mongodb]") instead of silently falling through to the ephemeral in-process MemorySaver — so resumable interactive sessions and durable transcripts survive a restart on Mongo deployments. ask_user pause tokens are made durable on Mongo too via a MongoPauseTokenStore. Selected automatically from the graph backend, exactly like the Postgres path; no config change required. See docs/concepts/conversation-persistence.md.
  • Conversation managers. Named, swappable strategies for keeping history in the context window — SummarizingConversationManager (default: summarize overflow), SlidingWindowConversationManager(window_size=N) (keep the last N messages, no LLM summary), and NullConversationManager (unbounded). Pass one as SymfonicAgent(conversation_manager=…). Thin wrappers over the existing compaction + windowing engine — orthogonal to HMS memory. See docs/concepts/conversation-managers.md.
  • symfonic examples CLI. Copy a curated, runnable example straight into your project without cloning the repo — symfonic examples list shows what's available and symfonic examples add <name> copies it into ./<name>/ and prints how to run it. The curated set (minimal_agent, basic_agent, tool_agent, skill_agent, sub_agents, memory_agent, e2e_basic_agent) is bundled in the wheel as data and runs on MockModelProvider — no API key, no extra dependencies. Requires the [cli] extra. Also includes real_agent, which runs against a real Anthropic model ([anthropic] extra + an API key); examples list / add state each example's requirements.
  • Full-code example walkthroughs. Each of the nine core learning-path examples now has a documentation page with the complete source and a step-by-step explanation, so the code is readable without a checkout.

Fixed

  • Durable checkpointer was never selected (Postgres or Mongo). The engine's graph-backend selectors read a non-existent .backend attribute (the store exposes _backend), so every deployment silently fell through to the ephemeral in-process MemorySaver regardless of a durable Postgres/Mongo graph backend — transcripts and resumable state did not survive a restart. Now reads the real backend. Surfaced by end-to-end testing the Mongo path.
  • Mongo checkpoints lost their channel values. MongoDBSaver serializes the whole checkpoint in one call; the safe serializer degraded the entire checkpoint to None when it contained a non-serializable runtime channel (deps), so get_transcript / restart-resume returned empty on Mongo. The serializer now sanitizes channel values per entry, keeping messages.
  • Examples install guidance. Corrected the docs claim that an editable/dev install "exposes" the examples tree — it's running from the repo root (which puts examples on sys.path) that makes python -m examples.<name> work; the extras are only needed for examples that import them.

[8.10.0] - 2026-07-08

Added

  • AWS Bedrock provider. Run any Bedrock model (Claude, Llama, Nova, Mistral, …) through the Converse API via AWSBedrockProvider (pip install symfonic-core[aws]). The wire family is auto-detected per model — Claude gets extended thinking, other vendors are treated wire-neutral. Region and credentials resolve through the standard boto3 chain; LLM_MODEL is a Bedrock model / inference-profile id. symfonic init --llm-provider aws scaffolds it end-to-end.
  • OpenRouter provider. Reach many vendors through one OpenAI-compatible gateway via OpenRouterProvider (pip install symfonic-core[openrouter]). LLM_MODEL is a fully-qualified id (anthropic/claude-sonnet-4.5); auth via OPENROUTER_API_KEY. symfonic init --llm-provider openrouter scaffolds it.

Fixed

  • Bedrock cost telemetry. Claude-on-Bedrock calls now report correct token cost (Anthropic list rates) instead of $0. Non-Anthropic Bedrock models need a pricing override.

[8.9.1] - 2026-07-08

Added

  • Sub-agent delegation. A parent agent can hand a self-contained task to a named child agent through the built-in run_agent / list_agents tools. Declare children with SymfonicAgent(sub_agents=[SubAgent(...)]); each child runs isolated, inherits the parent's tenant scope, and is bounded by a delegation-depth guard. See the Sub-agents guide.
  • Sampling controls on ModelConfig. New top_p (all providers) and top_k (Anthropic, Google, Ollama) knobs, alongside the existing temperature / max_tokens. See Model Tuning.
  • Scaffolded projects (symfonic init) now ship a working researcher sub-agent, a visible "delegating…" indicator in the chat UI, and tighter default reply lengths.

Fixed

  • The built-in mock chat model now streams incrementally, so streamed UIs and end-to-end tests observe real token deltas instead of an empty response.

[8.8.0] - 2026-07-07

Added

  • Pluggable metrics persistence. A storage-agnostic MetricsStore lets per-LLM-call usage and cost survive worker restarts, with Postgres and MongoDB reference backends. Fully additive — behaviour is unchanged when no store is configured.

[8.7.1] - 2026-07-03

Fixed

  • Consolidated hotfix across the agent engine, memory backends, prompt cache, pricing/observability, and metrics endpoints. Hardens state-key propagation, conversation isolation in the messages cache, precondition gating, and metrics-router authentication.

Packaging

  • The published source distribution is now curated: it contains the package, tests, license, README, and a hand-picked set of user-facing guides and examples. Internal tooling, local artifacts, and internal-only notes are no longer included.
  • Installation now defaults to public PyPI (pip install symfonic-core); no extra package index configuration is required.
  • Added an explicit MIT LICENSE file to the distribution.

[8.7.0] - 2026-07-01

Added

  • Opt-in rolling messages-region cache ladder (messages_cache_policy="rolling"). For long agentic tool-loops it converts the previously ~quadratic uncached token tail into a roughly linear one by holding prior cache markers as reads and adding one new marker per iteration. No default behavior change — the request wire is byte-identical under the default policy.

[8.6.0 – 8.6.9] - 2026-06

Changed

  • A series of reliability and correctness improvements across memory backends, prompt caching, usage/cost accounting, and the diagnostics CLI.

[8.0.0] - 2026-06-13

Added

  • Major release of the 8.x line: the agent orchestration layer, 5-layer Hierarchical Memory System (HMS), capability-based dependency injection, streaming event taxonomy, and the FastAPI bridge.

For upgrade guidance between major lines, see Migrate 8.x to 9.x and Migrate 6.x to 7.x.