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]¶
[9.12.0] - 2026-08-08¶
Added¶
- Prompt blocks — a pinned standing-context lane inside the cached prefix.
FrameworkConfig.prompt_blocksdeclares named blocks (BOUNDARIES,IDENTITY,RULES,ENVIRONMENT,USER_PROFILE, …), each served by one source and rendered into theL0 + L1cached region rather than the uncachedMEMORY_CONTEXTplaceholder. 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/operatingare authored and render verbatim;profile/sessionare 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, andDatabaseBlockSource(rides the existing[postgres]extra). Capabilities —offline_safe,scope_aware, history, writability — are structural: validation asksisinstance(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 withpython -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 byon_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 readsOPENAI_BASE_URLfor every client, so the two could not diverge before. No API key is required whenEMBEDDING_BASE_URLis 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 eachon_source_failurepolicy does, and howrender_whenremoves a block. Copyable withsymfonic 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.scopedefaults toNoneonrun()/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 declaresscope_aware, or it carries arender_whengate — 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: aStaticBlockSourceignores 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 ownon_source_failuredecides what that means —fail_closedraisesSecurityScopeErrornaming the blocks and the remedy,omitandlast_known_gooddegrade as declared. The alternatives were both wrong in the same way: refusing everyfail_closedblock deletes an authored BOUNDARIES block from every single-tenant deployment, and omitting silently deletes it too, just without saying so.
Fixed¶
ask_userstructured elicitation now works end to end. It was unreachable, and each defect hid the next: the tool was bound to the model but never enteredtool_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_typedrecognised 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_userquestion 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_secondsnow governs router staleness as well as pause-token validity, so a caller threading a stablerun_idthroughrun()for tracing still recovers._interrupt_pendingcarries the same guard forexperimental_interruptusers. The preset receivesAgentConfig, notFrameworkConfig, so the newsymfonic.core.config.AgentConfig.ask_user_pause_ttl_secondsfield 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'sinterrupt()re-fired instead of returning and the run could not advance. It now usesCommand(resume=...), asresume()does.- A
render_whenpredicate that raises now fails the turn at the engine. The gate refused correctly, and a blanketexcept Exceptionone 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_atstamp, 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 whentiktokenhappened to be importable — which was never a decision:tiktokenis not a declared dependency, it arrives transitively withlangchain-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_checklistdocumentation 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.
AgentPermissionis exactlyread/append/replace/rewrite;clearanddeletedo 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.lockdownstripsprompt_block_self_editfrom every delegated child config (including an explicitly-suppliedSubAgentSpec.config, which is sanitised rather than trusted) and rejects a pre-built child carrying a block-edit tool. The guard matches the wholememory_block_*namespace, so a verb added later is caught rather than admitted. - Misconfigurations are construction-time errors, not runtime checks. A
writable
platformblock, a per-tenant block on a deployment-global source, anoperator_editableblock 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_blocksdefaults 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 ownSOUL:node in the graph, scope-keyed, instead of into a shared config dict. Consolidation gains aprofile_fields: frozenset[str]argument: the domain'ssoul_schemais read to learn which field names make up a profile, and is never written to. A domain declaringtimezonenow getstimezonecorrections promoted — the previous implementation only ever handled a hardcodedname/role/personality/language.ConsolidationReport.profile_updates, mirroring the existingsoul_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 withpython -m examples.memory_lifecycle.
Fixed¶
- Profile corrections no longer corrupt the extraction schema. The previous
apply_soul_correctionswrote instance values intoDomainTemplate.soul_schema— adict[str, str]of field names to their expected types, which is rendered to the model asSchema: {...}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_schemadict 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 bysymfonic initcarried this. The new signature takes an immutablefrozenset, so the leak is gone by construction rather than by remembering to copy.
Deprecated¶
SleepConsolidator.run(soul_schema=...), and the same argument onquick_nap()/nightly_nap(). Still accepted, and still functional: when supplied withoutprofile_fields, the field set is derived fromsoul_schema.keys()and aDeprecationWarningis emitted. The dict is now read-only on every path — nothing mutates a caller's dictionary any more. Passprofile_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
durabilitymarker saysexpired, whosetransientTTL has elapsed, or whosevalid_untiltimestamp has passed. Until now the SEMANTIC layer had no expiry path at all:cleanup_working_ttlonly covers WORKING,decay_importancelowers importance but never removes anything, andretract_nodefires 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 labelledINCIDENT:are treated as transient even without an explicitdurability, 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 ofSOUL:/AGENT_IDENTITY:, which are exempt from decay. A futurevalid_untilextends an incident's life; nothing makes one permanent.ConsolidationReport.nodes_expiredreports 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 absorbSOUL: nameintoSOUL: roleand stamp a supersede-retraction on the loser, corrupting the user profile.AGENT_IDENTITYwas 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 thekimi-clicredential file and auto-refreshes the short-lived (~15 min) token on every request — local dev sessions no longer 401 mid-run or need a manualkimirefresh. The rotated refresh token is written back safely (file lock + atomic write) so yourkimi-clilogin stays valid; tokens are never logged. ExplicitKIMI_OAUTH_ACCESS_TOKENopts 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 on401/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 meteredMOONSHOT_API_KEY. Env-vars-only (KIMI_OAUTH_ACCESS_TOKEN,KIMI_OAUTH_BASE_URL,KIMI_OAUTH_PLATFORM); production guard identical toCodexOAuthProvider. Defaults to the kimi.com subscription/coding endpoint (https://api.kimi.com/coding/v1, modelkimi-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 realSystemMessage+HumanMessageround-trip and is deselected by default.
Changed¶
AnthropicOAuthProvideris no longer deprecated. It is now a supported dev/test-only provider (same posture asCodexOAuthProvider): construction no longer emits aDeprecationWarning— instead it logs a one-time ToS caveat. It still auto-loads the Claude Code token from the Keychain /~/.claude/.credentials.jsonand is never for production.
Fixed¶
CodexOAuthProvidernow works through the normal agent path. The Codex Responses backend rejectsrole: "system"(400 System messages are not allowed), which crashed everySymfonicAgentrun on the first call. The provider now rewrites outgoing system messages to thedeveloperrole on the Responses-API path. (Verified live against the Codex backend.)symfonic init --llm-provider openrouternow points adopters atOPENROUTER_API_KEYinstead of a genericLLM_API_KEYhint.KimiProviderobservability — Kimi spans now reportgen_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_toolingvalidates procedural skills against your registered tools. A skill's tool identifier is itsaction_toolmetadata (elsecontent) and must be the exact name of a registered tool. A name that matches nothing is now dropped with aLazySkillResolutionWarninginstead of leaking into the system prompt as a phantom tool. Correctly-named skills are unaffected; put descriptive text insteps/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 withnever 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(...)andAgentBuildernow re-validate the assembled config, enforcing field constraints and warning on unknown fields instead of silently keeping them. - Declarative sub-agent descriptions preserved. A
SubAgentSpecwith onlydescriptionset 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-wiringFrameworkConfig+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 explicitextraction_template_pathstill 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'sModelProviderand every behaviour flag, but with its own fresh domain so its tool manifest auto-derives from its own tools. The pre-builtSubAgent(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
SubAgentSpecinherits the parent's provider by default (providers are stateless, so one instance is safe to share); passprovider=to override per child. AgentResponse.delegated_tois now populated. The field (added empty in 9.0.1) is wired: a parent run records the sub-agent(s) it delegated to viarun_agentand 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__awaitsflush_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-effortatexitguard emits aRuntimeWarningif 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 guideand a wheel-residentAGENTS.md— an offline quick reference (decision tree, canonical imports, a real quickstart, footguns) discoverable from an installed wheel, no repo needed. Plusllms.txtfor online agents.AgentResponse.delegated_to— the sub-agent(s) a parent delegated to (tuple[str, ...], empty = answered directly).
Changed¶
tool_manifestauto-derives silently from your registered tools (was aUserWarning); 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.corenow points tosymfonic.agentin its docstring, and theLazyToolingWarningcarries an actionableFix:line.- Sharing one
ModelProvideracross 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.0andlangchain-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 plainpip install -U symfonic-corepulls 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 requireslanggraph-checkpoint>=3(i.e.langgraph>=1.0), which the oldlanggraph<1.0core 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_clienttransport 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(needspip install "symfonic-core[mongodb]") instead of silently falling through to the ephemeral in-processMemorySaver— so resumable interactive sessions and durable transcripts survive a restart on Mongo deployments. ask_user pause tokens are made durable on Mongo too via aMongoPauseTokenStore. Selected automatically from the graph backend, exactly like the Postgres path; no config change required. Seedocs/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), andNullConversationManager(unbounded). Pass one asSymfonicAgent(conversation_manager=…). Thin wrappers over the existing compaction + windowing engine — orthogonal to HMS memory. Seedocs/concepts/conversation-managers.md. symfonic examplesCLI. Copy a curated, runnable example straight into your project without cloning the repo —symfonic examples listshows what's available andsymfonic 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 onMockModelProvider— no API key, no extra dependencies. Requires the[cli]extra. Also includesreal_agent, which runs against a real Anthropic model ([anthropic]extra + an API key);examples list/addstate 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
.backendattribute (the store exposes_backend), so every deployment silently fell through to the ephemeral in-processMemorySaverregardless 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.
MongoDBSaverserializes the whole checkpoint in one call; the safe serializer degraded the entire checkpoint toNonewhen it contained a non-serializable runtime channel (deps), soget_transcript/ restart-resume returned empty on Mongo. The serializer now sanitizes channel values per entry, keepingmessages. - 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
examplesonsys.path) that makespython -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_MODELis a Bedrock model / inference-profile id.symfonic init --llm-provider awsscaffolds it end-to-end. - OpenRouter provider. Reach many vendors through one OpenAI-compatible
gateway via
OpenRouterProvider(pip install symfonic-core[openrouter]).LLM_MODELis a fully-qualified id (anthropic/claude-sonnet-4.5); auth viaOPENROUTER_API_KEY.symfonic init --llm-provider openrouterscaffolds 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_agentstools. Declare children withSymfonicAgent(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. Newtop_p(all providers) andtop_k(Anthropic, Google, Ollama) knobs, alongside the existingtemperature/max_tokens. See Model Tuning. - Scaffolded projects (
symfonic init) now ship a workingresearchersub-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
MetricsStorelets 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
LICENSEfile 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.