foley.agent.decompose

decompose_context — narrative passage → a sparse, salience-ranked event list.

The moat of the SELECT stage (report 05 §2): turning prose into a tastefully sparse, correctly-diegetic SoundEvent list is the hard, defensible part — not the CLAP encoder. Two impls sit behind the Decomposer seam:

  • KeywordDecomposer — the deterministic default and the hermetic CI fake: a built-in cue lexicon, zero dependencies, same passage → identical list.

  • AnthropicDecomposer — the LLM-backed impl behind the foley[agent] extra; anthropic is imported lazily inside .decompose only, so import foley stays dol-only.

decompose_context() is the pure tool wrapper (Python-API == agent == future-MCP surface): it resolves the default decomposer, calls it, and records the GenAI span on the real path.

class foley.agent.decompose.AnthropicDecomposer(*, client=None, model: str = 'claude-opus-4-8', max_tokens: int = 2000)[source]

LLM-backed decomposer (foley[agent]): Claude → a structured event list.

anthropic is imported lazily inside decompose() so import foley stays dol-only. Stashes the returned Message on self.last_response so decompose_context() can record the GenAI span (token usage / model / stop_reason). Model, thinking, and structured-output shape follow the claude-api conventions (claude-opus-4-8, adaptive thinking — never budget_tokens).

decompose(context: str, *, max_events: int = 6, seconds: float | None = None) list[SoundEvent][source]

Call Claude and round-trip each event through SoundEvent.from_dict().

class foley.agent.decompose.KeywordDecomposer[source]

Deterministic cue-lexicon decomposer — the zero-dependency default and CI fake.

Scans the passage against _CUE_LEXICON, emits one SoundEvent per matched cue in first-appearance order (so salience descends with reading order), dedupes by canonical query, and truncates to max_events (the sparse density budget). No RNG, no network, no anthropic — same passage → identical list.

decompose(context: str, *, max_events: int = 6, seconds: float | None = None) list[SoundEvent][source]

Return <= max_events deterministic SoundEvents for context.

Parameters:
  • context – The narrative passage.

  • max_events – The sparse density cap (the salience budget).

  • seconds – Accepted for signature parity (the per-second density window is a later refinement); ignored here.

foley.agent.decompose.decompose_context(context: str, *, max_events: int = 6, seconds: float | None = None, decomposer: Decomposer | None = None, _span=None) list[SoundEvent][source]

Decompose a passage into <= max_events sparse SoundEvents.

The pure SELECT tool (Python-API == agent == future-MCP surface): resolves the default decomposer when decomposer is None, calls it, and records the GenAI span on the real path (the fake’s last_response is None → no-op).

Parameters:
  • context – The narrative passage.

  • max_events – The sparse density cap.

  • seconds – Optional passage duration (density-window hint; forwarded, else ignored).

  • decomposer – An injected Decomposer (the DI seam); defaults to _default_decomposer().

  • _span – Internal — the obs span handle find() opens for GenAI recording.