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 installof 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-strictjob 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:
- 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.
- 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.
- 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):
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:
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_collaboratorsplacement (persona top-level, not insideriscear:).- 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 toClusterIP+ 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 isnone; set tooidc+ 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-extreports 1.5.0 or higher. -
scenarios/first-scenario.jsonruns to completion + emits a trace. - At least one custom persona registered via entry point, with project-prefix ID.
-
fcc audit personas --strictpasses 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¶
- Restructuring existing documentation — the docs complement to this CI / deploy story.
- Upgrading consumers to v1.5 stable — if you are already on v1.4.x preview bridges.
- Adopting universal services — RC-coordinated plumbing for cross-ecosystem work.
- Codename decoder — reference for every codename in your audit + trace output.
- ADR-006 Dual-bus event architecture — the design behind the LaneRouter you will see in traces.
- ADR-012 CRDT multi-user collaboration — when multiple engineers co-author a scenario.