Skip to content

symfonic.agent.backend.turn_observation

turn_observation

What one compatibility turn produced that its kernel result does not carry.

CORE-227. The legacy AgentResponse reports two memory effects the kernel's AgentResult has no field for: how many memories hydration put in front of the model (memory_entries_used) and which extraction operations the reply carried (extracted_ops). The kernel strips a complete extraction envelope before any adopter sees the text and keeps the parsed operations on its ModelTurn for an opted-in post-model stage, and the retrieval stage reports its admitted count on its own trace. Neither reaches the result.

So the facade route opens an observation around one kernel call, and the two producers record into it: :func:turn_of the latest round's extraction, and the bundle's wrapped retrieval handler the latest admitted count. With no observation open -- every native Agent turn -- both records are no-ops, so the native path is unchanged. The record is a mutable object held by a :class:~contextvars.ContextVar, so a kernel task spawned inside the call writes into the same object the caller reads, and a nested facade turn (a delegated child) opens its own and cannot overwrite its parent's.

TurnObservation dataclass

TurnObservation(extraction_ops: tuple[Mapping[str, Any], ...] = (), extraction_seen: bool = False, recalled: int | None = None)

The last round's extraction and the last admitted recall count.

observe_recall

observe_recall(counts: Mapping[str, int] | None) -> None

Record the retrieval stage's admitted count, when it reported one.

Source code in src/symfonic/agent/backend/turn_observation.py
def observe_recall(counts: Mapping[str, int] | None) -> None:
    """Record the retrieval stage's admitted count, when it reported one."""
    observation = _ACTIVE.get()
    if observation is None or not counts or "admitted" not in counts:
        return
    observation.recalled = int(counts["admitted"])

observe_turn

observe_turn(turn: Any) -> None

Record turn's extraction; the final round is the last one recorded.

Source code in src/symfonic/agent/backend/turn_observation.py
def observe_turn(turn: Any) -> None:
    """Record ``turn``'s extraction; the final round is the last one recorded."""
    observation = _ACTIVE.get()
    if observation is None:
        return
    observation.extraction_ops = tuple(getattr(turn, "extraction_ops", ()) or ())
    observation.extraction_seen = bool(getattr(turn, "extraction_seen", False))

observing

observing() -> Iterator[TurnObservation]

Open one observation for the kernel call made inside this block.

Source code in src/symfonic/agent/backend/turn_observation.py
@contextmanager
def observing() -> Iterator[TurnObservation]:
    """Open one observation for the kernel call made inside this block."""
    observation = TurnObservation()
    token = _ACTIVE.set(observation)
    try:
        yield observation
    finally:
        _ACTIVE.reset(token)