Designing Your First Workflow¶
This tutorial teaches you to load a workflow graph, understand its nodes and edges, run BFS traversal, and see the persona activation order.
What is a Workflow Graph?¶
A workflow graph defines how FCC personas collaborate. It consists of:
- Nodes: Personas that participate in the workflow
- Edges: Directed connections between personas, carrying artifacts or feedback
- Metadata: ID, title, and description of the workflow
Workflow graphs are stored as JSON files in data/workflows/.
Loading the Base Workflow Graph¶
from fcc.workflow.graph import WorkflowGraph
graph = WorkflowGraph.from_json("data/workflows/base_sequence.json")
print(f"ID: {graph.meta.id}")
print(f"Title: {graph.meta.title}")
print(f"Description: {graph.meta.description}")
print(f"Nodes: {len(graph)}")
print(f"Edges: {len(graph.edges)}")
Output:
ID: fcc_base_sequence
Title: FCC Personas Collaboration - Base Sequence
Description: Canonical FCC workflow graph: 5 personas, handoffs and feedback loops.
Nodes: 5
Edges: 11
Understanding Nodes¶
Each node represents a persona in the workflow:
Output:
RC: Research Crafter (type: persona)
BC: Blueprint Crafter (type: persona)
DE: Documentation Evangelist (type: persona)
RB: Runbook Crafter (type: persona)
UG: User Guide Crafter (type: persona)
Access individual nodes:
rc_node = graph.get_node("RC")
print(f"Node: {rc_node.name}")
# Check if a node exists
print("RC" in graph) # True
print("CIA" in graph) # False (not in base workflow)
Understanding Edges¶
Edges are directed connections between nodes. Each edge has:
from_id: The source nodeto_id: The destination nodelabel: What flows along this edgetype: Eitherhandoff(forward flow) orfeedback(iterative refinement)
# List all edges
for edge in graph.edges:
arrow = "-->" if edge.type == "handoff" else "-.>"
print(f" {edge.from_id} {arrow} {edge.to_id} [{edge.label}]")
Output:
RC --> BC [research_inventory]
BC --> DE [blueprints_specs]
DE -.> BC [standards_edits]
DE --> RB [publish_handoff]
DE --> UG [publish_handoff]
RB -.> BC [operational_feedback]
RB -.> RC [operational_findings]
UG -.> BC [usability_feedback]
UG -.> RC [user_feedback]
BC --> RB [updated_specs]
BC --> UG [updated_specs]
Filtering Edges by Type¶
# Get only handoff edges (forward flow)
handoffs = graph.handoffs()
print(f"Handoff edges: {len(handoffs)}")
for h in handoffs:
print(f" {h.from_id} -> {h.to_id}: {h.label}")
# Get only feedback edges (iterative refinement)
feedbacks = graph.feedbacks()
print(f"Feedback edges: {len(feedbacks)}")
for f in feedbacks:
print(f" {f.from_id} -> {f.to_id}: {f.label}")
Querying Node Relationships¶
Find which nodes are connected to a given node:
# Who does BC send artifacts to?
successors = graph.successors("BC")
print(f"BC sends to: {[n.id for n in successors]}")
# ['DE', 'RB', 'UG']
# Who sends artifacts to BC?
predecessors = graph.predecessors("BC")
print(f"BC receives from: {[n.id for n in predecessors]}")
# ['RC', 'DE', 'RB', 'UG']
# What edges leave BC?
outgoing = graph.outgoing_edges("BC")
for e in outgoing:
print(f" BC -> {e.to_id}: {e.label} ({e.type})")
# What edges arrive at BC?
incoming = graph.incoming_edges("BC")
for e in incoming:
print(f" {e.from_id} -> BC: {e.label} ({e.type})")
BFS Traversal¶
BFS (Breadth-First Search) traversal shows the order in which personas are activated when starting from a given node:
Output:
This tells you: starting from RC, the simulation first activates RC, then BC (via research_inventory), then DE (via blueprints_specs), then RB and UG (via publish_handoff).
Try starting from a different node:
# BFS from DE (starting at the Critique phase)
order = graph.bfs_from("DE")
print(f"BFS from DE: {order}")
# ['DE', 'BC', 'RB', 'UG', 'RC']
Topological Order¶
Topological order processes nodes in dependency order, ignoring feedback edges. This shows the "natural" processing sequence:
Output:
The difference between BFS and topological order:
- BFS follows all edges (handoff and feedback), visiting each node once
- Topological order only considers handoff edges, producing a dependency-respecting sequence
In the base graph, both happen to produce the same order. In larger graphs with more complex edge patterns, they can differ.
Running a Simulation on This Graph¶
Connect the workflow graph to the simulation engine:
from fcc.simulation.engine import SimulationEngine
engine = SimulationEngine(graph=graph, max_steps=50)
history = engine.run(start_node="RC", initial_payload="Document the auth API")
print(f"Events: {len(history.events)}")
for event in history.events[:3]:
print(f" Step {event['step']}: {event['actor']} (via {event['edge_label']})")
Validated Loading¶
For production use, validate the workflow graph against its JSON schema:
graph = WorkflowGraph.from_json_validated(
json_path="data/workflows/base_sequence.json",
schema_path="data/schemas/workflow.schema.json",
)
print(f"Validated graph: {graph.meta.title}")
This raises a jsonschema.ValidationError if the graph does not conform to the schema.
Serialization¶
Save a workflow graph back to JSON:
# Serialize to dict
data = graph.to_dict()
# Write to file
graph.to_json("output/my_workflow.json", indent=2)
Next Steps¶
- Multi-Persona Pipelines -- Scale to the 20-node extended workflow
- Champion Orchestration -- Add champion personas to coordinate teams
- Feedback Loops -- Understand feedback edge behavior in detail