diff --git a/CLAUDE.md b/CLAUDE.md index 3ab7f87..70f48ad 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2,7 +2,7 @@ ## Project Overview -**@uncaged/workflow** is a workflow engine that executes single-file ESM bundles. Each workflow is a self-contained `.esm.js` file with an XXH64 hash as its version identifier. +This monorepo implements a workflow engine that executes single-file ESM bundles. Each workflow is a self-contained `.esm.js` file with an XXH64 hash as its version identifier. Shared types live in `@uncaged/workflow-protocol`; bundle authors typically depend on `@uncaged/workflow-runtime`. ### Key Terms @@ -19,14 +19,27 @@ ``` workflow/ packages/ - workflow/ # @uncaged/workflow — core lib (types, hash, ULID, JSONL, registry) - cli-workflow/ # @uncaged/cli-workflow — CLI (uncaged-workflow command) + workflow-protocol/ # @uncaged/workflow-protocol — shared types + Result + workflow-runtime/ # @uncaged/workflow-runtime — createWorkflow, type re-exports + workflow-util/ # @uncaged/workflow-util — Base32, ULID, logger, storage paths, refs helpers + workflow-reactor/ # @uncaged/workflow-reactor — LLM fn + thread reactor (tool calls) + workflow-cas/ # @uncaged/workflow-cas — CAS store, hash, Merkle + workflow-register/ # @uncaged/workflow-register — bundle validation, registry YAML, model resolution + workflow-execute/ # @uncaged/workflow-execute — engine, extract, fork, GC, workflowAsAgent + cli-workflow/ # @uncaged/cli-workflow — uncaged-workflow CLI + workflow-agent-cursor/ # @uncaged/workflow-agent-cursor + workflow-agent-hermes/ # @uncaged/workflow-agent-hermes + workflow-agent-llm/ # @uncaged/workflow-agent-llm + workflow-util-agent/ # @uncaged/workflow-util-agent — buildAgentPrompt, spawnCli + workflow-template-develop/ # @uncaged/workflow-template-develop + workflow-template-solve-issue/ # @uncaged/workflow-template-solve-issue + workflow-dashboard/ # @uncaged/workflow-dashboard — React dashboard (private app) docs/ # RFCs, conventions biome.json # root Biome config tsconfig.json # root TypeScript config ``` -- `workflow` is the core; `cli-workflow` depends on it +- Execution stack layers: `workflow-protocol` → (`workflow-runtime`, `workflow-util`, `workflow-reactor`) → (`workflow-cas`, `workflow-register`) → `workflow-execute` → `cli-workflow` - Packages use `workspace:*` protocol ## Language & Paradigm @@ -167,10 +180,10 @@ type Result = { ok: true; value: T } | { ok: false; error: E }; Never use `console.log/warn/error` directly — Biome's `noConsole` rule enforces this. -All logging goes through the structured logger from `@uncaged/workflow`: +All logging goes through the structured logger from `@uncaged/workflow-util`: ```typescript -import { createLogger } from "@uncaged/workflow"; +import { createLogger } from "@uncaged/workflow-util"; const log = createLogger(); diff --git a/docs/architecture.md b/docs/architecture.md index 1a3ac48..0f06ef1 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -1,6 +1,6 @@ -# @uncaged/workflow — Architecture +# Uncaged workflow — Architecture -**Last updated:** 2026-05-06 by 小橘 🍊(NEKO Team) +**Last updated:** 2026-05-09 --- @@ -8,72 +8,106 @@ A workflow engine that executes single-file ESM bundles. Each workflow is a self-contained `.esm.js` file identified by its XXH64 hash (Crockford Base32). No daemon — processes start on demand and exit when done. -## Package Structure +The implementation lives in **15** Bun workspace packages under `packages/`, using the `workspace:*` protocol. -| Package | npm Name | Purpose | -|---------|----------|---------| -| `workflow` | `@uncaged/workflow` | Core: types, engine, ExtractFn, hash/ULID/registry | -| `cli-workflow` | `@uncaged/cli-workflow` | CLI: `uncaged-workflow` command | -| `workflow-agent-cursor` | `@uncaged/workflow-agent-cursor` | Cursor CLI agent (extracts workspace from ctx) | -| `workflow-agent-hermes` | `@uncaged/workflow-agent-hermes` | Hermes CLI agent | -| `workflow-agent-llm` | `@uncaged/workflow-agent-llm` | OpenAI-compatible LLM agent | -| `workflow-template-develop` | `@uncaged/workflow-template-develop` | Develop workflow template (roles in `src/roles/`) | -| `workflow-template-solve-issue` | `@uncaged/workflow-template-solve-issue` | Solve-issue workflow template (roles in `src/roles/`) | -| `workflow-util-agent` | `@uncaged/workflow-util-agent` | `buildAgentPrompt` + `spawnCli` utilities | +## Package map -Monorepo with **bun workspace**, `workspace:*` protocol. +Grouped by responsibility (npm name → folder). -## Core Types +| Layer | Package | One-line role | +|-------|---------|----------------| +| Contract | `@uncaged/workflow-protocol` → `workflow-protocol` | Shared TypeScript types and `Result` helpers; peer `zod` only — no other workspace deps. | +| Author API | `@uncaged/workflow-runtime` → `workflow-runtime` | `createWorkflow` and re-exports of protocol workflow types for bundle authors. | +| Shared infra | `@uncaged/workflow-util` → `workflow-util` | Base32/ULID, logger, storage root paths, global CAS dir, ref-field helpers. | +| LLM plumbing | `@uncaged/workflow-reactor` → `workflow-reactor` | `createLlmFn`, `createThreadReactor`, and related tool-call types for threaded LLM invocation. | +| CAS | `@uncaged/workflow-cas` → `workflow-cas` | `CasStore` implementation, XXH64 hashing, Merkle helpers over CAS payloads. | +| Registry / bundles | `@uncaged/workflow-register` → `workflow-register` | Bundle validation & dynamic export extraction, `workflow.yaml` registry I/O, provider/model resolution. | +| Engine | `@uncaged/workflow-execute` → `workflow-execute` | Thread execution, worker entry path, fork/GC, extract pipeline, `workflowAsAgent`. | +| CLI | `@uncaged/cli-workflow` → `cli-workflow` | `uncaged-workflow` binary (depends on engine, registry, CAS, protocol, util, runtime). | +| Agent adapters | `@uncaged/workflow-agent-cursor` → `workflow-agent-cursor` | `AgentFn` via `cursor-agent` CLI + workspace extraction. | +| | `@uncaged/workflow-agent-hermes` → `workflow-agent-hermes` | `AgentFn` via `hermes chat` CLI. | +| | `@uncaged/workflow-agent-llm` → `workflow-agent-llm` | `AgentFn` via OpenAI-compatible HTTP (`LlmProvider` from runtime). | +| Agent shared | `@uncaged/workflow-util-agent` → `workflow-util-agent` | `buildAgentPrompt`, `spawnCli` for CLI-backed agents. | +| Templates | `@uncaged/workflow-template-develop` → `workflow-template-develop` | Develop workflow definition, roles, descriptor builder. | +| | `@uncaged/workflow-template-solve-issue` → `workflow-template-solve-issue` | Solve-issue workflow definition, roles, descriptor builder. | +| Dashboard | `@uncaged/workflow-dashboard` → `workflow-dashboard` | Private Vite + React app (`src/main.tsx`); only `react` / `react-dom` dependencies — no workspace packages. | -```typescript -// --- Sentinel values --- -const START = "__start__"; -const END = "__end__"; +## Dependency graph (workspace packages) -// --- RoleMeta: maps role names → their meta types --- -type RoleMeta = Record>; +Bottom-up layering for the execution stack: -// --- Role Definition: pure data, no execution logic --- -type RoleDefinition = { - description: string; // human-readable - systemPrompt: string; // given to agent - extractPrompt: string; // given to extractor - schema: z.ZodType; // meta shape (Zod v4) -}; - -// --- Workflow Definition: pure data, no agent binding --- -type WorkflowDefinition = { - description: string; - roles: { [K in keyof M & string]: RoleDefinition }; - moderator: Moderator; -}; - -// --- Agent: raw string output, reads role info from context --- -type AgentFn = (ctx: AgentContext) => Promise; - -// --- Agent Binding: runtime assignment --- -type AgentBinding = { - agent: AgentFn; - overrides?: Partial>; -}; - -// --- Extract: structured data from context --- -type ExtractFn = (schema: z.ZodType, prompt: string, ctx: ExtractContext) => Promise; - -// --- Moderator: pure routing function --- -type Moderator = (ctx: ModeratorContext) => (keyof M & string) | typeof END; - -// --- Composition --- -// createWorkflow(def, binding, extract) => WorkflowFn +```mermaid +flowchart BT + subgraph L0["Layer 0 — contract"] + protocol["@uncaged/workflow-protocol"] + end + subgraph L1["Layer 1 — on protocol"] + runtime["@uncaged/workflow-runtime"] + util["@uncaged/workflow-util"] + reactor["@uncaged/workflow-reactor"] + end + subgraph L2["Layer 2 — protocol + util"] + cas["@uncaged/workflow-cas"] + register["@uncaged/workflow-register"] + end + subgraph L3["Layer 3 — engine"] + execute["@uncaged/workflow-execute"] + end + subgraph L4["Layer 4 — CLI"] + cli["@uncaged/cli-workflow"] + end + runtime --> protocol + util --> protocol + reactor --> protocol + cas --> protocol + cas --> util + register --> protocol + register --> util + execute --> protocol + execute --> runtime + execute --> util + execute --> cas + execute --> reactor + execute --> register + cli --> protocol + cli --> util + cli --> cas + cli --> execute + cli --> register + cli --> runtime ``` -## Three-Phase Engine Loop +**Adjacent consumers** (not in the main CLI stack): -Each role execution has three distinct phases with progressive context: +- `@uncaged/workflow-util-agent` → `@uncaged/workflow-runtime` +- `@uncaged/workflow-agent-llm` → `@uncaged/workflow-runtime` +- `@uncaged/workflow-agent-cursor` → `@uncaged/workflow-runtime`, `@uncaged/workflow-util-agent`, `zod` +- `@uncaged/workflow-agent-hermes` → `@uncaged/workflow-runtime`, `@uncaged/workflow-util-agent` +- `@uncaged/workflow-template-develop` → `@uncaged/workflow-register`, `@uncaged/workflow-runtime`, `zod` +- `@uncaged/workflow-template-solve-issue` → `@uncaged/workflow-register`, `@uncaged/workflow-runtime`, `zod` (dev-only workspace deps: `@uncaged/workflow-cas`, `@uncaged/workflow-execute` for tests/tooling per `package.json`) + +## Package roles (detail) + +- **`workflow-protocol`** — Pure types (`WorkflowFn`, contexts, `CasStore` interface, descriptor shapes), `START` / `END`, `ok` / `err`. Depends only on peer `zod` for schema-related types in signatures. +- **`workflow-runtime`** — Workflow author surface: `createWorkflow` from `src/create-workflow.js`, re-exports protocol types/constants used when authoring bundles. +- **`workflow-util`** — Cross-cutting utilities: Crockford Base32, ULID, `createLogger`, `getDefaultWorkflowStorageRoot`, `getGlobalCasDir`, ref normalization; re-exports `ok`/`err` from protocol. +- **`workflow-cas`** — Filesystem CAS (`createCasStore`), `hashString` / `hashWorkflowBundleBytes`, Merkle node serialization and helpers (`merkle.js`). +- **`workflow-register`** — Bundle pipeline (`validateWorkflowBundle`, `extractBundleExports`, descriptor builders), registry YAML read/write, `resolveModel` / `splitProviderModelRef`. +- **`workflow-execute`** — `executeThread`, supervisor/worker wiring (`engine/`), fork/GC/pause gate, `createExtract` + LLM extract helpers (`extract/`), `workflowAsAgent`. Imports `@uncaged/workflow-reactor` for LLM-backed extract/supervisor paths (`extract-fn.ts`, `supervisor.ts`). +- **`workflow-reactor`** — `createLlmFn`, `createThreadReactor`, and thread tool-invocation types — consumed by `workflow-execute`. +- **`cli-workflow`** — CLI commands and HTTP/dashboard-related wiring (`hono`, `yaml`); composes register + execute + CAS + util. +- **`workflow-agent-*`** — Replaceable `AgentFn` implementations (Cursor / Hermes CLIs, or HTTP LLM). +- **`workflow-util-agent`** — Shared prompt assembly and subprocess spawning for CLI agents. +- **`workflow-template-*`** — Concrete `WorkflowDefinition` graphs + Zod role schemas + descriptor builders for publishing bundles. +- **`workflow-dashboard`** — Standalone React UI; no published library entry matching `src/index.ts`. + +## Three-phase engine loop + +Each role round is implemented in `packages/workflow-runtime/src/create-workflow.ts` (`advanceOneRound`): moderator → agent → extractor, with progressive context types from `@uncaged/workflow-protocol`. ``` ┌─→ Phase 1: MODERATOR -│ Context: ModeratorContext { threadId, start, steps } +│ Context: ModeratorContext { threadId, depth, start, steps } │ Action: moderator(ctx) → role name | END │ │ Phase 2: AGENT @@ -82,90 +116,80 @@ Each role execution has three distinct phases with progressive context: │ │ Phase 3: EXTRACTOR │ Context: ExtractContext = AgentCtx + { agentContent } -│ Action: extract(schema, extractPrompt, ctx) → typed meta +│ Action: runtime.extract(schema, extractPrompt, ctx) → typed meta │ -│ Merge: RoleStep { role, content, meta, timestamp } +│ Merge: RoleStep { role, contentHash, meta, refs, timestamp } │ Append to steps └─────────────────────────────────────────────────────┘ ``` -### Context Types (progressive) +### Context types (progressive) + +Defined in `packages/workflow-protocol/src/types.ts`: ```typescript -// Phase 1: Moderator sees accumulated state only -type ModeratorContext = { - threadId: string; - start: StartStep; - steps: RoleStep[]; -}; - -// Phase 2: Agent knows its identity +type ModeratorContext = ThreadContext; type AgentContext = ModeratorContext & { currentRole: { name: string; systemPrompt: string }; }; - -// Phase 3: Extractor has agent output -type ExtractContext = AgentContext & { - agentContent: string; -}; - -// ThreadContext is an alias for AgentContext (backward compat) -type ThreadContext = AgentContext; +type ExtractContext = AgentContext & { agentContent: string }; ``` -### Key Properties +### Key properties -- **Moderator is synchronous and pure** — no I/O, no state mutation -- **Agent gets context, not instructions** — reads `ctx.currentRole.systemPrompt` -- **Extractor is a general tool** — not limited to post-agent extraction; agents can use it too (e.g. Cursor agent extracts workspace path before execution) -- **extractPrompt is a call parameter**, not context state — different callers use different prompts +- **Moderator is synchronous and pure** — no I/O, no state mutation inside `createWorkflow`’s moderator call path. +- **Agent receives `AgentContext`** — reads `ctx.currentRole.systemPrompt`; raw output becomes `agentContent` for extract. +- **Extractor is `WorkflowRuntime.extract`** — supplied by the engine from registry-resolved LLM config (`workflow-execute`); stores agent body in CAS and yields `contentHash` + `refs` on each step (`create-workflow.ts`). +- **`extractPrompt` is a call parameter** on `RoleDefinition`, not implicit context state. -## Agent Information Sources +## Agent information sources An agent has exactly three information sources: 1. **Prior knowledge** — LLM training, agent memory, agent skills -2. **Thread context** — `AgentContext` (start, steps, currentRole) +2. **Thread context** — `AgentContext` (`start`, `steps`, `currentRole`) 3. **Derived information** — from 1 & 2 (e.g. tool calls, shell commands) -No hidden environment parameters. If an agent needs something (like a workspace path), it extracts it from context using `ExtractFn`. +No hidden environment parameters. If an agent needs something (like a workspace path), it obtains it via `ExtractFn` (e.g. Cursor agent). -## Bundle Contract +## Bundle contract -A workflow bundle is a single `.esm.js` file with two named exports: +A workflow bundle is a single `.esm.js` file with two named exports (see `WorkflowFn` / `WorkflowDescriptor` in `packages/workflow-protocol/src/types.ts`): ```typescript -// Named exports (no default export) export const descriptor: WorkflowDescriptor; export const run: WorkflowFn; type WorkflowFn = ( - input: { prompt: string; steps: RoleOutput[] }, - options: { threadId: string; maxRounds: number }, -) => AsyncGenerator; + thread: ThreadContext, + runtime: WorkflowRuntime, +) => AsyncGenerator; ``` +`RoleOutput` carries `contentHash`, `meta`, and `refs` (agent text lives in CAS, addressed by hash). + ### Constraints - Single `.esm.js` file -- No dynamic `import()` -- All static imports must be Node built-in modules only -- XXH64 hash (Crockford Base32) = globally unique version ID +- No dynamic `import()` in bundles (loader exempt in engine) +- Portable bundle static imports are constrained by validation in `@uncaged/workflow-register` (`validateWorkflowBundle`) +- XXH64 hash (Crockford Base32) = version ID ### Why AsyncGenerator? -- Each `yield` → engine writes to `.data.jsonl`, checks abort/pause -- `return` → engine marks thread complete -- Fork = pass historical steps as `input.steps` to a new generator -- Zero injection — bundle doesn't import from the engine +- Each `yield` lets `workflow-execute` persist state, CAS rows, and enforce pause/abort +- `return` supplies `WorkflowCompletion` +- Fork replays historical steps into a new thread context +- Bundle does not import the engine — only protocol/runtime types at build time -## Storage Layout +## Storage layout ``` ~/.uncaged/workflow/ +├── cas/ # Global content-addressed blobs (see getGlobalCasDir) ├── bundles/ -│ ├── C9NMV6V2TQT81.esm.js # Crockford Base32 of XXH64 -│ └── C9NMV6V2TQT81.yaml # Role descriptor +│ ├── C9NMV6V2TQT81.esm.js # Crockford Base32 of XXH64 +│ └── C9NMV6V2TQT81.yaml # Role descriptor sidecar (when present) ├── logs/ # One folder per bundle hash │ └── C9NMV6V2TQT81/ │ ├── 01KQXKW…YG.data.jsonl # Thread state @@ -173,7 +197,7 @@ type WorkflowFn = ( └── workflow.yaml # Registry ``` -### ID Encoding: Crockford Base32 +### ID encoding: Crockford Base32 - Case-insensitive, filesystem-safe, no ambiguous chars (0/O, 1/I/L) - Bundle hash: XXH64 → 13-char @@ -181,45 +205,36 @@ type WorkflowFn = ( ### Registry (`workflow.yaml`) -```yaml -workflows: - solve-issue: - hash: "C9NMV6V2TQT81" - timestamp: 1714963200000 - history: - - hash: "A7BKR3M1NPQ40" - timestamp: 1714876800000 -``` +Managed by `@uncaged/workflow-register` (`readWorkflowRegistry`, `writeWorkflowRegistry`, …). Shape includes workflow entries and a top-level `config` section used for extract/supervisor model resolution. ### Thread JSONL -**`.data.jsonl`** — Line 1: start record, Line 2+: role outputs +**`.data.jsonl`** — Line 1: start record; following lines: role steps with CAS-backed content. ```jsonc // Start record { "name": "solve-issue", "hash": "C9NMV6V2TQT81", "threadId": "01KQXKW…", "parameters": { "prompt": "Fix bug #3", "options": { "maxRounds": 5 } }, "timestamp": 1714963200000 } -// Role output -{ "role": "planner", "content": "...", "meta": { "phases": [...] }, "timestamp": ... } +// Role output (engine persists contentHash + refs; body in ~/.uncaged/workflow/cas/) +{ "role": "planner", "contentHash": "…", "meta": { "phases": [...] }, "refs": ["…"], "timestamp": ... } ``` -**`.info.jsonl`** — Structured debug log +**`.info.jsonl`** — Structured debug log via `@uncaged/workflow-util` `createLogger`: ```jsonc { "tag": "4KNMR2PX", "content": "Loading bundle...", "timestamp": ... } ``` -Tags are 8-char Crockford Base32 (40-bit random), one per call site. `grep "4KNMR2PX"` → instant code location. +Tags are 8-char Crockford Base32 (40-bit random), one per call site. `grep "4KNMR2PX"` → code location. -## Execution Model +## Execution model -- **No daemon.** `uncaged-workflow run ` starts a worker process -- Same bundle's threads share one process (memory efficiency) -- Process exits when all threads complete -- Thread termination via IPC within the process +- **No daemon.** `uncaged-workflow run ` starts a worker process (`workflow-execute` worker entry via `getWorkerHostScriptPath`) +- Threads share bundle-scoped workers as implemented in CLI/engine +- Pause/resume/abort via engine IPC and pause gate (`createThreadPauseGate`) -## CLI Commands +## CLI commands | Priority | Command | Description | |----------|---------|-------------| @@ -239,18 +254,16 @@ Tags are 8-char Crockford Base32 (40-bit random), one per call site. `grep "4KNM | P2 | `resume ` | Resume a paused thread | | P3 | `fork [--from-role ]` | Fork from historical state | -All commands implemented and tested. ✅ - -## Design Decisions +## Design decisions | Decision | Rationale | |----------|-----------| | **Role = pure data** | Decouples definition from execution; same role with different agents | -| **Agent bound at runtime** | WorkflowDefinition is reusable; agent choice is deployment concern | -| **Three-phase context** | Each phase sees only what it needs; clean separation | -| **ExtractFn as general tool** | Agents use it for pre-execution extraction; engine uses it for meta | -| **Single-file ESM** | Hash = version, no dependency hell, self-contained | -| **No daemon** | OS handles process lifecycle; unnecessary complexity | +| **Agent bound at runtime** | `WorkflowDefinition` is reusable; agent choice is deployment concern | +| **Three-phase context** | Each phase sees only what it needs; types live in `workflow-protocol` | +| **`WorkflowRuntime.extract` + CAS `contentHash`** | Large agent bodies deduplicated globally; Merkle roots summarize threads | +| **`workflow-reactor` split** | LLM tool-calling loop isolated from filesystem/registry concerns | +| **Single-file ESM** | Hash = version, self-contained bundle | +| **No daemon** | OS handles process lifecycle | | **Crockford Base32** | Filesystem-safe, readable, compact | -| **No concurrency in registry** | Different workflows have different constraints; belongs at workflow/role level | -| **No dryRun** | Tests use mock agents + mock fetch; simpler architecture | +| **15-package split** | Clear boundaries: protocol ↔ runtime author API ↔ util/CAS/register ↔ execute ↔ CLI ↔ agents/templates/UI | diff --git a/docs/plans/2025-05-07-workflow-as-agent.md b/docs/plans/2025-05-07-workflow-as-agent.md index a21e585..afa480b 100644 --- a/docs/plans/2025-05-07-workflow-as-agent.md +++ b/docs/plans/2025-05-07-workflow-as-agent.md @@ -1,5 +1,7 @@ # Workflow-as-Agent Implementation Plan +> ⚠️ This plan references the pre-split package structure. File paths have changed. + > **For Hermes:** Use subagent-driven-development skill to implement this plan task-by-task. **Goal:** Enable workflows to invoke other workflows as agents, backed by global CAS and refs tracking. diff --git a/packages/workflow-agent-cursor/README.md b/packages/workflow-agent-cursor/README.md index ebd8f2f..51534a1 100644 --- a/packages/workflow-agent-cursor/README.md +++ b/packages/workflow-agent-cursor/README.md @@ -7,10 +7,10 @@ The agent builds a full prompt (system + task + step history via `@uncaged/workf ## Install ```bash -bun add @uncaged/workflow-agent-cursor @uncaged/workflow @uncaged/workflow-util-agent zod +bun add @uncaged/workflow-agent-cursor @uncaged/workflow-runtime @uncaged/workflow-util-agent zod ``` -In this monorepo: `"@uncaged/workflow-agent-cursor": "workspace:*"` plus `workspace:*` for `@uncaged/workflow` and `@uncaged/workflow-util-agent`. +In this monorepo: `"@uncaged/workflow-agent-cursor": "workspace:*"` plus `workspace:*` for `@uncaged/workflow-runtime` and `@uncaged/workflow-util-agent`, and `zod` ^4. ## Usage @@ -28,9 +28,8 @@ const agent = createCursorAgent({ | Export | Description | |--------|-------------| -| `createCursorAgent(config)` | Returns `AgentFn` that runs `cursor-agent` with `buildAgentPrompt(ctx)` | +| `createCursorAgent(config)` | Returns `AgentFn` that runs `cursor-agent` with `buildAgentPrompt(ctx)` from `@uncaged/workflow-util-agent` | | `CursorAgentConfig` | `model`, `timeout`, `extract` (must supply workspace path) | | `validateCursorAgentConfig` | Config validation result | -| `buildAgentPrompt` | Re-exported from `@uncaged/workflow-util-agent` | Requires `cursor-agent` on `PATH` at runtime. diff --git a/packages/workflow-agent-hermes/README.md b/packages/workflow-agent-hermes/README.md index 38f35c9..ba3bcf0 100644 --- a/packages/workflow-agent-hermes/README.md +++ b/packages/workflow-agent-hermes/README.md @@ -7,10 +7,10 @@ The agent composes the same thread-aware prompt as other CLI-backed agents via ` ## Install ```bash -bun add @uncaged/workflow-agent-hermes @uncaged/workflow @uncaged/workflow-util-agent +bun add @uncaged/workflow-agent-hermes @uncaged/workflow-runtime @uncaged/workflow-util-agent ``` -In this monorepo: use `workspace:*` for all three `@uncaged/*` packages. +In this monorepo: use `workspace:*` for `@uncaged/workflow-agent-hermes`, `@uncaged/workflow-runtime`, and `@uncaged/workflow-util-agent`. ## Usage @@ -30,6 +30,5 @@ const agent = createHermesAgent({ | `createHermesAgent(config)` | Returns `AgentFn` wrapping `hermes chat -q ...` | | `HermesAgentConfig` | `model`, `timeout` | | `validateHermesAgentConfig` | Config validation result | -| `buildAgentPrompt` | Re-exported from `@uncaged/workflow-util-agent` | Requires `hermes` on `PATH` at runtime. diff --git a/packages/workflow-agent-llm/README.md b/packages/workflow-agent-llm/README.md index 96ff225..4973cfc 100644 --- a/packages/workflow-agent-llm/README.md +++ b/packages/workflow-agent-llm/README.md @@ -1,16 +1,16 @@ # @uncaged/workflow-agent-llm -`AgentFn` adapter that calls an OpenAI-compatible `POST /chat/completions` endpoint using `@uncaged/workflow`’s `LlmProvider` (base URL, API key, model). +`AgentFn` adapter that calls an OpenAI-compatible `POST /chat/completions` endpoint using `LlmProvider` from `@uncaged/workflow-runtime`. Single-turn: system text is the current role’s `systemPrompt`, user text is the thread’s initial prompt (`ctx.start.content`). Errors from HTTP, JSON, or empty choices are thrown as `Error` with a JSON payload string. ## Install ```bash -bun add @uncaged/workflow-agent-llm @uncaged/workflow +bun add @uncaged/workflow-agent-llm @uncaged/workflow-runtime zod ``` -In this monorepo: `"@uncaged/workflow-agent-llm": "workspace:*"`, `"@uncaged/workflow": "workspace:*"`. +In this monorepo: `"@uncaged/workflow-agent-llm": "workspace:*"`, `"@uncaged/workflow-runtime": "workspace:*"` (and satisfy `zod` ^4 as required by `@uncaged/workflow-runtime`). ## Usage diff --git a/packages/workflow-cas/README.md b/packages/workflow-cas/README.md new file mode 100644 index 0000000..909e44a --- /dev/null +++ b/packages/workflow-cas/README.md @@ -0,0 +1,31 @@ +# @uncaged/workflow-cas + +Content-addressable storage implementation, bundle hashing, and Merkle helpers. + +## What This Package Does + +It implements `CasStore` from `@uncaged/workflow-protocol`, hashes workflow bundle bytes and strings with XXH64, and builds serializable Merkle nodes for thread/step/content payloads used when persisting execution artifacts. + +## Key Exports + +From `src/index.ts`: + +- **CAS:** `createCasStore` +- **Hash:** `hashString`, `hashWorkflowBundleBytes` +- **Merkle:** `createContentMerkleNode`, `getContentMerklePayload`, `parseMerkleNode`, `putContentMerkleNode`, `putStepMerkleNode`, `putThreadMerkleNode`, `serializeMerkleNode` +- **Types:** `CasStore`, `MerkleNode`, `MerkleNodeType`, `StepMerklePayload`, `ThreadMerklePayload` + +## Dependencies + +- **Workspace:** `@uncaged/workflow-protocol` (`CasStore` contract), `@uncaged/workflow-util` +- **npm:** `xxhashjs`, `yaml` + +## Usage + +```typescript +import { createCasStore, hashWorkflowBundleBytes } from "@uncaged/workflow-cas"; +import { getGlobalCasDir } from "@uncaged/workflow-util"; + +const store = createCasStore(getGlobalCasDir()); +const hash = await hashWorkflowBundleBytes(esmJsBytes); +``` diff --git a/packages/workflow-execute/README.md b/packages/workflow-execute/README.md new file mode 100644 index 0000000..a9b093a --- /dev/null +++ b/packages/workflow-execute/README.md @@ -0,0 +1,33 @@ +# @uncaged/workflow-execute + +Thread engine: execution, fork/GC, extract pipeline, supervisor/worker wiring, and workflow-as-agent. + +## What This Package Does + +It runs `WorkflowFn` generators against disk-backed threads, integrates CAS and registry-backed extract (`createExtract`), coordinates LLM tool usage via `@uncaged/workflow-reactor`, handles fork plans and garbage collection, and exposes `workflowAsAgent` for nesting workflows. + +## Key Exports + +From `src/index.ts`: + +- **Engine:** `createWorkflow` (engine-local re-export), `executeThread`, `getWorkerHostScriptPath` +- **Fork / parse:** `buildForkPlan`, `parseThreadDataJsonl`, `selectForkHistoricalSteps`, `tryParseRoleStepRecord`, `tryParseWorkflowResultRecord` +- **GC / pause:** `garbageCollectCas`, `createThreadPauseGate` +- **Engine types:** `ExecuteThreadIo`, `ExecuteThreadOptions`, `ForkHistoricalStep`, `ForkPlan`, `GcResult`, `ParsedThreadStartRecord`, `PrefilledDiskStep`, `SupervisorDecision`, `ThreadPauseGate` +- **Extract:** `buildExtractUserContent`, `createExtract`, `extractFunctionToolFromZodSchema`, `llmErrorToCause`, `llmExtract`, types `ExtractFn`, `ExtractThreadContext`, `LlmError`, `LlmExtractArgs` +- **Agent composition:** `workflowAsAgent`, `WorkflowAsAgentOptions` + +## Dependencies + +- **Workspace:** `@uncaged/workflow-protocol`, `@uncaged/workflow-runtime`, `@uncaged/workflow-util`, `@uncaged/workflow-cas`, `@uncaged/workflow-reactor`, `@uncaged/workflow-register` +- **npm:** `yaml` +- **Peer:** `zod` ^4 + +`@uncaged/workflow-reactor` is used for LLM-backed extract and supervisor flows (`extract-fn.ts`, `supervisor.ts`). + +## Usage + +```typescript +import { executeThread } from "@uncaged/workflow-execute"; +// Typical callers are CLI/tests that supply ExecuteThreadIo (paths, CAS, abort, logger, …). +``` diff --git a/packages/workflow-protocol/README.md b/packages/workflow-protocol/README.md new file mode 100644 index 0000000..5eeb562 --- /dev/null +++ b/packages/workflow-protocol/README.md @@ -0,0 +1,29 @@ +# @uncaged/workflow-protocol + +Shared workflow types, sentinel constants, and `Result` helpers. + +## What This Package Does + +It defines the cross-package contract for bundles and the engine: thread/step shapes, `WorkflowFn`, agent/extract contexts, descriptor types, and `CasStore` as an interface. Implementations (CAS store, CLI, extract) depend on these types so bundles stay decoupled from Node APIs. + +## Key Exports + +From `src/index.ts`: + +- **Types:** `Result`, `CasStore`, `WorkflowRoleSchema`, `WorkflowRoleDescriptor`, `WorkflowDescriptor`, `RoleMeta`, `RoleOutput`, `StartStep`, `RoleStep`, `ThreadContext`, `ModeratorContext`, `AgentContext`, `ExtractContext`, `WorkflowCompletion`, `WorkflowResult`, `LlmProvider`, `ProviderConfig`, `ResolvedModel`, `WorkflowConfig`, `ExtractFn`, `AgentFn`, `AgentBinding`, `WorkflowRuntime`, `WorkflowFn`, `RoleDefinition`, `Moderator`, `WorkflowDefinition`, `AdvanceOutcome` +- **Constants:** `START`, `END` +- **Functions:** `ok`, `err` + +## Dependencies + +- **Peer:** `zod` ^4 — used in type positions for schemas (`ExtractFn`, `RoleDefinition`, etc.) + +No workspace packages; this is the bottom layer. + +## Usage + +```typescript +import { END, START, type WorkflowFn, type ThreadContext } from "@uncaged/workflow-protocol"; +``` + +Concrete `WorkflowFn` implementations are built with `@uncaged/workflow-runtime` (`createWorkflow`). diff --git a/packages/workflow-reactor/README.md b/packages/workflow-reactor/README.md new file mode 100644 index 0000000..f30c089 --- /dev/null +++ b/packages/workflow-reactor/README.md @@ -0,0 +1,26 @@ +# @uncaged/workflow-reactor + +LLM calling abstraction and thread “reactor” for structured tool invocation. + +## What This Package Does + +It exposes `createLlmFn` (chat completion wrapper) and `createThreadReactor` (multi-turn tool loop configuration) plus supporting message/tool types. `@uncaged/workflow-execute` consumes this for extractor and supervisor paths that talk to OpenAI-style APIs with tools. + +## Key Exports + +From `src/index.ts`: + +- **Functions:** `createLlmFn`, `createThreadReactor` +- **Types:** `ChatMessage`, `LlmFn`, `StructuredToolSpec`, `ThreadReactorConfig`, `ThreadReactorFn`, `ThreadReactorInvokeArgs`, `ToolCall`, `ToolDefinition` + +## Dependencies + +- **Workspace:** `@uncaged/workflow-protocol` +- **Peer:** `zod` ^4 + +## Usage + +```typescript +import { createLlmFn, createThreadReactor } from "@uncaged/workflow-reactor"; +// Usually composed inside @uncaged/workflow-execute rather than directly by applications. +``` diff --git a/packages/workflow-register/README.md b/packages/workflow-register/README.md new file mode 100644 index 0000000..dc4b9fa --- /dev/null +++ b/packages/workflow-register/README.md @@ -0,0 +1,38 @@ +# @uncaged/workflow-register + +Bundle validation, dynamic export extraction, registry YAML, and model/provider resolution. + +## What This Package Does + +It validates workflow `.esm.js` bundles, extracts `descriptor` / `run` exports at runtime, reads and writes `workflow.yaml`, and resolves which LLM endpoint/model to use from registry config (`resolveModel`, `splitProviderModelRef`). + +## Key Exports + +From `src/index.ts`: + +- **Bundle:** `buildDescriptor`, `importWorkflowBundleModule`, `validateWorkflowBundle`, `ensureUncagedWorkflowSymlink`, `extractBundleExports`, `stringifyWorkflowDescriptor`, `validateWorkflowDescriptor` +- **Bundle types:** `ExtractBundleExportsOptions`, `ExtractedBundleExports`, `WorkflowBundleValidationInput`, `WorkflowDescriptor`, `WorkflowRoleDescriptor`, `WorkflowRoleSchema` +- **Registry:** `getRegisteredWorkflow`, `listRegisteredWorkflowNames`, `parseWorkflowRegistryYaml`, `readWorkflowRegistry`, `registerWorkflowVersion`, `rollbackWorkflowToHistoryHash`, `stringifyWorkflowRegistryYaml`, `unregisterWorkflow`, `workflowRegistryPath`, `writeWorkflowRegistry` +- **Registry types:** `WorkflowConfig`, `WorkflowHistoryEntry`, `WorkflowRegistryEntry`, `WorkflowRegistryFile` +- **Config:** `resolveModel`, `splitProviderModelRef`, types `ProviderConfig`, `ResolvedModel` + +## Dependencies + +- **Workspace:** `@uncaged/workflow-protocol`, `@uncaged/workflow-util` +- **Peer:** `acorn`, `yaml`, `zod` ^4 — parsing/validation at runtime for consumers + +## Usage + +```typescript +import { readFile } from "node:fs/promises"; +import { readWorkflowRegistry, validateWorkflowBundle } from "@uncaged/workflow-register"; +import { getDefaultWorkflowStorageRoot } from "@uncaged/workflow-util"; + +const reg = await readWorkflowRegistry(getDefaultWorkflowStorageRoot()); +if (!reg.ok) throw new Error(reg.error.message); + +const path = "./my.esm.js"; +const source = await readFile(path, "utf8"); +const v = validateWorkflowBundle({ filePath: path, source }); +if (!v.ok) throw new Error(v.error); +``` diff --git a/packages/workflow-runtime/README.md b/packages/workflow-runtime/README.md new file mode 100644 index 0000000..0227176 --- /dev/null +++ b/packages/workflow-runtime/README.md @@ -0,0 +1,28 @@ +# @uncaged/workflow-runtime + +Workflow author API: `createWorkflow` plus re-exports of protocol workflow types. + +## What This Package Does + +Bundle code imports `createWorkflow` to turn a `WorkflowDefinition` plus `AgentBinding` into a `WorkflowFn` generator. It re-exports the protocol types and constants most authors need so workflows rarely import `@uncaged/workflow-protocol` directly. + +## Key Exports + +From `src/index.ts`: + +- **Functions:** `createWorkflow`, `ok`, `err` +- **Types:** `AgentBinding`, `AgentContext`, `AgentFn`, `CasStore`, `ExtractContext`, `ExtractFn`, `LlmProvider`, `Moderator`, `ModeratorContext`, `Result`, `RoleDefinition`, `RoleMeta`, `RoleOutput`, `RoleStep`, `StartStep`, `ThreadContext`, `WorkflowCompletion`, `WorkflowDefinition`, `WorkflowDescriptor`, `WorkflowFn`, `WorkflowResult`, `WorkflowRoleDescriptor`, `WorkflowRoleSchema`, `WorkflowRuntime` +- **Constants:** `END`, `START` + +## Dependencies + +- **Workspace:** `@uncaged/workflow-protocol` — contract types and helpers +- **Peer:** `zod` ^4 — matches schema usage on role definitions + +## Usage + +```typescript +import { createWorkflow, type WorkflowDefinition, type AgentBinding } from "@uncaged/workflow-runtime"; + +export const run = createWorkflow(myDefinition, myBinding); +``` diff --git a/packages/workflow-util/README.md b/packages/workflow-util/README.md new file mode 100644 index 0000000..d7a0836 --- /dev/null +++ b/packages/workflow-util/README.md @@ -0,0 +1,32 @@ +# @uncaged/workflow-util + +Shared utilities: encoding, IDs, logging, storage paths, and ref-field normalization. + +## What This Package Does + +It provides filesystem-safe Base32 and ULID generation, the structured logger used across packages, helpers for the default workflow data directory and global CAS path, and utilities to merge/normalize `refs` on steps. It re-exports `ok`/`err` from protocol for convenience. + +## Key Exports + +From `src/index.ts`: + +- **Base32:** `CROCKFORD_BASE32_ALPHABET`, `decodeCrockfordBase32Bits`, `decodeCrockfordToUint64`, `encodeCrockfordBase32Bits`, `encodeUint64AsCrockford` +- **Logger:** `createLogger` +- **Refs:** `mergeRefsWithContentHash`, `normalizeRefsField` +- **Result:** `ok`, `err` (from `@uncaged/workflow-protocol`) +- **Paths:** `getDefaultWorkflowStorageRoot`, `getGlobalCasDir` +- **ULID:** `generateUlid` +- **Types:** `CreateLoggerOptions`, `LogFn`, `LoggerSink`, `Result` + +## Dependencies + +- **Workspace:** `@uncaged/workflow-protocol` — `Result` and shared types used by helpers + +## Usage + +```typescript +import { createLogger, getDefaultWorkflowStorageRoot, generateUlid } from "@uncaged/workflow-util"; + +const log = createLogger(); +log("4KNMR2PX", "example"); +```