Skip to content

symfonic.agent.context.jit

jit

JITContextManager -- minimal prompt, no inline hydration.

Maps to the pre-7.2 FrameworkConfig.jit_context=True path. The strategy renders the ~210-token JIT system prompt and returns memory_context=None so the engine skips the invocation_state["memory_context"] stamping. Memory is loaded reactively by the LLM via the hydrate_context tool, not by this strategy.

Type-level invariant. :class:JITContextManager does NOT accept a :class:MemoryOrchestrator argument. The orchestrator is unreachable from the JIT path by construction; the "no hydration" guarantee is visible in the constructor signature.

JITContextManager

JITContextManager(
    *, build_prompt: Callable[..., Awaitable[str | None]]
)

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 (tenant_id: str, namespace: str, *, query: str, scope: FrameworkTenantScope | None = None) -> str | None. The engine binds this to :meth:SymfonicAgent._build_jit_prompt_for_strategy. v8.0: scope is forwarded so the synth-time L1 PRE-FLIGHT state carries the full multi-level path, not just the tenant_id / namespace scalars (the strategy boundary previously destroyed the brand/org levels).

required
Source code in src/symfonic/agent/context/jit.py
def __init__(
    self,
    *,
    build_prompt: Callable[..., Awaitable[str | None]],
) -> None:
    """Wire the strategy to the engine's JIT prompt builder.

    Args:
        build_prompt: Async callable with signature
            ``(tenant_id: str, namespace: str, *, query: str,
            scope: FrameworkTenantScope | None = None) -> str | None``.
            The engine binds this to
            :meth:`SymfonicAgent._build_jit_prompt_for_strategy`.
            v8.0: ``scope`` is forwarded so the synth-time L1
            PRE-FLIGHT state carries the full multi-level path, not
            just the ``tenant_id`` / ``namespace`` scalars (the
            strategy boundary previously destroyed the brand/org
            levels).
    """
    self._build_prompt = build_prompt

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
async def assemble(
    self,
    *,
    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.
    """
    _ = intent_verdict  # OQ-1 resolution: ignored in v1.
    _ = auto_hydrate  # JIT never hydrates inline; flag noted only.
    start = time.monotonic()

    if scope is not None:
        tenant_id = scope.tenant_id
        namespace = scope.namespace or scope.sub_tenant_id or "default"
    else:
        tenant_id = "unknown"
        namespace = "default"

    system_prompt = await self._build_prompt(
        tenant_id=tenant_id,
        namespace=namespace,
        query=query,
        # v8.0 BLOCKER FIX: thread the FULL scope object through so the
        # L1 PRE-FLIGHT synth-state carries the multi-level path, not
        # just tenant_id/namespace scalars.  Without this the JIT
        # strategy boundary destroyed the brand/org levels and the
        # active-skills getter reconstructed a 1-level scope, hiding
        # brand-level PRE-FLIGHT skills under always-on prefix isolation.
        scope=scope,
    )
    latency_ms = (time.monotonic() - start) * 1000.0
    return AssembledContext(
        system_prompt=system_prompt,
        memory_context=None,
        state_overrides={},
        activation_log=None,
        metadata={
            "strategy_name": "jit",
            "latency_ms": latency_ms,
            "entries_count": 0,
        },
    )