From 893919413313f2271819bb8e474f88e8b052851c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E6=A9=98?= Date: Wed, 6 May 2026 04:20:05 +0000 Subject: [PATCH] init: bun workspace + RFC-001 workflow engine design MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - @uncaged/workflow (core lib) + @uncaged/cli-workflow (CLI) - RFC-001: full design doc covering storage, threading, CLI 小橘 --- .gitignore | 4 + docs/rfc-001-workflow-engine.md | 225 +++++++++++++++++++++++++++++ package.json | 9 ++ packages/cli-workflow/package.json | 15 ++ packages/cli-workflow/src/cli.ts | 3 + packages/workflow/package.json | 11 ++ packages/workflow/src/index.ts | 2 + 7 files changed, 269 insertions(+) create mode 100644 .gitignore create mode 100644 docs/rfc-001-workflow-engine.md create mode 100644 package.json create mode 100644 packages/cli-workflow/package.json create mode 100644 packages/cli-workflow/src/cli.ts create mode 100644 packages/workflow/package.json create mode 100644 packages/workflow/src/index.ts diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..13a80f0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +dist/ +bun.lock +*.tgz diff --git a/docs/rfc-001-workflow-engine.md b/docs/rfc-001-workflow-engine.md new file mode 100644 index 0000000..ff2804d --- /dev/null +++ b/docs/rfc-001-workflow-engine.md @@ -0,0 +1,225 @@ +# RFC-001: Workflow Engine Design + +**Author:** 小橘 🍊(NEKO TeamοΌ‰ +**Date:** 2026-05-06 +**Status:** Draft + +--- + +## 1. Package Structure + +| Package | npm Name | Binary | +|---------|----------|--------| +| Core lib | `@uncaged/workflow` | β€” | +| CLI | `@uncaged/cli-workflow` | `uncaged-workflow` | + +Future: `@uncaged/cli` umbrella, invoke via `uncaged workflow `. + +Monorepo uses **bun workspace**. + +## 2. Workflow Physical Implementation + +A **Workflow** is a single-file ESM module that default-exports a function: + +```typescript +type WorkflowFn = ( + prompt: string, + options: { isDryRun: boolean; maxRounds: number } +) => Promise<{ returnCode: number; summary: string }>; +``` + +### Constraints + +- Single `.esm.js` file +- No dynamic `import()` +- All static imports must be Node built-in modules only + +This guarantees the file is self-contained, and its **XXH64 hash** (encoded as Crockford Base32) serves as a globally unique version identifier. + +### Role Descriptor (Optional) + +A YAML file alongside the bundle describes roles for tooling/agent consumption: + +```yaml +description: "Workflow brief introduction" +roles: + planner: + description: "Analyzes the issue and creates a plan" + schema: + type: object + properties: + plan: + type: string + files: + type: array + items: + type: string + coder: + description: "Implements the plan" + schema: + type: object + properties: + diff: + type: string +``` + +Format: `{ description: string, roles: Record }` + +This file is **not required** for execution. + +## 3. Storage Layout + +All data lives under `~/.uncaged/workflow/`: + +``` +~/.uncaged/workflow/ +β”œβ”€β”€ bundles/ # ESM bundles +β”‚ β”œβ”€β”€ C9NMV6V2TQT81.esm.js # Crockford Base32 of XXH64 hash +β”‚ └── C9NMV6V2TQT81.yaml # Role descriptor (optional) +β”œβ”€β”€ logs/ # Thread data, one folder per bundle hash +β”‚ └── C9NMV6V2TQT81/ +β”‚ β”œβ”€β”€ 01KQXKW18CT8G75T53R8F4G7YG.data.jsonl +β”‚ └── 01KQXKW18CT8G75T53R8F4G7YG.info.jsonl +└── workflow.yaml # Registry +``` + +**Not** a git repo. **Not** an npm package. Bundles are self-contained single files. + +### ID Encoding + +All IDs use **Crockford Base32**: +- Better readability than Base64 +- Higher density than hex (shorter filenames) +- ULID: 10 chars timestamp (high 2 bits zero-padded for future use) + 16 chars random + +## 4. Registry (`workflow.yaml`) + +```yaml +workflows: + solve-issue: + hash: "C9NMV6V2TQT81" + timestamp: 1714963200000 + history: + - hash: "A7BKR3M1NPQ40" + timestamp: 1714876800000 + - hash: "X2FGH8J4KLM56" + timestamp: 1714790400000 +``` + +Type: + +```typescript +{ + workflows: Record +} +``` + +No concurrency control or timeout settings in the registry β€” those belong to each workflow/role/adapter. + +## 5. Thread JSONL Format + +### `.data.jsonl` β€” Thread State + +**Line 1: Start record** + +```jsonc +{ + "name": "solve-issue", + "hash": "C9NMV6V2TQT81", + "threadId": "01KQXKW18CT8G75T53R8F4G7YG", + "parameters": { + "prompt": "Fix the login redirect bug in #3", + "options": { + "isDryRun": false, + "maxRounds": 5 + } + }, + "timestamp": 1714963200000 +} +``` + +**Line 2+: Role outputs** + +```jsonc +{ + "role": "planner", + "content": "Plan: modify auth middleware...", + "meta": { "plan": "...", "files": ["src/auth.ts"] }, + "timestamp": 1714963201000 +} +``` + +### `.info.jsonl` β€” Debug Log + +```jsonc +{ + "tag": "4KNMR2PX", // 40-bit random, Crockford Base32 (8 chars) + "content": "Loading workflow bundle...", + "timestamp": 1714963200500 +} +``` + +## 6. Execution Model + +- **No daemon.** `uncaged-workflow run ` starts a worker process. +- Same bundle's threads share one process (memory efficiency). +- Process exits automatically when all threads complete. +- Thread termination requires **IPC** within the process (not just kill PID). + +## 7. CLI Requirements + +### P1 (Must Have) + +| Command | Description | +|---------|-------------| +| `uncaged-workflow add ` | Register a workflow bundle | +| `uncaged-workflow list` | List registered workflows | +| `uncaged-workflow show ` | Show workflow details | +| `uncaged-workflow remove ` | Remove a workflow | +| `uncaged-workflow run [--prompt] [--dry-run] [--max-rounds]` | Start a thread | +| `uncaged-workflow threads [name]` | List threads (optionally filter by workflow) | +| `uncaged-workflow thread ` | Show thread state | +| `uncaged-workflow thread rm ` | Delete a thread | +| `uncaged-workflow ps` | List running threads | +| `uncaged-workflow kill ` | Terminate a running thread (via IPC) | + +### P2 (Should Have) + +| Command | Description | +|---------|-------------| +| `uncaged-workflow history ` | Show version history | +| `uncaged-workflow rollback [hash]` | Switch to a previous version | +| `uncaged-workflow pause ` | Pause a running thread | +| `uncaged-workflow resume ` | Resume a paused thread | + +### P3 (Nice to Have) + +| Command | Description | +|---------|-------------| +| `uncaged-workflow fork [--from-role ]` | Fork from a historical thread state | + +## 8. Design Decisions & Rationale + +### Why single-file ESM? +- Hash = version. No ambiguity. +- No dependency hell. Self-contained. +- Simple to distribute, store, and verify. + +### Why no daemon? +- Unnecessary complexity for process-per-bundle model. +- OS process management (systemd, etc.) handles restarts. +- IPC within process handles thread lifecycle. + +### Why Crockford Base32? +- Case-insensitive, filesystem-safe. +- No ambiguous characters (0/O, 1/I/L). +- More compact than hex (13 chars for 64-bit vs 16). + +### Why not control concurrency in registry? +- Different workflows have different constraints. +- Same workflow may allow cross-project concurrency but not intra-project. +- Concurrency belongs at workflow/role/adapter level. diff --git a/package.json b/package.json new file mode 100644 index 0000000..01edf45 --- /dev/null +++ b/package.json @@ -0,0 +1,9 @@ +{ + "name": "@uncaged/workflow-monorepo", + "private": true, + "workspaces": ["packages/*"], + "scripts": { + "build": "bun run --filter '*' build", + "test": "bun run --filter '*' test" + } +} diff --git a/packages/cli-workflow/package.json b/packages/cli-workflow/package.json new file mode 100644 index 0000000..ba38880 --- /dev/null +++ b/packages/cli-workflow/package.json @@ -0,0 +1,15 @@ +{ + "name": "@uncaged/cli-workflow", + "version": "0.1.0", + "type": "module", + "bin": { + "uncaged-workflow": "src/cli.ts" + }, + "dependencies": { + "@uncaged/workflow": "workspace:*" + }, + "scripts": { + "build": "echo 'TODO'", + "test": "bun test" + } +} diff --git a/packages/cli-workflow/src/cli.ts b/packages/cli-workflow/src/cli.ts new file mode 100644 index 0000000..1f29714 --- /dev/null +++ b/packages/cli-workflow/src/cli.ts @@ -0,0 +1,3 @@ +#!/usr/bin/env bun +// @uncaged/cli-workflow - uncaged-workflow CLI +console.log('uncaged-workflow'); diff --git a/packages/workflow/package.json b/packages/workflow/package.json new file mode 100644 index 0000000..3d4529f --- /dev/null +++ b/packages/workflow/package.json @@ -0,0 +1,11 @@ +{ + "name": "@uncaged/workflow", + "version": "0.1.0", + "type": "module", + "main": "src/index.ts", + "types": "src/index.ts", + "scripts": { + "build": "echo 'TODO'", + "test": "bun test" + } +} diff --git a/packages/workflow/src/index.ts b/packages/workflow/src/index.ts new file mode 100644 index 0000000..322c6a9 --- /dev/null +++ b/packages/workflow/src/index.ts @@ -0,0 +1,2 @@ +// @uncaged/workflow - core library +export {};