Skip to content

Migrating from symfonic-core v6.x to v7.0

v7.0.0 is a major release. It introduces the four v7.0 Thalamic Architecture subsystems (intent filter, N-hop spreading, STM summary, fabrication executor, nightly_nap) and removes one long-deprecated constructor kwarg. Everything else is additive with v6.x observable behaviour preserved at defaults.

TL;DR — if your code does not reference SymfonicAgent(enable_hms_prompt=...), upgrading is a drop-in.

Breaking changes

B1 — SymfonicAgent(enable_hms_prompt=...) removed

Status: Landed.

The constructor kwarg has emitted DeprecationWarning since v5.6.0. In v7.0 it raises TypeError because Python does not accept unknown keyword arguments.

Before (v6.x)

from symfonic.agent.engine import SymfonicAgent

agent = SymfonicAgent(
    model_provider=provider,
    enable_hms_prompt=True,          # deprecated kwarg
)

After (v7.0)

from symfonic.agent.config import FrameworkConfig
from symfonic.agent.engine import SymfonicAgent

config = FrameworkConfig(enable_hms_prompt=True)
agent = SymfonicAgent(
    model_provider=provider,
    config=config,
)

The FrameworkConfig path has been available since v6.0 and is unchanged in v7.0.

Migration script

Run this one-liner to detect the pattern across a codebase:

grep -rn "SymfonicAgent(.*enable_hms_prompt=" path/to/your/code

If nothing matches, you are done.

Additive changes (no migration required)

FrameworkConfig knobs

Field Default v6.x behaviour at default To change behaviour
intent_filter_mode "off" Classifier never runs; full hydration every turn "observe" for telemetry only; "enforce" to skip semantic/episodic/procedural on action verdicts
hydration_max_hops 0 Single asyncio.gather of 1st-degree neighbours (byte-identical to v6.1.x) >=1 enables BFS with per-hop decay
stm_summary_mode "off" WorkingLayer.summarize() returns "" (legacy stub) "extractive" produces bullets with no LLM cost; "llm" issues one micro-call with extractive fallback
fabrication_check_mode "off" Detector never runs; v6.1.10 TOOL DISCIPLINE prompt directive is the only defence "observe" emits telemetry; "revise" forces metacog revise; "refuse" replaces draft with safe envelope
nightly_nap_enabled False nightly_nap method available but operator-invoked Schedule agent._sleep_consolidator.nightly_nap(scope) via your scheduler
nightly_nap_cron "0 3 * * *" No effect — recommendation only Set to your preferred off-peak cron expression

DomainTemplate additions

Field Default Purpose
identifier_keys [] Extra keys the fabrication detector should flag in addition to the built-in lexicon (task_id, pid, job_id, ...). Empty preserves v6.x behaviour.

AgentResponse additions

Field Default Populated when
intent_verdict None intent_filter_mode != "off"
fabrication_findings None fabrication_check_mode != "off" AND detector fires

Enabling the new subsystems

The v7.0 subsystems are opt-in. Here is a recommended adoption order:

1. Intent filter (lowest risk)

Start in observe mode for a burn-in week. The classifier emits ExtensionEvent(type="intent_classified") to callback handlers that implement on_intent_classified. Tune the corpus before flipping to enforce.

config = FrameworkConfig(intent_filter_mode="observe")

Once confident, flip to enforce:

config = FrameworkConfig(intent_filter_mode="enforce")

2. N-hop spreading

Start with hydration_max_hops=1 (explicit 1-hop, still a single BFS level but now with spreading_access_count bumping per v6.2 T02). Measure payload size and latency before raising:

config = FrameworkConfig(hydration_max_hops=2)

The default base_decay=0.5 and max_total_nodes=50 guard against runaway expansion.

3. STM summary

Start with extractive — zero LLM cost, deterministic output:

config = FrameworkConfig(stm_summary_mode="extractive")

Upgrade to "llm" if your domain needs better compression and you are willing to pay one micro-call per summary. The extractive path is the automatic fallback on LLM failure.

4. Fabrication check

Start in observe to collect findings:

config = FrameworkConfig(fabrication_check_mode="observe")

Implement on_fabrication_detected on your callback handler to capture the events. When confident the detector is not producing false positives, escalate to revise or refuse.

fabrication_check_mode='revise' requires metacognition_enabled=True. The revise path threads findings into MetacognitiveMiddleware.evaluate() to produce a genuine revised draft. Without metacog enabled there is no reviser to run, so the agent emits a UserWarning at construction and downgrades the effective mode to refuse semantics (safe-envelope replacement + fabrication_blocked event). If you want real revise behaviour, pair the two flags:

config = FrameworkConfig(
    fabrication_check_mode="revise",
    metacognition_enabled=True,
)

5. nightly_nap

Enable the entrypoint:

config = FrameworkConfig(
    nightly_nap_enabled=True,
    nightly_nap_cron="0 3 * * *",  # 03:00 UTC daily
)

Wire your scheduler (APScheduler / celery-beat / external cron) to invoke agent._sleep_consolidator.nightly_nap(scope) per tenant at the recommended cron cadence. The engine does not schedule this itself — operator responsibility.

Tests

v7.0.0 adds dedicated test files per subsystem; your test suite should remain green after the upgrade because defaults preserve v6.x behaviour. If a v6-era test fails, please file an issue — it indicates an inadvertent default flip.

  • tests/agent/triage/test_intent_classifier.py — T06
  • tests/agent/triage/test_intent_wiring.py — T07
  • tests/memory/unit/test_spreading_nhop.py — T08
  • tests/memory/unit/working/test_stm_summary.py — T09
  • tests/agent/middleware/test_fabrication.py — T10
  • tests/agent/middleware/test_fabrication_wiring.py — T11
  • tests/core/learning/test_nightly_nap.py — T12
  • tests/release/test_v7_0_0_release_gate.py — T15 pin

v7.0.2 note — DomainTemplate.description now reaches the LLM

DomainTemplate.description has been a public field since v5.6.x, but no prompt builder ever consumed it — every value set there was silently dropped. v7.0.2 wires it into both the full HMS and JIT system prompts for the first time.

If your v6.x / v7.0.0 / v7.0.1 deployment set DomainTemplate(description="..."), upgrading to v7.0.2 will cause that prose to reach the LLM's system prompt. This can affect:

  • Token budgets — the prompt will be larger in both modes.
  • LLM behaviour — the directives will actually steer responses. Review the description text before upgrading.
  • Truncation — descriptions over 2000 chars (full HMS) or 400 chars (JIT) are capped and marked ... [truncated], with a single WARNING log per agent lifetime. Raise the caps via the new FrameworkConfig.domain_description_max_chars tuple if needed:
cfg = FrameworkConfig(
    domain=DomainTemplate(description="..."),
    domain_description_max_chars=(5000, 800),  # (full_hms, jit)
)

No migration is required for callers who never populated domain.description — the rendered prompt shows a short fallback which the LLM treats as a no-op.

See docs/releases/v7.0.2.md for the full rationale including why the JIT template uses an inline line instead of a new ## 1a. section.

References