Critical fixes: - C1: Create standalone ADR-006 file (edge type consistency), extract from open-questions.md inline content - C2: Convert CallResult from plain interface to TypeBox schema, aligning with 'TypeBox as single source of truth' constraint - C3: Add fromJSON() cycle detection specification - enforce ADR-002 DAG invariant even on deserialized input - C4: Rewrite consumer-integration.md Phase 4 to use ADR-005 event-append pattern instead of direct signal mutation - C5: Fix operator precedence bug in consumer-integration.md (missing parentheses around OR condition) Warnings addressed: - W1: Fix immutability claim - operation graph is 'conventionally immutable', not prevented by API - W2: Add EventLogProjection to reactive exports map - W3: Add CallResult/CallResultSchema to schema exports map - W4: Fix reactive-execution.md Level 1 error handling to use event-append pattern instead of direct signal mutation - W5: Remove duplicate dataFlow inference description in schema.md - W6: Clarify ADR-006 project context (flowgraph vs taskgraph) Suggestions implemented: - S1: Add 'reviewed' document lifecycle status between draft/stable, update all docs to reviewed status - S2: Add carve-out note for analysis result types in schema.md constraints (they are ephemeral, not serialized) - S3: Add isComplete() and getAggregateStatus() convenience methods to WorkflowReactiveRoot specification
status, last_updated
| status | last_updated |
|---|---|
| reviewed | 2026-05-21 |
@alkdev/flowgraph Architecture
Workflow graph library — DAG-based operation orchestration over graphology, with ujsx template composition and reactive execution.
Why This Exists
Flowgraph fills the gap between the operation registry (@alkdev/operations) and the call graph observability layer (@alkdev/alkhub). Operations define what can be called. The call graph records what was called. Flowgraph defines how calls are orchestrated — the structure, validation, and execution of workflows.
Without flowgraph:
- Workflows are ad-hoc — the hub coordinator manually chains
registry.execute()calls with no structural validation, no type checking between steps, and no way to reuse workflow patterns. - Call templates are hardcoded — the SDD pipeline (architect → reviewer → decomposer → coordinator → specialist) is a recurring pattern with no reusable definition.
- Abort cascading is manual — when the 3rd of 5 operations fails, the coordinator must explicitly cancel the remaining operations. Flowgraph's DAG enables structural abort propagation.
- No precondition checking — there's no way to validate that operation A's output schema is compatible with operation B's input schema before attempting the call.
Flowgraph provides three conceptual graphs, each built for a different purpose:
-
Operation Graph (Static) — built from
OperationSpecs at startup; nodes are operations, edges are type-compatibility relationships. Enables cycle detection, topological ordering, and call template validation. -
Call Graph (Dynamic) — built at runtime from call events; nodes are call invocations with status and timestamps, edges are parent-child relationships. Enables abort cascading, observability, and DAG queries.
-
Workflow Template (Declarative) — a ujsx tree that defines a reusable workflow structure. A template is a validated path through the operation graph, instantiated as a call graph at runtime.
Core Principle
The graph is the specification. The template is the authoring surface. The call graph is the execution record.
The operation graph provides static type checking and structural validation. The ujsx template provides human-readable, composable workflow definitions. The call graph captures what actually happened. Flowgraph is the bridge between all three.
Relationship to Sibling Packages
| Package | Relationship |
|---|---|
@alkdev/operations |
Peer dependency. Provides OperationSpec, OperationRegistry, CallEvent, PendingRequestMap. Flowgraph consumes operation types but does not depend on a specific runtime. |
@alkdev/ujsx |
Direct dependency. Workflow templates are UNode trees rendered through HostConfigs. Flowgraph provides the workflow-specific host configurations (graphology DAG, reactive execution). |
@alkdev/taskgraph |
Pattern reference. Flowgraph follows the same graphology-wrapping pattern (FlowGraph class like TaskGraph class) but enforces DAG invariants instead of allowing cycles. |
@alkdev/typebox |
Direct dependency. All schemas are TypeBox Modules. Runtime validation, JSON Schema export, and Value.Check/Value.Errors. |
@alkdev/pubsub |
Consumer dependency, not flowgraph's dep. For event-driven call graph population. The hub coordinator uses pubsub to subscribe to call events and feed them to flowgraph — flowgraph itself works in-memory with no pubsub import. |
@alkdev/cograph |
Future consumer. The cognitive graph depends on flowgraph for workflow templates and execution tracking. |
Current State
Flowgraph is in Phase 0/1 (exploration → architecture). No code exists yet. This architecture document set defines the WHAT and WHY before any implementation.
Architecture Documents
| Document | Content |
|---|---|
| schema.md | TypeBox Module, TypeScript types, enums (CallStatus, EdgeType, NodeStatus), node/edge attribute schemas, SerializedGraph factory |
| operation-graph.md | Static graph from OperationSpecs, type-compatibility edges, construction paths, validation |
| call-graph.md | Dynamic graph from call events, node lifecycle, abort cascading, fromCallEvents construction |
| workflow-templates.md | ujsx components (<Operation>, <Sequential>, <Parallel>, <Conditional>, <Map>), composition rules, template→DAG hydration, serialization |
| host-configs.md | Graphology HostConfig (template→DAG analysis), Reactive HostConfig (template→execution engine), Instance types, removeChild |
| reactive-execution.md | Signal-driven status propagation, computed preconditions, abort cascade via signals, ReactiveRoot integration, lifecycle and ownership, error boundaries |
| analysis.md | Type-compatibility checking (input/output schema matching), compatibility depth, precondition validation, execution ordering, performance characteristics |
| error-handling.md | FlowgraphError hierarchy, CycleError, TypeIncompatError, ValidationError, error collection strategy |
| build-distribution.md | Package structure, exports map, dependencies, platform targets |
| flowgraph-api.md | FlowGraph class public API: constructor, type parameters, methods, delegation model, immutability guarantees |
| consumer-integration.md | End-to-end walkthrough from operation specs to running workflow, common patterns, module dependency map |
Design Decisions
| ADR | Decision |
|---|---|
| 001 | ujsx tree as workflow template intermediate representation |
| 002 | Enforce DAG invariants — no cycles in flowgraph |
| 003 | Storage is not flowgraph's concern — in-memory graph with export/import boundary |
| 004 | No schema version field in serialized format — consumers wrap in their own versioned envelope |
| 005 | Execution Event Log as single source of truth — call protocol events as ground truth, status/result/call-graph as projections |
| 006 | edgeType as universal required attribute on all edges; single shared graph per FlowGraph instance |
Open Questions
All unresolved design questions across the architecture are tracked in open-questions.md, organized by theme with cross-references between related questions.
Consumer Context
alkhub (hub-spoke coordinator)
The hub instantiates flowgraph to:
- Build the operation graph at startup from the registry
- Validate call templates before execution
- Populate call graphs at runtime from call protocol events
- Query call graphs for observability (what's running, what failed, what's blocked)
- Persist call graph state via
export()→ Postgres
OpenCode Plugin (future)
An OpenCode plugin that provides workflow tools:
workflow.validate— validate a template against the operation graphworkflow.run— instantiate a template as a call graph and execute itworkflow.status— query a running call graph
Cograph (future)
The cognitive graph uses flowgraph's templates and operation graph to define procedural knowledge: which operations can be composed, what the valid execution paths are, and what preconditions each step requires.
Source Structure
src/
component/ # ujsx components for workflow definition
operation.ts # <Operation name="classify" />
sequential.ts # <Sequential>...</Sequential>
parallel.ts # <Parallel>...</Parallel>
conditional.ts # <Conditional test={fn}>...</Conditional>
map.ts # <Map over={array} as="item">...</Map>
index.ts
host/
graphology.ts # HostConfig: ujsx tree → graphology DAG
reactive.ts # HostConfig: ujsx tree → reactive execution engine
schema/
enums.ts # CallStatus, NodeStatus, EdgeType, OperationType
node.ts # OperationNodeAttributes, CallNodeAttributes
edge.ts # OperationEdgeAttrs, CallEdgeAttrs, TemplateEdgeAttrs
graph.ts # FlowGraphSerialized (graphology export format)
index.ts
graph/
construction.ts # FlowGraph class (like TaskGraph)
validation.ts # Cycle detection, type-compat validation, precondition checks
queries.ts # topologicalOrder, ancestors, descendants, hasCycles
mutation.ts # updateNode, updateEdge, removeNode, removeEdge
index.ts
reactive/
workflow.ts # ReactiveRoot for workflow state
node-status.ts # Per-node status signals + computed preconditions + blockedByFailure
index.ts
analysis/
type-compat.ts # Schema compatibility checking between operation input/output
workflow.ts # Execution ordering, precondition resolution, path validation
defaults.ts # Default status, edge type, etc.
index.ts
error/
index.ts # FlowgraphError, CycleError, TypeIncompatError, ValidationError
index.ts # Barrel export
Key Design Decisions
1. ujsx as Template IR
Workflow templates are UNode trees. This gives us:
- Composability —
<Sequential>and<Parallel>compose naturally as parent-child structure - Serialization —
UNodetrees are JSON, trivially stored and transmitted - Host targets — the same template renders to a graphology DAG (analysis) or a reactive execution engine (runtime) via different
HostConfigimplementations - Reconciler support — incremental template updates via ujsx's reconciler (add/remove/reorder steps without full rebuild)
This is a design decision worth documenting because it's a non-obvious choice. The alternative is to define templates as plain data structures (arrays of step objects), which is simpler but loses composability, host switching, and reconciler benefits.
2. DAG-Only, No Cycles
Flowgraph enforces acyclicity. The FlowGraph class rejects cycle-creating edges at mutation time (unlike TaskGraph, which allows cycles and detects them via hasCycles()). This is because:
- Operation graphs represent type flow — a cycle means an operation's output feeds back into its own input, which is almost certainly a design error.
- Call graphs represent execution order — cycles are physically impossible (you can't have a call that is its own ancestor).
- Workflow templates represent validated paths through the operation graph — they must be DAGs by construction.
3. Storage is Decoupled
Flowgraph handles in-memory graph construction, validation, and analysis. Persistence is the caller's concern. The export()/fromJSON() boundary provides a clean serialization format (graphology native JSON) that the hub can store in Postgres. This follows the same pattern as taskgraph.
4. Template → DAG → Execution is a Pipeline, Not a Monolith
The three representations serve different phases:
- Template (ujsx tree) → authoring, composition, serialization
- DAG (graphology) → validation, type checking, topological ordering
- Execution (reactive signals) → runtime status tracking, preconditions, abort propagation
Each can exist independently. You can validate a template without executing it. You can build a call graph from events without a template. You can run a reactive workflow directly from a DAG.
Document Lifecycle
Architecture documents use YAML frontmatter with status and last_updated fields:
---
status: draft | reviewed | stable | deprecated
last_updated: YYYY-MM-DD
---
| Status | Meaning | Transitions |
|---|---|---|
draft |
Under active development. Content may change significantly. Implementation should not start until the document reaches reviewed. |
→ reviewed when all open questions are resolved and cross-cutting issues are addressed. |
reviewed |
Architecture is final and reviewed. Implementation may begin. API contracts are specified but not yet verified by tests. Changes require a review cycle. | → stable when implementation is complete and API contracts are verified by tests. → draft if a fundamental redesign is needed (rare). |
stable |
API contracts are locked and verified by tests. Changes require a review cycle and may warrant an ADR if they affect documented decisions. | → deprecated when superseded. → draft if a fundamental redesign is needed (rare). |
deprecated |
Superseded by another document. Kept for reference. Links should point to the replacement. | Removed when no longer referenced. |
ADR documents use a separate Status field in their body: Proposed, Accepted, Deprecated, or Superseded. ADRs never revert from Accepted.
References
- Call protocol architecture:
@alkdev/alkhub_ts/docs/architecture/call-graph.md - Call graph storage schema:
@alkdev/alkhub_ts/docs/architecture/storage/call-graph.md - Operation types and registry:
@alkdev/operations/src/types.ts,@alkdev/operations/src/registry.ts - ujsx architecture:
@alkdev/ujsx/docs/architecture/ - ujsx research on flowgraph HostConfigs:
@alkdev/ujsx/docs/research/reconciler/05-flowgraph-host-configs.md - Taskgraph architecture:
@alkdev/taskgraph_ts/docs/architecture/ - SDD process:
docs/sdd_process.md