Skip to content

symfonic.memory.working.stm_summary

stm_summary

Short-Term Memory (STM) summary generation for evicted working turns.

v7.0 T09. WorkingLayer.summarize used to return "" as a stub; this module provides two real implementations:

  • extractive (default when stm_summary_mode == "extractive") — latency-free. For each dropped entry take the first and last sentence, dedup, bullet it. No LLM call.
  • llm (stm_summary_mode == "llm") — one micro-call per summary through the caller-supplied model provider. Falls back to the extractive path on any upstream failure so hydration never stalls on an outage.

stm_summary_mode == "off" preserves v6.1.8 behaviour: the engine skips this module entirely and WorkingLayer.summarize still returns the empty stub.

STMSummary dataclass

STMSummary(
    text: str,
    mode_used: SummaryMode,
    entry_count: int,
    truncated: bool = False,
)

Result of an STM summary pass.

Attributes:

Name Type Description
text str

Rendered summary (may be empty when no source entries).

mode_used SummaryMode

Actual mode applied. When the requested mode was llm but the call failed, this is extractive — downstream telemetry can count fallbacks.

entry_count int

Number of source entries that fed the summary.

truncated bool

True when the deterministic path truncated to the configured token budget.

summarize_entries

summarize_entries(
    entries: list[MemoryEntry],
    *,
    mode: SummaryMode,
    max_chars: int = 1000,
    model_provider: _ModelProviderLike | None = None,
    model_config: Any | None = None,
    observability_hook: Any | None = None,
    run_id: str = "",
) -> STMSummary

Summarise entries according to mode.

Parameters:

Name Type Description Default
entries list[MemoryEntry]

Dropped working-memory entries (FIFO evictees).

required
mode SummaryMode

off / extractive / llm.

required
max_chars int

Upper bound on the summary body. 0 disables truncation.

1000
model_provider _ModelProviderLike | None

Required when mode == "llm". When absent the classifier silently downgrades to extractive.

None
model_config Any | None

Passed verbatim to model_provider.get_chat_model.

None
observability_hook Any | None

v8.3.2 (G3) -- when supplied AND mode == "llm", the summary LLM call emits on_llm_start/on_llm_end on the always-on ObservabilityHook so the (previously SILENT) STM summary tokens are visible on cortex/OTel spans. None resolves to a NoOpObservabilityHook -> byte-identical when unwired. NOTE: the only in-tree caller (WorkingLayer.summarize at working.py:206) invokes this in mode="extractive" and never threads a hook; the llm path is for direct callers.

None
run_id str

Engine run identifier stamped on the hook events.

''

Returns:

Type Description
STMSummary

STMSummary. Never raises.

Source code in src/symfonic/memory/working/stm_summary.py
def summarize_entries(
    entries: list[MemoryEntry],
    *,
    mode: SummaryMode,
    max_chars: int = 1000,
    model_provider: _ModelProviderLike | None = None,
    model_config: Any | None = None,
    observability_hook: Any | None = None,
    run_id: str = "",
) -> STMSummary:
    """Summarise ``entries`` according to ``mode``.

    Args:
        entries: Dropped working-memory entries (FIFO evictees).
        mode: ``off`` / ``extractive`` / ``llm``.
        max_chars: Upper bound on the summary body. ``0`` disables
            truncation.
        model_provider: Required when ``mode == "llm"``. When absent
            the classifier silently downgrades to ``extractive``.
        model_config: Passed verbatim to ``model_provider.get_chat_model``.
        observability_hook: v8.3.2 (G3) -- when supplied AND
            ``mode == "llm"``, the summary LLM call emits
            ``on_llm_start``/``on_llm_end`` on the always-on
            ``ObservabilityHook`` so the (previously SILENT) STM summary
            tokens are visible on cortex/OTel spans.  ``None`` resolves to
            a ``NoOpObservabilityHook`` -> byte-identical when unwired.
            NOTE: the only in-tree caller (``WorkingLayer.summarize`` at
            ``working.py:206``) invokes this in ``mode="extractive"`` and
            never threads a hook; the ``llm`` path is for direct callers.
        run_id: Engine run identifier stamped on the hook events.

    Returns:
        STMSummary. Never raises.
    """
    if mode == "off" or not entries:
        return STMSummary(text="", mode_used="off", entry_count=0)

    if mode == "llm" and model_provider is not None:
        try:
            text, truncated = _summarise_via_llm(
                entries, model_provider, model_config, max_chars,
                observability_hook=observability_hook,
                run_id=run_id,
            )
            return STMSummary(
                text=text,
                mode_used="llm",
                entry_count=len(entries),
                truncated=truncated,
            )
        except Exception:
            logger.warning(
                "LLM STM summary failed; falling back to extractive",
                exc_info=True,
            )

    text, truncated = _summarise_extractive(entries, max_chars=max_chars)
    return STMSummary(
        text=text,
        mode_used="extractive",
        entry_count=len(entries),
        truncated=truncated,
    )