chore: add LICENSE, README, update package.json and AGENTS.md for npm release prep
This commit is contained in:
114
AGENTS.md
114
AGENTS.md
@@ -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 001–006
|
||||||
|
|
||||||
|
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
6
LICENSE
Normal 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
190
LICENSE-APACHE
Normal 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
21
LICENSE-MIT
Normal 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
320
README.md
Normal 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.
|
||||||
@@ -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",
|
||||||
|
|||||||
Reference in New Issue
Block a user