Skip to content

Upgrading Consumers to v1.5 Stable Bridges

If your project consumed POLARIS or LYRA symbols from fcc.plugins.v1_5_preview during the v1.4.x preview window, this tutorial is how you move to the v1.5.0 stable fcc.api.* surface before the preview paths are removed in v1.6.0.

This tutorial targets three audiences:

  • PAOM, ice_ext, and the 10 constellation vocabularies — the primary consumers that pinned against the preview namespace.
  • Community plugin authors who depend on POLARIS or LYRA bridges for archive or graph-of-thought capabilities.
  • Internal FCC teams maintaining scenarios, demos, or notebooks that used preview imports.

You will end this tutorial with:

  • Every fcc.plugins.v1_5_preview.* import replaced with the stable fcc.api.* (or module-level) equivalent.
  • A dependency pin update to fcc-agent-team-ext>=1.5.0,<2.0.0.
  • Updated unit tests that pin the new import surface explicitly.
  • A clear deprecation path for any code you cannot immediately move.

What changed in v1.5.0

v1.5.0 promoted POLARIS and LYRA from the preview namespace to stable public API. The full rationale lives in docs/migrations/v1.5.0-api-additions.md; the short version is:

  • POLARIS — long-term model archive coordination.
  • Preview: fcc.plugins.v1_5_preview.polaris_bridge
  • Stable aggregate: fcc.api.get_polaris_bridge, fcc.api.PolarisBridgeProtocol, fcc.api.PolarisMockBridge
  • Stable module home: fcc.archive.polaris_bridge
  • LYRA — graph-of-thought + harmonic vocabulary expansion.
  • Preview: fcc.plugins.v1_5_preview.lyra_bridge
  • Stable aggregate: fcc.api.get_lyra_bridge, fcc.api.LyraBridgeProtocol, fcc.api.LyraMockBridge
  • Stable module home: fcc.knowledge.lyra_bridge

Both bridges gain the 1.x SemVer commitment — method signatures stay compatible for the entire 1.x line. The mock fallback classes (PolarisMockBridge, LyraMockBridge) return functional in-memory implementations when the live backend is absent, so downstream code can exercise the full protocol without any live service.

ADR-008 documents the POLARIS absorption design. The LYRA bridge follows the same pattern but remains "mock-stable" because the live lyra package has not shipped yet (see the codename decoder LYRA entry).

Deprecation window

The preview import paths keep working throughout the v1.5.x line:

Release Preview imports Stable imports Recommended action
v1.4.0 not present not present
v1.4.4 preview added not present pin preview if you need early access
v1.5.0 preview + deprecation notice stable added begin migration
v1.5.1 preview + deprecation notice stable migrate
v1.5.2 preview + deprecation notice stable migrate
v1.5.3 preview + deprecation notice stable migrate
v1.6.0 removed stable must be migrated

That is one minor-release deprecation window — the standard FCC policy established in v1.4.4. v1.6.0 will not ship the preview paths and your imports will ImportError if still pointing there.

Step-by-step migration

Step 1 — Audit your imports

Grep your codebase for preview imports:

grep -rn "fcc.plugins.v1_5_preview" --include="*.py" src/ tests/
# Also check configs + docs
grep -rn "fcc.plugins.v1_5_preview" --include="*.md" --include="*.toml" --include="*.yaml" .

Record every occurrence. You will change every one.

Step 2 — POLARIS bridge replacements

Before (v1.4.x):

# Preview namespace — will be removed in v1.6.0
from fcc.plugins.v1_5_preview.polaris_bridge import (
    PolarisBridgeProtocol,
    PolarisMockBridge,
    get_polaris_bridge,
)

bridge = get_polaris_bridge()
archive_id = bridge.push_archive(manifest_path)
models = bridge.query_models(zachman_cell="ENGINEER/WHEN")

After (v1.5.0+):

# Preferred — single public API surface, 1.x SemVer guaranteed
from fcc.api import (
    PolarisBridgeProtocol,
    PolarisMockBridge,
    get_polaris_bridge,
)

bridge = get_polaris_bridge()
archive_id = bridge.push_archive(manifest_path)
models = bridge.query_models(zachman_cell="ENGINEER/WHEN")

Or, if you prefer module-level imports:

# Also valid — direct stable-module import
from fcc.archive.polaris_bridge import (
    PolarisBridgeProtocol,
    PolarisMockBridge,
    get_polaris_bridge,
)

Functional equivalence. The stable imports resolve to the same Python object as the preview imports via a re-export. This is an invariant pinned by the test suite:

import fcc.api
import fcc.archive.polaris_bridge
assert fcc.api.get_polaris_bridge is fcc.archive.polaris_bridge.get_polaris_bridge

Your runtime behaviour does not change.

Step 3 — LYRA bridge replacements

Before (v1.4.x):

from fcc.plugins.v1_5_preview.lyra_bridge import (
    LyraBridgeProtocol,
    LyraMockBridge,
    get_lyra_bridge,
)

bridge = get_lyra_bridge()
thoughts = bridge.graph_of_thought_query("persona", depth=2)
terms = bridge.vocab_lookup("curiosity", namespace="lyra-seed")
related = bridge.harmonic_expand("persona")

After (v1.5.0+):

# Preferred
from fcc.api import (
    LyraBridgeProtocol,
    LyraMockBridge,
    get_lyra_bridge,
)

bridge = get_lyra_bridge()
thoughts = bridge.graph_of_thought_query("persona", depth=2)
terms = bridge.vocab_lookup("curiosity", namespace="lyra-seed")
related = bridge.harmonic_expand("persona")

Or via the stable module home:

from fcc.knowledge.lyra_bridge import (
    LyraBridgeProtocol,
    LyraMockBridge,
    get_lyra_bridge,
)

Step 4 — Availability sentinels + no-op fallbacks

If your code inspects whether POLARIS / LYRA are live (backed by the real ice_ext.plugin.bridge for POLARIS or lyra.api for LYRA) rather than the mock:

Before (v1.4.x):

from fcc.plugins.v1_5_preview.polaris_bridge import polaris_available

if polaris_available():
    # live backend
    ...
else:
    # mock fallback
    ...

After (v1.5.0+):

from fcc.archive.polaris_bridge import polaris_available

if polaris_available():
    # live backend
    ...
else:
    # mock fallback
    ...

The polaris_available / lyra_available helpers live at module level and are also covered by the 1.x SemVer commitment.

Step 5 — Dependency pin update

Update the FCC pin in every consumer:

# pyproject.toml — before
dependencies = [
    "fcc-agent-team-ext>=1.4.4,<1.5.0",   # forced pin for preview
]

# pyproject.toml — after
dependencies = [
    "fcc-agent-team-ext>=1.5.0,<2.0.0",   # stable 1.x bridge surface
]

For setup.py / setup.cfg / requirements.txt use the same specifier. The <2.0.0 upper bound is defensive — FCC's SemVer commits to preserving 1.x backward compatibility indefinitely.

Regenerate your lockfile (poetry lock, pip-compile, etc.) and verify the resolver picked up 1.5.x.

Step 6 — Suppressing DeprecationWarnings during the transition

The preview modules issue DeprecationWarning on import. If you are temporarily unable to migrate (e.g. a vendored fork of a library still on preview imports), silence the warning locally — but do not silence globally:

import warnings

with warnings.catch_warnings():
    warnings.filterwarnings(
        "ignore",
        category=DeprecationWarning,
        module=r"fcc\.plugins\.v1_5_preview\..*",
    )
    # The preview import fires inside this scope
    from fcc.plugins.v1_5_preview.polaris_bridge import get_polaris_bridge
    bridge = get_polaris_bridge()

This is a band-aid, not a solution. Open a tracking issue in your repo so the preview imports get removed before v1.6.0.

Step 7 — Testing the new imports

Add a test that pins the new surface explicitly. A common pattern:

# tests/test_fcc_integration.py
"""Pin the v1.5.0 POLARIS + LYRA bridge surface."""

import fcc.api
import fcc.archive.polaris_bridge
import fcc.knowledge.lyra_bridge


def test_polaris_identity():
    """fcc.api re-exports are identity-equal to module-level homes."""
    assert fcc.api.get_polaris_bridge is fcc.archive.polaris_bridge.get_polaris_bridge
    assert fcc.api.PolarisBridgeProtocol is fcc.archive.polaris_bridge.PolarisBridgeProtocol
    assert fcc.api.PolarisMockBridge is fcc.archive.polaris_bridge.PolarisMockBridge


def test_lyra_identity():
    assert fcc.api.get_lyra_bridge is fcc.knowledge.lyra_bridge.get_lyra_bridge
    assert fcc.api.LyraBridgeProtocol is fcc.knowledge.lyra_bridge.LyraBridgeProtocol
    assert fcc.api.LyraMockBridge is fcc.knowledge.lyra_bridge.LyraMockBridge


def test_mock_bridge_functional():
    """PolarisMockBridge + LyraMockBridge cover the full protocol."""
    pbridge = fcc.api.PolarisMockBridge()
    archive_id = pbridge.push_archive("/tmp/fake-manifest.yaml")
    assert archive_id.startswith("archive-")

    lbridge = fcc.api.LyraMockBridge()
    terms = lbridge.vocab_lookup("curiosity", namespace="lyra-seed")
    assert isinstance(terms, list)


def test_no_preview_imports():
    """This package does not import preview paths anymore."""
    import subprocess
    result = subprocess.run(
        ["grep", "-r", "fcc.plugins.v1_5_preview", "src/"],
        capture_output=True,
        text=True,
    )
    assert result.returncode != 0, (
        "Preview imports found — migrate before v1.6.0:\n" + result.stdout
    )

The last test is the one that closes the migration. Once it passes in CI you can be confident v1.6.0 will not ImportError on your code.

SemVer contract — what you can rely on

For every symbol in fcc.api.__all__:

  • Method signatures stay compatible for the full 1.x line. New optional parameters may be added (additive); existing parameters do not change type or semantics.
  • Return types continue to satisfy the documented Protocol. get_polaris_bridge() always returns something that structurally satisfies PolarisBridgeProtocol — whether that is a live bridge, a mock, or a future adapter.
  • Protocol method contracts do not change incompatibly. Adding a new protocol method is considered a breaking change that bumps 1.x → 2.x; never within 1.x.
  • Mock classes remain usable. PolarisMockBridge() and LyraMockBridge() continue to default-construct without arguments and continue to implement the full protocol.
  • Availability sentinels remain callable. polaris_available() and lyra_available() remain () -> bool.

What can change inside 1.x:

  • The internal implementation (the file layout, private helpers, docstrings). None of this is contract.
  • The mock's in-memory storage structure. Your tests should not pin PolarisMockBridge._storage or similar private attributes.
  • The no-op + live-adapter selection logic, as long as the returned object still satisfies the protocol.

Dependency-pin guidance for downstream ecosystems

Consumer Previous pin v1.5 stable pin Notes
PAOM fcc>=1.3.3,<1.4.0 fcc>=1.5.0,<2.0.0 PAOM has always consumed the stable API — the preview was optional
ice_ext fcc>=1.4.1,<1.5.0 + preview imports fcc>=1.5.0,<2.0.0 via fcc.archive.polaris_bridge The primary POLARIS consumer; KGSE persona tracks future promotions
ATHENIUM fcc>=1.2.1,<1.3.0 fcc>=1.5.0,<2.0.0 when ready JV L2 library, no POLARIS/LYRA dependency today
MNEMOSYNE fcc>=1.2.1,<1.3.0 fcc>=1.5.0,<2.0.0 when ready JV L2 library, no POLARIS/LYRA dependency today
Constellation verticals (Ophiuchus, Serpens, Libra, Crater, Scutum, Norma, Pyxis, Vela, Columba, Caelum) fcc>=1.4.0,<1.5.0 fcc>=1.5.0,<2.0.0 10 public repos at v0.2.0; bump in lockstep

Worked example — migrating an ice_ext consumer

ice_ext is the canonical POLARIS consumer + the tutorial target for this migration. Here is a representative before / after diff.

Before — v1.4.4 era preview imports:

# src/ice_ext_consumer/archive_client.py
from __future__ import annotations

import warnings
from pathlib import Path
from typing import Iterator

from fcc.plugins.v1_5_preview.polaris_bridge import (
    PolarisBridgeProtocol,
    get_polaris_bridge,
    polaris_available,
)


class ArchiveClient:
    """Legacy ArchiveClient pinned to v1.4.x preview imports."""

    def __init__(self) -> None:
        if not polaris_available():
            warnings.warn(
                "POLARIS live backend not available; using mock.",
                stacklevel=2,
            )
        self._bridge: PolarisBridgeProtocol = get_polaris_bridge()

    def push(self, manifest: Path) -> str:
        return self._bridge.push_archive(manifest)

    def query(self, zachman_cell: str) -> Iterator[dict]:
        yield from self._bridge.query_models(zachman_cell=zachman_cell)

After — v1.5.0 stable imports:

# src/ice_ext_consumer/archive_client.py
from __future__ import annotations

import warnings
from pathlib import Path
from typing import Iterator

from fcc.api import PolarisBridgeProtocol, get_polaris_bridge
from fcc.archive.polaris_bridge import polaris_available


class ArchiveClient:
    """v1.5+ ArchiveClient — stable fcc.api surface, 1.x SemVer guaranteed."""

    def __init__(self) -> None:
        if not polaris_available():
            warnings.warn(
                "POLARIS live backend not available; using mock.",
                stacklevel=2,
            )
        self._bridge: PolarisBridgeProtocol = get_polaris_bridge()

    def push(self, manifest: Path) -> str:
        return self._bridge.push_archive(manifest)

    def query(self, zachman_cell: str) -> Iterator[dict]:
        yield from self._bridge.query_models(zachman_cell=zachman_cell)

The diff is deliberately minimal — only the import lines change. Runtime behaviour, protocol method calls, and mock fallback semantics are preserved bit-for-bit.

Migrating scenario files + demos

Scenarios and demos that reference the preview module in their persona prompts or scenario JSON need parallel updates. A typical scenario JSON:

{
  "id": "POLARIS-ARCHIVE-001",
  "name": "Archive a manifest via the stable POLARIS bridge",
  "workflow": "base",
  "personas": ["LTAS"],
  "setup": {
    "ai_config": {
      "provider": "mock",
      "model": "mock-default"
    },
    "resources": {
      "polaris_bridge_import": "fcc.api.get_polaris_bridge"
    }
  },
  "steps": [
    {
      "phase": "create",
      "persona": "LTAS",
      "prompt": "Using the import path {{polaris_bridge_import}}, push the manifest at /tmp/fake-manifest.yaml and return the archive ID."
    }
  ]
}

Update the polaris_bridge_import resource from fcc.plugins.v1_5_preview.polaris_bridge.get_polaris_bridge to fcc.api.get_polaris_bridge. Your scenario's persona prompts will inherit the update automatically.

Migrating notebooks

FCC ships 45–48 notebooks under notebooks/. The v1.5.0 notebooks already use stable imports. If you have forked a notebook that used preview imports:

  1. Open the notebook + search cells for fcc.plugins.v1_5_preview.
  2. Replace every hit with the stable equivalent.
  3. Re-run the notebook top-to-bottom to confirm runtime behaviour is unchanged.
  4. Commit the notebook + an accompanying *.md export so non-Jupyter readers see the change in the git diff.

CI integration for the migration

Add a CI step that enforces "no preview imports" in your consumer repo. A minimal GitHub Actions job:

# .github/workflows/no-preview-imports.yml
name: No Preview Imports
on:
  pull_request:

jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Ban preview imports
        run: |
          if grep -rn "fcc.plugins.v1_5_preview" \
              --include="*.py" --include="*.ipynb" \
              --include="*.md" --include="*.yaml" \
              --include="*.json" \
              src/ tests/ notebooks/ docs/ scenarios/; then
            echo "::error::Preview imports found — migrate to fcc.api.* before v1.6.0"
            exit 1
          fi
          echo "OK — no preview imports"

Run this as a required check on your protected branch once you have completed the migration. It is the cheapest possible insurance against regressions.

Common pitfalls

Star-import shadowing. If your package uses from fcc.api import *, be aware that __all__ on fcc.api may not yet include every POLARIS / LYRA symbol. Name imports explicitly.

Import-time side effects. Consumers that triggered LYRA seed-graph construction at module import time will notice the mock initialises lazily in v1.5.0. Explicitly instantiate once at startup if you need the graph pre-warmed.

Version overlap. A workspace with both a preview-pinned package and a stable-pinned package may end up pulling both FCC versions if your resolver is lax. Use lockfiles + pin to a single FCC version per workspace.

Docs still pointing at preview. Search your docs for references to fcc.plugins.v1_5_preview and update them. The v1.5.0 API additions migration doc and this tutorial are the only documents allowed to mention the preview paths (as "before" examples).

Availability sentinel imported from preview. The polaris_available / lyra_available helpers live at fcc.archive.polaris_bridge / fcc.knowledge.lyra_bridge — not at fcc.api top level. This is intentional — the sentinels are module-level because they inspect the environment, not instance state. Import them from the module, not the aggregate namespace.

Mock type assumptions. If your tests check isinstance(bridge, PolarisMockBridge) to detect the mock, the isinstance check still works across the migration (identity is preserved). But if you relied on a string repr of the bridge type in logs, re-verify the log format — the module path has changed from fcc.plugins.v1_5_preview.polaris_bridge.PolarisMockBridge to fcc.archive.polaris_bridge.PolarisMockBridge.

Deprecation warnings in tests. If your test runner treats DeprecationWarning as an error (common with filterwarnings = error in pytest config), preview imports in transitive dependencies will break your test run. Either scope the error filter to exclude fcc.plugins.v1_5_preview.* or finish migrating transitive consumers first.

Timing + release-planning guidance

When you ship your v1.5-compatible consumer, align the release with your downstream consumers' own release cadence. A realistic timeline:

Week Your consumer Downstream of your consumer
0 Merge migration PR Pinned to your pre-migration tag
1 Tag release with migration noted in CHANGELOG Receive release notes
2-3 Your docs + samples updated Begin their own migration
4-6 CI enforcement (no-preview-imports job) Verify tests pass on both FCC 1.5.x + (later) 1.6.0-rc
6-8 Complete their migration; pin your consumer to migrated version

The window matters because FCC's v1.6.0 removes the preview paths. If your consumer ships a migration + your downstream has not migrated, your v1.5-pinned release still works on v1.5.x, but the clock starts ticking toward v1.6.0 removal.

Coordination with KGSE

The Knowledge Graph Serializer Elevator (KGSE) persona shipped in v1.5.0 explicitly to coordinate future preview-to-stable promotions. KGSE's role responsibilities include:

  • Tracking ice_ext STATUS.md §N items that request upstream promotion.
  • Maintaining the queue of preview-namespace symbols that are candidates for elevation.
  • Producing the quarterly list RC uses during roadmap review.

If your migration surfaces a symbol that is still preview-only and should be stable (e.g. a helper function you rely on that has not yet been promoted), open an issue citing KGSE in the pattern-absorption-proposal template. KGSE will pick it up within a release cycle. See adopting universal services, Step 4 for the full proposal workflow.

Checklist for "done"

  • grep -r "fcc.plugins.v1_5_preview" src/ returns zero matches.
  • grep -r "fcc.plugins.v1_5_preview" tests/ returns zero matches (except in migration-specific tests that deliberately assert the preview path is deprecated).
  • pyproject.toml pins fcc-agent-team-ext>=1.5.0,<2.0.0.
  • Lockfile regenerated.
  • New identity tests (Step 7) pass locally + in CI.
  • DeprecationWarning suppressions — if any — have a tracking issue open.
  • Consumer README + CHANGELOG updated to call out the migration.

Next steps