chore: add LICENSE, README, update package.json and AGENTS.md for npm release prep

This commit is contained in:
2026-05-22 06:17:54 +00:00
parent e45b8c0cc0
commit 6fb633c05b
6 changed files with 616 additions and 44 deletions

114
AGENTS.md
View File

@@ -1,60 +1,86 @@
## Memory Tools (via @alkdev/open-memory plugin) # @alkdev/flowgraph — Agent Guide
You have access to two tools for managing your context and accessing session history: Project-specific context for AI agents working on this codebase.
### memory({tool: "...", args: {...}}) ## What This Is
Read-only tool for introspecting your session history and context state. Available operations: DAG-based workflow orchestration library. Wraps graphology `DirectedGraph`, enforces DAG invariants, provides ujsx template composition and reactive signal-driven execution. Sits between `@alkdev/operations` (what can be called) and `@alkdev/alkhub` (what was called) — defines *how calls are orchestrated*.
- `memory({tool: "help"})` — full reference with examples
- `memory({tool: "summary"})` — quick counts of projects, sessions, messages, todos
- `memory({tool: "sessions"})` — list recent sessions (useful for finding past work)
- `memory({tool: "children", args: {sessionId: "ses_..."}})` — list sub-agent sessions spawned from a parent
- `memory({tool: "messages", args: {sessionId: "..."}})` — read a session's conversation
- `memory({tool: "messages", args: {sessionId: "...", role: "assistant"}})` — read only assistant messages
- `memory({tool: "messages", args: {sessionId: "...", showTools: true}})` — include tool-call output
- `memory({tool: "message", args: {messageId: "msg_..."}})` — read a single message by ID
- `memory({tool: "search", args: {query: "..."}})` — search across all conversations
- `memory({tool: "compactions", args: {sessionId: "..."}})` — view compaction checkpoints
- `memory({tool: "context"})` — check your current context window usage
### memory_compact() ## Build & Test Commands
Trigger compaction on the current session. This summarizes the conversation so far to free context space. ```bash
npm run build # tsup (ESM + CJS + dts + sourcemaps)
npm run build:tsc # tsc type-check only
npm run lint # tsc --noEmit
npm run test # vitest run
npm run test:watch # vitest watch
npm run test:coverage # vitest run --coverage
```
**When to use memory_compact:** Always run `npm run lint && npm run test` after making changes.
- When context is above 80% (check with `memory({tool: "context"})`)
- When you notice you're losing track of earlier conversation details
- At natural breakpoints in multi-step tasks (after completing a subtask, before starting a new one)
- When the system prompt shows a yellow/red/critical context warning
- Proactively, rather than waiting for automatic compaction at 92%
**When NOT to use memory_compact:** ## Source Structure
- When context is below 50% (it wastes a compaction cycle)
- In the middle of a complex edit that you need immediate context for
- When the task is nearly complete (just finish the task instead)
Compaction preserves your most important context in a structured summary — you will continue the session with the summary as your starting point. ```
src/
index.ts # Barrel — re-exports all sub-modules
component/ # ujsx workflow components (Operation, Sequential, Parallel, Conditional, Map)
host/ # HostConfig implementations (GraphologyHostConfig, ReactiveHostConfig)
schema/ # TypeBox schemas, enums, node/edge attribute types
graph/ # FlowGraph class (construction, mutation, query, serialization)
reactive/ # WorkflowReactiveRoot, signal-driven status, event log projection
analysis/ # Pure functions: typeCompat, validate, topologicalOrder, parallelGroups, criticalPath
error/ # FlowgraphError hierarchy
```
## Worktree Tool (via @alkimiadev/open-coordinator plugin) ## Subpath Exports
You have access to the `worktree` tool for git worktree management and session coordination. Call with `{action, args}`: The package has 8 export subpaths. Root `@alkdev/flowgraph` re-exports everything. Subpath imports make dependencies explicit:
### Coordinator Operations (available when session is not spawned by another session) | Subpath | Key Exports |
|---------|-------------|
| `/graph` | `FlowGraph`, `FlowGraphOptions`, `OperationSpec`, `CallEventMapValue` |
| `/schema` | `CallStatus`, `NodeStatus`, `EdgeType`, `OperationType`, all node/edge attribute types |
| `/component` | `Operation`, `Sequential`, `Parallel`, `Conditional`, `Map` |
| `/host` | `GraphologyHostConfig`, `ReactiveHostConfig` |
| `/analysis` | `typeCompat`, `buildTypeEdges`, `validateGraph`, `validateSchema`, `validate`, `validateTemplate`, `topologicalOrder`, `parallelGroups`, `criticalPath` |
| `/reactive` | `WorkflowReactiveRoot`, `EventLogProjection`, `WorkflowNode`, `FailurePolicy` |
| `/error` | `FlowgraphError`, `ConstructionError`, `CycleError`, `InvalidInputError`, `InvalidTransitionError` |
- `worktree({action: "list"})` — List git worktrees ## Key Patterns
- `worktree({action: "dashboard"})` — Worktree dashboard with session info
- `worktree({action: "spawn", args: {tasks: [...], prompt: "..."}})` — Spawn parallel worktrees + sessions
- `worktree({action: "sessions"})` — Query spawned session status
- `worktree({action: "message", args: {sessionID: "...", message: "..."}})` — Message a session
- `worktree({action: "abort", args: {sessionID: "..."}})` — Abort a session
- `worktree({action: "cleanup", args: {action: "remove", pathOrBranch: "..."}})` — Remove worktree
- `worktree({action: "help"})` — Show all available operations
### Implementation Operations (available when session is spawned by a coordinator) - **TypeBox schema + Static type pairs**: Every schema is exported as both a const (runtime) and an inferred type. `const FooSchema = Type.Object({...}); type Foo = Static<typeof FooSchema>;`
- **Delegation model**: `FlowGraph` wraps a graphology `DirectedGraph` (does not extend it). `flowGraph.graph` is an escape hatch that bypasses validation.
- **DAG enforcement**: `addEdge()` throws `CycleError` if the edge would create a cycle. `fromJSON()` validates DAG invariants on deserialization.
- **Event log as source of truth**: Call protocol events (`call.requested`, `call.responded`, `call.error`, `call.aborted`, `call.completed`) are appended to `WorkflowReactiveRoot`. Status/results are derived projections.
- **Signal-driven execution**: `@preact/signals-core` powers `WorkflowReactiveRoot`. `preconditions`, `canStart`, `blockedByFailure` are `ReadonlySignal<boolean>` computed from predecessor status.
- **`dispose()` is mandatory**: `WorkflowReactiveRoot.dispose()` must be called to release signal subscriptions.
- `worktree({action: "current"})` — Show your worktree mapping ## Architecture Docs
- `worktree({action: "notify", args: {message: "...", level: "info|blocking"}})` — Report to coordinator
- `worktree({action: "status"})` — Show worktree git status
The plugin auto-injects `workdir` for bash commands when the session is mapped to a worktree. `docs/architecture/` contains detailed specs (all `status: reviewed`):
- `README.md` — overview, relationship to sibling packages, design decisions
- `flowgraph-api.md` — FlowGraph class full API
- `consumer-integration.md` — end-to-end integration walkthrough (5 phases)
- `schema.md` — TypeBox Module, all node/edge attribute schemas
- `operation-graph.md` — static graph from OperationSpecs
- `call-graph.md` — dynamic graph from call events
- `workflow-templates.md` — ujsx components, composition rules, template→DAG hydration
- `host-configs.md` — GraphologyHostConfig, ReactiveHostConfig
- `reactive-execution.md` — signal-driven status propagation, lifecycle, error boundaries
- `analysis.md` — type-compatibility checking, precondition validation, execution ordering
- `error-handling.md` — FlowgraphError hierarchy
- `build-distribution.md` — package structure, exports map
- `decisions/` — ADRs 001006
Consult these for anything non-trivial. The README is surface-level; architecture docs are the specification.
## Constraints for Agent Modifications
- **Never add cycles** — this is a DAG-only library. Any edge that would create a cycle must throw `CycleError`.
- **Never mutate operation graphs after construction** — `fromSpecs()` graphs are conventionally immutable.
- **Keep schema as pure data** — `src/schema/` contains TypeBox schemas and types only, no runtime logic.
- **Keep analysis as pure functions** — `src/analysis/` functions take a `FlowGraph` or `DirectedGraph` as input and return results without side effects.
- **Maintain the delegation model** — `FlowGraph` delegates to graphology. Don't expose raw graphology methods that could violate DAG invariants without explicit validation.
- **tsup builds all subpath entries**`tsup.config.ts` lists every subpath. If you add a new top-level module, add the entry there and update `package.json` exports.

6
LICENSE Normal file
View File

@@ -0,0 +1,6 @@
This project is dual-licensed under either of the following:
- MIT License (LICENSE-MIT)
- Apache License 2.0 (LICENSE-APACHE)
You may choose either license at your option.

190
LICENSE-APACHE Normal file
View File

@@ -0,0 +1,190 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to the Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by the Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable by
such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding any notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed as
modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limitation damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
Copyright 2026 alkdev
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

21
LICENSE-MIT Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 alkdev
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

320
README.md Normal file
View File

@@ -0,0 +1,320 @@
# @alkdev/flowgraph
DAG-based workflow orchestration over graphology, with ujsx template composition and reactive signal-driven execution.
## What This Does
Flowgraph sits between `@alkdev/operations` (which defines *what can be called*) and `@alkdev/alkhub` (which records *what was called*). Flowgraph defines **how calls are orchestrated** — the structure, validation, and execution of workflows.
Three conceptual graphs, each for a different purpose:
1. **Operation Graph** — static graph built from `OperationSpec`s at startup. Nodes are operations, edges are type-compatibility relationships. Enables cycle detection, topological ordering, and validation.
2. **Call Graph** — dynamic graph built from call protocol events at runtime. Nodes are call invocations with status/timestamps, edges are parent-child relationships. Enables abort cascading and observability.
3. **Workflow Template** — declarative ujsx tree defining a reusable workflow structure. A validated path through the operation graph, instantiated as a call graph at runtime.
**The graph is the specification. The template is the authoring surface. The call graph is the execution record.**
## Installation
```bash
npm install @alkdev/flowgraph
```
Peer dependency: `@alkdev/operations ^0.1.0`
## Quick Start
### Build an Operation Graph
```typescript
import { FlowGraph } from "@alkdev/flowgraph/graph";
import type { OperationSpec } from "@alkdev/flowgraph/graph";
const specs: OperationSpec[] = [
{ namespace: "task", name: "classify", type: "query", version: "1.0.0", inputSchema: {...}, outputSchema: {...} },
{ namespace: "task", name: "enrich", type: "query", version: "1.0.0", inputSchema: {...}, outputSchema: {...} },
{ namespace: "task", name: "summarize", type: "mutation", version: "1.0.0", inputSchema: {...}, outputSchema: {...} },
];
const graph = FlowGraph.fromSpecs(specs);
// Type-compatibility edges added automatically
graph.hasEdge("task.classify", "task.enrich");
```
### Define a Workflow Template
```typescript
import { h } from "@alkdev/ujsx";
import { Operation, Sequential, Parallel, Conditional } from "@alkdev/flowgraph/component";
const template = h(Sequential, {},
h(Operation, { name: "task.classify" }),
h(Conditional, {
test: (results) => results["task.classify"].output.confidence > 0.8,
},
h(Parallel, {},
h(Operation, { name: "task.enrich" }),
h(Operation, { name: "task.summarize" }),
),
h(Operation, { name: "task.classify" }),
),
);
```
### Validate the Template
```typescript
import { validateTemplate } from "@alkdev/flowgraph/analysis";
const errors = validateTemplate(template, graph);
if (errors.length > 0) {
for (const error of errors) {
console.error(`[${error.type}]`, error);
}
}
```
### Populate a Call Graph from Events
```typescript
import { FlowGraph } from "@alkdev/flowgraph/graph";
const callGraph = FlowGraph.fromCallEvents(eventArray);
callGraph.filterByStatus("running");
callGraph.children("req_abc123");
callGraph.lineage("req_xyz789");
callGraph.duration("req_abc123");
```
### Drive Reactive Execution
```typescript
import { WorkflowReactiveRoot } from "@alkdev/flowgraph/reactive";
const workflow = new WorkflowReactiveRoot(dag, {
failurePolicy: "abort-dependents",
});
// Append call protocol events — status derives reactively
workflow.append({ type: "call.requested", requestId: "req_1", operationId: "task.classify", input: {}, timestamp: "..." });
workflow.append({ type: "call.responded", requestId: "req_1", output: { confidence: 0.95 }, timestamp: "..." });
// Read reactive state
workflow.getStatus("task.enrich");
workflow.getResult("task.classify");
// Abort cascading
workflow.abortAll();
workflow.dispose();
```
## Subpath Exports
| Subpath | Purpose | Key Exports |
|---------|---------|-------------|
| `@alkdev/flowgraph` | Root — re-exports everything | All public types and functions |
| `@alkdev/flowgraph/graph` | Core DAG class | `FlowGraph`, `FlowGraphOptions`, `OperationSpec`, `CallEventMapValue` |
| `@alkdev/flowgraph/schema` | TypeBox schemas and types | `CallStatus`, `NodeStatus`, `EdgeType`, `OperationType`, `CallNodeAttrs`, `OperationNodeAttrs`, `OperationEdgeAttrs`, `CallEdgeAttrs`, `TemplateEdgeAttrs`, `CallResult`, `FlowGraphSerialized` |
| `@alkdev/flowgraph/component` | ujsx workflow components | `Operation`, `Sequential`, `Parallel`, `Conditional`, `Map` |
| `@alkdev/flowgraph/host` | Rendering backends | `GraphologyHostConfig`, `ReactiveHostConfig` |
| `@alkdev/flowgraph/analysis` | Validation and analysis functions | `typeCompat`, `buildTypeEdges`, `validateGraph`, `validateSchema`, `validate`, `validateTemplate`, `validatePreconditions`, `topologicalOrder`, `parallelGroups`, `criticalPath`, `reachableFrom` |
| `@alkdev/flowgraph/reactive` | Reactive execution engine | `WorkflowReactiveRoot`, `EventLogProjection`, `WorkflowNode`, `ReactiveContext`, `FailurePolicy`, `AggregateStatus` |
| `@alkdev/flowgraph/error` | Error hierarchy | `FlowgraphError`, `ConstructionError`, `DuplicateNodeError`, `DuplicateEdgeError`, `NodeNotFoundError`, `CycleError`, `InvalidInputError`, `InvalidTransitionError` |
## Core API: FlowGraph Class
`FlowGraph<NodeAttrs, EdgeAttrs>` wraps a graphology `DirectedGraph` and enforces DAG invariants. It delegates graph operations to graphology while adding flowgraph-specific construction, mutation, and query methods.
### Factory Methods
```typescript
FlowGraph.fromSpecs(specs: OperationSpec[]): OperationGraph
FlowGraph.fromCallEvents(events: CallEventMapValue[]): CallGraph
FlowGraph.fromJSON(data: FlowGraphSerialized): FlowGraph
```
### Node Operations
```typescript
graph.addNode(key, attrs) // throws DuplicateNodeError
graph.removeNode(key) // throws NodeNotFoundError
graph.updateNode(key, partialAttrs) // throws NodeNotFoundError
graph.hasNode(key): boolean
graph.getNodeAttributes(key): NodeAttrs
graph.forEachNode(callback): void
```
### Edge Operations
```typescript
graph.addEdge(source, target, attrs?) // throws NodeNotFoundError, DuplicateEdgeError, CycleError
graph.removeEdge(source, target) // no-op if not found
graph.hasEdge(source, target): boolean
graph.getEdgeAttributes(source, target): EdgeAttrs
graph.forEachEdge(callback): void
```
### Traversal
```typescript
graph.topologicalOrder(): string[]
graph.ancestors(nodeId): string[]
graph.descendants(nodeId): string[]
graph.predecessors(nodeId): string[]
graph.successors(nodeId): string[]
graph.reachableFrom(nodeIds): Set<string>
graph.hasCycles(): boolean
graph.findCycles(): string[][]
```
### Call Graph Convenience
```typescript
graph.addCall(attrs: CallNodeAttrs): void
graph.addDependency(source, target): void
graph.updateStatus(requestId, status, extra?): void // throws InvalidTransitionError
graph.updateCall(requestId, partialAttrs): void
graph.removeCall(requestId): void
graph.updateFromEvent(event: CallEventMapValue): void
graph.filterByStatus(status: CallStatus): string[]
graph.getRoots(): string[]
graph.children(requestId): string[]
graph.duration(requestId): number
graph.lineage(requestId): string[]
```
### Serialization
```typescript
graph.export(): FlowGraphSerialized
graph.toJSON(): FlowGraphSerialized
graph.toString(): string
```
### Escape Hatch
```typescript
graph.graph // → DirectedGraph (raw graphology instance)
```
Direct mutation via `graph.graph` bypasses flowgraph validation. Use with caution.
## Schema Enums
| Enum | Values |
|------|--------|
| `CallStatus` | `pending`, `running`, `completed`, `failed`, `aborted` |
| `NodeStatus` | `idle`, `waiting`, `ready`, `running`, `completed`, `failed`, `skipped`, `aborted` |
| `EdgeType` | `triggered`, `depends_on`, `typed`, `sequential`, `conditional` |
| `OperationType` | `query`, `mutation`, `subscription` |
Call status transitions: `pending → running → completed|failed|aborted`. Terminal states are immutable. `InvalidTransitionError` is thrown on invalid transitions.
## Workflow Components
| Component | Props | Behavior |
|-----------|-------|----------|
| `<Operation>` | `name`, `input?`, `retries?`, `timeout?` | Declares an operation node in the workflow |
| `<Sequential>` | `id?` | Children execute in order; edges are `sequential` |
| `<Parallel>` | `id?`, `maxConcurrency?` | Children execute concurrently |
| `<Conditional>` | `test`, `else?` | Branches on `test(results)`. Children = then-branch, `else` prop = else-branch |
| `<Map>` | `over`, `as` | Iterates over `over` collection, binding each item as `as` variable |
## Analysis Functions
```typescript
import { typeCompat, validateTemplate, topologicalOrder, parallelGroups, criticalPath } from "@alkdev/flowgraph/analysis";
typeCompat(outputSchema, inputSchema): TypeCompatResult | undefined
validateTemplate(template, operationGraph): AnyValidationError[]
topologicalOrder(graph): string[]
parallelGroups(graph): string[][] // topological generations
criticalPath(graph): string[] // longest path
validateGraph(graph): GraphValidationError[]
validateSchema(graph, schema): ValidationError[]
validate(graph, schema): AnyValidationError[] // combined
```
## Reactive Execution
`WorkflowReactiveRoot` implements `EventLogProjection` — call protocol events are the source of truth, status/results are derived projections.
```typescript
const workflow = new WorkflowReactiveRoot(dag, {
failurePolicy: "abort-dependents", // or "continue-running"
parallelGroups: { group1: { siblings: ["a", "b"], maxConcurrency: 2 } },
});
// Per-node reactive signals
workflow.statusMap // Map<string, Signal<NodeStatus>>
workflow.preconditions // Map<string, ReadonlySignal<boolean>> — all predecessors completed/skipped
workflow.canStart // Map<string, ReadonlySignal<boolean>> — preconditions + concurrency
workflow.blockedByFailure // Map<string, ReadonlySignal<boolean>> — any predecessor failed/aborted
workflow.resultMap // Map<string, ReadonlySignal<CallResult | undefined>>
// Event-driven updates
workflow.append(event: CallEventMapValue): void
// Queries
workflow.getStatus(nodeId): NodeStatus
workflow.getResult(nodeId): CallResult | undefined
workflow.isComplete(): boolean
workflow.getAggregateStatus(): AggregateStatus
// Lifecycle
workflow.abortAll(): void
workflow.abortNode(nodeId): void
workflow.dispose(): void // mandatory cleanup — releases signal subscriptions
```
## Error Hierarchy
```
FlowgraphError (base)
├── ConstructionError
│ ├── DuplicateNodeError (readonly key)
│ ├── DuplicateEdgeError (readonly source, target)
│ ├── NodeNotFoundError (readonly key)
│ ├── CycleError (readonly cycles: string[][])
│ └── InvalidInputError (readonly errors: ValidationError[])
└── InvalidTransitionError (readonly requestId, from, to)
```
## Design Principles
1. **DAG-only, no cycles**`addEdge()` rejects cycle-creating edges at mutation time (ADR-002). This differs from taskgraph, which allows cycles and detects them after the fact.
2. **Storage is decoupled** — flowgraph handles in-memory graph construction, validation, and analysis. Persistence is the caller's concern. `export()`/`fromJSON()` provides the serialization boundary.
3. **Template → DAG → Execution is a pipeline** — each representation serves a different phase and can exist independently. Validate a template without executing it. Build a call graph from events without a template. Run reactive execution directly from a DAG.
4. **Event log as source of truth** — call protocol events (`call.requested`, `call.responded`, `call.error`, `call.aborted`, `call.completed`) are the ground truth. Status, results, and the call graph are projections derived from the event log (ADR-005).
5. **Delegation, not inheritance**`FlowGraph` wraps a graphology `DirectedGraph`, exposing a curated API. The raw graphology instance is available via the `.graph` escape hatch.
## For AI Agents
When working with this library programmatically:
- **Use subpath imports** — `@alkdev/flowgraph/graph`, `@alkdev/flowgraph/analysis`, etc. The root export re-exports everything but subpath imports make dependencies explicit.
- **Always call `dispose()` on `WorkflowReactiveRoot`** — signal subscriptions leak without it.
- **Function-valued props don't survive JSON serialization** — `Conditional.test` and `Map.over` with function values need runtime resolution. Use string references for stored templates.
- **`fromSpecs()` graphs are conventionally immutable** — don't mutate operation graphs after construction. If the registry changes, rebuild via `fromSpecs()`.
- **Call graph mutation uses event protocol** — use `updateFromEvent()` or `addCall()`/`updateStatus()`, not direct node mutation.
- **`typeCompat()` returns `undefined` for `unknown`/`any` schemas** — this means "no meaningful check possible", not "incompatible".
- **Architecture specs are in `docs/architecture/`** — detailed design decisions, ADRs, and open questions live there. This README is a surface-level guide. Consult the architecture docs for anything non-trivial.
## Dependencies
| Package | Relationship |
|---------|-------------|
| `graphology` | Direct — the underlying directed graph data structure |
| `graphology-dag` | Direct — topological sort, cycle detection, DAG traversal |
| `@alkdev/ujsx` | Direct — `UNode` trees and `HostConfig` for workflow template rendering |
| `@alkdev/typebox` | Direct — all schemas are TypeBox Modules |
| `@preact/signals-core` | Direct — reactive state management for `WorkflowReactiveRoot` |
| `@alkdev/operations` | **Peer** — provides `OperationSpec`, `OperationRegistry`, call event types |
## License
Dual-licensed under [MIT](LICENSE-MIT) or [Apache-2.0](LICENSE-APACHE) at your option.

View File

@@ -112,6 +112,15 @@
"operations" "operations"
], ],
"license": "MIT OR Apache-2.0", "license": "MIT OR Apache-2.0",
"repository": {
"type": "git",
"url": "git+https://git.alk.dev/alkdev/flowgraph.git"
},
"homepage": "https://git.alk.dev/alkdev/flowgraph",
"bugs": {
"url": "https://git.alk.dev/alkdev/flowgraph/issues"
},
"sideEffects": false,
"dependencies": { "dependencies": {
"@alkdev/typebox": "^0.34.49", "@alkdev/typebox": "^0.34.49",
"@alkdev/ujsx": "^0.1.0", "@alkdev/ujsx": "^0.1.0",