Skip to content

Moving to 11.0: kernel adoption, and what 11.0 retires

This source tree declares 11.5.0 in pyproject.toml and is a private release candidate. The private 11.3.0 line introduced the public SymfonicAgent and AgentBuilder refusals described below. Source metadata does not establish which artifact an adopter has installed. The 11.0.0 release retired three per-call arguments and served stream_typed from the kernel projection; both are listed in the changelog. That release did not remove legacy code. In this private 11.5.0 candidate, agent/engine.py is physically absent from the source tree, wheel, and sdist after the separate erasure decision. The historical retirement evidence remains a record of the earlier source state. This candidate has not been published to public PyPI.

Adopters who depend on a retired argument stay on 9.12, the newest release that still honours it. The retirement is not backported.

SymfonicAgent and AgentBuilder refuse from 11.3.0

From 11.3.0, SymfonicAgent(...) and AgentBuilder().build() raise EntryPointRemovedError before anything is built: before any argument is read, any process-wide setting is installed, or any legacy body runs. This is an explicit break inside 11.x with no compatibility shim. Both names still import, so the error reaches you instead of an ImportError. The public SymfonicAgent name in this source tree resolves to the refusing class in entry_point_removal, without importing the engine or cutover package:

from symfonic.agent import EntryPointRemovedError, SymfonicAgent

try:
    SymfonicAgent(provider)
except EntryPointRemovedError as exc:
    exc.entry_point       # "SymfonicAgent" (or "AgentBuilder" from build())
    exc.removed_in        # "11.3"
    exc.replacements      # ("Agent", "AgentHost", "agent_from_config")
    exc.last_legacy_line  # "9.12"

It is a ConfigurationError, so an existing except ConfigurationError still catches it. Move one agent to symfonic.Agent and a hosted application to symfonic.platform.AgentHost. An existing FrameworkConfig carries over through symfonic.migration.agent_from_config, which honours the settings it translates and refuses every other active setting by name (framework_config_agent.v1); stock settings need no translation. The two classes do not accept the same arguments as SymfonicAgent, so use the migration guide rather than renaming the call. symfonic-core==9.12.* is the newest public PyPI line on which either entry point still constructs (private 11.0 to 11.2.6 did too). It predates the 11.x features, so pinning it does not migrate an 11.x application. The 11.3.0 entry-point refusal preceded physical engine removal in this checkout. Every SymfonicAgent section below describes releases before 11.3.0.

QUICK consolidation keeps ancestor-visible semantics by default. A deployment with terminal session ownership can opt into exact_session_owner=True when composing quick_phases; see the 11.0.2 changelog for its fail-closed limits.

Choose the right entry point

Your project Starting point What to verify
New agent from symfonic import Agent; simple-agent guide Compose the capabilities your application needs
New application with UI and services Scaffold Generated configuration, storage, providers, tenancy and enabled capabilities
Existing SymfonicAgent or AgentBuilder application From 11.3.0 both refuse (see above). Move to Agent, AgentHost or agent_from_config with the migration guide, or pin symfonic-core==9.12.* Arguments, switches, persistence and continuation paths exercised by your application
Release or operational validation Agent regression evals Evidence from the generated application, not just library unit tests

Kernel adoption is composition: importing Agent does not automatically enable memory, extraction, consolidation, delegation, or telemetry. An existing FrameworkConfig setting is not proof that a separately composed capability consumes it. Consult the migration guide for the corresponding public composition entry point.

ask_user on an existing SymfonicAgent

FrameworkConfig(ask_user_enabled=True) is still an active compatibility setting. On run, stream, and stream_typed, it declines the migrated kernel route and sends the turn to the legacy body, where the built-in ask_user tool, checkpointer, question event, and HMAC pause token remain implemented. The fallback is counted and names ask_user_enabled. The inert_on value in agent/cutover/pause_surfaces.py describes only what the migrated route would do if this setting were admitted; it does not describe the behavior of the SymfonicAgent call after routing.

The first turn must supply both a tenant scope and a non-empty session_id. Without them, all three entry points raise SymfonicAgentError before calling the model because a later resume could not find a stable checkpoint. A pause is minted by the legacy body. resume() is routed separately and may redeem through the kernel continuation or the legacy continuation according to the deployment's continuation switch and the token it accepts. Exercise one real pause and redemption through your transport before rollout, including the question event and pause token returned to the client.

Choose one public composition for human interaction:

Agent entry point Supported human-interaction setup
Existing SymfonicAgent Keep ask_user_enabled=True, install the ask-user extra, and provide scope plus session_id on the first turn. No warning is emitted for the flag because it selects working legacy behavior; the missing identity inputs already fail loudly.
Native Agent and AgentHost on this source tree (11.0.3 and later) Compose human_interaction(...) with durable payload, checkpoint, and consumption ports, and install human_continuation(...) on the host. The capability contributes the built-in ask_user tool spec and registration. On an earlier package, verify that it contains human_continuation(...).

At the native composition root, choose the default and the maximum pause lifetime explicitly. A per-pause request over the maximum is refused; it is never silently shortened. The optional ask_user_description is the exact text bound to the model for this deployment, so use a localized string when the deployment is localized. There is no per-request locale-table lookup.

from symfonic.capabilities.human import human_interaction
from symfonic.capabilities.human.values import TTLPolicy

human_pause_capability = human_interaction(
    signer=signer,
    binding=binding_for_this_run,
    encode_token=encode_token,
    decode_token=decode_token,
    checkpoints=durable_checkpoints,
    consumption=durable_consumption,
    ttl=TTLPolicy(default_seconds=300, maximum_seconds=900),
    ask_user_description="Pregúntale a la persona responsable antes de continuar.",
)

Do not add ask_user_tool_spec or InteractionRegistration beside a SymfonicAgent merely to restore the flag: those are parts of the native capability composition, not a patch for the compatibility facade. Move to that composition when the application moves to Agent and AgentHost. There is no recorded target version for admitting ask_user_enabled onto the kernel route; until its blocking and chunk projections gain a pause shape and admission is re-evaluated, the compatibility route serves the flag.

The ask-user extra remains meaningful for SymfonicAgent. It supplies the token-signing and supported SQLite/Postgres checkpoint dependencies used by the compatibility path, so a consumer that may enable the flag should retain it.

The SymfonicAgent compatibility behavior above was already present in the 11.0.0 source. The clarification of what inert_on means and the native human_continuation(...) API landed after the 11.0.0 wheel was built; both are included from the 11.0.3 source tree on. An installed 11.0.0 wheel does not gain that API from a documentation update.

Supported FP-4 compositions

The generated scaffold composes platform.governance() and the native QUICK maintenance cycle. Its MEMORY_QUICK_NAP_TURNS is a real cadence: 0 omits the coordinator and a positive value constructs ConsolidationSchedule and a complete quick_phases roster. The generated Agent-only worker currently refuses DEEP because it has no admitted native v1 owner, and the scaffold does not schedule that task. nightly_nap_enabled and nightly_nap_cron were operator metadata, not an engine scheduler.

LearningPolicy belongs to a DEEP factory that also receives the extractor collaborator. The model spelling is deliberately different at that boundary: phase_12_llm_model becomes procedural_model_name. The generated scaffold does not compose an LLM extractor, so it rejects every phase-12 LLM model/cap setting early rather than accepting a no-op. enable_semantic_merge is also not a policy field: QUICK uses an explicit opt-in plus chat_model and embedding collaborators. The executable DEEP composition is examples/migration_parity/consolidation.py; it makes the entity extractor, regex confidence boundary, and optional merge judge visible at the composition root.

from symfonic.capabilities.memory import quick_phases

phases = quick_phases(
    graph=graph_backend,
    enable_semantic_merge=True,
    chat_model=chat_model,             # required merge judge collaborator
    embedding_provider=embeddings,
    lookback_hours=24,
    spreading_weight=0.5,
)

Prompt cache directives belong to stable provider-system contributions. The history passed to Agent.run is separate and this does not replace a legacy rolling-history cache policy.

from symfonic.capabilities.prompting import (
    CacheDirective, CacheTtl, Layer, PromptContribution, StaticSource,
)

rules = PromptContribution(
    contribution_id="deployment.rules",
    source=StaticSource("Stable operating rules."),
    layer=Layer.L1,
    cache=CacheDirective(cacheable=True, ttl=CacheTtl.ONE_HOUR),
)

governance() supplies the default credential-hygiene composition. Add a reflector and a trigger policy when the deployment needs metacognition. Recall sensitivity is factual public subject state (metacognition_sensitive_tags), which a retrieval integration must pass; it is not inferred automatically from retrieved prose. ConfidenceOnlyMetacognitionPolicy is an explicit opt-out of the default selective triggers.

For a durable human pause, create the capability with the deployment's durable payload/checkpoint/consumption ports, then install this host-level continuation factory. The resolver receives authenticated token claims and looks up only the signed scope hash; callers never submit a scope with the token. A successful resume is at-most-once. If host construction fails before the token is claimed, the caller can retry it.

from symfonic.platform import AgentHost, human_continuation

host = AgentHost(
    composer=compose_agent_for_scope,
    resources=resources,
    continuation_factory=human_continuation(
        capability=human_pause_capability,
        scope_for_claims=scope_for_authenticated_claims,
    ),
)
result = await host.resume(token, answer)

AgentHost.resume(token, answer) remains the external recovery entry point. Its host adapter authenticates the token, resolves the signed scope, claims the single-use token, and restores the paused turn before it invokes the scoped agent. Agent.continue_recovered(...) is the corresponding trusted in-process integration seam; its value is available as from symfonic.agent.facade_types import RecoveredContinuation. Constructing that value does not authenticate a token, select a scope, or authorize a recovery. Do not build it from a transport request; hosts construct it only after the verified recovery steps above.

Custom state and SQLite checkpoint migration

FrameworkConfig.state_class remains a compatibility-facade LangGraph schema hook. Native Agent has no graph-state-class or reducer-registration seam, so there is no approved conversion from a legacy state class. The supported native extension is a caller-owned typed mapping supplied for each run() or stream() and read by a composed capability:

from examples.custom_state_extension import (
    AccountTurn,
    AccountTurnCapability,
    build_agent,
)

capability = AccountTurnCapability()
agent = build_agent(provider, capability)
state: AccountTurn = {"account_id": "acme-42", "plan": "enterprise"}
result = await agent.run("continue", state=state)

examples/custom_state_extension.py is the executable public-composition example; its focused public test exercises both run() and a drained stream(). The mapping is copied and frozen for that one invocation. It is neither stored on the Agent nor included in RecoveredContinuation: a resumed human pause restores its documented prompt, message history, response, and authenticated scope binding, not arbitrary caller state. Keep durable application state in a deployment-owned store and rehydrate it through the capability composition you control; no automatic legacy state_class or checkpoint-file import is supported.

For the same reason, a legacy dev_sqlite_checkpoint_path is not a native configuration alias. A legacy LangGraph SQLite file is not imported into the human-interaction checkpoint format. Compose the durable payload, checkpoint, and single-use consumption ports explicitly, then add human_continuation(...) to AgentHost as shown above. The fresh-process journey covers successful resume plus scope mismatch, missing/corrupt payload, expiry, and replay; it does not certify an unimplemented file-format migration.

The scaffold rejects ASK_USER_ENABLED=true, a non-empty DEV_SQLITE_CHECKPOINT_PATH, and every non-empty PROMPT_LAYER_MODE value at configuration load with the public migration route. This prevents a legacy setting from looking accepted when the scaffold has no durable resolver or explicit prompt contribution for it.

The same early-rejection rule covers uncomposed LLM extraction settings (PHASE_12_USE_LLM_EXTRACTOR and its model/caps) and legacy speaker, layer, role, blend, and relevance controls. Their names appear in generated settings only so a non-default request fails with a route to deep_phases, RetrievalPolicy, or a composed governance policy; the scaffold does not silently accept settings it cannot apply.

The scaffold also rejects MESSAGES_CACHE_POLICY, SYSTEM_PREFIX_CACHE_TTL, and MANIFEST_CACHE_POSITION. A deployment that wants a provider cache must declare stable PromptContribution regions as above; it must separately choose and measure any history-cache strategy. The explicit Anthropic-only route is NativeCacheComposition; it is not legacy policy parity. test_fp4_scaffold_migration.py exercises these rejection paths from generated settings.

The scaffold also rejects SYNTHESIZE_ON_EMPTY_FINAL=true. Before this, the setting was silently ignored and an empty final stayed empty. To get one no-tool summary call after an empty text run(), compose the capability explicitly:

from symfonic.capabilities.final_response import TerminalSynthesisCapability

host, resources = build_agent(
    extra_capabilities=lambda scope, resources: [TerminalSynthesisCapability()],
)

The summary call is a separate model call, and its usage is added to the result. Streaming and typed (output_type) calls ignore the capability. A SymfonicAgent configured with that flag still runs its legacy body.

From an existing FrameworkConfig

symfonic.migration.agent_from_config builds a plain symfonic.Agent from a FrameworkConfig. It is not a full translation. It reads only the settings listed in TRANSLATED_SETTINGS:

Setting On the returned Agent
synthesize_on_empty_final=True TerminalSynthesisCapability(): one no-tool summary call after an empty text run(), including a run that used up its round budget
agent.model Agent(model=...) carrying only the fields that differ from ModelConfig(), applied over your provider's default; the summary call uses the same model. Every field except context_window may differ from ModelConfig()
agent.recursion_limit max_model_rounds when it differs from the stock 50, converted the same way the kernel route does it (31 gives 15 rounds)

The two agent.* settings are translated only when you changed them. At their stock values they ask for nothing, so the Agent keeps its own defaults: model=None, which resolves to the model your provider declares (gpt-4o for OpenAIProvider, claude-sonnet-4-5 for AnthropicProvider), and the native budget of 10 rounds. A stock config is not translated into ModelConfig(), whose Claude model name every provider would be asked for, or into the legacy 24 rounds. Setting recursion_limit=50 explicitly is still the stock value, so it also gets 10 rounds; if you need 24, construct the Agent yourself with max_model_rounds=24.

When agent.model differs from stock, it is translated field by field. The Agent starts from the ModelConfig your provider declares and applies only the fields you changed: change only temperature to 0.2 and OpenAIProvider is asked for gpt-4o at 0.2, not for the stock claude-sonnet-4-5. Set model_name as well to choose a different model; a non-stock model_name always overrides the provider's. A provider that declares no default (OpenRouterProvider, AWSBedrockProvider, CodexOAuthProvider, KimiOAuthProvider) resolves to ModelConfig(), just as Agent(model=None) does on it, so changing only another field still asks it for claude-sonnet-4-5; set model_name too. A field you set to its stock value is indistinguishable from one you left alone, so it also takes the provider's value. Which fields reach the model API, and how, depends on the provider. Several built-in providers do not read max_retries, for example, and OllamaProvider does not read max_tokens.

Every field except context_window may differ from stock. Only one of them, structured_output_repair_attempts, is read by the Agent itself, as the repair budget of a typed run, whatever the provider. The others (model_name, temperature, max_tokens, max_retries, top_p, top_k, thinking, extra_headers, timeout_seconds and http_client), when changed, are passed through unchanged to your provider, which decides what each does: a provider may use a field, ignore it silently, or warn and drop it, as the OpenAI-family providers do with top_k. The exception is context_window, refused as agent.model.context_window: it was the budget of the legacy compaction node, which the Agent does not run, and no Agent stage reads it. A field added to ModelConfig later is refused by this boundary until it is classified. agent.model must be an exact ModelConfig; None, a string or a subclass is refused.

A recursion_limit that is not an int, or is a bool, is refused. So is one below 3: on the legacy graph, 3 is the smallest limit that guarantees one model-and-tool round, and the conversion would round a smaller one up to a full round. Every setting left at its stock value, including the two above, keeps the native Agent default. That does not reproduce the legacy defaults; for those, see legacy default parity. auto_hydrate and lazy_tooling are not flags on the native agent. Setting either to False is accepted only when you pass no capabilities of your own. Memory and tool routing run only when you compose their capabilities.

Legacy automatic consolidation is retired on the v11 adapter. Pass FrameworkConfig(auto_consolidate=False) deliberately; omitted stock True and explicit True both refuse before an Agent is constructed. This opt-out does not compose consolidation. Native QUICK requires a separately proved AgentHost with an exact terminal case kind, an in-memory journal, and a positive cadence. It is not default or PostgreSQL consolidation parity. With the explicit False, self_reflection, reflection_prompt and skill_auto_approve remain inert because their legacy consolidation reader is unreachable; they do not become native behavior. Other consolidation settings remain refused. Every other setting that differs from stock is refused before an Agent exists, so no provider, memory or tool call is made. The error is UnsupportedFrameworkSettingError. It is a ConfigurationError that lists every refused setting at once. It also carries the contract version framework_config_agent.v1, the line (11.0) and the legacy pin (9.12). Exact FrameworkConfig instances only; a subclass is refused under the path __class__. synthesize_on_empty_final=True beside a TerminalSynthesisCapability you pass in capabilities is refused the same way, under synthesize_on_empty_final: an Agent takes only one. Pass one or the other. With the flag left at False, your own capability is honoured.

domain.* settings

No domain.* setting is translated. A FrameworkConfig.domain that differs from DomainTemplate() in any field is refused like every other active setting, under its own path (domain.name, domain.description, domain.onboarding_checklist, domain.core_preferences, and so on), with the same contract version and 9.12 pin. That is the intended result of the default-deny boundary, not a gap to work around: move those values out of the config, leave domain at stock, and compose them on the Agent yourself.

Legacy domain.* field Public composition
name, description, onboarding_checklist persona_sources(DomainPersona(name=..., description=..., onboarding=...)) inside PromptingCapability(sources=[...]), all from symfonic.capabilities.prompting. DomainPersona also takes role and tone, which DomainTemplate does not have
core_preferences core_preference_migration(preferences) from symfonic.platform: put its .sources (the same as guardrail_sources(.guardrails)) in PromptingCapability, and pass its .sensitive_terms to governance(reflector=..., sensitive_terms=...)
any other domain.* field No 11.x translation. Remove it from the config to compose; for some of these fields no native owner is defined yet, so check what your application relied on before dropping it

These are prompt and review inputs, not enforcement. A Guardrail is text the model reads, ordered by priority; its sensitive_tags become terms that draw the governance review stage (the reflector you pass) to a turn. Neither blocks, approves or refuses a tool call. core_preference_migration copies each statement verbatim and never turns it into a guard, precondition, approval or refusal. If a preference must stop a tool from running, supply that executable check yourself; the prose alone does not decide it.

To have a reviewer judge each draft against the preferences, as the legacy critic did, use core_preference_governance(core_preference_migration(...), enforce=True, reviewer=...) from symfonic.platform instead of governance(...). It reviews every governed turn's draft before it is published and fails closed: a reviewer that raises or returns an invalid answer asks for a revision rather than passing the draft. It judges output, not tool calls, and does not gate them. The legacy critic ran without being asked; this replacement runs only when you declare it (feature preservation matrix).

A sketch of an AgentHost composer. config, resources.provider, resources.tools and memory_for(scope) stand for your own objects; none of them is a Symfonic API.

from symfonic.migration import agent_from_config

def composer(scope, resources):
    return agent_from_config(
        config,                      # your FrameworkConfig(auto_consolidate=False)
        resources.provider,          # your ModelProvider
        instructions="You are a helpful assistant.",
        tools=resources.tools,
        capabilities=memory_for(scope),  # your capabilities for this scope
    )

The result is a symfonic.Agent, so it can be returned from an AgentHost composer. The config never adds a system prompt, a tool or a memory port. Sessions and history go through each call's session_id and history, as on any Agent.

Behind create_kernel_agent_router, a turn that used up its round budget returns the synthesized summary from POST /chat when TerminalSynthesisCapability produced one: the summary alone, without text the model spoke beside earlier tool calls. Synthesis does not run when the last round already spoke beside its tool call, so when the Agent composes TerminalSynthesisCapability, POST /chat returns that last round's text instead, as the legacy body did. It makes no further model or tool call to do so. Text from an earlier round is never returned. Without a synthesized summary, a round-limited turn is still a 502 if the last round's text is missing or blank, or if the Agent does not compose synthesis. The stream routes do not synthesize and are unchanged. A composer that raises UnsupportedFrameworkSettingError answers every chat and stream route with 502 {"detail": "the turn failed"}, the same response as a refused retired setting. The body never quotes your configuration; the symfonic.platform log records the contract version and the refused setting paths. The composer runs on a scope's first request, so the refusal arrives then, before any provider, memory or tool call.

The installed-artifact FP-4 probe verifies generated settings and the public prompt, governance, and continuation extension seams. It does not exercise the generated application's setup.agent.build_agent, API, or worker lifecycle; that application-closure journey is tracked for FP-5.

Direct transcript reads (BREAKING since private 11.4.0)

Private 11.4.0 was published; 11.2.6 behaves as before. LegacyTranscriptReaderFactory(...).create(path) no longer constructs SymfonicAgent; it returns the native SQLite reader over a saver it opens on path. Calling that reader's get_transcript(scope, session_id) directly differs from 11.2.6 in two ways, and no shim restores the old behaviour:

  • From private 11.4.0, an absent checkpoint or one without a native-family owner record matching the scope's tenant, sub-tenant, namespace and session raises TranscriptReadRefused from symfonic.core.contracts.conversation_reader. This is a ConversationRefused subclass with contract_version="direct_transcript_read.v1", refused_in="11.4", last_legacy_line="9.12" and replacements=("Agent", "AgentHost"). All these cases carry the same generic message, revealing no checkpoint existence. Only a valid native conversation with no messages returns []. The earlier private candidate silently returned [] for unsupported checkpoints; 11.2.6 returned legacy rows.
  • A scope the conversation owner refuses as ambiguous raises ConversationRefused before any read: no tenant, a sub-tenant of literally "_", or a ":" in the tenant or sub-tenant. 11.2.6 read the thread a "_" or colon scope derived (a sub-tenant of "_" derives the same thread as no sub-tenant) and refused a missing tenant with InteractionConfigurationError.

Like the PostgreSQL reader, the reader takes no principal: any holder can read another principal's native conversation in the same tenant, sub-tenant and namespace. It is not an authorization boundary; do not expose it to callers. Reads through ConversationAuthority (including /chats/{id}/transcript on the selected root) check the principal and refuse these cases before reaching the reader. If a conversation is discarded between authorization and the compatibility reader's second lookup, that lookup now raises the same generic refusal instead of returning an empty list. Adopters that call the reader directly on historical checkpoints the authority did not file receive the typed refusal and migration guidance. Compose Agent/AgentHost with ConversationAuthority for native conversation reads. Applications that require legacy checkpoint reads must pin symfonic-core==9.12.*; this change adds no checkpoint conversion. See conversation persistence.

What compatibility means here

The compatibility facade and legacy implementations remained in the source at the original 11.0 and 11.3 cutovers. This engine-free checkout removes the engine implementation and refuses legacy automatic consolidation at the migration adapter unless explicitly switched off. This documentation adds no new deprecation. It does not promise that every historical argument is accepted: existing validation and retirement refusals still apply. Test the exact installed artifact and configuration you use.

In the pre-11.3 source tree, a kernel default route did not prove that its legacy fallback was unreachable. Migrating the examples did not authorize deleting the implementation behind that live compatibility route. These were checks for the historical cutover, not claims that this engine-free 11.5.0 candidate still has an engine fallback.

Pause and resume need particular care. Do not assume a legacy token can be redeemed by a kernel-native checkpoint path. The continuation migration window has eligibility checks and depends on a legacy destination; its release-line bound is derived from that destination's retirement line. Validate outstanding pauses and their redemption path before changing a deployment.

The release/retirement contracts contain version markers such as 10.4 and 11.0. 10.4 is a contract input naming the declared bound of the compatibility window; no 10.x release was ever cut, so the newest version that honours the legacy pins is 9.12. This page does not change those markers, and it does not replace the fourteen-condition retirement gate.

Acceptance checklist for an adopter

Use an isolated test tenant and a generated project built from the candidate wheel. Record the wheel version, configuration and model used.

Feature Evidence to require
Memory and recall A fact persists, reaches a later turn's prompt, and stays invisible to another tenant
Vector retrieval The configured embedder and vector backend are actually called; graph-only mode is reported explicitly
Extraction Durable extracted records, or an explicit empty/degraded outcome; a fluent answer is not evidence of a write
QUICK consolidation Cadence triggers a completed cycle; inspect phases and durable effects, not only a scheduling event
DEEP consolidation Manual and scheduled entry points use the intended composition, with scope exclusion, rollback and restart checks
Tools and delegation Final arguments, execution/result correlation and child scope; refusal must prevent execution
Procedural skills Draft, review, approval, prompt inclusion and enforcement are separate observable steps
Observability Conversation/run/event correlation, safe counters and opt-in redacted content capture

Run applicable scenario packs and explicitly report non-applicable ones. Keep deterministic assertions about storage, isolation and tool execution separate from judgments about model answers. A run with a scripted provider does not establish live-model quality; a successful live-model run does not establish atomicity or tenant isolation.

Bounded work per sweep is not a promise of bounded total database size or constant-cost vector search. Maintainers can inspect the source-repository evidence in docs/design/sc20-bounded-consolidation-worksets.md; that internal design document is not published as part of this site.

Release handoff

Before calling a candidate ready, check documentation links and examples, build/install the wheel, generate the scaffold, run its applicable evals and exercise the UI against that same backend. Keep the resulting evidence with the candidate SHA.

This 11.5.0 source tree is an engine-free private release candidate: the engine removal is already present in its source, wheel, and sdist. Private publication follows a reviewed merge and the release gates; this page does not announce a private publication or a public PyPI release. Any new deprecation requires its own decision.