symfonic.agent.context¶
context ¶
Public surface of the context-assembly package (Roadmap Item 8).
Exports:
- :class:
ContextManager-- the per-turn assembly protocol. - :class:
AssembledContext-- the strategy return dataclass. - :class:
JITContextManager, :class:StratifiedHMSContextManager-- the two concrete strategies shipped in 7.2. - :func:
make_context_manager-- factory dispatching onFrameworkConfig.context_strategywith fallback to the legacyjit_contextflag.
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. |
memory_context |
HydratedMemory | None
|
Frozen :class: |
state_overrides |
dict[str, Any]
|
Per-strategy state additions for LangGraph
invocation -- today carries |
activation_log |
dict[str, Any] | None
|
Spreading-activation log consumed by |
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
|
required |
intent_verdict
|
Any | None
|
Optional :class: |
required |
auto_hydrate
|
bool
|
v7.7.8 (Jarvio 9th-recurrence): explicit
hydration-on-this-turn flag. Pre-7.7.8 the engine
misused |
True
|
Returns:
| Type | Description |
|---|---|
AssembledContext
|
class: |
AssembledContext
|
memory-context bundle. |
Source code in src/symfonic/agent/context/protocol.py
JITContextManager ¶
Reactive-hydration strategy. Returns memory_context=None.
Constructor takes a single callable -- build_prompt -- that
renders the JIT system prompt. The engine wires this to its
existing _build_jit_system_prompt method so the byte-for-byte
template-rendering path is preserved. The strategy itself is a
thin orchestration shell.
Wire the strategy to the engine's JIT prompt builder.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
build_prompt
|
Callable[..., Awaitable[str | None]]
|
Async callable with signature
|
required |
Source code in src/symfonic/agent/context/jit.py
assemble
async
¶
assemble(
*,
query: str,
scope: FrameworkTenantScope | None,
intent_verdict: Any | None,
auto_hydrate: bool = True,
) -> AssembledContext
Render the JIT prompt; skip hydration unconditionally.
intent_verdict is accepted to satisfy the
:class:ContextManager protocol but is ignored in v1 (OQ-1 in
the design note resolves to "defer reactive in-strategy
hydration to 7.3"). Reactive hydration still works -- the LLM
calls the hydrate_context tool mid-turn via the tool-call
channel.
v7.7.8: auto_hydrate accepted to satisfy the updated
protocol; JIT never hydrates inline so the flag is informational
only. Critical for parity with stratified: scope reaches the
prompt template regardless of the flag.
Source code in src/symfonic/agent/context/jit.py
StratifiedHMSContextManager ¶
StratifiedHMSContextManager(
*,
hydrate: Callable[..., Awaitable[HydratedMemory]],
resolve_tools: Callable[
..., Awaitable[list[str] | None]
],
build_prompt: Callable[..., Awaitable[str | None]],
read_strat_overrides: Callable[[], dict[str, Any]],
enable_hms_prompt: bool,
)
Full-HMS strategy with inline hydration.
Constructor takes two injected callables:
hydrate(query, scope, intent_verdict) -> HydratedMemory-- bound to the engine's existing_hydratemethod which already fireson_hydration_completeframework hooks (preserves the Phase 4 OTel emit-site behaviour).build_prompt(scope, memory_context, active_tools, prospective_tasks, activation_log, query) -> str | None-- bound to the engine's existing_build_hms_system_promptmethod which honours the stratigraphic L0/L1/L2 wrap and stashesbrain_prompt_version/system_prompt_hashon the engine.
The third injected callable surfaces engine-side state that the
builder mutates: read_strat_overrides() -> dict[str, Any] reads
the per-call stratigraphic metadata after the builder returns and
moves it into AssembledContext.state_overrides -- replacing the
legacy _apply_strat_metadata_to_overrides side-channel pour.
Wire the strategy to engine-side helpers.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
hydrate
|
Callable[..., Awaitable[HydratedMemory]]
|
Async callable with signature
|
required |
resolve_tools
|
Callable[..., Awaitable[list[str] | None]]
|
Async callable resolving the per-turn
tool manifest. Bound to
:meth: |
required |
build_prompt
|
Callable[..., Awaitable[str | None]]
|
Async callable matching
:meth: |
required |
read_strat_overrides
|
Callable[[], dict[str, Any]]
|
Sync callable returning a dict of
|
required |
enable_hms_prompt
|
bool
|
Mirrors
:attr: |
required |
Source code in src/symfonic/agent/context/stratified.py
assemble
async
¶
assemble(
*,
query: str,
scope: FrameworkTenantScope | None,
intent_verdict: Any | None,
auto_hydrate: bool = True,
) -> AssembledContext
Hydrate memory, render the HMS prompt, return the bundle.
v7.7.8 (Jarvio 9th-recurrence): hydration gates on the explicit
auto_hydrate flag, NOT on scope is None. Pre-7.7.8 the
engine misused scope=None to signal "skip hydration" --
but that ALSO stripped tenant_id from the prompt template
(the L1 body rendered Tenant: unknown) AND from the
plugin-render state dict at engine.py:4397 / 4713. Result:
every adopter running auto_hydrate=False saw the L1
PRE-FLIGHT block silently degrade to empty -- even with
v7.7.6/v7.7.7 fixes in place -- because the active-skills
getter resolved a missing tenant. Same architectural class as
v7.6.4 / v7.7.1 / v7.7.3 / v7.7.6 / v7.7.7: a parallel control
flag misroutes a value the render path requires.
Source code in src/symfonic/agent/context/stratified.py
make_context_manager ¶
make_context_manager(
config: FrameworkConfig,
*,
hydrate: Callable[..., Awaitable[HydratedMemory]],
resolve_tools: Callable[
..., Awaitable[list[str] | None]
],
build_hms_prompt: Callable[..., Awaitable[str | None]],
build_jit_prompt: Callable[..., Awaitable[str | None]],
read_strat_overrides: Callable[[], dict[str, Any]],
enable_hms_prompt: bool,
) -> ContextManager
Construct the :class:ContextManager strategy named by config.
The engine calls this exactly once at construction time. Strategies are immutable -- swapping at runtime is not supported in 7.2.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config
|
FrameworkConfig
|
The agent's :class: |
required |
hydrate
|
Callable[..., Awaitable[HydratedMemory]]
|
Bound to the engine's |
required |
resolve_tools
|
Callable[..., Awaitable[list[str] | None]]
|
Bound to the engine's |
required |
build_hms_prompt
|
Callable[..., Awaitable[str | None]]
|
Bound to the engine's
|
required |
build_jit_prompt
|
Callable[..., Awaitable[str | None]]
|
Bound to the engine's
|
required |
read_strat_overrides
|
Callable[[], dict[str, Any]]
|
Bound to a closure that snapshots the
engine's |
required |
enable_hms_prompt
|
bool
|
Mirrors the engine's |
required |
Returns:
| Type | Description |
|---|---|
ContextManager
|
Either a :class: |
ContextManager
|
class: |
ContextManager
|
func: |
Source code in src/symfonic/agent/context/__init__.py
resolve_strategy_name ¶
Determine which strategy name applies to the given config.
Precedence (per design §6.3):
- Explicit
config.context_strategyvalue wins ("jit"or"stratified"). None-> infer from legacyconfig.jit_context:Trueselects"jit",Falseselects"stratified".
Pure function -- no side effects, no engine state read. Exposed publicly so the engine and the factory share one truth source for dispatch precedence (avoids the classic "two factories disagree" bug class).