Skip to content

4+1 Process View

The Process View captures what FCC does at runtime. Where the Logical View focuses on static class structure, this page traces control and message flow: how a workflow step becomes an LLM call, how external tools invoke FCC via MCP, how two agents negotiate A2A capabilities, and how the compliance auditor runs as an out-of-band job. Every diagram on this page is a live sequence — reading them top- to-bottom shows a single execution trace.

Four diagrams follow: the Find to Create to Critique cycle, the MCP inbound call, an A2A agent card exchange, and a compliance audit run.

Find, Create, Critique cycle

The spine of the framework. SimulationEngine.run(start_node, payload) at src/fcc/simulation/engine.py:50 walks a WorkflowGraph one node at a time. For each node it resolves the persona and action, builds a prompt via the ActionEngine, calls the configured AIClient, validates the output, records a turn, and publishes one workflow.step event on the EventBus. A single workflow.completed event fires at the end.

Figure 1 traces a single end-to-end workflow run from the caller's perspective.

sequenceDiagram
    participant Caller
    participant SimulationEngine
    participant PersonaRegistry
    participant ActionEngine
    participant AIClient
    participant EventBus
    participant Observability

    Caller->>SimulationEngine: run(start_node, payload)
    loop for each node in graph
        SimulationEngine->>PersonaRegistry: by_id(persona_id)
        PersonaRegistry-->>SimulationEngine: PersonaSpec
        SimulationEngine->>ActionEngine: get_action_prompt(persona, action)
        ActionEngine-->>SimulationEngine: {system, user}
        SimulationEngine->>AIClient: complete(system, user)
        AIClient-->>SimulationEngine: raw_output
        SimulationEngine->>EventBus: publish(workflow.step)
        EventBus-->>Observability: span emit
    end
    SimulationEngine->>EventBus: publish(workflow.completed)
    SimulationEngine-->>Caller: MessageHistory

MCP inbound persona lookup

When an external tool (a CrewAI agent, an IDE extension, etc.) invokes FCC over MCP, the MCPServer at src/fcc/protocols/mcp/server.py receives a JSON-RPC request, resolves the tool name to a handler, and returns a structured result. The stream_manager multiplexes streaming responses for long-running tool calls.

Figure 2 traces an MCP persona_lookup call end-to-end.

@startuml
actor "External Tool" as ET
participant "MCP Server" as MCP
participant "ToolRegistry" as TR
participant "PersonaRegistry" as PR
participant "StreamManager" as SM

ET -> MCP : JSON-RPC persona_lookup(id)
MCP -> TR : resolve("persona_lookup")
TR --> MCP : handler
MCP -> PR : by_id(id)
PR --> MCP : PersonaSpec
MCP -> SM : open_stream(request_id)
MCP --> ET : initial ack
MCP -> SM : send(persona_card)
SM --> ET : chunk
MCP -> SM : close(request_id)
SM --> ET : end
@enduml

The MCP bridge is thin by design: handlers call the same Python APIs the CLI would, so there is no duplicate business logic.

A2A agent card exchange

Two FCC personas running in different processes can negotiate capabilities via the A2A protocol. The A2AServer at src/fcc/protocols/a2a/server.py exposes an agent card that declares skills; the client persona reads the card, finds a compatible skill, and invokes it.

Figure 3 shows a capability negotiation between a Research Crafter (RC) and a Blueprint Crafter Champion (BCHM).

sequenceDiagram
    participant RC as Research Crafter (client)
    participant BCHM as Blueprint Crafter Champion (server)
    participant CardStore as AgentCardStore

    RC->>BCHM: GET /.well-known/agent.json
    BCHM->>CardStore: load_card()
    CardStore-->>BCHM: AgentCard(skills)
    BCHM-->>RC: AgentCard
    RC->>RC: match(needed_skill, card.skills)
    RC->>BCHM: POST /skill/blueprint_draft
    BCHM->>BCHM: validate_against_riscear()
    BCHM-->>RC: SkillResult
    RC->>RC: record_collaboration_edge()

The collaboration edge that RC records feeds back into the cross-reference matrix at src/fcc/personas/cross_reference.py.

Compliance audit run

ComplianceAuditor.audit() is a batch job: given a PersonaRegistry, it enumerates every persona, classifies each against the EU AI Act risk tiers, runs all applicable quality gates, and assembles a ComplianceReport. The CompliancePipeline wraps the audit with event emission so dashboards and subscribers can react.

Figure 4 is a PlantUML activity diagram of one audit pass.

@startuml
start
:Load PersonaRegistry;
:Load RequirementRegistry (256+ EU AI Act, 29 NIST);
:Load 58 QualityGates;
while (more personas?) is (yes)
  :Pick next persona;
  :AIActClassifier.classify(persona);
  :Select applicable requirements;
  while (more gates?) is (yes)
    :Evaluate gate;
    if (pass?) then (yes)
      :record pass;
    else (no)
      :record AuditFinding;
    endif
  endwhile
  :emit compliance.persona_audited;
endwhile (no)
:Assemble ComplianceReport;
:Build EvidenceGraph;
:emit compliance.audit_completed;
stop
@enduml

How the async story fits together

The simulation engine is synchronous inside its loop; the concurrency seam is the event bus. EventBus.publish at src/fcc/messaging/bus.py:102 enqueues onto a bounded queue.Queue and returns immediately. Subscribers drain the queue on a worker thread pool; each subscriber has its own dead-letter queue (src/fcc/messaging/dlq.py) so a slow or failing subscriber cannot back-pressure the publisher. The MCP and A2A servers run on separate event loops and interact with the engine only through published events and direct Python calls — they never reach into engine state. The compliance pipeline runs detached from any live simulation, but its events land on the same bus so the dashboard can surface audit progress alongside live workflow steps.

That separation is the process-view invariant: the workflow critical path is synchronous and deterministic, while everything else (observability, protocol bridges, compliance reactions) rides the bus asynchronously.

See also

  • src/fcc/simulation/engine.py:50SimulationEngine.run
  • src/fcc/workflow/action_engine.py:51ActionEngine.run
  • src/fcc/messaging/bus.py:102EventBus.publish
  • src/fcc/protocols/mcp/server.py — MCP handler dispatch
  • src/fcc/protocols/a2a/server.py — A2A agent card server
  • src/fcc/compliance/pipeline.pyCompliancePipeline
  • Sequence diagrams deep-dive

v1.5.0 Process Impact

v1.5.0 introduced three new runtime flows that extend the Process View's async spine. The workflow critical path is still synchronous and deterministic; the new flows ride the lane-routed event bus as additional asynchronous rails.

GraphRAG retrieval pipeline (Pillar B). GraphRAG.query at src/fcc/rag/graphrag.py:166 sequences six steps — seed retrieval, BFS expansion, Zachman filter, persona-aware scoring, LYRA augmentation, context assembly — into one deterministic pipeline. Each step is synchronous; LYRA augmentation tolerates a raised exception and degrades to KG-only. Full trace: ../sequence-diagrams/graphrag-zachman-filter.md.

CRDT convergence loop (Pillar D). The MultiUserSession edit path is non-blocking: local edits fan out through a broadcaster callback, remote edits arrive via on_remote_edit(update), and the CRDT backend converges by Lamport + replica-id LWW (in-memory) or full Yjs semantics (YPy). No coordination primitive gates the merge. Presence and session events ride the UX lane; turn recording rides the PAOM lane. Full trace: ../sequence-diagrams/crdt-multi-user-merge.md.

Dual-bus event routing (ADR-006). The LaneRouter at src/fcc/messaging/lanes.py:132 replaces the single EventBus entry point: publish(event) classifies the event against event_lane_map.yaml (85 classifications) and dispatches to PAOMBus (orchestration), UXBus (live-view), or both. Subscribers register per lane; unknown events fall back to PAOM with a RuntimeWarning. Full flow: ../data-flow-diagrams/dual-bus-lane-routing.md.

Bidirectional federation sync (Pillar E). BidirectionalSync runs as a batch job against a NamespaceRegistry; conflicts reconcile per the target's ConflictPolicy and manual merges queue until a human resolves them. Full trace: ../sequence-diagrams/bidirectional-federation-sync.md.

The process-view invariant holds: workflow on the critical path, everything else on the bus. The new flows are additive.

v1.6.0 Impact

v1.6.0 executes three of the pillars that v1.5.0 introduced. Each pillar adds a new runtime flow; all three preserve the v1.5.0 invariant (workflow synchronous, everything else either offline batch or async-bus).

Pillar A fetch + manifest-build workflow. The owner guards the canonical-seed fetch behind the FCC_ACCEPT_EXTERNAL_LICENSES=1 env flag. publications/scripts/fetch_canonical_seeds.py is a one-shot offline job: it iterates the per-vertical CanonicalSource entries from seed_canonical.py, issues an HTTPS request with the bootstrap User-Agent + 30-second timeout, writes each file under publications/_output/seed_canonical/<vertical>/, computes SHA-256 + a 25 MB cap per file, and appends a FileResult entry to seed_canonical_manifest.json. When the endpoint refuses scrape or redirects to an auth wall, the fetcher writes a smoke subset and flags substituted: true in the manifest. The job emits no events — it is a build-time artifact producer, not a runtime flow — and its output is committed into the tree as the authoritative corpus manifest. Full trace: ../pillar-a-seed-lifecycle-v160.md.

Pillar C translation + glossary-preservation workflow. The translation flow is LLM-assisted with a deterministic post-check. A translator sub-agent reads the English baseline (frontend/src/i18n/locales/en/common.json plus the companion docs/i18n/ tree), emits a locale JSON, and hands off to the glossary enforcer. scripts/verify_glossary_preservation.py loads docs/i18n/glossary.csv, collects every row with do_not_translate=true (R.I.S.C.E.A.R., Zachman row/column names, persona IDs, canonical acronyms), and asserts that every do-not-translate term that appears in the English baseline also appears verbatim in the target locale. A single mismatch fails the release gate. Full trace: ../pillar-c-translation-flow-v160.md.

LYRA live bridge probe + fallback sequence. get_lyra_bridge() at src/fcc/knowledge/lyra_bridge.py:346 calls lyra_available() (which caches the result of import lyra.api), and on True constructs lyra.api.LyraAdapter(). On construction failure the accessor falls back to LyraMockBridge(_build_seed_graph()) and logs at DEBUG; on False it returns LyraMockBridge directly. The probe is synchronous and happens per-call, so a v1.6.x environment where the operator enables lyra.api partway through a process lifecycle picks up the live adapter on the next call without a restart (the sentinel can be reset via _reset_lyra_sentinel() for tests). Full trace: ../lyra-live-integration-v160.md.

Preview-namespace removal. The fcc.plugins.v1_5_preview.* import paths are removed in v1.6.0 — any runtime that still imports them raises ModuleNotFoundError at interpreter startup rather than a DeprecationWarning. This is a process-view observation because it is the only v1.6.0 change that can break a running process; operators must migrate their shims to fcc.knowledge.lyra_bridge / fcc.archive.polaris_bridge / the flat fcc.api namespace before upgrading.

The three flows together validate the v1.5.0 process-view invariant: the workflow critical path is untouched, Pillar A is an offline build-time job, Pillar C is a CI-gate + translator sub-agent loop, and the LYRA probe is a pure per-call resolution that degrades safely when the backend is absent.