Skip to content

Integrating FCC into a Mature Codebase

Adding FCC to a greenfield project is easy — pip install fcc-agent-team-ext and you are off. Adding FCC to a codebase that has been running in production for years, with established CI, observability, coding standards, and a team culture, is harder. This tutorial walks through integrating FCC without disrupting the existing dev loop, and without a big-bang rewrite.

You will end this tutorial with:

  • A successful pip install of FCC as a versioned dependency.
  • A bootstrap scenario JSON that exercises the full FCC cycle on a single piece of your codebase.
  • One custom persona registered as a plugin, representing your project's most distinctive role.
  • An audit-personas-strict job in your CI pipeline.
  • An optional Helm deployment of FCC's WebSocket bridge + Streamlit apps + React frontend + Jupyter notebooks for collaborators.
  • OpenTelemetry traces flowing from FCC into your existing APM stack.
  • An EU AI Act compliance classification baseline for every persona you expose.

The order matters. Each step is additive and reversible; no step requires abandoning tools you already have.

Motivation — why integrate FCC into an existing codebase?

Three drivers tend to justify the integration effort:

  1. Team coordination. Growing teams hit a wall where "who owns this change, who reviews it, and who signs it off" stops being obvious. FCC personas are named coordination primitives — Stakeholder, Integration Specialist, Governance Reviewer, Champion of a team — and adopting them lets you write down the coordination pattern instead of carrying it in heads.
  2. Persona-driven review. Instead of "Alice reviewed this", you get "the Privacy Handoff Validator (PHV) persona approved this change through gate QG-PRIV-03". That is machine-auditable and matches how auditors want to read your evidence.
  3. Plugin ecosystem. FCC has 11 plugin types (personas, engines, templates, scorers, validators, providers, governance, scenarios, workflows, subscribers, vocabulary providers). Once you are wired in, you can consume community plugins + ship your own without changing FCC itself.

The integration you get out of this tutorial is a seam, not a replacement. Your existing linter, test runner, release workflow, monitoring, and release notes all keep working. FCC joins them as an additional authority on coordination.

Step 1 — Pin a FCC version + install

Add FCC to your existing project's dependency manifest. If you are using pyproject.toml:

[project]
dependencies = [
    "fcc-agent-team-ext>=1.5.0,<2.0.0",
    # Optional umbrella for the Docker backend:
    # "fcc-agent-team-ext[full]>=1.5.0,<2.0.0",
]

If you are on setup.cfg or requirements.txt use the same version specifier. The >=1.5.0,<2.0.0 range is the SemVer commitment: every 1.x release is backward compatible on the fcc.api.* surface (see the SemVer commitment in v1.4.1 API additions).

Install + verify:

pip install -e .
python -c "import fcc; print(fcc.__version__)"
# Expected: 1.5.0 or higher within the 1.x line

Next, confirm you can load the registry:

from fcc._resources import get_personas_dir
from fcc.personas.registry import PersonaRegistry

registry = PersonaRegistry.from_yaml_directory(get_personas_dir())
assert len(registry) >= 164, f"Unexpected persona count: {len(registry)}"
print(f"Loaded {len(registry)} personas across {len(registry.by_category())} categories")

If this fails you almost certainly have a sys.path issue — FCC deliberately uses importlib.resources via fcc._resources to avoid Path(__file__) hacks, and wheel installs just work. Reinstall in a clean venv.

Step 2 — Scenarios bootstrap

A scenario is the smallest unit of FCC work. Create scenarios/first-scenario.json:

{
  "id": "BOOTSTRAP-001",
  "name": "First bootstrap scenario",
  "description": "Validates FCC integration with a trivial cycle.",
  "workflow": "base",
  "personas": ["RC", "CC", "QC"],
  "setup": {
    "artifact": "A single function in src/acme/pay.py."
  },
  "steps": [
    {
      "phase": "find",
      "persona": "RC",
      "prompt": "Identify the change's intent + affected code paths."
    },
    {
      "phase": "create",
      "persona": "CC",
      "prompt": "Draft the change."
    },
    {
      "phase": "critique",
      "persona": "QC",
      "prompt": "Evaluate the draft against quality gates QG-CODE-01 and QG-CODE-04."
    }
  ]
}

Run it (the simulation is deterministic by default — no AI API keys required):

fcc run-scenario scenarios/first-scenario.json --output traces/bootstrap-001.json

Read the trace:

import json
with open("traces/bootstrap-001.json") as f:
    trace = json.load(f)

for event in trace["events"]:
    print(f"{event['timestamp']} {event['type']}: {event.get('summary', '')}")

If this runs + produces a trace, FCC is installed and your project can invoke it. Commit the scenario + trace + a README.md entry.

Step 3 — First custom persona

Most mature codebases have a role that is not in FCC's default catalog. Typical examples: a "Release Notes Scribe", a "Canary-Deployment Watcher", a "Cost-Budget Owner". Register yours as a plugin.

Minimal persona YAML (acme/personas/release_scribe.yaml):

id: ACME_RNS
name: Release Notes Scribe
role_title: Release Notes Scribe
fcc_phase: Create
category: docs_as_code
zachman_cell: "PLANNER/WHEN"
riscear:
  role: "Curates every release's user-visible changes into a single, accurate release note."
  input:
    - CHANGELOG.md diff between tags
    - Pull-request bodies touching user-visible paths
  style: "Concise, chronological, user-voice."
  constraints:
    - "Must not leak internal-only changes."
    - "Must include migration hints for breaking changes."
  expected_output:
    - "A Markdown release note under RELEASE_NOTES/v{N.N.N}.md"
  archetype: "The Chronicler"
  responsibilities:
    - "Collect user-visible diffs."
    - "Cross-reference migration docs."
  role_skills:
    - "Technical writing"
    - "Change-log archaeology"
  role_adoption_checklist:
    - "Has read the last 3 release notes."
    - "Understands the project's user-voice style guide."
role_collaborators:
  - persona_id: CMW
    reason: "CMW owns the docs-publication pipeline that emits the note."
  - persona_id: DAR
    reason: "DAR reconstructs historical releases for the governance archive."

Register it via the plugin mechanism — create acme_fcc_plugin/__init__.py:

from pathlib import Path
from fcc.plugins import PersonaPlugin

class ACMEPersonaPlugin(PersonaPlugin):
    def get_persona_files(self) -> list[Path]:
        return [Path(__file__).parent / "personas" / "release_scribe.yaml"]

Expose it as an entry point in your pyproject.toml:

[project.entry-points."fcc.plugins.personas"]
acme = "acme_fcc_plugin:ACMEPersonaPlugin"

Install + verify:

pip install -e .
python -c "
from fcc._resources import get_personas_dir
from fcc.personas.registry import PersonaRegistry
r = PersonaRegistry.from_yaml_directory(get_personas_dir())
r.load_plugins()
print(r.get('ACME_RNS').name)
"
# Expected: Release Notes Scribe

Keep the custom persona's ID scoped with a project prefix (ACME_RNS, not RNS) to avoid collisions with future FCC personas. The v1.5.1 fcc audit personas --strict audit catches collisions deterministically — run it now and confirm zero findings.

Step 4 — fcc audit gates in CI

Add a CI job that runs the strict audit on every PR. For GitHub Actions:

# .github/workflows/fcc-audit.yml
name: FCC Audit
on:
  pull_request:
    paths:
      - "acme/personas/**"
      - "scenarios/**"
      - "pyproject.toml"

jobs:
  audit-personas-strict:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.11"
      - run: pip install -e .[dev]
      - run: fcc audit personas --strict
      - run: fcc audit gates --list --format json > gates.json
      - uses: actions/upload-artifact@v4
        with:
          name: gates
          path: gates.json

The strict audit checks:

  • role_collaborators placement (persona top-level, not inside riscear:).
  • Dimensions profile present.
  • Model card present.
  • EU AI Act classification present.
  • Cross-reference entries + reasons.
  • Zachman cell declaration.
  • R.I.S.C.E.A.R. completeness.

Every check can be individually opted out via config if a finding is acknowledged + deferred. Do not bypass via --no-verify; if a gate fires unexpectedly, fix the underlying issue.

For GitLab CI or Jenkins, adapt the same three steps (checkout + install + fcc audit personas --strict). The CLI returns non-zero on any finding.

Step 5 — Helm chart deploy

FCC ships a Helm chart at charts/fcc/ with four production images:

Image Purpose Chart default port
fcc-backend fcc protocol ws-bridge WebSocket + HTTP server 8000
fcc-frontend React 18 + D3 trace viewer 8080
fcc-streamlit 27 Streamlit apps 8501
fcc-jupyter JupyterLab with 48 FCC notebooks 8888

The chart at Helm version 0.4.3 + appVersion 1.5.3 is the version pinned by v1.5.3.

Install against your existing cluster:

helm repo add fcc https://rollingthunderfourtytwo-afk.github.io/l2_fcc_agent_team_ext/helm
helm install fcc fcc/fcc \
  --version 0.4.3 \
  --namespace fcc \
  --create-namespace \
  --set backend.image.tag="1.5.3" \
  --set frontend.enabled=true \
  --set streamlit.enabled=true \
  --set jupyter.enabled=false   # toggle for collaborators

Values you will almost certainly override:

  • backend.service.type — set to ClusterIP + create your own Ingress pointing at your existing gateway.
  • backend.env.OTEL_EXPORTER_OTLP_ENDPOINT — set to your existing collector (see Step 6).
  • backend.resources — defaults are conservative; size for your expected simulation throughput.
  • streamlit.auth.type — default is none; set to oidc + plumb into your existing identity provider.

Verify:

helm test fcc --namespace fcc
kubectl -n fcc logs deployment/fcc-backend | head -n 30
kubectl -n fcc get svc

The chart does not assume a specific ingress controller or cert manager — wire your own. The backend's /health endpoint returns 200 within ~250ms at steady state; use it for your existing readiness probes.

Step 6 — Observability wiring

FCC emits structured spans through an optional OpenTelemetry bridge. Install the extra + set the collector endpoint:

pip install "fcc-agent-team-ext[otel]>=1.5.0,<2.0.0"
export OTEL_EXPORTER_OTLP_ENDPOINT="https://your-collector.corp:4317"
export OTEL_SERVICE_NAME="fcc-bootstrap"

Instrument your existing simulation:

from fcc.simulation.engine import SimulationEngine
from fcc.observability.integration import instrument_simulation_engine

engine = SimulationEngine(registry=registry)
instrument_simulation_engine(engine)   # adds OTel spans + metrics

Every simulation step emits:

  • A span per phase transition (Find / Create / Critique).
  • A span per persona invocation.
  • Seven pre-defined metrics: scenario duration, persona invocations, gate pass count, gate fail count, feedback-loop count, trace size, error count.
  • A parent span per scenario run with the scenario ID as attribute.

If you already use Datadog / Honeycomb / Grafana Tempo, FCC's spans land natively with no additional configuration. If you use an in-house collector, add OTLP/gRPC ingestion on port 4317.

The LaneRouter architecture added in v1.4.2 (see ADR-006) gives you per-lane tracer providers — separate traces for the PAOM event bus and the UX bus. You can filter in your APM by the fcc.lane attribute.

When OTel is not installed (the [otel] extra is optional), FCC degrades to its in-memory FccTracer + FccMetrics — no telemetry is lost, it just stays local.

Step 7 — EU AI Act compliance integration

Every persona has an EU AI Act risk classification (persisted in src/fcc/data/personas/compliance_classifications.yaml since v1.5.1). Run the classifier against your custom personas:

from fcc.compliance.classifier import AIActClassifier
from fcc.personas.registry import PersonaRegistry
from fcc._resources import get_personas_dir

registry = PersonaRegistry.from_yaml_directory(get_personas_dir())
registry.load_plugins()

clf = AIActClassifier()
for persona in registry.all():
    classification = clf.classify(persona)
    print(f"{persona.id}: {classification.risk_category} ({classification.rationale[:60]}...)")

Typical results: most documentation personas classify as minimal_risk; personas that touch automated decisions about people (hiring, credit, benefits) climb to high_risk.

For each high_risk persona, run a full compliance audit:

from fcc.compliance.auditor import ComplianceAuditor

auditor = ComplianceAuditor()
audit = auditor.audit_persona(persona)
print(f"{persona.id}: {len(audit.findings)} findings, {len(audit.evidence)} evidence items")

The ComplianceAuditor walks 256+ EU AI Act requirements + 29 NIST AI RMF subcategories. For an auditor-facing integration, persist the audit output to your evidence repository via the CompliancePipeline reactive subscriber:

from fcc.compliance.pipeline import CompliancePipeline
from fcc.messaging.bus import EventBus

bus = EventBus()
pipeline = CompliancePipeline(bus=bus, auditor=auditor)
# Every PERSONA_MODIFIED event triggers re-audit

This is how FCC ships reactive re-audit — every persona change triggers an automatic compliance pass + emits evidence. Wire it into your existing audit-evidence store (S3 bucket, Glacier, etc.).

Rollback plan + gradual adoption

If any step misbehaves, rollback is cheap:

Step Rollback
1 — install pip uninstall fcc-agent-team-ext
2 — scenario Delete scenarios/first-scenario.json
3 — plugin Remove entry point from pyproject.toml
4 — CI Mark the audit-personas-strict job as continue-on-error: true
5 — Helm helm uninstall fcc --namespace fcc
6 — OTel Unset OTEL_EXPORTER_OTLP_ENDPOINT
7 — Compliance Skip reactive pipeline; keep manual audit

No step changes shape your existing codebase incompatibly — every seam is either a new file (scenarios, plugins) or a new process (strict audit, Helm deploy) that you can turn off.

Gradual adoption advice. Do Steps 1–4 in week one. Observe for a week. Do Steps 5–6 in week two. Observe for two weeks. Introduce Step 7 only after your team has two months of lived experience with the earlier steps — compliance-driven change is load-bearing and deserves the maturity.

Troubleshooting common integration failures

ImportError: cannot import name 'ACME_RNS' from 'fcc.plugins'. Your plugin entry point is not installed. Re-run pip install -e . from the package root. Confirm via python -c "import importlib.metadata; print(list(importlib.metadata.entry_points().select(group='fcc.plugins.personas')))".

Schema validation failed on YAML. Your custom persona YAML is missing a required field. Run fcc audit personas --strict — the error message points at the missing key. The most common misses are zachman_cell (required since v1.4.1) and role_collaborators at persona top level (required since v1.5.1, was nested inside riscear: in v1.4.x).

Helm chart templates but does not deploy. Run helm lint charts/fcc + read every warning. The most common cause is a missing values.yaml override for backend.env.OTEL_EXPORTER_OTLP_ENDPOINT when OTel is enabled.

OTel traces show up but with missing fcc.* attributes. The instrument_simulation_engine() call must happen before the first scenario runs. Call it once in your app's startup sequence, not per-scenario.

fcc audit personas --strict passes locally but fails in CI. CI has an older cached wheel. Clear the pip cache + rebuild. If the error persists, it is almost always a path-separator issue on Windows runners — FCC is tested on Linux-like filesystems; use Linux CI runners or WSL.

Compliance classifier returns unknown for a custom persona. The classifier uses rule-based heuristics over R.I.S.C.E.A.R. fields. If your persona's responsibilities + constraints do not match any rule, the classifier returns unknown + flags the persona for manual review. Open a ticket with the classification team + declare the persona's risk category manually in compliance_classifications.yaml.

Scenario + plugin testing patterns

Your integration needs regression tests. Two patterns that work well:

Pattern 1 — scenario smoke test

# tests/test_fcc_scenario.py
from pathlib import Path

from fcc._resources import get_personas_dir
from fcc.personas.registry import PersonaRegistry
from fcc.scenarios.loader import load_scenario
from fcc.simulation.engine import SimulationEngine


def test_bootstrap_scenario_runs_to_completion():
    registry = PersonaRegistry.from_yaml_directory(get_personas_dir())
    registry.load_plugins()

    scenario = load_scenario(Path("scenarios/first-scenario.json"))
    engine = SimulationEngine(registry=registry)
    trace = engine.run(scenario)

    assert trace.status == "completed"
    assert len(trace.events) > 0
    assert all(e.timestamp is not None for e in trace.events)

Pattern 2 — custom-persona registry assertion

# tests/test_acme_persona.py
from fcc._resources import get_personas_dir
from fcc.personas.registry import PersonaRegistry


def test_acme_rns_registered():
    registry = PersonaRegistry.from_yaml_directory(get_personas_dir())
    registry.load_plugins()

    rns = registry.get("ACME_RNS")
    assert rns.name == "Release Notes Scribe"
    assert rns.fcc_phase == "Create"
    assert rns.category == "docs_as_code"
    # R.I.S.C.E.A.R. completeness
    assert rns.riscear.role
    assert rns.riscear.expected_output
    # Cross-reference coverage
    assert any(c.persona_id == "CMW" for c in rns.role_collaborators)

Both tests run in ~1 second locally + in CI. Mandate both as required checks for your protected branch.

Working with events + the dual-bus architecture

As of v1.4.2 (ADR-006), FCC's event system is dual-bus: a PAOMBus for decision-workflow events and a UXBus for user-facing events. A LaneRouter routes events to the correct bus based on 85 lane- classified event types.

If your integration subscribes to events (e.g. to mirror FCC's trace output into your own observability stack), subscribe on both buses:

from fcc.messaging.bus import EventBus, LaneRouter
from fcc.messaging.events import EventType


paom_bus = EventBus(name="paom")
ux_bus = EventBus(name="ux")
router = LaneRouter(paom=paom_bus, ux=ux_bus)


def on_trace_event(event):
    # Mirror into your observability stack
    your_apm_client.send_span(event.to_span_dict())


paom_bus.subscribe(EventType.SCENARIO_COMPLETED, on_trace_event)
ux_bus.subscribe(EventType.SCENARIO_COMPLETED, on_trace_event)

The lane abstraction lets you filter in your APM by which bus produced the event — useful when the UX lane has higher traffic than the PAOM lane + you want separate dashboards.

Example — integration of a 40k-LOC Python service

A payments service integrated FCC in early 2026. Their retrospective:

  • Install + bootstrap (Steps 1–2): Half a day. No friction.
  • Custom personas (Step 3): Three personas — Release Scribe, Cost Owner, Canary Watcher. About a day of YAML + plugin wiring.
  • CI (Step 4): Two days including branch-protection policy.
  • Helm (Step 5): Deferred to month 2 — team was happy running FCC locally first.
  • OTel (Step 6): 2 hours once their collector endpoint was known.
  • Compliance (Step 7): Ongoing — classifier ran in week 6, audit pipeline went live in week 10.

Total elapsed time to "FCC is a first-class part of the repo": 8 weeks, of which roughly 10 engineer-days of active work.

Checklist for "done"

  • pip show fcc-agent-team-ext reports 1.5.0 or higher.
  • scenarios/first-scenario.json runs to completion + emits a trace.
  • At least one custom persona registered via entry point, with project-prefix ID.
  • fcc audit personas --strict passes in CI on every PR.
  • Helm chart (or local docker-compose) running if interactive use is desired.
  • OTel traces visible in APM with fcc.* attributes.
  • EU AI Act classification baseline persisted for every persona you ship.

Next steps