Skip to content

symfonic.core.learning.procedural_extractor

procedural_extractor

ProceduralExtractor protocol + ProceduralDraft dataclass.

v7.6 (Jarvio Ask 9) — opt-in surface for the LLM-backed Phase 12 extractor. Mirrors the v7.2 EntityExtractor pluggability shape:

  • the regex-based extractor in :mod:symfonic.core.learning.phases_procedural_promotion is the default (configured at v6.1) and produces flat verb-object skills;
  • the LLM-backed extractor in :mod:symfonic.core.learning.procedural_extractor_llm is opt-in via FrameworkConfig.phase_12_use_llm_extractor=True and produces structured :class:ProceduralDraft records that distinguish action / preflight / recovery / guardrail rules.

The protocol is intentionally minimal -- the dispatch site in :func:promote_episodic_to_procedural only needs to call extract and turn the returned drafts into :class:~symfonic.memory.models.entry.MemoryEntry records via the shared :func:_build_skill_entry_from_draft helper.

Design reference: .claude/docs/2026-05-28-jarvio-llm-procedural-extractor-eval.md §8.

ProceduralKind module-attribute

ProceduralKind = Literal[
    "action", "preflight", "recovery", "guardrail"
]

Allowed values for ProceduralDraft.kind.

The four-way split tracks the failure modes Jarvio identified in their deep-dive (symfonic-core-ask-llm-procedural-extractor.md):

  • action — repeated tool usage that should become a learned habit.
  • preflight — pre-flight check that prevents downstream failure ("before X, call Y"). The most load-bearing kind for v7.6 since the regex extractor structurally cannot represent it.
  • recovery — error-recovery rule ("after E, call R").
  • guardrail — negative rule ("never X — use Y instead").

VALID_PROCEDURAL_KINDS module-attribute

VALID_PROCEDURAL_KINDS: frozenset[str] = frozenset(
    {"action", "preflight", "recovery", "guardrail"}
)

Runtime mirror of :data:ProceduralKind for response-shape validation.

The Literal type is gone at runtime so the LLM-response parser uses this frozenset to clamp unknown kinds back to "action" (the most benign default — a noisy action draft surfaces to human review, a fabricated preflight rule could hide a real precondition gap).

ProceduralDraft dataclass

ProceduralDraft(
    kind: ProceduralKind,
    trigger: str,
    action: str,
    rationale: str,
    confidence: float = 0.0,
    precondition: str | list[str] | None = None,
    source_episode_ids: list[str] = list(),
)

Structured draft produced by a :class:ProceduralExtractor.

Frozen so the dispatch site can pass instances around without worrying about downstream mutation. Defaults match the LLM prompt's nullable / optional fields so a minimal response can still construct a valid draft (only kind, trigger, action, and rationale are strictly required).

precondition shape (v7.6.3 -- Jarvio multi-precondition ask):

  • None -- no pre-flight gate.
  • str -- single pre-flight rule (v7.6.0 byte-identical shape; preserved so consumers that isinstance(p, str) still match the common case).
  • list[str] -- ordered multi-pre-flight rule. Mirrors the v7.6.2 storage widening on :meth:~symfonic.memory.layers.procedural.layer.ProceduralLayer.seed_authored_skill, so LLM-extracted drafts and hand-authored skills share one storage shape. The v7.5.1 :class:~symfonic.memory.layers.procedural.router.CapabilityRouter renders one indented PRE-FLIGHT: line per entry in supplied order.

ProceduralExtractor

Bases: Protocol

Pluggable Phase 12 extractor interface.

Implementations MUST be best-effort: a malformed response, a missing field, or an exception during model invocation must yield [] rather than raise. The dispatch site logs the empty result via the standard Phase 12 telemetry line so consumers can tell "extractor found nothing" from "extractor crashed" via the cost_usd / llm_calls fields.