Skip to content

symfonic.agent.context.protocol

protocol

ContextManager protocol + AssembledContext dataclass (Roadmap Item 8).

Per-turn context-assembly seam introduced in 7.2 (PR-6). Two concrete strategies ship internal to symfonic-core:

  • :class:~symfonic.agent.context.jit.JITContextManager -- minimal ~210-token prompt, no inline hydration. Memory loaded reactively by the LLM via the hydrate_context tool.
  • :class:~symfonic.agent.context.stratified.StratifiedHMSContextManager -- full HMS body with inline L1/L2 hydration on every turn. Maps to the pre-7.2 jit_context=False production path.

The engine owns I/O around the call -- tool registration, conversation history truncation, _thread_id derivation, tenant_id/ sub_tenant_id stamping. The strategy owns prompt-template choice, the hydration cadence, and the L0/L1/L2 wrapping.

AssembledContext dataclass

AssembledContext(
    system_prompt: str | None = None,
    memory_context: HydratedMemory | None = None,
    state_overrides: dict[str, Any] = dict(),
    activation_log: dict[str, Any] | None = None,
    metadata: dict[str, Any] = dict(),
)

Output of :meth:ContextManager.assemble.

All fields are optional so the engine can no-op cleanly when the strategy returns nothing (e.g. the enable_hms_prompt=False path).

Attributes:

Name Type Description
system_prompt str | None

Rendered system prompt body. None when the HMS pipeline is disabled OR rendering failed (the engine then skips invocation_state["custom_system_prompt"]).

memory_context HydratedMemory | None

Frozen :class:HydratedMemory from the hydration pass. None for the JIT strategy (no inline hydration) and for the stratified strategy when scope is None.

state_overrides dict[str, Any]

Per-strategy state additions for LangGraph invocation -- today carries brain_prompt_version and system_prompt_hash from the stratigraphic builder. Empty dict in JIT mode + legacy stratified mode.

activation_log dict[str, Any] | None

Spreading-activation log consumed by stream and stream_typed to emit per-node events. None in JIT mode (no retrieval = no spreading activation).

metadata dict[str, Any]

Strategy-private telemetry slot (latency_ms, entries_count, strategy_name). Not consumed by the engine today; reserved for downstream observability.

ContextManager

Bases: Protocol

Per-turn context-assembly strategy.

Implementations are selected once at agent construction (via :func:~symfonic.agent.context.make_context_manager) and are invoked exactly once per turn from each of the three entry points (run / stream / stream_typed).

Two concrete strategies ship with the framework:

  • :class:~symfonic.agent.context.jit.JITContextManager
  • :class:~symfonic.agent.context.stratified.StratifiedHMSContextManager

The protocol is intentionally single-method. The JIT case returning memory_context=None is the type-level signal that no hydration happened; the engine branches on truthiness, not on the strategy class.

assemble async

assemble(
    *,
    query: str,
    scope: FrameworkTenantScope | None,
    intent_verdict: Any | None,
    auto_hydrate: bool = True,
) -> AssembledContext

Assemble the per-turn context for one agent invocation.

Parameters:

Name Type Description Default
query str

User query text. The canonical semantic anchor used for retrieval, embeddings, and tool-manifest gating.

required
scope FrameworkTenantScope | None

Tenant scope for memory isolation. Carries tenant_id / sub_tenant_id through to the prompt template AND the active-skills getter, regardless of hydration policy.

required
intent_verdict Any | None

Optional :class:IntentVerdict from the triage classifier. Used by the stratified strategy to gate layer skipping; ignored by JIT in v1 (per design §4.2 step 1).

required
auto_hydrate bool

v7.7.8 (Jarvio 9th-recurrence): explicit hydration-on-this-turn flag. Pre-7.7.8 the engine misused scope=None to signal "skip hydration", which had the side effect of stripping tenant_id from the prompt template AND from the plugin-render state dict -- so the L1 PRE-FLIGHT block never materialised on adopters running auto_hydrate=False. Strategies MUST gate hydration on this flag only; scope is for tenant identity, not hydration policy.

True

Returns:

Type Description
AssembledContext

class:AssembledContext with the rendered prompt and

AssembledContext

memory-context bundle.

Source code in src/symfonic/agent/context/protocol.py
async def assemble(
    self,
    *,
    query: str,
    scope: FrameworkTenantScope | None,
    intent_verdict: Any | None,
    auto_hydrate: bool = True,
) -> AssembledContext:
    """Assemble the per-turn context for one agent invocation.

    Args:
        query: User query text. The canonical semantic anchor used
            for retrieval, embeddings, and tool-manifest gating.
        scope: Tenant scope for memory isolation. Carries
            ``tenant_id`` / ``sub_tenant_id`` through to the prompt
            template AND the active-skills getter, regardless of
            hydration policy.
        intent_verdict: Optional :class:`IntentVerdict` from the
            triage classifier. Used by the stratified strategy to
            gate layer skipping; ignored by JIT in v1 (per design
            §4.2 step 1).
        auto_hydrate: v7.7.8 (Jarvio 9th-recurrence): explicit
            hydration-on-this-turn flag.  Pre-7.7.8 the engine
            misused ``scope=None`` to signal "skip hydration",
            which had the side effect of stripping ``tenant_id``
            from the prompt template AND from the plugin-render
            state dict -- so the L1 PRE-FLIGHT block never
            materialised on adopters running ``auto_hydrate=False``.
            Strategies MUST gate hydration on this flag only;
            ``scope`` is for tenant identity, not hydration policy.

    Returns:
        :class:`AssembledContext` with the rendered prompt and
        memory-context bundle.
    """
    ...