summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--.dockerignore13
-rw-r--r--.env.example1
-rw-r--r--.gitignore7
-rw-r--r--Dockerfile45
-rw-r--r--Dockerfile.dev21
-rwxr-xr-xbin/build7
-rwxr-xr-xbin/build-frontend17
-rwxr-xr-xbin/clean8
-rwxr-xr-xbin/dev_secrets27
-rwxr-xr-xbin/down7
-rwxr-xr-xbin/prod_secrets33
-rwxr-xr-xbin/test10
-rwxr-xr-xbin/up15
-rwxr-xr-xbin/up-backend15
-rwxr-xr-xbin/up-frontend8
-rw-r--r--biome.json52
-rw-r--r--bun.lock463
-rw-r--r--context.md169
-rw-r--r--docker-compose.prod.yml17
-rw-r--r--docker-compose.yml24
-rw-r--r--docker/entrypoint.dev.sh11
-rw-r--r--docker/entrypoint.sh8
-rw-r--r--harness-comparison.md373
-rw-r--r--package.json20
-rw-r--r--packages/api/package.json20
-rw-r--r--packages/api/src/agent-manager.ts86
-rw-r--r--packages/api/src/app.ts46
-rw-r--r--packages/api/src/index.ts37
-rw-r--r--packages/api/src/types.ts2
-rw-r--r--packages/api/tests/agent-manager.test.ts113
-rw-r--r--packages/api/tests/routes.test.ts106
-rw-r--r--packages/api/tsconfig.json10
-rw-r--r--packages/api/vitest.config.ts12
-rw-r--r--packages/core/package.json21
-rw-r--r--packages/core/src/agent/agent.ts142
-rw-r--r--packages/core/src/index.ts9
-rw-r--r--packages/core/src/llm/provider.ts9
-rw-r--r--packages/core/src/tools/list-files.ts38
-rw-r--r--packages/core/src/tools/read-file.ts33
-rw-r--r--packages/core/src/tools/registry.ts35
-rw-r--r--packages/core/src/tools/write-file.ts33
-rw-r--r--packages/core/src/types/index.ts53
-rw-r--r--packages/core/tests/agent/agent.test.ts244
-rw-r--r--packages/core/tests/tools/list-files.test.ts41
-rw-r--r--packages/core/tests/tools/read-file.test.ts36
-rw-r--r--packages/core/tests/tools/registry.test.ts50
-rw-r--r--packages/core/tests/tools/write-file.test.ts45
-rw-r--r--packages/core/tsconfig.json10
-rw-r--r--packages/core/vitest.config.ts14
-rw-r--r--packages/frontend/index.html12
-rw-r--r--packages/frontend/package.json25
-rw-r--r--packages/frontend/src/App.svelte32
-rw-r--r--packages/frontend/src/app.css4
-rw-r--r--packages/frontend/src/lib/chat.svelte.ts274
-rw-r--r--packages/frontend/src/lib/components/ChatInput.svelte45
-rw-r--r--packages/frontend/src/lib/components/ChatMessage.svelte26
-rw-r--r--packages/frontend/src/lib/components/ChatPanel.svelte58
-rw-r--r--packages/frontend/src/lib/components/Header.svelte54
-rw-r--r--packages/frontend/src/lib/components/ThemeSwitcher.svelte65
-rw-r--r--packages/frontend/src/lib/components/ToolCallDisplay.svelte50
-rw-r--r--packages/frontend/src/lib/config.ts6
-rw-r--r--packages/frontend/src/lib/types.ts57
-rw-r--r--packages/frontend/src/lib/ws.svelte.ts89
-rw-r--r--packages/frontend/src/main.ts9
-rw-r--r--packages/frontend/src/vite-env.d.ts2
-rw-r--r--packages/frontend/svelte.config.js5
-rw-r--r--packages/frontend/tests/chat-store.test.ts261
-rw-r--r--packages/frontend/tsconfig.json10
-rw-r--r--packages/frontend/vite.config.ts13
-rw-r--r--plan.md408
-rw-r--r--requirements.md353
-rw-r--r--research/ai-coding-assistants-established.md345
-rw-r--r--research/ai-coding-assistants-newer.md451
-rw-r--r--research/emerging-specialized.md473
-rw-r--r--research/general-agent-platforms.md299
-rw-r--r--research/multi-agent-orchestration.md235
-rw-r--r--research/multi-agent-roleplay.md439
-rw-r--r--research/pi-dev-harness.md322
-rw-r--r--tsconfig.base.json20
-rw-r--r--vitest.config.ts7
80 files changed, 7065 insertions, 0 deletions
diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..bbec9fe
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,13 @@
+node_modules/
+dist/
+build/
+.env
+*.db
+*.sqlite
+.DS_Store
+.git/
+context.md
+harness-comparison.md
+plan.md
+requirements.md
+research/
diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000..9a08117
--- /dev/null
+++ b/.env.example
@@ -0,0 +1 @@
+OPENCODE_API_KEY=your_opencode_go_api_key_here
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..b924793
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,7 @@
+node_modules/
+dist/
+build/
+.env
+*.db
+*.sqlite
+.DS_Store
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..a5ffeb3
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,45 @@
+# Production Dockerfile — multi-stage build for the API server only
+# Frontend deploys separately (e.g., Cloudflare Pages)
+
+FROM oven/bun:1 AS builder
+
+WORKDIR /app
+
+COPY package.json bun.lock ./
+COPY packages/core/package.json packages/core/package.json
+COPY packages/api/package.json packages/api/package.json
+COPY packages/frontend/package.json packages/frontend/package.json
+
+RUN bun install --frozen-lockfile
+
+COPY . .
+
+# --- Production image ---
+
+FROM oven/bun:1
+
+WORKDIR /app
+
+# Copy only what the API server needs
+COPY --from=builder /app/package.json ./
+COPY --from=builder /app/node_modules ./node_modules
+COPY --from=builder /app/packages/core ./packages/core
+COPY --from=builder /app/packages/api ./packages/api
+
+# Create workspace directory for file tools
+RUN mkdir -p workspace
+
+# Production entrypoint
+COPY docker/entrypoint.sh /usr/local/bin/entrypoint.sh
+RUN chmod +x /usr/local/bin/entrypoint.sh
+
+# Run as non-root
+USER bun
+
+EXPOSE 3000
+
+HEALTHCHECK --interval=30s --timeout=10s --retries=3 \
+ CMD bun -e "const r = await fetch('http://localhost:3000/health'); process.exit(r.ok ? 0 : 1)"
+
+ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
+CMD ["bun", "packages/api/src/index.ts"]
diff --git a/Dockerfile.dev b/Dockerfile.dev
new file mode 100644
index 0000000..99c6a39
--- /dev/null
+++ b/Dockerfile.dev
@@ -0,0 +1,21 @@
+FROM oven/bun:1
+
+WORKDIR /app
+
+# Copy dependency files for layer caching
+COPY package.json bun.lock ./
+COPY packages/core/package.json packages/core/package.json
+COPY packages/api/package.json packages/api/package.json
+COPY packages/frontend/package.json packages/frontend/package.json
+
+# Install dependencies (cached unless package files change)
+RUN bun install
+
+# Source code is volume-mounted at runtime, overriding this copy
+COPY . .
+
+# Dev entrypoint: re-runs bun install to pick up dependency changes, then exec's the command
+COPY docker/entrypoint.dev.sh /usr/local/bin/entrypoint.sh
+RUN chmod +x /usr/local/bin/entrypoint.sh
+
+ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
diff --git a/bin/build b/bin/build
new file mode 100755
index 0000000..3952364
--- /dev/null
+++ b/bin/build
@@ -0,0 +1,7 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
+PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
+
+sudo docker compose -f "$PROJECT_DIR/docker-compose.yml" build "$@"
diff --git a/bin/build-frontend b/bin/build-frontend
new file mode 100755
index 0000000..703e072
--- /dev/null
+++ b/bin/build-frontend
@@ -0,0 +1,17 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
+PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
+
+# Build frontend assets inside a temporary container
+# Output lands in packages/frontend/dist/ on the host (via bind mount)
+#
+# VITE_API_URL can be set to configure the API endpoint for the build.
+# Example: VITE_API_URL=https://api.example.com bin/build-frontend
+
+sudo VITE_API_URL="${VITE_API_URL:-}" \
+ docker compose -f "$PROJECT_DIR/docker-compose.yml" \
+ run --rm \
+ -e VITE_API_URL \
+ frontend bun run --cwd packages/frontend build "$@"
diff --git a/bin/clean b/bin/clean
new file mode 100755
index 0000000..1a3d459
--- /dev/null
+++ b/bin/clean
@@ -0,0 +1,8 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
+PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
+
+# Stop containers, remove volumes, remove images
+sudo docker compose -f "$PROJECT_DIR/docker-compose.yml" down --volumes --rmi local "$@"
diff --git a/bin/dev_secrets b/bin/dev_secrets
new file mode 100755
index 0000000..98af50d
--- /dev/null
+++ b/bin/dev_secrets
@@ -0,0 +1,27 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+# Force GPG to use terminal-based pinentry (required for SSH sessions)
+export GPG_TTY=$(tty)
+
+echo "Checking dev secrets for Dispatch..."
+echo ""
+
+# --- OpenCode Go API Key ---
+
+if gopass show -o projects/ai-api/opencode_go_key &>/dev/null; then
+ echo "[ok] OpenCode Go API key exists"
+else
+ echo "OpenCode Go API key not found in gopass."
+ echo ""
+ echo " 1. Go to https://opencode.ai/auth"
+ echo " 2. Sign in and copy your API key"
+ echo " 3. Paste it below"
+ echo ""
+ read -rp "Enter your OpenCode Go API key: " OPENCODE_KEY
+ echo "$OPENCODE_KEY" | gopass insert -f projects/ai-api/opencode_go_key
+ echo "[ok] OpenCode Go API key stored"
+fi
+
+echo ""
+echo "All dev secrets are configured."
diff --git a/bin/down b/bin/down
new file mode 100755
index 0000000..12a6000
--- /dev/null
+++ b/bin/down
@@ -0,0 +1,7 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
+PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
+
+sudo docker compose -f "$PROJECT_DIR/docker-compose.yml" down "$@"
diff --git a/bin/prod_secrets b/bin/prod_secrets
new file mode 100755
index 0000000..68cc6d9
--- /dev/null
+++ b/bin/prod_secrets
@@ -0,0 +1,33 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+# Force GPG to use terminal-based pinentry (required for SSH sessions)
+export GPG_TTY=$(tty)
+
+echo "Checking production secrets for Dispatch..." >&2
+echo "" >&2
+
+# --- OpenCode Go API Key (shared across environments) ---
+
+if gopass show -o projects/ai-api/opencode_go_key &>/dev/null; then
+ echo "[ok] OpenCode Go API key exists" >&2
+else
+ echo "OpenCode Go API key not found in gopass." >&2
+ echo "" >&2
+ echo " 1. Go to https://opencode.ai/auth" >&2
+ echo " 2. Sign in and copy your API key" >&2
+ echo " 3. Paste it below" >&2
+ echo "" >&2
+ read -rp "Enter your OpenCode Go API key: " OPENCODE_KEY
+ echo "$OPENCODE_KEY" | gopass insert -f projects/ai-api/opencode_go_key
+ echo "[ok] OpenCode Go API key stored" >&2
+fi
+
+echo "" >&2
+echo "All production secrets are configured. Outputting .env:" >&2
+echo "" >&2
+
+# --- Output .env format to stdout ---
+
+echo "OPENCODE_API_KEY=$(gopass show -o projects/ai-api/opencode_go_key)"
+echo "DISPATCH_MODEL=deepseek-v4-flash-free"
diff --git a/bin/test b/bin/test
new file mode 100755
index 0000000..61b31ce
--- /dev/null
+++ b/bin/test
@@ -0,0 +1,10 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
+PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
+
+# Run the test suite inside the API container
+# Uses the api service since it has access to the full workspace
+sudo docker compose -f "$PROJECT_DIR/docker-compose.yml" \
+ run --rm api bun run test "$@"
diff --git a/bin/up b/bin/up
new file mode 100755
index 0000000..25eb484
--- /dev/null
+++ b/bin/up
@@ -0,0 +1,15 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+# Force GPG to use terminal-based pinentry (required for SSH sessions)
+export GPG_TTY=$(tty)
+
+SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
+PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
+
+# Load secrets from gopass
+OPENCODE_API_KEY="$(gopass show -o projects/ai-api/opencode_go_key)"
+
+# Start all services
+sudo OPENCODE_API_KEY="$OPENCODE_API_KEY" \
+ docker compose -f "$PROJECT_DIR/docker-compose.yml" up "$@"
diff --git a/bin/up-backend b/bin/up-backend
new file mode 100755
index 0000000..722e5eb
--- /dev/null
+++ b/bin/up-backend
@@ -0,0 +1,15 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+# Force GPG to use terminal-based pinentry (required for SSH sessions)
+export GPG_TTY=$(tty)
+
+SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
+PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
+
+# Load secrets from gopass
+OPENCODE_API_KEY="$(gopass show -o projects/ai-api/opencode_go_key)"
+
+# Start API service only
+sudo OPENCODE_API_KEY="$OPENCODE_API_KEY" \
+ docker compose -f "$PROJECT_DIR/docker-compose.yml" up api "$@"
diff --git a/bin/up-frontend b/bin/up-frontend
new file mode 100755
index 0000000..82b4523
--- /dev/null
+++ b/bin/up-frontend
@@ -0,0 +1,8 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
+PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
+
+# Frontend has no secrets — start frontend service only
+sudo docker compose -f "$PROJECT_DIR/docker-compose.yml" up frontend "$@"
diff --git a/biome.json b/biome.json
new file mode 100644
index 0000000..8943de3
--- /dev/null
+++ b/biome.json
@@ -0,0 +1,52 @@
+{
+ "$schema": "https://biomejs.dev/schemas/2.4.15/schema.json",
+ "assist": { "actions": { "source": { "organizeImports": "on" } } },
+ "linter": {
+ "enabled": true,
+ "rules": {
+ "recommended": true
+ }
+ },
+ "formatter": {
+ "enabled": true,
+ "indentStyle": "tab",
+ "lineWidth": 100
+ },
+ "javascript": {
+ "formatter": {
+ "quoteStyle": "double",
+ "semicolons": "always"
+ }
+ },
+ "css": {
+ "parser": {
+ "tailwindDirectives": true
+ }
+ },
+ "overrides": [
+ {
+ "includes": ["**/*.css"],
+ "linter": {
+ "rules": {
+ "correctness": {
+ "noUnknownProperty": "off"
+ }
+ }
+ }
+ },
+ {
+ "includes": ["**/*.svelte"],
+ "linter": {
+ "rules": {
+ "correctness": {
+ "noUnusedImports": "off",
+ "noUnusedVariables": "off"
+ }
+ }
+ }
+ }
+ ],
+ "files": {
+ "includes": ["**", "!**/node_modules", "!**/dist", "!**/build"]
+ }
+}
diff --git a/bun.lock b/bun.lock
new file mode 100644
index 0000000..8646766
--- /dev/null
+++ b/bun.lock
@@ -0,0 +1,463 @@
+{
+ "lockfileVersion": 1,
+ "configVersion": 1,
+ "workspaces": {
+ "": {
+ "name": "dispatch",
+ "devDependencies": {
+ "@biomejs/biome": "^2.4.15",
+ "typescript": "^5.7.0",
+ "vitest": "^3.0.0",
+ },
+ },
+ "packages/api": {
+ "name": "@dispatch/api",
+ "version": "0.0.1",
+ "dependencies": {
+ "@dispatch/core": "workspace:*",
+ "hono": "^4.0.0",
+ },
+ "devDependencies": {
+ "@types/bun": "latest",
+ },
+ },
+ "packages/core": {
+ "name": "@dispatch/core",
+ "version": "0.0.1",
+ "dependencies": {
+ "@ai-sdk/openai-compatible": "^0.2.0",
+ "ai": "^4.0.0",
+ "zod": "^3.23.0",
+ },
+ "devDependencies": {
+ "@types/bun": "latest",
+ },
+ },
+ "packages/frontend": {
+ "name": "@dispatch/frontend",
+ "version": "0.0.1",
+ "dependencies": {
+ "svelte": "^5.0.0",
+ },
+ "devDependencies": {
+ "@sveltejs/vite-plugin-svelte": "^5.0.0",
+ "@tailwindcss/vite": "^4.0.0",
+ "daisyui": "^5.0.0",
+ "svelte-check": "^4.0.0",
+ "tailwindcss": "^4.0.0",
+ "vite": "^6.0.0",
+ },
+ },
+ },
+ "packages": {
+ "@ai-sdk/openai-compatible": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "@ai-sdk/provider-utils": "2.2.8" }, "peerDependencies": { "zod": "^3.0.0" } }, "sha512-LkvfcM8slJedRyJa/MiMiaOzcMjV1zNDwzTHEGz7aAsgsQV0maLfmJRi/nuSwf5jmp0EouC+JXXDUj2l94HgQw=="],
+
+ "@ai-sdk/provider": ["@ai-sdk/[email protected]", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-qZMxYJ0qqX/RfnuIaab+zp8UAeJn/ygXXAffR5I4N0n1IrvA6qBsjc8hXLmBiMV2zoXlifkacF7sEFnYnjBcqg=="],
+
+ "@ai-sdk/provider-utils": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "nanoid": "^3.3.8", "secure-json-parse": "^2.7.0" }, "peerDependencies": { "zod": "^3.23.8" } }, "sha512-fqhG+4sCVv8x7nFzYnFo19ryhAa3w096Kmc3hWxMQfW/TubPOmt3A6tYZhl4mUfQWWQMsuSkLrtjlWuXBVSGQA=="],
+
+ "@ai-sdk/react": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider-utils": "2.2.8", "@ai-sdk/ui-utils": "1.2.11", "swr": "^2.2.5", "throttleit": "2.1.0" }, "peerDependencies": { "react": "^18 || ^19 || ^19.0.0-rc", "zod": "^3.23.8" }, "optionalPeers": ["zod"] }, "sha512-jK1IZZ22evPZoQW3vlkZ7wvjYGYF+tRBKXtrcolduIkQ/m/sOAVcVeVDUDvh1T91xCnWCdUGCPZg2avZ90mv3g=="],
+
+ "@ai-sdk/ui-utils": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "@ai-sdk/provider-utils": "2.2.8", "zod-to-json-schema": "^3.24.1" }, "peerDependencies": { "zod": "^3.23.8" } }, "sha512-3zcwCc8ezzFlwp3ZD15wAPjf2Au4s3vAbKsXQVyhxODHcmu0iyPO2Eua6D/vicq/AUm/BAo60r97O6HU+EI0+w=="],
+
+ "@biomejs/biome": ["@biomejs/[email protected]", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.4.15", "@biomejs/cli-darwin-x64": "2.4.15", "@biomejs/cli-linux-arm64": "2.4.15", "@biomejs/cli-linux-arm64-musl": "2.4.15", "@biomejs/cli-linux-x64": "2.4.15", "@biomejs/cli-linux-x64-musl": "2.4.15", "@biomejs/cli-win32-arm64": "2.4.15", "@biomejs/cli-win32-x64": "2.4.15" }, "bin": { "biome": "bin/biome" } }, "sha512-j5VH3a/h/HXTKBM50MDMxRCzkeLv9S2XJcW2WgnZT1+xyisi+0bISrXR82gCX+8S9lvK0skEvHJRN+3Ktr2hlw=="],
+
+ "@biomejs/cli-darwin-arm64": ["@biomejs/[email protected]", "", { "os": "darwin", "cpu": "arm64" }, "sha512-rF3PPqLq1yoST79zaQbDjVJwsuIeci/O+9bgNmC5QpgOqz6aqYuzA4abyAGx+mgyiDXn4A049xAN8gijbuR1Qg=="],
+
+ "@biomejs/cli-darwin-x64": ["@biomejs/[email protected]", "", { "os": "darwin", "cpu": "x64" }, "sha512-/5KHXYMfSJs1fNXiX30xFtI8JcCFV6zaVVLxOa0M2sfqBKHkpQhRTv94yxQWxeTY2lzo2OuTlNvPC+hDQt2wcQ=="],
+
+ "@biomejs/cli-linux-arm64": ["@biomejs/[email protected]", "", { "os": "linux", "cpu": "arm64" }, "sha512-owaAMZD/T4LrD0ELNCk0Km3qrRHuM0X6EAyVE1FSqGY0rbLoiDLrO4Us2tllm6cAeB2Ioa9C2C08NZPdr8+0Ug=="],
+
+ "@biomejs/cli-linux-arm64-musl": ["@biomejs/[email protected]", "", { "os": "linux", "cpu": "arm64" }, "sha512-ZPcxznxm0pogHBLZhYntyR3sR+MrZjqJIKEr7ZqVen0Rl+P/4upVmfYXjftizi9RoqZntg33fv/1fbdhbYXpEQ=="],
+
+ "@biomejs/cli-linux-x64": ["@biomejs/[email protected]", "", { "os": "linux", "cpu": "x64" }, "sha512-0jj7THz12GbUOLmMibktK6DZjqz2zV64KFxyBtcFTKPiiOIY0a7vns1elpO1dERvxpsZ5ik0oFfz0oGwFde1+g=="],
+
+ "@biomejs/cli-linux-x64-musl": ["@biomejs/[email protected]", "", { "os": "linux", "cpu": "x64" }, "sha512-CNq/9W38SYSH023lfcQ4KKU8K0YX8T//FZUhcgtMMRABDojx5XsMV7jlweAvGSl389wJQB29Qo6Zb/a+jdvt+w=="],
+
+ "@biomejs/cli-win32-arm64": ["@biomejs/[email protected]", "", { "os": "win32", "cpu": "arm64" }, "sha512-ouhkYdlhp/1GghEJPdWwD/Vi3gQ1nFxuSpMolWsbq3Lsq3QUR4jl6UdhhscdCugKU5vOEuMiJhvKj66O0OCq+w=="],
+
+ "@biomejs/cli-win32-x64": ["@biomejs/[email protected]", "", { "os": "win32", "cpu": "x64" }, "sha512-zBrGq5mx5wwpnow4+2BxUvleDM+GNd4sLbPaMapsSLQLD0NGRCquqPBTgN+7XkUteHvj7M+BstuI8tmnV7+HgQ=="],
+
+ "@dispatch/api": ["@dispatch/api@workspace:packages/api"],
+
+ "@dispatch/core": ["@dispatch/core@workspace:packages/core"],
+
+ "@dispatch/frontend": ["@dispatch/frontend@workspace:packages/frontend"],
+
+ "@esbuild/aix-ppc64": ["@esbuild/[email protected]", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="],
+
+ "@esbuild/android-arm": ["@esbuild/[email protected]", "", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="],
+
+ "@esbuild/android-arm64": ["@esbuild/[email protected]", "", { "os": "android", "cpu": "arm64" }, "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg=="],
+
+ "@esbuild/android-x64": ["@esbuild/[email protected]", "", { "os": "android", "cpu": "x64" }, "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg=="],
+
+ "@esbuild/darwin-arm64": ["@esbuild/[email protected]", "", { "os": "darwin", "cpu": "arm64" }, "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg=="],
+
+ "@esbuild/darwin-x64": ["@esbuild/[email protected]", "", { "os": "darwin", "cpu": "x64" }, "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA=="],
+
+ "@esbuild/freebsd-arm64": ["@esbuild/[email protected]", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg=="],
+
+ "@esbuild/freebsd-x64": ["@esbuild/[email protected]", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ=="],
+
+ "@esbuild/linux-arm": ["@esbuild/[email protected]", "", { "os": "linux", "cpu": "arm" }, "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw=="],
+
+ "@esbuild/linux-arm64": ["@esbuild/[email protected]", "", { "os": "linux", "cpu": "arm64" }, "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ=="],
+
+ "@esbuild/linux-ia32": ["@esbuild/[email protected]", "", { "os": "linux", "cpu": "ia32" }, "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA=="],
+
+ "@esbuild/linux-loong64": ["@esbuild/[email protected]", "", { "os": "linux", "cpu": "none" }, "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng=="],
+
+ "@esbuild/linux-mips64el": ["@esbuild/[email protected]", "", { "os": "linux", "cpu": "none" }, "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw=="],
+
+ "@esbuild/linux-ppc64": ["@esbuild/[email protected]", "", { "os": "linux", "cpu": "ppc64" }, "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA=="],
+
+ "@esbuild/linux-riscv64": ["@esbuild/[email protected]", "", { "os": "linux", "cpu": "none" }, "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w=="],
+
+ "@esbuild/linux-s390x": ["@esbuild/[email protected]", "", { "os": "linux", "cpu": "s390x" }, "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg=="],
+
+ "@esbuild/linux-x64": ["@esbuild/[email protected]", "", { "os": "linux", "cpu": "x64" }, "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw=="],
+
+ "@esbuild/netbsd-arm64": ["@esbuild/[email protected]", "", { "os": "none", "cpu": "arm64" }, "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg=="],
+
+ "@esbuild/netbsd-x64": ["@esbuild/[email protected]", "", { "os": "none", "cpu": "x64" }, "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ=="],
+
+ "@esbuild/openbsd-arm64": ["@esbuild/[email protected]", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A=="],
+
+ "@esbuild/openbsd-x64": ["@esbuild/[email protected]", "", { "os": "openbsd", "cpu": "x64" }, "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw=="],
+
+ "@esbuild/openharmony-arm64": ["@esbuild/[email protected]", "", { "os": "none", "cpu": "arm64" }, "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg=="],
+
+ "@esbuild/sunos-x64": ["@esbuild/[email protected]", "", { "os": "sunos", "cpu": "x64" }, "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w=="],
+
+ "@esbuild/win32-arm64": ["@esbuild/[email protected]", "", { "os": "win32", "cpu": "arm64" }, "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg=="],
+
+ "@esbuild/win32-ia32": ["@esbuild/[email protected]", "", { "os": "win32", "cpu": "ia32" }, "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ=="],
+
+ "@esbuild/win32-x64": ["@esbuild/[email protected]", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="],
+
+ "@jridgewell/gen-mapping": ["@jridgewell/[email protected]", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
+
+ "@jridgewell/remapping": ["@jridgewell/[email protected]", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
+
+ "@jridgewell/resolve-uri": ["@jridgewell/[email protected]", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
+
+ "@jridgewell/sourcemap-codec": ["@jridgewell/[email protected]", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
+
+ "@jridgewell/trace-mapping": ["@jridgewell/[email protected]", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
+
+ "@opentelemetry/api": ["@opentelemetry/[email protected]", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],
+
+ "@rollup/rollup-android-arm-eabi": ["@rollup/[email protected]", "", { "os": "android", "cpu": "arm" }, "sha512-F5QXMSiFebS9hKZj02XhWLLnRpJ3B3AROP0tWbFBSj+6kCbg5m9j5JoHKd4mmSVy5mS/IMQloYgYxCuJC0fxEQ=="],
+
+ "@rollup/rollup-android-arm64": ["@rollup/[email protected]", "", { "os": "android", "cpu": "arm64" }, "sha512-GxxTKApUpzRhof7poWvCJHRF51C67u1R7D6DiluBE8wKU1u5GWE8t+v81JvJYtbawoBFX1hLv5Ei4eVjkWokaw=="],
+
+ "@rollup/rollup-darwin-arm64": ["@rollup/[email protected]", "", { "os": "darwin", "cpu": "arm64" }, "sha512-tua0TaJxMOB1R0V0RS1jFZ/RpURFDJIOR2A6jWwQeawuFyS4gBW+rntLRaQd0EQ4bd6Vp44Z2rXW+YYDBsj6IA=="],
+
+ "@rollup/rollup-darwin-x64": ["@rollup/[email protected]", "", { "os": "darwin", "cpu": "x64" }, "sha512-CSKq7MsP+5PFIcydhAiR1K0UhEI1A2jWXVKHPCBZ151yOutENwvnPocgVHkivu2kviURtCEB6zUQw0vs8RrhMg=="],
+
+ "@rollup/rollup-freebsd-arm64": ["@rollup/[email protected]", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-+O8OkVdyvXMtJEciu2wS/pzm1IxntEEQx3z5TAVy4l32G0etZn+RsA48ARRrFm6Ri8fvqPQfgrvNxSjKAbnd3g=="],
+
+ "@rollup/rollup-freebsd-x64": ["@rollup/[email protected]", "", { "os": "freebsd", "cpu": "x64" }, "sha512-Iw3oMskH3AfNuhU0MSN7vNbdi4me/NiYo2azqPz/Le16zHSa+3RRmliCMWWQmh4lcndccU40xcJuTYJZxNo/lw=="],
+
+ "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/[email protected]", "", { "os": "linux", "cpu": "arm" }, "sha512-EIPRXTVQpHyF8WOo219AD2yEltPehLTcTMz2fn6JsatLYSzQf00hj3rulF+yauOlF9/FtM2WpkT/hJh/KJFGhA=="],
+
+ "@rollup/rollup-linux-arm-musleabihf": ["@rollup/[email protected]", "", { "os": "linux", "cpu": "arm" }, "sha512-J3Yh9PzzF1Ovah2At+lHiGQdsYgArxBbXv/zHfSyaiFQEqvNv7DcW98pCrmdjCZBrqBiKrKKe2V+aaSGWuBe/w=="],
+
+ "@rollup/rollup-linux-arm64-gnu": ["@rollup/[email protected]", "", { "os": "linux", "cpu": "arm64" }, "sha512-BFDEZMYfUvLn37ONE1yMBojPxnMlTFsdyNoqncT0qFq1mAfllL+ATMMJd8TeuVMiX84s1KbcxcZbXInmcO2mRg=="],
+
+ "@rollup/rollup-linux-arm64-musl": ["@rollup/[email protected]", "", { "os": "linux", "cpu": "arm64" }, "sha512-pc9EYOSlOgdQ2uPl1o9PF6/kLSgaUosia7gOuS8mB69IxJvlclko1MECXysjs5ryez1/5zjYqx3+xYU0TU6R1A=="],
+
+ "@rollup/rollup-linux-loong64-gnu": ["@rollup/[email protected]", "", { "os": "linux", "cpu": "none" }, "sha512-NxnomyxYerDh5n4iLrNa+sH+Z+U4BMEE46V2PgQ/hoB909i8gV1M5wPojWg9fk1jWpO3IQnOs20K4wyZuFLEFQ=="],
+
+ "@rollup/rollup-linux-loong64-musl": ["@rollup/[email protected]", "", { "os": "linux", "cpu": "none" }, "sha512-nbJnQ8a3z1mtmrwImCYhc6BGpThAyYVRQxw9uKSKG4wR6aAYno9sVjJ0zaZcW9BPJX1GbrDPf+SvdWjgTuDmnw=="],
+
+ "@rollup/rollup-linux-ppc64-gnu": ["@rollup/[email protected]", "", { "os": "linux", "cpu": "ppc64" }, "sha512-2EU6acNrQLd8tYvo/LXW535wupT3m6fo7HKo6lr7ktQoItxTyOL1ZCR/GfGCuXl2vR+zmfI6eRXkSemafv+iVg=="],
+
+ "@rollup/rollup-linux-ppc64-musl": ["@rollup/[email protected]", "", { "os": "linux", "cpu": "ppc64" }, "sha512-WeBtoMuaMxiiIrO2IYP3xs6GMWkJP2C0EoT8beTLkUPmzV1i/UcOSVw1d5r9KBODtHKilG5yFxsGRnBbK3wJ4A=="],
+
+ "@rollup/rollup-linux-riscv64-gnu": ["@rollup/[email protected]", "", { "os": "linux", "cpu": "none" }, "sha512-FJHFfqpKUI3A10WrWKiFbBZ7yVbGT4q4B5o1qKFFojqpaYoh9LrQgqWCmmcxQzVSXYtyB5bzkXrYzlHTs21MYA=="],
+
+ "@rollup/rollup-linux-riscv64-musl": ["@rollup/[email protected]", "", { "os": "linux", "cpu": "none" }, "sha512-mcEl6CUT5IAUmQf1m9FYSmVqCJlpQ8r8eyftFUHG8i9OhY7BkBXSUdnLH5DOf0wCOjcP9v/QO93zpmF1SptCCw=="],
+
+ "@rollup/rollup-linux-s390x-gnu": ["@rollup/[email protected]", "", { "os": "linux", "cpu": "s390x" }, "sha512-ynt3JxVd2w2buzoKDWIyiV1pJW93xlQic1THVLXilz429oijRpSHivZAgp65KBu+cMcgf1eVVjdnTLvPxgCuoQ=="],
+
+ "@rollup/rollup-linux-x64-gnu": ["@rollup/[email protected]", "", { "os": "linux", "cpu": "x64" }, "sha512-Boiz5+MsaROEWDf+GGEwF8VMHGhlUoQMtIPjOgA5fv4osupqTVnJteQNKJwUcnUog2G55jYXH7KZFFiJe0TEzQ=="],
+
+ "@rollup/rollup-linux-x64-musl": ["@rollup/[email protected]", "", { "os": "linux", "cpu": "x64" }, "sha512-+qfSY27qIrFfI/Hom04KYFw3GKZSGU4lXus51wsb5EuySfFlWRwjkKWoE9emgRw/ukoT4Udsj4W/+xxG8VbPKg=="],
+
+ "@rollup/rollup-openbsd-x64": ["@rollup/[email protected]", "", { "os": "openbsd", "cpu": "x64" }, "sha512-VpTfOPHgVXEBeeR8hZ2O0F3aSso+JDWqTWmTmzcQKted54IAdUVbxE+j/MVxUsKa8L20HJhv3vUezVPoquqWjA=="],
+
+ "@rollup/rollup-openharmony-arm64": ["@rollup/[email protected]", "", { "os": "none", "cpu": "arm64" }, "sha512-IPOsh5aRYuLv/nkU51X10Bf75Bsf6+gZdx1X+QP5QM6lIJFHHqbHLG0uJn/hWthzo13UAc2umiUorqZy3axoZg=="],
+
+ "@rollup/rollup-win32-arm64-msvc": ["@rollup/[email protected]", "", { "os": "win32", "cpu": "arm64" }, "sha512-4QzE9E81OohJ/HKzHhsqU+zcYYojVOXlFMs1DdyMT6qXl/niOH7AVElmmEdUNHHS/oRkc++d5k6Vy85zFs0DEw=="],
+
+ "@rollup/rollup-win32-ia32-msvc": ["@rollup/[email protected]", "", { "os": "win32", "cpu": "ia32" }, "sha512-zTPgT1YuHHcd+Tmx7h8aml0FWFVelV5N54oHow9SLj+GfoDy/huQ+UV396N/C7KpMDMiPspRktzM1/0r1usYEA=="],
+
+ "@rollup/rollup-win32-x64-gnu": ["@rollup/[email protected]", "", { "os": "win32", "cpu": "x64" }, "sha512-DRS4G7mi9lJxqEDezIkKCaUIKCrLUUDCUaCsTPCi/rtqaC6D/jjwslMQyiDU50Ka0JKpeXeRBFBAXwArY52vBw=="],
+
+ "@rollup/rollup-win32-x64-msvc": ["@rollup/[email protected]", "", { "os": "win32", "cpu": "x64" }, "sha512-QVTUovf40zgTqlFVrKA1uXMVvU2QWEFWfAH8Wdc48IxLvrJMQVMBRjuQyUpzZCDkakImib9eVazbWlC6ksWtJw=="],
+
+ "@sveltejs/acorn-typescript": ["@sveltejs/[email protected]", "", { "peerDependencies": { "acorn": "^8.9.0" } }, "sha512-lVJX6qEgs/4DOcRTpo56tmKzVPtoWAaVbL4hfO7t7NVwl9AAXzQR6cihesW1BmNMPl+bK6dreu2sOKBP2Q9CIA=="],
+
+ "@sveltejs/vite-plugin-svelte": ["@sveltejs/[email protected]", "", { "dependencies": { "@sveltejs/vite-plugin-svelte-inspector": "^4.0.1", "debug": "^4.4.1", "deepmerge": "^4.3.1", "kleur": "^4.1.5", "magic-string": "^0.30.17", "vitefu": "^1.0.6" }, "peerDependencies": { "svelte": "^5.0.0", "vite": "^6.0.0" } }, "sha512-Y1Cs7hhTc+a5E9Va/xwKlAJoariQyHY+5zBgCZg4PFWNYQ1nMN9sjK1zhw1gK69DuqVP++sht/1GZg1aRwmAXQ=="],
+
+ "@sveltejs/vite-plugin-svelte-inspector": ["@sveltejs/[email protected]", "", { "dependencies": { "debug": "^4.3.7" }, "peerDependencies": { "@sveltejs/vite-plugin-svelte": "^5.0.0", "svelte": "^5.0.0", "vite": "^6.0.0" } }, "sha512-J/Nmb2Q2y7mck2hyCX4ckVHcR5tu2J+MtBEQqpDrrgELZ2uvraQcK/ioCV61AqkdXFgriksOKIceDcQmqnGhVw=="],
+
+ "@tailwindcss/node": ["@tailwindcss/[email protected]", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.21.0", "jiti": "^2.6.1", "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.3.0" } }, "sha512-aFb4gUhFOgdh9AXo4IzBEOzBkkAxm9VigwDJnMIYv3lcfXCJVesNfbEaBl4BNgVRyid92AmdviqwBUBRKSeY3g=="],
+
+ "@tailwindcss/oxide": ["@tailwindcss/[email protected]", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.3.0", "@tailwindcss/oxide-darwin-arm64": "4.3.0", "@tailwindcss/oxide-darwin-x64": "4.3.0", "@tailwindcss/oxide-freebsd-x64": "4.3.0", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.0", "@tailwindcss/oxide-linux-arm64-gnu": "4.3.0", "@tailwindcss/oxide-linux-arm64-musl": "4.3.0", "@tailwindcss/oxide-linux-x64-gnu": "4.3.0", "@tailwindcss/oxide-linux-x64-musl": "4.3.0", "@tailwindcss/oxide-wasm32-wasi": "4.3.0", "@tailwindcss/oxide-win32-arm64-msvc": "4.3.0", "@tailwindcss/oxide-win32-x64-msvc": "4.3.0" } }, "sha512-F7HZGBeN9I0/AuuJS5PwcD8xayx5ri5GhjYUDBEVYUkexyA/giwbDNjRVrxSezE3T250OU2K/wp/ltWx3UOefg=="],
+
+ "@tailwindcss/oxide-android-arm64": ["@tailwindcss/[email protected]", "", { "os": "android", "cpu": "arm64" }, "sha512-TJPiq67tKlLuObP6RkwvVGDoxCMBVtDgKkLfa/uyj7/FyxvQwHS+UOnVrXXgbEsfUaMgiVvC4KbJnRr26ho4Ng=="],
+
+ "@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/[email protected]", "", { "os": "darwin", "cpu": "arm64" }, "sha512-oMN/WZRb+SO37BmUElEgeEWuU8E/HXRkiODxJxLe1UTHVXLrdVSgfaJV7pSlhRGMSOiXLuxTIjfsF3wYvz8cgQ=="],
+
+ "@tailwindcss/oxide-darwin-x64": ["@tailwindcss/[email protected]", "", { "os": "darwin", "cpu": "x64" }, "sha512-N6CUmu4a6bKVADfw77p+iw6Yd9Q3OBhe0veaDX+QazfuVYlQsHfDgxBrsjQ/IW+zywL8mTrNd0SdJT/zgtvMdA=="],
+
+ "@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/[email protected]", "", { "os": "freebsd", "cpu": "x64" }, "sha512-zDL5hBkQdH5C6MpqbK3gQAgP80tsMwSI26vjOzjJtNCMUo0lFgOItzHKBIupOZNQxt3ouPH7RPhvNhiTfCe5CQ=="],
+
+ "@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/[email protected]", "", { "os": "linux", "cpu": "arm" }, "sha512-R06HdNi7A7OEoMsf6d4tjZ71RCWnZQPHj2mnotSFURjNLdBC+cIgXQ7l81CqeoiQftjf6OOblxXMInMgN2VzMA=="],
+
+ "@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/[email protected]", "", { "os": "linux", "cpu": "arm64" }, "sha512-qTJHELX8jetjhRQHCLilkVLmybpzNQAtaI/gaoVoidn/ufbNDbAo8KlK2J+yPoc8wQxvDxCmh/5lr8nC1+lTbg=="],
+
+ "@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/[email protected]", "", { "os": "linux", "cpu": "arm64" }, "sha512-Z6sukiQsngnWO+l39X4pPbiWT81IC+PLKF+PHxIlyZbGNb9MODfYlXEVlFvej5BOZInWX01kVyzeLvHsXhfczQ=="],
+
+ "@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/[email protected]", "", { "os": "linux", "cpu": "x64" }, "sha512-DRNdQRpSGzRGfARVuVkxvM8Q12nh19l4BF/G7zGA1oe+9wcC6saFBHTISrpIcKzhiXtSrlSrluCfvMuledoCTQ=="],
+
+ "@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/[email protected]", "", { "os": "linux", "cpu": "x64" }, "sha512-Z0IADbDo8bh6I7h2IQMx601AdXBLfFpEdUotft86evd/8ZPflZe9COPO8Q1vw+pfLWIUo9zN/JGZvwuAJqduqg=="],
+
+ "@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/[email protected]", "", { "dependencies": { "@emnapi/core": "^1.10.0", "@emnapi/runtime": "^1.10.0", "@emnapi/wasi-threads": "^1.2.1", "@napi-rs/wasm-runtime": "^1.1.4", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.8.1" }, "cpu": "none" }, "sha512-HNZGOUxEmElksYR7S6sC5jTeNGpobAsy9u7Gu0AskJ8/20FR9GqebUyB+HBcU/ax6BHuiuJi+Oda4B+YX6H1yA=="],
+
+ "@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/[email protected]", "", { "os": "win32", "cpu": "arm64" }, "sha512-Pe+RPVTi1T+qymuuRpcdvwSVZjnll/f7n8gBxMMh3xLTctMDKqpdfGimbMyioqtLhUYZxdJ9wGNhV7MKHvgZsQ=="],
+
+ "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/[email protected]", "", { "os": "win32", "cpu": "x64" }, "sha512-Mvrf2kXW/yeW/OTezZlCGOirXRcUuLIBx/5Y12BaPM7wJoryG6dfS/NJL8aBPqtTEx/Vm4T4vKzFUcKDT+TKUA=="],
+
+ "@tailwindcss/vite": ["@tailwindcss/[email protected]", "", { "dependencies": { "@tailwindcss/node": "4.3.0", "@tailwindcss/oxide": "4.3.0", "tailwindcss": "4.3.0" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7 || ^8" } }, "sha512-t6J3OrB5Fc0ExuhohouH0fWUGMYL6PTLhW+E7zIk/pdbnJARZDCwjBznFnkh5ynRnIRSI4YjtTH0t6USjJISrw=="],
+
+ "@types/bun": ["@types/[email protected]", "", { "dependencies": { "bun-types": "1.3.14" } }, "sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw=="],
+
+ "@types/chai": ["@types/[email protected]", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="],
+
+ "@types/deep-eql": ["@types/[email protected]", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="],
+
+ "@types/diff-match-patch": ["@types/[email protected]", "", {}, "sha512-xFdR6tkm0MWvBfO8xXCSsinYxHcqkQUlcHeSpMC2ukzOb6lwQAfDmW+Qt0AvlGd8HpsS28qKsB+oPeJn9I39jg=="],
+
+ "@types/estree": ["@types/[email protected]", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
+
+ "@types/node": ["@types/[email protected]", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-AOQwYUNolgy3VosiRqXrACUXTN8nJUtPl7FJXMqZVyxiiCLhQuG3jXKvCS1ALr+Y2OmZhzzLVlYPEqJaiqkaJQ=="],
+
+ "@types/trusted-types": ["@types/[email protected]", "", {}, "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="],
+
+ "@vitest/expect": ["@vitest/[email protected]", "", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" } }, "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig=="],
+
+ "@vitest/mocker": ["@vitest/[email protected]", "", { "dependencies": { "@vitest/spy": "3.2.4", "estree-walker": "^3.0.3", "magic-string": "^0.30.17" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" }, "optionalPeers": ["msw", "vite"] }, "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ=="],
+
+ "@vitest/pretty-format": ["@vitest/[email protected]", "", { "dependencies": { "tinyrainbow": "^2.0.0" } }, "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA=="],
+
+ "@vitest/runner": ["@vitest/[email protected]", "", { "dependencies": { "@vitest/utils": "3.2.4", "pathe": "^2.0.3", "strip-literal": "^3.0.0" } }, "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ=="],
+
+ "@vitest/snapshot": ["@vitest/[email protected]", "", { "dependencies": { "@vitest/pretty-format": "3.2.4", "magic-string": "^0.30.17", "pathe": "^2.0.3" } }, "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ=="],
+
+ "@vitest/spy": ["@vitest/[email protected]", "", { "dependencies": { "tinyspy": "^4.0.3" } }, "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw=="],
+
+ "@vitest/utils": ["@vitest/[email protected]", "", { "dependencies": { "@vitest/pretty-format": "3.2.4", "loupe": "^3.1.4", "tinyrainbow": "^2.0.0" } }, "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA=="],
+
+ "acorn": ["[email protected]", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="],
+
+ "ai": ["[email protected]", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "@ai-sdk/provider-utils": "2.2.8", "@ai-sdk/react": "1.2.12", "@ai-sdk/ui-utils": "1.2.11", "@opentelemetry/api": "1.9.0", "jsondiffpatch": "0.6.0" }, "peerDependencies": { "react": "^18 || ^19 || ^19.0.0-rc", "zod": "^3.23.8" }, "optionalPeers": ["react"] }, "sha512-dIE2bfNpqHN3r6IINp9znguYdhIOheKW2LDigAMrgt/upT3B8eBGPSCblENvaZGoq+hxaN9fSMzjWpbqloP+7Q=="],
+
+ "aria-query": ["[email protected]", "", {}, "sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g=="],
+
+ "assertion-error": ["[email protected]", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="],
+
+ "axobject-query": ["[email protected]", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="],
+
+ "bun-types": ["[email protected]", "", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="],
+
+ "cac": ["[email protected]", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="],
+
+ "chai": ["[email protected]", "", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw=="],
+
+ "chalk": ["[email protected]", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="],
+
+ "check-error": ["[email protected]", "", {}, "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA=="],
+
+ "chokidar": ["[email protected]", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="],
+
+ "clsx": ["[email protected]", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
+
+ "daisyui": ["[email protected]", "", {}, "sha512-HemJcjl0Gk9rQ8BcgofN6p+EURrqftQG9wK1Hkxs98i49xe68+QxpNvry+PyxwkIUgrbMpNmZ5ZWjmtffAjfhQ=="],
+
+ "debug": ["[email protected]", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
+
+ "deep-eql": ["[email protected]", "", {}, "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q=="],
+
+ "deepmerge": ["[email protected]", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="],
+
+ "dequal": ["[email protected]", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="],
+
+ "detect-libc": ["[email protected]", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
+
+ "devalue": ["[email protected]", "", {}, "sha512-4CXDYRBGqN+57wVJkuXBYmpAVUSg3L6JAQa/DFqm238G73E1wuyc/JhGQJzN7vUf/CMphYau2zXbfWzDR5aTEw=="],
+
+ "diff-match-patch": ["[email protected]", "", {}, "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw=="],
+
+ "enhanced-resolve": ["[email protected]", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.3" } }, "sha512-wE4fDO8OjJhrPFH69HUQStq5oKvGRTNXEyW+k5C/pUQLASSsTu7obd2V3GvCDgPcY9AWjhJ4jz9Kh7iRvrxhJg=="],
+
+ "es-module-lexer": ["[email protected]", "", {}, "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA=="],
+
+ "esbuild": ["[email protected]", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="],
+
+ "esm-env": ["[email protected]", "", {}, "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA=="],
+
+ "esrap": ["[email protected]", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" }, "peerDependencies": { "@typescript-eslint/types": "^8.2.0" }, "optionalPeers": ["@typescript-eslint/types"] }, "sha512-4KijP+NxCWthMCUC3qHbE6n4vCjqgJS1uAYKhuT/GWfFTf1Qyive2TgOjep+gzbSzRfnNyaN/UU9YmdOt8Eg0A=="],
+
+ "estree-walker": ["[email protected]", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="],
+
+ "expect-type": ["[email protected]", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="],
+
+ "fdir": ["[email protected]", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
+
+ "fsevents": ["[email protected]", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
+
+ "graceful-fs": ["[email protected]", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
+
+ "hono": ["[email protected]", "", {}, "sha512-xa3eYXYXx68XTT4hZ7dRzsXBhaq85ToSrlUJNoR0gwz/1Ap/CNwX47wfvV7pc/xWhjKVVkLT7zBJy8chhNguqQ=="],
+
+ "is-reference": ["[email protected]", "", { "dependencies": { "@types/estree": "^1.0.6" } }, "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw=="],
+
+ "jiti": ["[email protected]", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ=="],
+
+ "js-tokens": ["[email protected]", "", {}, "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ=="],
+
+ "json-schema": ["[email protected]", "", {}, "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="],
+
+ "jsondiffpatch": ["[email protected]", "", { "dependencies": { "@types/diff-match-patch": "^1.0.36", "chalk": "^5.3.0", "diff-match-patch": "^1.0.5" }, "bin": { "jsondiffpatch": "bin/jsondiffpatch.js" } }, "sha512-3QItJOXp2AP1uv7waBkao5nCvhEv+QmJAd38Ybq7wNI74Q+BBmnLn4EDKz6yI9xGAIQoUF87qHt+kc1IVxB4zQ=="],
+
+ "kleur": ["[email protected]", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="],
+
+ "lightningcss": ["[email protected]", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="],
+
+ "lightningcss-android-arm64": ["[email protected]", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="],
+
+ "lightningcss-darwin-arm64": ["[email protected]", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ=="],
+
+ "lightningcss-darwin-x64": ["[email protected]", "", { "os": "darwin", "cpu": "x64" }, "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w=="],
+
+ "lightningcss-freebsd-x64": ["[email protected]", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig=="],
+
+ "lightningcss-linux-arm-gnueabihf": ["[email protected]", "", { "os": "linux", "cpu": "arm" }, "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw=="],
+
+ "lightningcss-linux-arm64-gnu": ["[email protected]", "", { "os": "linux", "cpu": "arm64" }, "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ=="],
+
+ "lightningcss-linux-arm64-musl": ["[email protected]", "", { "os": "linux", "cpu": "arm64" }, "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg=="],
+
+ "lightningcss-linux-x64-gnu": ["[email protected]", "", { "os": "linux", "cpu": "x64" }, "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA=="],
+
+ "lightningcss-linux-x64-musl": ["[email protected]", "", { "os": "linux", "cpu": "x64" }, "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg=="],
+
+ "lightningcss-win32-arm64-msvc": ["[email protected]", "", { "os": "win32", "cpu": "arm64" }, "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw=="],
+
+ "lightningcss-win32-x64-msvc": ["[email protected]", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="],
+
+ "locate-character": ["[email protected]", "", {}, "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA=="],
+
+ "loupe": ["[email protected]", "", {}, "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ=="],
+
+ "magic-string": ["[email protected]", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
+
+ "mri": ["[email protected]", "", {}, "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="],
+
+ "ms": ["[email protected]", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
+
+ "nanoid": ["[email protected]", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ=="],
+
+ "pathe": ["[email protected]", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
+
+ "pathval": ["[email protected]", "", {}, "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ=="],
+
+ "picocolors": ["[email protected]", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
+
+ "picomatch": ["[email protected]", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="],
+
+ "postcss": ["[email protected]", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg=="],
+
+ "react": ["[email protected]", "", {}, "sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q=="],
+
+ "readdirp": ["[email protected]", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
+
+ "rollup": ["[email protected]", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.60.4", "@rollup/rollup-android-arm64": "4.60.4", "@rollup/rollup-darwin-arm64": "4.60.4", "@rollup/rollup-darwin-x64": "4.60.4", "@rollup/rollup-freebsd-arm64": "4.60.4", "@rollup/rollup-freebsd-x64": "4.60.4", "@rollup/rollup-linux-arm-gnueabihf": "4.60.4", "@rollup/rollup-linux-arm-musleabihf": "4.60.4", "@rollup/rollup-linux-arm64-gnu": "4.60.4", "@rollup/rollup-linux-arm64-musl": "4.60.4", "@rollup/rollup-linux-loong64-gnu": "4.60.4", "@rollup/rollup-linux-loong64-musl": "4.60.4", "@rollup/rollup-linux-ppc64-gnu": "4.60.4", "@rollup/rollup-linux-ppc64-musl": "4.60.4", "@rollup/rollup-linux-riscv64-gnu": "4.60.4", "@rollup/rollup-linux-riscv64-musl": "4.60.4", "@rollup/rollup-linux-s390x-gnu": "4.60.4", "@rollup/rollup-linux-x64-gnu": "4.60.4", "@rollup/rollup-linux-x64-musl": "4.60.4", "@rollup/rollup-openbsd-x64": "4.60.4", "@rollup/rollup-openharmony-arm64": "4.60.4", "@rollup/rollup-win32-arm64-msvc": "4.60.4", "@rollup/rollup-win32-ia32-msvc": "4.60.4", "@rollup/rollup-win32-x64-gnu": "4.60.4", "@rollup/rollup-win32-x64-msvc": "4.60.4", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-WHeFSbZYsPu3+bLoNRUuAO+wavNlocOPf3wSHTP7hcFKVnJeWsYlCDbr3mTS14FCizf9ccIxXA8sGL8zKeQN3g=="],
+
+ "sade": ["[email protected]", "", { "dependencies": { "mri": "^1.1.0" } }, "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A=="],
+
+ "secure-json-parse": ["[email protected]", "", {}, "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw=="],
+
+ "siginfo": ["[email protected]", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="],
+
+ "source-map-js": ["[email protected]", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
+
+ "stackback": ["[email protected]", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="],
+
+ "std-env": ["[email protected]", "", {}, "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg=="],
+
+ "strip-literal": ["[email protected]", "", { "dependencies": { "js-tokens": "^9.0.1" } }, "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg=="],
+
+ "svelte": ["[email protected]", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/estree": "^1.0.5", "@types/trusted-types": "^2.0.7", "acorn": "^8.12.1", "aria-query": "5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", "devalue": "^5.8.1", "esm-env": "^1.2.1", "esrap": "^2.2.4", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" } }, "sha512-4D6lyrMHmDaZalQOEBMCWCCidyZjSnec14/oPn0k627G6goxcck9xqMwz1tFLlQz+ZFvtTTHfFOlUayuAz0z6Q=="],
+
+ "svelte-check": ["[email protected]", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "chokidar": "^4.0.1", "fdir": "^6.2.0", "picocolors": "^1.0.0", "sade": "^1.7.4" }, "peerDependencies": { "svelte": "^4.0.0 || ^5.0.0-next.0", "typescript": ">=5.0.0" }, "bin": { "svelte-check": "bin/svelte-check" } }, "sha512-67adfgBox5eNSNIvIIwgFizKGdcRrGpiMoNO2obHcYuLz7iTa8Xgm/NGU3ntMFnNm8K1grFOIG6HhMLX/vcN8w=="],
+
+ "swr": ["[email protected]", "", { "dependencies": { "dequal": "^2.0.3", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-2CC6CiKQtEwaEeNiqWTAw9PGykW8SR5zZX8MZk6TeAvEAnVS7Visz8WzphqgtQ8v2xz/4Q5K+j+SeMaKXeeQIA=="],
+
+ "tailwindcss": ["[email protected]", "", {}, "sha512-y6nxMGB1nMW9R6k96e5gdIFzcfL/gTJRNaqGes1YvkLnPVXzWgbqFF2yLC0T8G774n24cx3Pe8XrKoniCOAH+Q=="],
+
+ "tapable": ["[email protected]", "", {}, "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A=="],
+
+ "throttleit": ["[email protected]", "", {}, "sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw=="],
+
+ "tinybench": ["[email protected]", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="],
+
+ "tinyexec": ["[email protected]", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="],
+
+ "tinyglobby": ["[email protected]", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg=="],
+
+ "tinypool": ["[email protected]", "", {}, "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg=="],
+
+ "tinyrainbow": ["[email protected]", "", {}, "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw=="],
+
+ "tinyspy": ["[email protected]", "", {}, "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q=="],
+
+ "typescript": ["[email protected]", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
+
+ "undici-types": ["[email protected]", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="],
+
+ "use-sync-external-store": ["[email protected]", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="],
+
+ "vite": ["[email protected]", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ=="],
+
+ "vite-node": ["[email protected]", "", { "dependencies": { "cac": "^6.7.14", "debug": "^4.4.1", "es-module-lexer": "^1.7.0", "pathe": "^2.0.3", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" }, "bin": { "vite-node": "vite-node.mjs" } }, "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg=="],
+
+ "vitefu": ["[email protected]", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["vite"] }, "sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg=="],
+
+ "vitest": ["[email protected]", "", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/expect": "3.2.4", "@vitest/mocker": "3.2.4", "@vitest/pretty-format": "^3.2.4", "@vitest/runner": "3.2.4", "@vitest/snapshot": "3.2.4", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "debug": "^4.4.1", "expect-type": "^1.2.1", "magic-string": "^0.30.17", "pathe": "^2.0.3", "picomatch": "^4.0.2", "std-env": "^3.9.0", "tinybench": "^2.9.0", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.14", "tinypool": "^1.1.1", "tinyrainbow": "^2.0.0", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", "vite-node": "3.2.4", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@types/debug": "^4.1.12", "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "@vitest/browser": "3.2.4", "@vitest/ui": "3.2.4", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@types/debug", "@types/node", "@vitest/browser", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A=="],
+
+ "why-is-node-running": ["[email protected]", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="],
+
+ "zimmerframe": ["[email protected]", "", {}, "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ=="],
+
+ "zod": ["[email protected]", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
+
+ "zod-to-json-schema": ["[email protected]", "", { "peerDependencies": { "zod": "^3.25.28 || ^4" } }, "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA=="],
+
+ "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/[email protected]", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" }, "bundled": true }, "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw=="],
+
+ "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/[email protected]", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="],
+
+ "@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/[email protected]", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="],
+
+ "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/[email protected]", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" }, "bundled": true }, "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow=="],
+
+ "@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/[email protected]", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg=="],
+
+ "@tailwindcss/oxide-wasm32-wasi/tslib": ["[email protected]", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+ }
+}
diff --git a/context.md b/context.md
new file mode 100644
index 0000000..896eed4
--- /dev/null
+++ b/context.md
@@ -0,0 +1,169 @@
+# Dispatch — Phase 1 Implementation Context
+
+This file captures all decisions, open questions, and constraints established during planning. It serves as the source of truth for any agent working on Phase 1 implementation.
+
+---
+
+## Stack Decisions
+
+| Layer | Choice | Notes |
+|---|---|---|
+| Runtime | **Bun** | Runtime + package manager. Native SQLite, fast installs/execution |
+| Backend framework | **Hono.js** | Lightweight, WebSocket support |
+| Frontend framework | **Vite + Svelte + DaisyUI** | Strict TypeScript throughout |
+| Language | **TypeScript (strict mode)** | Both frontend and backend |
+| LLM SDK | **Vercel AI SDK (`ai`)** | Provider-agnostic, streaming, tool calling |
+| Default LLM | **DeepSeek V4 Flash Free** | Via OpenCode Go (Zen). Hardcoded for Phase 1 |
+| Database | **SQLite (no ORM)** | `bun:sqlite` (native), no Drizzle or other ORM |
+| Testing | **Vitest** | Set up across all packages from day one |
+| Linting/Formatting | **Biome** | Single tool for both linting and formatting. Confirmed compatible with OpenCode |
+| Package Manager | **Bun workspaces** | Monorepo with `@dispatch/*` packages |
+| Dev Server | **Separate ports + CORS** | Frontend `:5173`, backend `:3000`, explicit CORS |
+
+---
+
+## Resolved Decisions
+
+### Streaming Architecture: WebSocket Only
+All real-time communication flows over a single WebSocket connection:
+- Chat messages (user -> server)
+- Streaming LLM tokens (server -> client)
+- Tool call notifications and results (server -> client)
+- Agent status updates (server -> client)
+
+`POST /chat` is not a streaming endpoint — it just queues/sends a message. The WebSocket handles all real-time data. `GET /status` remains as a simple REST endpoint for polling agent state.
+
+### Database: SQLite Without ORM
+No Drizzle ORM. Use `better-sqlite3` (or equivalent) directly with raw SQL. Keep it simple. The question of whether to set up persistence in Phase 1 or defer to Phase 5 is still open — the user indicated willingness to use SQLite but the timing wasn't finalized. **Default assumption: defer heavy persistence to Phase 5, but the library choice is locked in.**
+
+### Tool Scoping: Working Directory
+File tools (`read_file`, `write_file`, `list_files`) will be scoped to a configurable working directory from Phase 1. This prevents accidental filesystem damage and provides a clean foundation for the permission system in Phase 2.
+
+### Testing: Vitest From Day One
+Vitest set up across the monorepo. Unit tests for core logic (agent loop, tool registry, individual tools).
+
+### DaisyUI Theme: Configurable With Persistence
+Support multiple DaisyUI themes with a user-selectable theme switcher in settings. The selected theme is remembered across sessions (localStorage or equivalent).
+
+### Default LLM Provider: DeepSeek V4 Flash via OpenRouter
+The Phase 1 hardcoded model is DeepSeek V4 Flash, accessed through OpenRouter. This means:
+- The Vercel AI SDK OpenRouter provider will be used
+- A single `OPENROUTER_API_KEY` env var (or equivalent) is needed
+- Model ID will be the OpenRouter model string for DeepSeek V4 Flash
+
+---
+
+## Resolved Decisions (Previously Open Questions)
+
+### 1. Package Manager and Monorepo Tooling: Bun Workspaces
+**Decision:** Bun as both runtime and package manager, using Bun workspaces.
+
+Bun workspaces work similarly to pnpm workspaces — `packages/core`, `packages/api`, and `packages/frontend` reference each other as `@dispatch/*` dependencies, and Bun symlinks them locally.
+
+### 2. Runtime: Bun
+**Decision:** Bun is the runtime.
+
+Implications:
+- Native SQLite via `bun:sqlite` — no need for `better-sqlite3`
+- Bun is faster for installs, script execution, and testing
+- Vitest is the test runner (works with Bun)
+- No Node.js version to manage
+
+### 3. Dev Server Setup: Separate Ports + CORS
+**Decision:** Frontend on `:5173` (Vite dev server), backend on `:3000` (Hono). Explicit CORS configuration on the backend.
+
+This means:
+- Hono backend needs CORS middleware allowing the frontend origin
+- WebSocket connections go directly to `:3000` from the frontend
+- No Vite proxy configuration needed
+- Production will also be cross-origin (matches the split deployment model)
+
+### 4. Biome Compatibility: Confirmed
+**Decision:** Biome is fully compatible with OpenCode.
+
+OpenCode has Biome as a **built-in formatter**. It auto-detects `biome.json(c)` config files and handles `.js`, `.jsx`, `.ts`, `.tsx`, `.html`, `.css`, `.md`, `.json`, `.yaml`, and more. Just needs a `biome.json` in the project root and formatters enabled in OpenCode config (`"formatter": true` or `"formatter": {}`).
+
+---
+
+## Phase 1 Scope (from plan.md)
+
+**Goal:** Chat with one agent in a browser, watch it read and write files.
+
+### Backend Tasks
+- Project scaffolding (monorepo with `packages/core`, `packages/api`, `packages/frontend`)
+- Agent runtime: message -> LLM -> tool call -> result -> repeat loop
+- Vercel AI SDK integration with streaming responses
+- Single provider config (DeepSeek V4 Flash via OpenRouter, env var for API key)
+- Basic tools:
+ - `read_file` — read file contents (scoped to working directory)
+ - `write_file` — write/overwrite a file (scoped to working directory)
+ - `list_files` — glob/list directory contents (scoped to working directory)
+- HTTP API:
+ - `POST /chat` — send a message (non-streaming, queues to agent)
+ - `GET /status` — agent status (idle, running, etc.)
+- WebSocket: stream agent output tokens and tool calls in real-time
+
+### Frontend Tasks
+- Single chat panel — text input field, send button
+- Streamed response rendering (tokens appear as they arrive via WebSocket)
+- Tool call display (collapsible: show tool name, arguments, result)
+- Model/provider indicator in header
+- Basic layout: chat takes full screen, clean and minimal
+- Theme switcher in settings (DaisyUI themes, persisted to localStorage)
+
+### Done When
+Open a browser, type "read the contents of package.json and summarize it," see the agent call `read_file`, stream back a summary. Ask it to create a new file — it calls `write_file` and confirms.
+
+---
+
+## Project Structure (Planned)
+
+```
+dispatch/
+ packages/
+ core/ # Agent runtime, LLM integration, tools
+ src/
+ agent/ # Agent loop, lifecycle
+ llm/ # Vercel AI SDK wrapper, provider config
+ tools/ # Tool registry, built-in tools (read_file, write_file, list_files)
+ types/ # Shared TypeScript types
+ tests/
+ api/ # Hono HTTP + WebSocket server
+ src/
+ routes/ # HTTP route handlers
+ ws/ # WebSocket handlers
+ tests/
+ frontend/ # Vite + Svelte + DaisyUI client
+ src/
+ lib/ # Svelte components, stores, utilities
+ routes/ # Page routes (if using SvelteKit) or views
+ tests/
+ biome.json # Biome config (pending compatibility check)
+ tsconfig.base.json # Shared TypeScript config
+ package.json # Root workspace config
+ .env.example # Environment variables template (OPENROUTER_API_KEY, etc.)
+```
+
+---
+
+## Concurrency Map (What Can Be Built in Parallel)
+
+Once scaffolding is complete, the following sections can be developed concurrently:
+
+```
+[A] Scaffolding (sequential — must be first)
+ |
+ +---> [B] Core Agent Runtime (agent loop, LLM, tool system)
+ |
+ +---> [C] API Server Shell (Hono setup, route stubs, WebSocket setup)
+ |
+ +---> [D] Frontend Shell (Svelte app, chat UI, WebSocket client, theme system)
+
+Then integration (sequential — depends on B, C, D):
+
+[E] Wire core into API (connect agent runtime to routes/WebSocket)
+[F] Wire frontend to API (connect UI to live WebSocket)
+[G] End-to-end testing and polish
+```
+
+Sections B, C, and D are independent and can be coded by concurrent agents. Section E requires B and C. Section F requires C and D. Section G requires everything.
diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml
new file mode 100644
index 0000000..2809e01
--- /dev/null
+++ b/docker-compose.prod.yml
@@ -0,0 +1,17 @@
+services:
+ api:
+ build:
+ context: .
+ dockerfile: Dockerfile
+ ports:
+ - "3000:3000"
+ environment:
+ OPENCODE_API_KEY: ${OPENCODE_API_KEY}
+ DISPATCH_MODEL: ${DISPATCH_MODEL:-deepseek-v4-flash-free}
+ DISPATCH_WORKING_DIR: /app/workspace
+ volumes:
+ - workspace:/app/workspace
+ restart: unless-stopped
+
+volumes:
+ workspace:
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..770c83a
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,24 @@
+services:
+ api:
+ build:
+ context: .
+ dockerfile: Dockerfile.dev
+ command: ["bun", "--watch", "packages/api/src/index.ts"]
+ ports:
+ - "3000:3000"
+ volumes:
+ - .:/app
+ environment:
+ OPENCODE_API_KEY: ${OPENCODE_API_KEY:-}
+ DISPATCH_MODEL: ${DISPATCH_MODEL:-deepseek-v4-flash-free}
+ DISPATCH_WORKING_DIR: /app
+
+ frontend:
+ build:
+ context: .
+ dockerfile: Dockerfile.dev
+ command: ["bun", "run", "--cwd", "packages/frontend", "dev", "--", "--host"]
+ ports:
+ - "5173:5173"
+ volumes:
+ - .:/app
diff --git a/docker/entrypoint.dev.sh b/docker/entrypoint.dev.sh
new file mode 100644
index 0000000..b28b44f
--- /dev/null
+++ b/docker/entrypoint.dev.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+set -euo pipefail
+
+# Install/update dependencies.
+# Source code is bind-mounted from the host; node_modules lives on the host
+# filesystem via the bind mount so it persists across container restarts
+# and is shared between the api and frontend services.
+bun install
+
+# Execute the main command
+exec "$@"
diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh
new file mode 100644
index 0000000..4f35d94
--- /dev/null
+++ b/docker/entrypoint.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+set -euo pipefail
+
+# Production entrypoint
+# Future phases: add database migrations here
+
+# Execute the main command
+exec "$@"
diff --git a/harness-comparison.md b/harness-comparison.md
new file mode 100644
index 0000000..1fd7aec
--- /dev/null
+++ b/harness-comparison.md
@@ -0,0 +1,373 @@
+# Open-Source AI Agent Harness Comparison
+
+This report evaluates 20+ open-source AI agent frameworks against the Dispatch requirements. Frameworks are rated on a 15-point checklist covering architecture, configuration, tooling, integration, and session management. Each requirement scores 1 (fully supported), 0.5 (partial), or 0 (not supported), for a maximum of 15.
+
+Frameworks that are archived, in maintenance mode, or clearly unsuitable (GPT-Engineer, Mentat, Sweep, Bolt.diy, SuperAGI, Semantic Kernel) are excluded from the main comparison but noted in the appendix.
+
+---
+
+## The Critical Gap: No Framework Has a Three-Layer Hierarchy
+
+The single most distinctive Dispatch requirement -- a three-layer **dispatch -> orchestrator -> subagent** architecture -- does not exist natively in any open-source framework evaluated. Every framework is either:
+
+- **Single-agent** (Aider, Crush, Plandex, Pi)
+- **Two-layer** (Claude Code, Goose, Cline, Agency Swarm, CrewAI)
+- **Flat pool** (AutoGen, MetaGPT, CAMEL)
+- **DAG-based** (LangGraph, ChatDev 2.0) -- the closest to true hierarchy via subgraph nesting
+
+This means **any choice involves building the hierarchical orchestration layer yourself**. The question becomes: which framework gives you the best foundation to build on?
+
+---
+
+## Top Contenders
+
+### Tier 1: Highest Feature Alignment (87% match)
+
+#### Goose (Block / AAIF) -- 13/15
+
+| Requirement | Rating | Notes |
+|---|---|---|
+| 1. Three-layer hierarchy | Partial | Main agent -> subagents (2 layers). Cannot nest further. |
+| 2. Config-driven orchestrators | **Full** | YAML recipes define subagent behavior, extensions, parameters |
+| 3. Parallel subagent execution | **Full** | Native parallel subagent support via trigger keywords |
+| 4. Strict hierarchy communication | **Full** | Subagents cannot spawn further subagents or manage extensions |
+| 5. User-to-agent messaging | **Full** | Continuous sessions, real-time subagent visibility |
+| 6. Conflict prevention | Partial | Process isolation; no explicit file-scope assignment |
+| 7. Role-scoped tooling | **Full** | Per-subagent extension sets via recipes |
+| 8. Skills system | **Full** | `~/.agents/skills/` and `.agents/skills/` with SKILL.md |
+| 9. LSP integration | **None** | No LSP support |
+| 10. Shell + directory perms | **Full** | Permission modes (auto/approve/smart_approve), allowlists |
+| 11. Session management | **Full** | Start, resume, search, model switching |
+| 12. HITL checkpoints | **Full** | Permission modes, per-action approval |
+| 13. State persistence | **Full** | Session persistence across restarts |
+| 14. Provider-agnostic LLM | **Full** | 15+ providers |
+| 15. Multiple interfaces | **Full** | Desktop app, CLI, API (ACP server) |
+
+**Language**: Rust + TypeScript. **Stars**: 45.5k. **Status**: Very active, moved to Linux Foundation AAIF (Apr 2026). Apache 2.0 license.
+
+**Strengths**: Closest to Dispatch's architecture out of the box. Config-driven recipes, parallel subagents, strict hierarchy enforcement, skills system, permission model, and multi-interface support all map directly to requirements. The MCP extension ecosystem (70+ extensions) provides broad tool coverage.
+
+**Weaknesses**: Only 2 layers of hierarchy (no recursive nesting). No LSP. Written in Rust, which makes deep architectural modification harder than Python/TypeScript. Subagents cannot spawn sub-subagents.
+
+**Extensibility verdict**: Adding a third layer would mean building an orchestrator abstraction that manages multiple Goose agent instances, each of which manages its own subagents. The recipe system could potentially be extended to define orchestrator types.
+
+---
+
+#### Cline -- 13/15
+
+| Requirement | Rating | Notes |
+|---|---|---|
+| 1. Three-layer hierarchy | Partial | Coordinator -> specialist agents (2 layers via SDK) |
+| 2. Config-driven orchestrators | Partial | Code-driven SDK; CLI flags provide some config |
+| 3. Parallel subagent execution | **Full** | Kanban enables parallel agents with separate worktrees |
+| 4. Strict hierarchy communication | Partial | Coordinator pattern implies parent-mediated, not enforced |
+| 5. User-to-agent messaging | **Full** | Interactive CLI, `ask_question` tool |
+| 6. Conflict prevention | Partial | Kanban uses Git worktrees for isolation |
+| 7. Role-scoped tooling | **Full** | Per-agent tool sets via plugin system |
+| 8. Skills system | **Full** | `.agents/skills/` with SKILL.md files |
+| 9. LSP integration | **Full** | VS Code extension integrates with editor LSP |
+| 10. Shell + directory perms | **Full** | Command permission allow/deny lists |
+| 11. Session management | **Full** | History, persistence, model override per run |
+| 12. HITL checkpoints | **Full** | Plan/Act modes, per-action approval, auto-approve toggle |
+| 13. State persistence | **Full** | Sessions persist across restarts, snapshot/restore |
+| 14. Provider-agnostic LLM | **Full** | 200+ models via OpenRouter, plus direct providers |
+| 15. Multiple interfaces | **Full** | CLI, VS Code, JetBrains, Kanban web, SDK |
+
+**Language**: TypeScript. **Stars**: 62k. **Status**: Very active (v3.0.7, May 2026). Apache 2.0 license.
+
+**Strengths**: The only framework with real LSP integration (through VS Code). Broadest interface coverage (IDE extensions, CLI, web Kanban, SDK). Git worktree isolation in Kanban is a creative approach to conflict prevention. The SDK (`@cline/core`, `@cline/agents`, `@cline/llms`) is well-layered and embeddable.
+
+**Weaknesses**: Orchestration is code-driven, not config-driven -- no YAML orchestrator definitions. LSP integration is tied to the VS Code extension host; unclear if it works outside IDE context. Hierarchy enforcement is not strict. TypeScript-only.
+
+**Extensibility verdict**: The layered SDK architecture is a strong foundation. You could build the dispatch and orchestrator layers on top of `@cline/core` and `@cline/agents`. The plugin system (`AgentPlugin`) provides lifecycle hooks. However, adding config-driven orchestrator definitions would require custom work.
+
+---
+
+#### Claude Code (Anthropic) -- 13/15 (NOT fully open source)
+
+| Requirement | Rating | Notes |
+|---|---|---|
+| 1. Three-layer hierarchy | Partial | Main agent -> subagents (2 layers). Agent teams add peer coordination |
+| 2. Config-driven orchestrators | Partial | Subagents defined via YAML frontmatter in .md files |
+| 3. Parallel subagent execution | **Full** | Multiple concurrent subagents + agent teams |
+| 4. Strict hierarchy communication | **Full** | Subagents report to parent only |
+| 5. User-to-agent messaging | **Full** | Shift+Down for in-process subagent messaging |
+| 6. Conflict prevention | Partial | Git worktrees, file locking in agent teams |
+| 7. Role-scoped tooling | **Full** | `tools` and `disallowedTools` in subagent YAML frontmatter |
+| 8. Skills system | **Full** | Full Agent Skills standard, YAML frontmatter, multi-scope |
+| 9. LSP integration | **Full** | Through IDE integrations (VS Code, JetBrains) |
+| 10. Shell + directory perms | **Full** | allow/ask/deny rules, wildcard matching, sandboxing |
+| 11. Session management | **Full** | Resume, fork (`/fork`), model switch (`/model`), persistence |
+| 12. HITL checkpoints | **Full** | Permission modes, plan mode, hooks for custom approval |
+| 13. State persistence | **Full** | Full session persistence in `~/.claude/projects/` |
+| 14. Provider-agnostic LLM | Partial | Primarily Claude; Bedrock/Vertex/Azure as backends |
+| 15. Multiple interfaces | **Full** | CLI, VS Code, JetBrains, Desktop, Web, Slack, SDKs |
+
+**Language**: Proprietary core (distributed as npm binary); Shell/Python/TypeScript for installer, plugins, examples. **Stars**: 125k. **Status**: Very active. **License**: Partially open source -- core engine is proprietary.
+
+**Strengths**: Highest overall feature coverage. Best permission system. Best skills system. Chat forking built-in. Agent SDK available in Python and TypeScript. Hooks system (PreToolUse, PostToolUse, SubagentStart/Stop) provides excellent lifecycle control.
+
+**Weaknesses**: **Core engine is proprietary.** You cannot fork or modify the agent loop. Primarily Claude-only for models. Building on top means depending on Anthropic's closed binary. This is a dealbreaker if full ownership of the codebase is required.
+
+**Extensibility verdict**: If you're comfortable depending on a proprietary core, Claude Code's Agent SDK is probably the fastest path to a Dispatch-like system. But you'd be building on a dependency you cannot modify or audit internally.
+
+---
+
+### Tier 2: Strong Architectural Foundation (53-60% match)
+
+#### LangGraph -- 9/15
+
+| Requirement | Rating | Notes |
+|---|---|---|
+| 1. Three-layer hierarchy | **Full** | Arbitrary subgraph nesting with private state schemas |
+| 2. Config-driven orchestrators | **None** | Purely code-defined (Python) |
+| 3. Parallel subagent execution | **Full** | Parallel edges, `Send()` for map-reduce fan-out |
+| 4. Strict hierarchy communication | **Full** | Subgraph state isolation via separate schemas |
+| 5. User-to-agent messaging | **Full** | `interrupt()` / `Command(resume=...)` anywhere |
+| 6. Conflict prevention | **None** | No file-scope mechanisms |
+| 7. Role-scoped tooling | **Full** | Per-node tool sets |
+| 8. Skills system | **None** | No skills/instruction injection system |
+| 9. LSP integration | **None** | No LSP |
+| 10. Shell + directory perms | **None** | No built-in shell or permissions |
+| 11. Session management | Partial | Checkpointer history, time travel, `update_state()` |
+| 12. HITL checkpoints | **Full** | `interrupt()`, static breakpoints, approval patterns |
+| 13. State persistence | **Full** | Multiple checkpointers (SQLite, Postgres, CosmosDB) |
+| 14. Provider-agnostic LLM | **Full** | All LangChain providers + standalone |
+| 15. Multiple interfaces | Partial | Python API, LangSmith Studio, LangGraph API |
+
+**Language**: Python. **Stars**: 32.4k. **Status**: Very active (v1.2.0, May 2026).
+
+**Key insight**: LangGraph is the **only framework that natively supports arbitrary hierarchy depth** via subgraph nesting. This is the single most important Dispatch requirement. The trade-off: it has zero application-layer features (no skills, no shell, no LSP, no session management in the user-facing sense). It's a low-level orchestration runtime, not an end-user tool.
+
+**Extensibility verdict**: LangGraph provides the best orchestration primitives but you'd need to build everything else on top -- the CLI, the skills system, the shell with permissions, the LSP integration, session management UI. It's essentially a graph execution engine, not an agent harness.
+
+---
+
+#### CrewAI -- 9/15
+
+| Requirement | Rating | Notes |
+|---|---|---|
+| 1. Three-layer hierarchy | Partial | Manager -> agents (2 layers); Flows chain crews |
+| 2. Config-driven orchestrators | **Full** | YAML `agents.yaml`, `tasks.yaml` |
+| 3. Parallel subagent execution | **Full** | `async_execution=True` on tasks |
+| 4. Strict hierarchy communication | Partial | Manager delegates but `allow_delegation` enables P2P |
+| 5. User-to-agent messaging | Partial | `@human_feedback` at configured points only |
+| 6. Conflict prevention | **None** | No file-scope mechanisms |
+| 7. Role-scoped tooling | **Full** | Per-agent tools, task tool overrides |
+| 8. Skills system | Partial | Agent templates, prompt customization |
+| 9. LSP integration | **None** | No LSP |
+| 10. Shell + directory perms | **None** | Code execution deprecated |
+| 11. Session management | Partial | Flow persist/fork via `@persist` |
+| 12. HITL checkpoints | **Full** | `@human_feedback`, `human_input=True` |
+| 13. State persistence | **Full** | `@persist` with SQLite |
+| 14. Provider-agnostic LLM | **Full** | Many providers via LiteLLM |
+| 15. Multiple interfaces | Partial | CLI + Python API |
+
+**Language**: Python. **Stars**: 51.7k. **Status**: Very active, backed by CrewAI Inc.
+
+**Extensibility verdict**: Best config-driven agent/task definitions. The YAML approach maps well to Dispatch's config-driven orchestrators. However, no shell access, no LSP, no skills directory system. Better suited for general automation than coding-specific workflows.
+
+---
+
+#### ChatDev 2.0 -- 9/15
+
+| Requirement | Rating | Notes |
+|---|---|---|
+| 1. Three-layer hierarchy | Partial | DAG + subgraph nesting, not strict tree |
+| 2. Config-driven orchestrators | **Full** | Full YAML workflow definitions |
+| 3. Parallel subagent execution | **Full** | Map/Tree modes with `max_parallel` |
+| 4. Strict hierarchy communication | Partial | Edge-routed but no parent-only enforcement |
+| 5. User-to-agent messaging | Partial | Human nodes in DAG at predefined points |
+| 6. Conflict prevention | **None** | No file-scope mechanisms |
+| 7. Role-scoped tooling | **Full** | Per-node tooling in YAML |
+| 8. Skills system | Partial | `.agents/skills` directory exists |
+| 9. LSP integration | **None** | No LSP |
+| 10. Shell + directory perms | **None** | No permission system |
+| 11. Session management | Partial | Context snapshots, no forking/resume |
+| 12. HITL checkpoints | **Full** | Human nodes + edge conditions |
+| 13. State persistence | Partial | Artifacts persist, no execution recovery |
+| 14. Provider-agnostic LLM | **Full** | Per-node provider config |
+| 15. Multiple interfaces | **Full** | Web UI, CLI, HTTP API, Python SDK |
+
+**Language**: Python + Vue.js. **Stars**: 33.1k. **Status**: Active (v2.2.0, Mar 2026). Very new (released Jan 2026).
+
+**Extensibility verdict**: Best zero-code workflow definition system. YAML DAGs with subgraphs, parallel execution, and multiple interfaces. The main risk is maturity -- ChatDev 2.0 is only months old and documentation is still evolving.
+
+---
+
+#### OpenHands -- 8.5/15
+
+| Requirement | Rating | Notes |
+|---|---|---|
+| 1. Three-layer hierarchy | **None** | Single Conversation -> Agent -> Tools pipeline |
+| 2. Config-driven orchestrators | Partial | SDK is code-driven, config template exists |
+| 3. Parallel subagent execution | **None** | One agent step at a time per conversation |
+| 4. Strict hierarchy communication | **None** | No hierarchy enforced |
+| 5. User-to-agent messaging | **Full** | `send_message()` at any time via WebSocket |
+| 6. Conflict prevention | **None** | No scope assignment |
+| 7. Role-scoped tooling | **Full** | Per-agent tool sets via typed Action/Observation |
+| 8. Skills system | **Full** | Three skill types, YAML frontmatter, MCP integration |
+| 9. LSP integration | **None** | No LSP |
+| 10. Shell + directory perms | Partial | Risk-based security (LOW/MEDIUM/HIGH), not directory-based |
+| 11. Session management | Partial | Persistence + resume, no forking or model switching |
+| 12. HITL checkpoints | **Full** | ConfirmationPolicy with configurable thresholds |
+| 13. State persistence | **Full** | Auto-save, resume, incremental events |
+| 14. Provider-agnostic LLM | **Full** | 100+ providers via LiteLLM |
+| 15. Multiple interfaces | **Full** | CLI, React GUI, Cloud, Enterprise, SDK, REST API |
+
+**Language**: Python + TypeScript. **Stars**: 74.1k. **Status**: Very active (v1.7.0, May 2026). MIT license.
+
+**Extensibility verdict**: The four-package SDK architecture (`openhands.sdk`, `openhands.tools`, `openhands.workspace`, `openhands.agent_server`) is clean and composable. The event-driven design provides good extension points. However, no native multi-agent hierarchy -- you'd build the orchestration layer from scratch using the SDK primitives. Best choice if you want a battle-tested single-agent SDK with excellent security and skills, and are willing to build hierarchy on top.
+
+---
+
+#### Crush (OpenCode successor) -- 8/15
+
+| Requirement | Rating | Notes |
+|---|---|---|
+| 1. Three-layer hierarchy | **None** | Single-agent with `agent` tool for sub-tasks |
+| 2. Config-driven orchestrators | **None** | No orchestrator concept |
+| 3. Parallel subagent execution | **None** | Sub-agent tool is sequential |
+| 4. Strict hierarchy communication | Partial | Agent tool returns results; no P2P |
+| 5. User-to-agent messaging | **None** | User types at main session only |
+| 6. Conflict prevention | **None** | No scope assignment |
+| 7. Role-scoped tooling | **Full** | Different agents can have different tool sets via config |
+| 8. Skills system | **Full** | Agent Skills standard, reads `.crush/`, `.claude/`, `.agents/` |
+| 9. LSP integration | **Full** | Built-in LSP with configurable language servers |
+| 10. Shell + directory perms | Partial | `allowed_tools` allowlist, no directory scoping |
+| 11. Session management | **Full** | Save/load/switch, model switching, SQLite persistence |
+| 12. HITL checkpoints | **None** | No checkpoint system |
+| 13. State persistence | **Full** | SQLite-based session persistence |
+| 14. Provider-agnostic LLM | **Full** | 15+ providers |
+| 15. Multiple interfaces | **Full** | Interactive TUI, CLI, scripting |
+
+**Language**: Go. **Stars**: 24.4k. **Status**: Very active (v0.70.0, May 2026). MIT license.
+
+**Key insight**: The only CLI-native framework with built-in LSP integration for compiler diagnostics. If LSP is a hard requirement, Crush is one of only two options (the other being Cline's VS Code extension). However, it has no multi-agent architecture at all.
+
+---
+
+#### Pi.dev -- 6.5/15
+
+| Requirement | Rating | Notes |
+|---|---|---|
+| 1. Three-layer hierarchy | **None** | Single-agent. Subagent extension is a bash demo. |
+| 2. Config-driven orchestrators | **None** | No orchestrator concept |
+| 3. Parallel subagent execution | **None** | Subagent extension demo: 8 tasks, 4 concurrent, via bash |
+| 4. Strict hierarchy communication | **None** | No agent communication framework |
+| 5. User-to-agent messaging | **Full** | Built-in steer/follow-up message queuing |
+| 6. Conflict prevention | **None** | "YOLO mode" by design |
+| 7. Role-scoped tooling | Partial | `--tools` flag restricts globally, no role system |
+| 8. Skills system | Partial | Agent Skills standard, but no `default/agents/project/` dirs |
+| 9. LSP integration | **None** | No LSP |
+| 10. Shell + directory perms | **None** | Full unrestricted access by design |
+| 11. Session management | **Full** | Tree-structured, fork, clone, resume, model switch |
+| 12. HITL checkpoints | Partial | `tool_call` event can block; no built-in checkpoint system |
+| 13. State persistence | **Full** | JSONL auto-save, sessions survive restarts |
+| 14. Provider-agnostic LLM | **Full** | 15+ providers, cross-provider context handoff |
+| 15. Multiple interfaces | **Full** | TUI, CLI, JSON, RPC, SDK, web UI package |
+
+**Language**: TypeScript. **Stars**: 51.4k. **Status**: Very active (v0.75.3, May 2026). MIT license.
+
+**Key insight**: Pi has the best session management (tree-structured branching with fork/clone/resume) and the most powerful extension system (30+ lifecycle events, full tool registration, UI components). The `@earendil-works/pi-ai` and `@earendil-works/pi-agent-core` packages are clean, well-documented TypeScript libraries. However, the maintainer explicitly rejects multi-agent patterns ("sub-agents are an anti-pattern"). Building Dispatch on Pi means fighting its philosophy.
+
+**Best use**: Harvest `pi-ai` (LLM abstraction) and `pi-agent-core` (agent runtime) as libraries in a custom system, rather than extending the Pi CLI.
+
+---
+
+### Eliminated Frameworks
+
+| Framework | Score | Reason for Elimination |
+|---|---|---|
+| AutoGen (AG2) | 8.5/15 | **Maintenance mode.** Microsoft recommends migrating to Agent Framework. No new features. |
+| Agency Swarm | 8/15 | 2-layer only, code-defined, no LSP/shell/sessions. Active but smaller community (4.4k stars). |
+| CAMEL | 6.5/15 | Research-oriented. Sequential workforce. No production features (sessions, perms). |
+| Plandex | 6/15 | Single-agent. Strong for plan-then-execute but no hierarchy or multi-agent. |
+| MetaGPT | 5/15 | Flat role pool, sequential, broadcast communication. Team pivoting to commercial MGX. |
+| Aider | 5/15 | Single-agent pair programmer. No hierarchy, no subagents, no persistence. |
+| Semantic Kernel | 4.5/15 | SDK-only, no interfaces, experimental orchestration. Being superseded by MS Agent Framework. |
+| SuperAGI | 5.5/15 | Stale (v0.0.11). Single-agent. No hierarchy. |
+| SWE-agent | 5/15 | Maintenance mode, superseded by mini-SWE-agent. Academic benchmarking tool. |
+| GPT-Engineer | N/A | Archived April 2026. |
+| Mentat | N/A | Archived January 2025. |
+| Sweep | N/A | Pivoted to JetBrains plugin. |
+| Bolt.diy | N/A | Web app builder, not an agent harness. |
+| Continue | N/A | Pivoted to CI/CD checks product. |
+
+---
+
+## Summary: Ratings at a Glance
+
+| Rank | Framework | Score | % | Language | Stars | Key Strength | Key Gap |
+|---|---|---|---|---|---|---|---|
+| 1 | **Goose** | 13/15 | 87% | Rust/TS | 45.5k | Closest architecture match (recipes, subagents, permissions) | No LSP, no 3rd layer |
+| 1 | **Cline** | 13/15 | 87% | TypeScript | 62k | Only framework with LSP + parallel agents + SDK | Config-driven orchestration is weak |
+| 1 | **Claude Code** | 13/15 | 87% | Proprietary | 125k | Most complete feature set, best permissions/skills | **Not fully open source** |
+| 4 | **LangGraph** | 9/15 | 60% | Python | 32.4k | Only native arbitrary-depth hierarchy | Zero application features (no shell, skills, UI) |
+| 4 | **CrewAI** | 9/15 | 60% | Python | 51.7k | Best config-driven agents (YAML) | No shell, no LSP, no skills dirs |
+| 4 | **ChatDev 2.0** | 9/15 | 60% | Python/Vue | 33.1k | Best zero-code YAML workflows | Very new (Jan 2026), immature |
+| 7 | **OpenHands** | 8.5/15 | 57% | Python/TS | 74.1k | Best SDK architecture, 100+ LLM providers | No hierarchy, no parallel agents |
+| 8 | **Crush** | 8/15 | 53% | Go | 24.4k | Only CLI with built-in LSP | No multi-agent anything |
+| 9 | **Pi.dev** | 6.5/15 | 43% | TypeScript | 51.4k | Best session management + extension system | Anti-multi-agent philosophy |
+
+---
+
+## Recommendation: Build vs. Extend
+
+No existing framework is a drop-in match. The choice depends on which gaps you're most willing to fill:
+
+### Option A: Extend Goose
+**Best if**: You want the most features out of the box and are comfortable with Rust/TypeScript.
+- **Already have**: Subagents, parallel execution, config recipes, skills, permissions, session management, multi-interface
+- **Must build**: Third orchestrator layer, LSP integration, custom directory permissions, skills directory restructuring
+- **Risk**: Rust codebase makes deep architectural changes harder. Goose's subagent model may resist being generalized into a full orchestrator pattern.
+
+### Option B: Build on Cline SDK
+**Best if**: You want LSP and a well-layered TypeScript SDK.
+- **Already have**: LSP, parallel agents (Kanban), skills, permissions, sessions, plugins, IDE integration
+- **Must build**: Config-driven orchestrator definitions, third dispatch layer, strict hierarchy enforcement
+- **Risk**: Cline is IDE-first; extracting the SDK for standalone use may have rough edges.
+
+### Option C: Build on LangGraph
+**Best if**: You prioritize getting the hierarchy right and are comfortable building everything else.
+- **Already have**: Arbitrary hierarchy depth, parallel execution, interrupts, state persistence, provider support
+- **Must build**: Skills system, shell + permissions, LSP, session management UI, CLI/TUI, config-driven orchestrator definitions
+- **Risk**: Enormous amount of application-layer work. LangGraph is an execution engine, not an end-user tool.
+
+### Option D: Build from Scratch, Harvest Libraries
+**Best if**: You want full architectural control and no framework fights.
+- **Harvest from Pi.dev**: `@earendil-works/pi-ai` (LLM abstraction), `@earendil-works/pi-agent-core` (agent runtime)
+- **Harvest from Cline**: `@cline/llms` (provider gateway), `@cline/agents` (stateless agent loop)
+- **Harvest from Crush**: LSP integration patterns (Go)
+- **Must build**: Everything else -- dispatch layer, orchestrator management, hierarchy enforcement, skills system, permissions, session management
+- **Risk**: Highest initial effort, but cleanest architecture alignment.
+
+---
+
+## Sources
+
+- [Goose GitHub](https://github.com/aaif-goose/goose) -- Architecture, subagents, skills, security docs
+- [Goose Documentation](https://goose-docs.ai/) -- Subagents, recipes, config, sessions, security guides
+- [Cline GitHub](https://github.com/cline/cline) -- SDK, multi-agent, Kanban, plugins
+- [Cline Documentation](https://docs.cline.bot/) -- SDK architecture, tools, CLI, building agents
+- [Claude Code GitHub](https://github.com/anthropics/claude-code) -- Installer, plugins, examples
+- [Claude Code Documentation](https://code.claude.com/docs/en/overview) -- Subagents, skills, permissions, hooks, agent teams, SDK
+- [LangGraph GitHub](https://github.com/langchain-ai/langgraph) -- Graph API, subgraphs, interrupts, persistence
+- [LangGraph Documentation](https://docs.langchain.com/oss/python/langgraph/) -- Overview, subgraphs, interrupts, persistence
+- [CrewAI GitHub](https://github.com/crewAIInc/crewAI) -- YAML config, agents, tasks, flows
+- [CrewAI Documentation](https://docs.crewai.com/) -- Agents, tasks, flows, processes, memory
+- [ChatDev GitHub](https://github.com/OpenBMB/ChatDev) -- Workflow authoring guide, YAML definitions
+- [OpenHands GitHub](https://github.com/OpenHands/OpenHands) -- SDK overview
+- [OpenHands SDK Docs](https://docs.openhands.dev/sdk) -- Architecture, agent, conversation, LLM, skills, security
+- [Crush GitHub](https://github.com/charmbracelet/crush) -- LSP, sessions, skills, providers
+- [Pi.dev GitHub](https://github.com/earendil-works/pi) -- Extensions, skills, SDK, sessions
+- [Pi.dev Extensions Docs](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/extensions.md) -- Event lifecycle, tool registration
+- [Pi.dev Blog](https://mariozechner.at/posts/2025-11-30-pi-coding-agent/) -- Design philosophy
+- [Agency Swarm GitHub](https://github.com/VRSEN/agency-swarm) -- Communication flows, agents
+- [Agency Swarm Docs](https://agency-swarm.ai/) -- Agencies, agents, running
+- [AutoGen GitHub](https://github.com/microsoft/autogen) -- Maintenance mode notice, teams, HITL
+- [CAMEL GitHub](https://github.com/camel-ai/camel) -- Workforce, toolkits, model factory
+- [MetaGPT GitHub](https://github.com/geekan/MetaGPT) -- Roles, teams, serialization
+- [Plandex GitHub](https://github.com/plandex-ai/plandex) -- Plan-execute workflow, diff sandbox
+- [Aider GitHub](https://github.com/Aider-AI/aider) -- Conventions, modes, LLM support
+- [SWE-agent GitHub](https://github.com/SWE-agent/SWE-agent) -- Architecture, tools, batch mode
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..6c15f29
--- /dev/null
+++ b/package.json
@@ -0,0 +1,20 @@
+{
+ "name": "dispatch",
+ "private": true,
+ "workspaces": [
+ "packages/*"
+ ],
+ "scripts": {
+ "dev:api": "bun --watch packages/api/src/index.ts",
+ "dev:frontend": "bun run --cwd packages/frontend dev",
+ "check": "biome check .",
+ "check:fix": "biome check --write .",
+ "test": "vitest run",
+ "test:watch": "vitest"
+ },
+ "devDependencies": {
+ "@biomejs/biome": "^2.4.15",
+ "typescript": "^5.7.0",
+ "vitest": "^3.0.0"
+ }
+}
diff --git a/packages/api/package.json b/packages/api/package.json
new file mode 100644
index 0000000..deea9f6
--- /dev/null
+++ b/packages/api/package.json
@@ -0,0 +1,20 @@
+{
+ "name": "@dispatch/api",
+ "version": "0.0.1",
+ "private": true,
+ "type": "module",
+ "main": "src/index.ts",
+ "scripts": {
+ "dev": "bun --watch src/index.ts",
+ "test": "vitest run",
+ "test:watch": "vitest",
+ "typecheck": "tsc --noEmit"
+ },
+ "dependencies": {
+ "hono": "^4.0.0",
+ "@dispatch/core": "workspace:*"
+ },
+ "devDependencies": {
+ "@types/bun": "latest"
+ }
+}
diff --git a/packages/api/src/agent-manager.ts b/packages/api/src/agent-manager.ts
new file mode 100644
index 0000000..f60b8d3
--- /dev/null
+++ b/packages/api/src/agent-manager.ts
@@ -0,0 +1,86 @@
+import {
+ Agent,
+ type AgentEvent,
+ type AgentStatus,
+ createListFilesTool,
+ createReadFileTool,
+ createWriteFileTool,
+} from "@dispatch/core";
+
+const SYSTEM_PROMPT = `You are Dispatch, a helpful AI coding assistant. You have access to the following tools for working with files in the current working directory:
+
+- read_file: Read the contents of a file
+- write_file: Write content to a file (creates parent directories if needed)
+- list_files: List files and directories
+
+When asked to work with files, use these tools. Always confirm what you did after completing an action. Be concise and helpful.`;
+
+export class AgentManager {
+ private agent: Agent | null = null;
+ private status: AgentStatus = "idle";
+ private messageCount = 0;
+ private eventListeners: Set<(event: AgentEvent) => void> = new Set();
+
+ private getOrCreateAgent(): Agent {
+ if (!this.agent) {
+ const apiKey = process.env.OPENCODE_API_KEY ?? "";
+ const model = process.env.DISPATCH_MODEL ?? "deepseek-v4-flash-free";
+ const workingDirectory = process.env.DISPATCH_WORKING_DIR ?? process.cwd();
+
+ const tools = [
+ createReadFileTool(workingDirectory),
+ createWriteFileTool(workingDirectory),
+ createListFilesTool(workingDirectory),
+ ];
+
+ this.agent = new Agent({
+ model,
+ apiKey,
+ baseURL: "https://opencode.ai/zen/v1",
+ systemPrompt: SYSTEM_PROMPT,
+ tools,
+ workingDirectory,
+ });
+ }
+ return this.agent;
+ }
+
+ getStatus(): AgentStatus {
+ return this.status;
+ }
+
+ getMessageCount(): number {
+ return this.messageCount;
+ }
+
+ onEvent(listener: (event: AgentEvent) => void): () => void {
+ this.eventListeners.add(listener);
+ return () => {
+ this.eventListeners.delete(listener);
+ };
+ }
+
+ private emit(event: AgentEvent): void {
+ for (const listener of this.eventListeners) {
+ listener(event);
+ }
+ }
+
+ async processMessage(message: string): Promise<void> {
+ const agent = this.getOrCreateAgent();
+
+ this.messageCount += 1;
+
+ try {
+ for await (const event of agent.run(message)) {
+ this.status = event.type === "status" ? event.status : this.status;
+ this.emit(event);
+ }
+ } catch (err) {
+ const errorMsg = err instanceof Error ? err.message : String(err);
+ this.status = "error";
+ this.emit({ type: "error", error: errorMsg });
+ this.emit({ type: "status", status: "error" });
+ }
+ }
+}
diff --git a/packages/api/src/app.ts b/packages/api/src/app.ts
new file mode 100644
index 0000000..9c31eab
--- /dev/null
+++ b/packages/api/src/app.ts
@@ -0,0 +1,46 @@
+import { Hono } from "hono";
+import { cors } from "hono/cors";
+import { AgentManager } from "./agent-manager.js";
+
+export const agentManager = new AgentManager();
+
+export const app = new Hono();
+
+app.use(
+ "*",
+ cors({
+ origin: "http://localhost:5173",
+ credentials: true,
+ allowHeaders: ["Content-Type", "Authorization"],
+ allowMethods: ["GET", "POST", "OPTIONS"],
+ }),
+);
+
+app.get("/health", (c) => {
+ return c.json({ ok: true });
+});
+
+app.get("/status", (c) => {
+ return c.json({
+ status: agentManager.getStatus(),
+ messageCount: agentManager.getMessageCount(),
+ });
+});
+
+app.post("/chat", async (c) => {
+ const body = await c.req.json<{ message?: unknown }>();
+ const message = body.message;
+
+ if (typeof message !== "string" || message.trim() === "") {
+ return c.json({ error: "message must be a non-empty string" }, 400);
+ }
+
+ if (agentManager.getStatus() === "running") {
+ return c.json({ error: "agent is already running" }, 409);
+ }
+
+ // Non-blocking — let the agent run in the background
+ agentManager.processMessage(message).catch(console.error);
+
+ return c.json({ status: "ok" });
+});
diff --git a/packages/api/src/index.ts b/packages/api/src/index.ts
new file mode 100644
index 0000000..02b04b6
--- /dev/null
+++ b/packages/api/src/index.ts
@@ -0,0 +1,37 @@
+import { createBunWebSocket } from "hono/bun";
+import { agentManager, app } from "./app.js";
+
+const { upgradeWebSocket, websocket } = createBunWebSocket();
+
+app.get(
+ "/ws",
+ upgradeWebSocket((_c) => {
+ return {
+ onOpen(_event, ws) {
+ // Send current status immediately
+ ws.send(JSON.stringify({ type: "status", status: agentManager.getStatus() }));
+
+ const unsubscribe = agentManager.onEvent((event) => {
+ ws.send(JSON.stringify(event));
+ });
+
+ // Store unsubscribe fn on the raw socket for cleanup
+ (ws as unknown as { _unsub?: () => void })._unsub = unsubscribe;
+ },
+ onClose(_event, ws) {
+ const unsub = (ws as unknown as { _unsub?: () => void })._unsub;
+ if (unsub) {
+ unsub();
+ }
+ },
+ };
+ }),
+);
+
+export { app };
+
+export default {
+ port: 3000,
+ fetch: app.fetch,
+ websocket,
+};
diff --git a/packages/api/src/types.ts b/packages/api/src/types.ts
new file mode 100644
index 0000000..a88e41b
--- /dev/null
+++ b/packages/api/src/types.ts
@@ -0,0 +1,2 @@
+// Re-export types from @dispatch/core for convenience
+export type { AgentEvent, AgentStatus } from "@dispatch/core";
diff --git a/packages/api/tests/agent-manager.test.ts b/packages/api/tests/agent-manager.test.ts
new file mode 100644
index 0000000..17b0bff
--- /dev/null
+++ b/packages/api/tests/agent-manager.test.ts
@@ -0,0 +1,113 @@
+import type { AgentEvent } from "@dispatch/core";
+import { describe, expect, it, vi } from "vitest";
+
+// Mock @dispatch/core's Agent to avoid real LLM calls
+vi.mock("@dispatch/core", async () => {
+ const actual = await vi.importActual<typeof import("@dispatch/core")>("@dispatch/core");
+ return {
+ ...actual,
+ Agent: class MockAgent {
+ status = "idle";
+ messages: unknown[] = [];
+ async *run(_message: string) {
+ yield { type: "status", status: "running" } as const;
+ await new Promise<void>((r) => setTimeout(r, 10));
+ yield { type: "text-delta", delta: "Hello " } as const;
+ yield { type: "text-delta", delta: "world" } as const;
+ yield {
+ type: "done",
+ message: { role: "assistant", content: "Hello world" },
+ } as const;
+ yield { type: "status", status: "idle" } as const;
+ }
+ },
+ };
+});
+
+// Import after mock is defined (Vitest hoists vi.mock automatically)
+const { AgentManager } = await import("../src/agent-manager.js");
+
+describe("AgentManager", () => {
+ it("initial status is idle", () => {
+ const manager = new AgentManager();
+ expect(manager.getStatus()).toBe("idle");
+ });
+
+ it("initial messageCount is 0", () => {
+ const manager = new AgentManager();
+ expect(manager.getMessageCount()).toBe(0);
+ });
+
+ it("event listeners receive events during processMessage", async () => {
+ const manager = new AgentManager();
+ const events: AgentEvent[] = [];
+ manager.onEvent((event) => {
+ events.push(event);
+ });
+
+ await manager.processMessage("test");
+
+ expect(events.length).toBeGreaterThan(0);
+ expect(events[0]).toEqual({ type: "status", status: "running" });
+
+ const lastEvent = events[events.length - 1];
+ expect(lastEvent).toEqual({ type: "status", status: "idle" });
+
+ const doneEvent = events.find((e) => e.type === "done");
+ expect(doneEvent).toBeDefined();
+ });
+
+ it("emits text-delta events during processMessage", async () => {
+ const manager = new AgentManager();
+ const events: AgentEvent[] = [];
+ manager.onEvent((event) => {
+ events.push(event);
+ });
+
+ await manager.processMessage("hello");
+
+ const textDeltas = events.filter((e) => e.type === "text-delta");
+ expect(textDeltas.length).toBeGreaterThan(0);
+ });
+
+ it("messageCount increments after processMessage", async () => {
+ const manager = new AgentManager();
+ await manager.processMessage("hello");
+ expect(manager.getMessageCount()).toBe(1);
+ await manager.processMessage("world");
+ expect(manager.getMessageCount()).toBe(2);
+ });
+
+ it("status returns to idle after processMessage completes", async () => {
+ const manager = new AgentManager();
+ await manager.processMessage("test");
+ expect(manager.getStatus()).toBe("idle");
+ });
+
+ it("unsubscribe removes listener", async () => {
+ const manager = new AgentManager();
+ const events: AgentEvent[] = [];
+ const unsubscribe = manager.onEvent((event) => {
+ events.push(event);
+ });
+
+ unsubscribe();
+ await manager.processMessage("test");
+
+ expect(events.length).toBe(0);
+ });
+
+ it("multiple listeners all receive events", async () => {
+ const manager = new AgentManager();
+ const listener1 = vi.fn();
+ const listener2 = vi.fn();
+
+ manager.onEvent(listener1);
+ manager.onEvent(listener2);
+
+ await manager.processMessage("test");
+
+ expect(listener1).toHaveBeenCalled();
+ expect(listener2).toHaveBeenCalled();
+ });
+});
diff --git a/packages/api/tests/routes.test.ts b/packages/api/tests/routes.test.ts
new file mode 100644
index 0000000..d5384b3
--- /dev/null
+++ b/packages/api/tests/routes.test.ts
@@ -0,0 +1,106 @@
+import { describe, expect, it, vi } from "vitest";
+
+// Mock @dispatch/core's Agent to avoid real LLM calls
+vi.mock("@dispatch/core", async () => {
+ const actual = await vi.importActual<typeof import("@dispatch/core")>("@dispatch/core");
+ return {
+ ...actual,
+ Agent: class MockAgent {
+ status = "idle";
+ messages: unknown[] = [];
+ async *run(_message: string) {
+ yield { type: "status", status: "running" } as const;
+ // Simulate some processing time so status stays "running"
+ await new Promise<void>((r) => setTimeout(r, 100));
+ yield { type: "text-delta", delta: "Hello " } as const;
+ yield { type: "text-delta", delta: "world" } as const;
+ yield {
+ type: "done",
+ message: { role: "assistant", content: "Hello world" },
+ } as const;
+ yield { type: "status", status: "idle" } as const;
+ }
+ },
+ };
+});
+
+const { app } = await import("../src/app.js");
+
+describe("GET /health", () => {
+ it("returns 200 with ok: true", async () => {
+ const res = await app.request("/health");
+ expect(res.status).toBe(200);
+ const body = await res.json();
+ expect(body).toEqual({ ok: true });
+ });
+});
+
+describe("GET /status", () => {
+ it("returns idle status initially", async () => {
+ const res = await app.request("/status");
+ expect(res.status).toBe(200);
+ const body = await res.json();
+ expect(body.status).toBe("idle");
+ expect(typeof body.messageCount).toBe("number");
+ });
+});
+
+describe("POST /chat", () => {
+ it("returns 200 with valid message", async () => {
+ const res = await app.request("/chat", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ message: "hello world" }),
+ });
+ expect(res.status).toBe(200);
+ const body = await res.json();
+ expect(body).toEqual({ status: "ok" });
+ });
+
+ it("returns 400 with empty message", async () => {
+ const res = await app.request("/chat", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ message: "" }),
+ });
+ expect(res.status).toBe(400);
+ });
+
+ it("returns 400 with whitespace-only message", async () => {
+ const res = await app.request("/chat", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ message: " " }),
+ });
+ expect(res.status).toBe(400);
+ });
+
+ it("returns 400 with missing message field", async () => {
+ const res = await app.request("/chat", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({}),
+ });
+ expect(res.status).toBe(400);
+ });
+
+ it("returns 409 when agent is already running", async () => {
+ // Start a message (non-blocking)
+ await app.request("/chat", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ message: "first message" }),
+ });
+
+ // Small delay to let the async generator start and emit "running" status
+ await new Promise<void>((r) => setTimeout(r, 20));
+
+ // Immediately send a second — agent should be running
+ const res = await app.request("/chat", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ message: "second message" }),
+ });
+ expect(res.status).toBe(409);
+ });
+});
diff --git a/packages/api/tsconfig.json b/packages/api/tsconfig.json
new file mode 100644
index 0000000..2d6fedd
--- /dev/null
+++ b/packages/api/tsconfig.json
@@ -0,0 +1,10 @@
+{
+ "extends": "../../tsconfig.base.json",
+ "compilerOptions": {
+ "outDir": "dist",
+ "rootDir": "src",
+ "types": ["@types/bun"]
+ },
+ "include": ["src/**/*.ts"],
+ "exclude": ["node_modules", "dist", "tests"]
+}
diff --git a/packages/api/vitest.config.ts b/packages/api/vitest.config.ts
new file mode 100644
index 0000000..854422e
--- /dev/null
+++ b/packages/api/vitest.config.ts
@@ -0,0 +1,12 @@
+import { defineConfig } from "vitest/config";
+
+export default defineConfig({
+ test: {
+ include: ["tests/**/*.test.ts"],
+ server: {
+ deps: {
+ inline: ["zod", "@dispatch/core"],
+ },
+ },
+ },
+});
diff --git a/packages/core/package.json b/packages/core/package.json
new file mode 100644
index 0000000..3741041
--- /dev/null
+++ b/packages/core/package.json
@@ -0,0 +1,21 @@
+{
+ "name": "@dispatch/core",
+ "version": "0.0.1",
+ "private": true,
+ "type": "module",
+ "main": "src/index.ts",
+ "types": "src/index.ts",
+ "scripts": {
+ "test": "vitest run",
+ "test:watch": "vitest",
+ "typecheck": "tsc --noEmit"
+ },
+ "dependencies": {
+ "ai": "^4.0.0",
+ "@ai-sdk/openai-compatible": "^0.2.0",
+ "zod": "^3.23.0"
+ },
+ "devDependencies": {
+ "@types/bun": "latest"
+ }
+}
diff --git a/packages/core/src/agent/agent.ts b/packages/core/src/agent/agent.ts
new file mode 100644
index 0000000..a20a4ce
--- /dev/null
+++ b/packages/core/src/agent/agent.ts
@@ -0,0 +1,142 @@
+import type { CoreMessage } from "ai";
+import { streamText } from "ai";
+import { createProvider } from "../llm/provider.js";
+import { createToolRegistry } from "../tools/registry.js";
+import type {
+ AgentConfig,
+ AgentEvent,
+ AgentStatus,
+ ChatMessage,
+ ToolCall,
+ ToolResult,
+} from "../types/index.js";
+
+function toCoreMessages(messages: ChatMessage[]): CoreMessage[] {
+ const result: CoreMessage[] = [];
+ for (const msg of messages) {
+ if (msg.role === "user") {
+ result.push({ role: "user", content: msg.content });
+ } else if (msg.role === "assistant") {
+ result.push({ role: "assistant", content: msg.content });
+ }
+ }
+ return result;
+}
+
+function formatError(err: unknown, config: AgentConfig): string {
+ const context = `[model=${config.model}, baseURL=${config.baseURL}]`;
+
+ if (err instanceof Error) {
+ const cause = err.cause ? ` | cause: ${JSON.stringify(err.cause)}` : "";
+ // AI SDK errors often have statusCode, responseBody, or url properties
+ const extras: string[] = [];
+ const errRecord = err as unknown as Record<string, unknown>;
+ if ("statusCode" in errRecord) extras.push(`status=${errRecord.statusCode}`);
+ if ("url" in errRecord) extras.push(`url=${errRecord.url}`);
+ if ("responseBody" in errRecord) extras.push(`body=${JSON.stringify(errRecord.responseBody)}`);
+ if ("responseHeaders" in errRecord)
+ extras.push(`headers=${JSON.stringify(errRecord.responseHeaders)}`);
+
+ const detail = extras.length > 0 ? ` (${extras.join(", ")})` : "";
+ return `${err.message}${detail}${cause} ${context}`;
+ }
+
+ return `${String(err)} ${context}`;
+}
+
+export class Agent {
+ status: AgentStatus = "idle";
+ messages: ChatMessage[] = [];
+
+ private config: AgentConfig;
+
+ constructor(config: AgentConfig) {
+ this.config = config;
+ }
+
+ async *run(userMessage: string): AsyncGenerator<AgentEvent> {
+ this.status = "running";
+ yield { type: "status", status: "running" };
+
+ this.messages.push({ role: "user", content: userMessage });
+
+ const registry = createToolRegistry(this.config.tools);
+ const providerFactory = createProvider({
+ apiKey: this.config.apiKey,
+ baseURL: this.config.baseURL,
+ });
+
+ try {
+ const result = streamText({
+ model: providerFactory(this.config.model),
+ system: this.config.systemPrompt,
+ messages: toCoreMessages(this.messages),
+ tools: registry.getAISDKTools(),
+ maxSteps: 10,
+ });
+
+ let fullText = "";
+ const toolCalls: ToolCall[] = [];
+ const toolResults: ToolResult[] = [];
+
+ for await (const event of result.fullStream) {
+ if (event.type === "text-delta") {
+ fullText += event.textDelta;
+ yield { type: "text-delta", delta: event.textDelta };
+ } else if (event.type === "tool-call") {
+ const toolCall: ToolCall = {
+ id: event.toolCallId,
+ name: event.toolName,
+ arguments: event.args as Record<string, unknown>,
+ };
+ toolCalls.push(toolCall);
+ yield { type: "tool-call", toolCall };
+ } else if (event.type === "error") {
+ const errorMsg = formatError(event.error, this.config);
+ yield { type: "error", error: errorMsg };
+ this.status = "error";
+ yield { type: "status", status: "error" };
+ return;
+ }
+ }
+
+ // Tool results are available from completed steps after streaming.
+ // The generic TOOLS type resolves to never[] at compile time, so
+ // we cast through unknown to access the runtime shape.
+ const steps = await result.steps;
+ for (const step of steps) {
+ const stepToolResults = step.toolResults as unknown as Array<{
+ toolCallId: string;
+ result: unknown;
+ }>;
+ for (const tr of stepToolResults) {
+ const toolResult: ToolResult = {
+ toolCallId: tr.toolCallId,
+ result: typeof tr.result === "string" ? tr.result : JSON.stringify(tr.result),
+ isError: false,
+ };
+ toolResults.push(toolResult);
+ yield { type: "tool-result", toolResult };
+ }
+ }
+
+ const assistantMessage: ChatMessage = {
+ role: "assistant",
+ content: fullText,
+ toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
+ toolResults: toolResults.length > 0 ? toolResults : undefined,
+ };
+ this.messages.push(assistantMessage);
+ yield { type: "done", message: assistantMessage };
+ } catch (err) {
+ const errorMsg = formatError(err, this.config);
+ yield { type: "error", error: errorMsg };
+ this.status = "error";
+ yield { type: "status", status: "error" };
+ return;
+ }
+
+ this.status = "idle";
+ yield { type: "status", status: "idle" };
+ }
+}
diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts
new file mode 100644
index 0000000..b3907fa
--- /dev/null
+++ b/packages/core/src/index.ts
@@ -0,0 +1,9 @@
+// @dispatch/core — Agent runtime, LLM integration, tools
+
+export { Agent } from "./agent/agent.js";
+export { createProvider } from "./llm/provider.js";
+export { createListFilesTool } from "./tools/list-files.js";
+export { createReadFileTool } from "./tools/read-file.js";
+export { createToolRegistry } from "./tools/registry.js";
+export { createWriteFileTool } from "./tools/write-file.js";
+export * from "./types/index.js";
diff --git a/packages/core/src/llm/provider.ts b/packages/core/src/llm/provider.ts
new file mode 100644
index 0000000..06f7b72
--- /dev/null
+++ b/packages/core/src/llm/provider.ts
@@ -0,0 +1,9 @@
+import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
+
+export function createProvider(config: { apiKey: string; baseURL: string }) {
+ return createOpenAICompatible({
+ name: "opencode-zen",
+ apiKey: config.apiKey,
+ baseURL: config.baseURL,
+ });
+}
diff --git a/packages/core/src/tools/list-files.ts b/packages/core/src/tools/list-files.ts
new file mode 100644
index 0000000..360ac98
--- /dev/null
+++ b/packages/core/src/tools/list-files.ts
@@ -0,0 +1,38 @@
+import { readdir } from "node:fs/promises";
+import { join, resolve } from "node:path";
+import { z } from "zod";
+import type { ToolDefinition } from "../types/index.js";
+
+export function createListFilesTool(workingDirectory: string): ToolDefinition {
+ return {
+ name: "list_files",
+ description: "List files and directories at a path relative to the working directory.",
+ parameters: z.object({
+ path: z
+ .string()
+ .optional()
+ .describe("Path to list, relative to the working directory. Defaults to '.'"),
+ }),
+ execute: async (args: Record<string, unknown>): Promise<string> => {
+ const relPath = (args.path as string | undefined) ?? ".";
+ const absolutePath = resolve(join(workingDirectory, relPath));
+ const absoluteWorkDir = resolve(workingDirectory);
+
+ if (!absolutePath.startsWith(`${absoluteWorkDir}/`) && absolutePath !== absoluteWorkDir) {
+ return `Error: Path "${relPath}" is outside the working directory.`;
+ }
+
+ try {
+ const entries = await readdir(absolutePath, { withFileTypes: true });
+ if (entries.length === 0) {
+ return "(empty directory)";
+ }
+ return entries
+ .map((entry) => (entry.isDirectory() ? `${entry.name}/` : entry.name))
+ .join("\n");
+ } catch (err) {
+ return `Error listing files: ${err instanceof Error ? err.message : String(err)}`;
+ }
+ },
+ };
+}
diff --git a/packages/core/src/tools/read-file.ts b/packages/core/src/tools/read-file.ts
new file mode 100644
index 0000000..476f243
--- /dev/null
+++ b/packages/core/src/tools/read-file.ts
@@ -0,0 +1,33 @@
+import { readFile } from "node:fs/promises";
+import { join, resolve } from "node:path";
+import { z } from "zod";
+import type { ToolDefinition } from "../types/index.js";
+
+export function createReadFileTool(workingDirectory: string): ToolDefinition {
+ return {
+ name: "read_file",
+ description: "Read the contents of a file relative to the working directory.",
+ parameters: z.object({
+ path: z.string().describe("Path to the file, relative to the working directory"),
+ }),
+ execute: async (args: Record<string, unknown>): Promise<string> => {
+ const filePath = args.path as string;
+ const absolutePath = resolve(join(workingDirectory, filePath));
+ const absoluteWorkDir = resolve(workingDirectory);
+
+ if (!absolutePath.startsWith(`${absoluteWorkDir}/`) && absolutePath !== absoluteWorkDir) {
+ return `Error: Path "${filePath}" is outside the working directory.`;
+ }
+
+ try {
+ return await readFile(absolutePath, "utf8");
+ } catch (err) {
+ const code = (err as NodeJS.ErrnoException).code;
+ if (code === "ENOENT") {
+ return `Error: File "${filePath}" not found.`;
+ }
+ return `Error reading file: ${err instanceof Error ? err.message : String(err)}`;
+ }
+ },
+ };
+}
diff --git a/packages/core/src/tools/registry.ts b/packages/core/src/tools/registry.ts
new file mode 100644
index 0000000..4699c93
--- /dev/null
+++ b/packages/core/src/tools/registry.ts
@@ -0,0 +1,35 @@
+import { tool } from "ai";
+import { z } from "zod";
+import type { ToolDefinition } from "../types/index.js";
+
+export function createToolRegistry(tools: ToolDefinition[]) {
+ const toolMap = new Map<string, ToolDefinition>(tools.map((t) => [t.name, t]));
+
+ return {
+ getTools(): ToolDefinition[] {
+ return [...toolMap.values()];
+ },
+
+ getTool(name: string): ToolDefinition | undefined {
+ return toolMap.get(name);
+ },
+
+ getAISDKTools() {
+ const result: Record<string, ReturnType<typeof tool>> = {};
+ for (const [name, def] of toolMap) {
+ const schema = def.parameters;
+ const t = tool({
+ description: def.description,
+ parameters: schema instanceof z.ZodObject ? schema : z.object({}),
+ execute: async (args) => {
+ return def.execute(args as Record<string, unknown>);
+ },
+ });
+ // The AI SDK tool() overloads cause type narrowing issues when
+ // execute is provided. The runtime value is correct.
+ result[name] = t as unknown as ReturnType<typeof tool>;
+ }
+ return result;
+ },
+ };
+}
diff --git a/packages/core/src/tools/write-file.ts b/packages/core/src/tools/write-file.ts
new file mode 100644
index 0000000..23bc72a
--- /dev/null
+++ b/packages/core/src/tools/write-file.ts
@@ -0,0 +1,33 @@
+import { mkdir, writeFile } from "node:fs/promises";
+import { dirname, join, resolve } from "node:path";
+import { z } from "zod";
+import type { ToolDefinition } from "../types/index.js";
+
+export function createWriteFileTool(workingDirectory: string): ToolDefinition {
+ return {
+ name: "write_file",
+ description: "Write content to a file relative to the working directory.",
+ parameters: z.object({
+ path: z.string().describe("Path to the file, relative to the working directory"),
+ content: z.string().describe("Content to write to the file"),
+ }),
+ execute: async (args: Record<string, unknown>): Promise<string> => {
+ const filePath = args.path as string;
+ const content = args.content as string;
+ const absolutePath = resolve(join(workingDirectory, filePath));
+ const absoluteWorkDir = resolve(workingDirectory);
+
+ if (!absolutePath.startsWith(`${absoluteWorkDir}/`) && absolutePath !== absoluteWorkDir) {
+ return `Error: Path "${filePath}" is outside the working directory.`;
+ }
+
+ try {
+ await mkdir(dirname(absolutePath), { recursive: true });
+ await writeFile(absolutePath, content, "utf8");
+ return `Successfully wrote to "${filePath}".`;
+ } catch (err) {
+ return `Error writing file: ${err instanceof Error ? err.message : String(err)}`;
+ }
+ },
+ };
+}
diff --git a/packages/core/src/types/index.ts b/packages/core/src/types/index.ts
new file mode 100644
index 0000000..a408e7d
--- /dev/null
+++ b/packages/core/src/types/index.ts
@@ -0,0 +1,53 @@
+import type { ZodType } from "zod";
+
+// Message types for the agent conversation
+export type MessageRole = "user" | "assistant" | "tool";
+
+export interface ChatMessage {
+ role: MessageRole;
+ content: string;
+ toolCalls?: ToolCall[];
+ toolResults?: ToolResult[];
+}
+
+export interface ToolCall {
+ id: string;
+ name: string;
+ arguments: Record<string, unknown>;
+}
+
+export interface ToolResult {
+ toolCallId: string;
+ result: string;
+ isError: boolean;
+}
+
+// Agent status
+export type AgentStatus = "idle" | "running" | "error";
+
+// Agent events emitted during execution (for WebSocket streaming)
+export type AgentEvent =
+ | { type: "status"; status: AgentStatus }
+ | { type: "text-delta"; delta: string }
+ | { type: "tool-call"; toolCall: ToolCall }
+ | { type: "tool-result"; toolResult: ToolResult }
+ | { type: "error"; error: string }
+ | { type: "done"; message: ChatMessage };
+
+// Tool definition interface
+export interface ToolDefinition {
+ name: string;
+ description: string;
+ parameters: ZodType;
+ execute: (args: Record<string, unknown>) => Promise<string>;
+}
+
+// Agent configuration
+export interface AgentConfig {
+ model: string;
+ apiKey: string;
+ baseURL: string;
+ systemPrompt: string;
+ tools: ToolDefinition[];
+ workingDirectory: string;
+}
diff --git a/packages/core/tests/agent/agent.test.ts b/packages/core/tests/agent/agent.test.ts
new file mode 100644
index 0000000..81d42c8
--- /dev/null
+++ b/packages/core/tests/agent/agent.test.ts
@@ -0,0 +1,244 @@
+import { describe, expect, it, vi } from "vitest";
+import { z } from "zod";
+import { Agent } from "../../src/agent/agent.js";
+import type { AgentConfig } from "../../src/types/index.js";
+
+// Mock the ai module's streamText
+vi.mock("ai", async (importOriginal) => {
+ const actual = await importOriginal<typeof import("ai")>();
+ return {
+ ...actual,
+ streamText: vi.fn(),
+ };
+});
+
+// Mock the provider
+vi.mock("@ai-sdk/openai-compatible", () => ({
+ createOpenAICompatible: vi.fn(() => (_model: string) => ({
+ type: "language-model",
+ modelId: _model,
+ })),
+}));
+
+function makeConfig(overrides: Partial<AgentConfig> = {}): AgentConfig {
+ return {
+ model: "test-model",
+ apiKey: "test-key",
+ baseURL: "https://example.com/v1",
+ systemPrompt: "You are a helpful assistant.",
+ tools: [],
+ workingDirectory: "/tmp",
+ ...overrides,
+ };
+}
+
+async function* makeFullStream(
+ events: Array<{ type: string; [key: string]: unknown }>,
+): AsyncGenerator<{ type: string; [key: string]: unknown }> {
+ for (const event of events) {
+ yield event;
+ }
+}
+
+interface MockStreamOptions {
+ events: Array<{ type: string; [key: string]: unknown }>;
+ steps?: Array<{ toolResults: Array<{ toolCallId: string; result: unknown }> }>;
+}
+
+function makeMockStreamResult(opts: MockStreamOptions) {
+ return {
+ fullStream: makeFullStream(opts.events),
+ steps: Promise.resolve(opts.steps ?? []),
+ } as ReturnType<typeof import("ai").streamText>;
+}
+
+describe("Agent", () => {
+ it("starts in idle status", () => {
+ const agent = new Agent(makeConfig());
+ expect(agent.status).toBe("idle");
+ });
+
+ it("has empty messages initially", () => {
+ const agent = new Agent(makeConfig());
+ expect(agent.messages).toHaveLength(0);
+ });
+
+ it("yields running then idle status events around a simple message", async () => {
+ const { streamText } = await import("ai");
+ vi.mocked(streamText).mockReturnValue(
+ makeMockStreamResult({
+ events: [
+ { type: "text-delta", textDelta: "Hello!" },
+ {
+ type: "finish",
+ finishReason: "stop",
+ usage: {},
+ providerMetadata: undefined,
+ response: {},
+ },
+ ],
+ }),
+ );
+
+ const agent = new Agent(makeConfig());
+ const events = [];
+ for await (const event of agent.run("hi")) {
+ events.push(event);
+ }
+
+ const types = events.map((e) => e.type);
+ expect(types[0]).toBe("status");
+ expect(events[0]).toMatchObject({ type: "status", status: "running" });
+
+ const lastStatusEvent = events.filter((e) => e.type === "status").at(-1);
+ expect(lastStatusEvent).toMatchObject({ type: "status", status: "idle" });
+ });
+
+ it("yields text-delta events", async () => {
+ const { streamText } = await import("ai");
+ vi.mocked(streamText).mockReturnValue(
+ makeMockStreamResult({
+ events: [
+ { type: "text-delta", textDelta: "Hello" },
+ { type: "text-delta", textDelta: " world" },
+ {
+ type: "finish",
+ finishReason: "stop",
+ usage: {},
+ providerMetadata: undefined,
+ response: {},
+ },
+ ],
+ }),
+ );
+
+ const agent = new Agent(makeConfig());
+ const events = [];
+ for await (const event of agent.run("test")) {
+ events.push(event);
+ }
+
+ const textDeltas = events.filter((e) => e.type === "text-delta");
+ expect(textDeltas).toHaveLength(2);
+ expect(textDeltas[0]).toMatchObject({ delta: "Hello" });
+ expect(textDeltas[1]).toMatchObject({ delta: " world" });
+ });
+
+ it("adds user message and assistant message to history", async () => {
+ const { streamText } = await import("ai");
+ vi.mocked(streamText).mockReturnValue(
+ makeMockStreamResult({
+ events: [
+ { type: "text-delta", textDelta: "Response" },
+ {
+ type: "finish",
+ finishReason: "stop",
+ usage: {},
+ providerMetadata: undefined,
+ response: {},
+ },
+ ],
+ }),
+ );
+
+ const agent = new Agent(makeConfig());
+ for await (const _ of agent.run("my question")) {
+ // consume generator
+ }
+
+ expect(agent.messages).toHaveLength(2);
+ expect(agent.messages[0]).toMatchObject({
+ role: "user",
+ content: "my question",
+ });
+ expect(agent.messages[1]).toMatchObject({
+ role: "assistant",
+ content: "Response",
+ });
+ });
+
+ it("yields done event with final message", async () => {
+ const { streamText } = await import("ai");
+ vi.mocked(streamText).mockReturnValue(
+ makeMockStreamResult({
+ events: [
+ { type: "text-delta", textDelta: "Done!" },
+ {
+ type: "finish",
+ finishReason: "stop",
+ usage: {},
+ providerMetadata: undefined,
+ response: {},
+ },
+ ],
+ }),
+ );
+
+ const agent = new Agent(makeConfig());
+ const events = [];
+ for await (const event of agent.run("test")) {
+ events.push(event);
+ }
+
+ const doneEvent = events.find((e) => e.type === "done");
+ expect(doneEvent).toBeDefined();
+ expect(doneEvent).toMatchObject({
+ type: "done",
+ message: { role: "assistant", content: "Done!" },
+ });
+ });
+
+ it("yields tool-call and tool-result events", async () => {
+ const { streamText } = await import("ai");
+ vi.mocked(streamText).mockReturnValue(
+ makeMockStreamResult({
+ events: [
+ {
+ type: "tool-call",
+ toolCallId: "tc1",
+ toolName: "read_file",
+ args: { path: "hello.txt" },
+ },
+ { type: "text-delta", textDelta: "Here is the file." },
+ {
+ type: "finish",
+ finishReason: "stop",
+ usage: {},
+ providerMetadata: undefined,
+ response: {},
+ },
+ ],
+ steps: [
+ {
+ toolResults: [{ toolCallId: "tc1", result: "file contents" }],
+ },
+ ],
+ }),
+ );
+
+ const toolDef = {
+ name: "read_file",
+ description: "reads a file",
+ parameters: z.object({ path: z.string() }),
+ execute: async (_args: Record<string, unknown>) => "file contents",
+ };
+
+ const agent = new Agent(makeConfig({ tools: [toolDef] }));
+ const events = [];
+ for await (const event of agent.run("read the file")) {
+ events.push(event);
+ }
+
+ const toolCallEvent = events.find((e) => e.type === "tool-call");
+ expect(toolCallEvent).toMatchObject({
+ type: "tool-call",
+ toolCall: { id: "tc1", name: "read_file" },
+ });
+
+ const toolResultEvent = events.find((e) => e.type === "tool-result");
+ expect(toolResultEvent).toMatchObject({
+ type: "tool-result",
+ toolResult: { toolCallId: "tc1", result: "file contents" },
+ });
+ });
+});
diff --git a/packages/core/tests/tools/list-files.test.ts b/packages/core/tests/tools/list-files.test.ts
new file mode 100644
index 0000000..ead1df3
--- /dev/null
+++ b/packages/core/tests/tools/list-files.test.ts
@@ -0,0 +1,41 @@
+import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { afterEach, beforeEach, describe, expect, it } from "vitest";
+import { createListFilesTool } from "../../src/tools/list-files.js";
+
+describe("list_files tool", () => {
+ let workDir: string;
+
+ beforeEach(async () => {
+ workDir = await mkdtemp(join(tmpdir(), "dispatch-test-"));
+ });
+
+ afterEach(async () => {
+ await rm(workDir, { recursive: true, force: true });
+ });
+
+ it("lists directory contents", async () => {
+ const tool = createListFilesTool(workDir);
+ await writeFile(join(workDir, "file1.txt"), "a");
+ await writeFile(join(workDir, "file2.txt"), "b");
+ await mkdir(join(workDir, "subdir"));
+ const result = await tool.execute({ path: "." });
+ expect(result).toContain("file1.txt");
+ expect(result).toContain("file2.txt");
+ expect(result).toContain("subdir/");
+ });
+
+ it("defaults to current directory when path is undefined", async () => {
+ const tool = createListFilesTool(workDir);
+ await writeFile(join(workDir, "hello.txt"), "hi");
+ const result = await tool.execute({});
+ expect(result).toContain("hello.txt");
+ });
+
+ it("blocks path traversal", async () => {
+ const tool = createListFilesTool(workDir);
+ const result = await tool.execute({ path: "../" });
+ expect(result).toMatch(/outside the working directory/i);
+ });
+});
diff --git a/packages/core/tests/tools/read-file.test.ts b/packages/core/tests/tools/read-file.test.ts
new file mode 100644
index 0000000..ce65b37
--- /dev/null
+++ b/packages/core/tests/tools/read-file.test.ts
@@ -0,0 +1,36 @@
+import { mkdtemp, rm, writeFile } from "node:fs/promises";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { afterEach, beforeEach, describe, expect, it } from "vitest";
+import { createReadFileTool } from "../../src/tools/read-file.js";
+
+describe("read_file tool", () => {
+ let workDir: string;
+
+ beforeEach(async () => {
+ workDir = await mkdtemp(join(tmpdir(), "dispatch-test-"));
+ });
+
+ afterEach(async () => {
+ await rm(workDir, { recursive: true, force: true });
+ });
+
+ it("reads an existing file", async () => {
+ const tool = createReadFileTool(workDir);
+ await writeFile(join(workDir, "hello.txt"), "Hello, world!");
+ const result = await tool.execute({ path: "hello.txt" });
+ expect(result).toBe("Hello, world!");
+ });
+
+ it("returns error for non-existent file", async () => {
+ const tool = createReadFileTool(workDir);
+ const result = await tool.execute({ path: "missing.txt" });
+ expect(result).toMatch(/not found/i);
+ });
+
+ it("blocks path traversal", async () => {
+ const tool = createReadFileTool(workDir);
+ const result = await tool.execute({ path: "../etc/passwd" });
+ expect(result).toMatch(/outside the working directory/i);
+ });
+});
diff --git a/packages/core/tests/tools/registry.test.ts b/packages/core/tests/tools/registry.test.ts
new file mode 100644
index 0000000..b6f1fca
--- /dev/null
+++ b/packages/core/tests/tools/registry.test.ts
@@ -0,0 +1,50 @@
+import { describe, expect, it } from "vitest";
+import { z } from "zod";
+import { createToolRegistry } from "../../src/tools/registry.js";
+import type { ToolDefinition } from "../../src/types/index.js";
+
+const mockTool: ToolDefinition = {
+ name: "mock_tool",
+ description: "A mock tool for testing",
+ parameters: z.object({ input: z.string() }),
+ execute: async (_args) => "mock result",
+};
+
+const anotherTool: ToolDefinition = {
+ name: "another_tool",
+ description: "Another mock tool",
+ parameters: z.object({ value: z.number() }),
+ execute: async (_args) => "another result",
+};
+
+describe("createToolRegistry", () => {
+ it("returns all tools via getTools()", () => {
+ const registry = createToolRegistry([mockTool, anotherTool]);
+ const tools = registry.getTools();
+ expect(tools).toHaveLength(2);
+ expect(tools.map((t) => t.name)).toContain("mock_tool");
+ expect(tools.map((t) => t.name)).toContain("another_tool");
+ });
+
+ it("retrieves specific tool by name", () => {
+ const registry = createToolRegistry([mockTool, anotherTool]);
+ const tool = registry.getTool("mock_tool");
+ expect(tool).toBeDefined();
+ expect(tool?.name).toBe("mock_tool");
+ });
+
+ it("returns undefined for unknown tool", () => {
+ const registry = createToolRegistry([mockTool]);
+ expect(registry.getTool("nonexistent")).toBeUndefined();
+ });
+
+ it("getAISDKTools returns correct format", () => {
+ const registry = createToolRegistry([mockTool, anotherTool]);
+ const aiTools = registry.getAISDKTools();
+ expect(aiTools).toHaveProperty("mock_tool");
+ expect(aiTools).toHaveProperty("another_tool");
+ // Each should have description and parameters (AI SDK tool format)
+ expect(aiTools.mock_tool).toHaveProperty("description");
+ expect(aiTools.mock_tool).toHaveProperty("parameters");
+ });
+});
diff --git a/packages/core/tests/tools/write-file.test.ts b/packages/core/tests/tools/write-file.test.ts
new file mode 100644
index 0000000..01d7253
--- /dev/null
+++ b/packages/core/tests/tools/write-file.test.ts
@@ -0,0 +1,45 @@
+import { mkdtemp, readFile, rm } from "node:fs/promises";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import { afterEach, beforeEach, describe, expect, it } from "vitest";
+import { createWriteFileTool } from "../../src/tools/write-file.js";
+
+describe("write_file tool", () => {
+ let workDir: string;
+
+ beforeEach(async () => {
+ workDir = await mkdtemp(join(tmpdir(), "dispatch-test-"));
+ });
+
+ afterEach(async () => {
+ await rm(workDir, { recursive: true, force: true });
+ });
+
+ it("writes a new file", async () => {
+ const tool = createWriteFileTool(workDir);
+ const result = await tool.execute({
+ path: "output.txt",
+ content: "test content",
+ });
+ expect(result).toMatch(/successfully wrote/i);
+ const written = await readFile(join(workDir, "output.txt"), "utf8");
+ expect(written).toBe("test content");
+ });
+
+ it("creates parent directories", async () => {
+ const tool = createWriteFileTool(workDir);
+ const result = await tool.execute({
+ path: "nested/dir/file.txt",
+ content: "nested",
+ });
+ expect(result).toMatch(/successfully wrote/i);
+ const written = await readFile(join(workDir, "nested/dir/file.txt"), "utf8");
+ expect(written).toBe("nested");
+ });
+
+ it("blocks path traversal", async () => {
+ const tool = createWriteFileTool(workDir);
+ const result = await tool.execute({ path: "../evil.txt", content: "bad" });
+ expect(result).toMatch(/outside the working directory/i);
+ });
+});
diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json
new file mode 100644
index 0000000..2d6fedd
--- /dev/null
+++ b/packages/core/tsconfig.json
@@ -0,0 +1,10 @@
+{
+ "extends": "../../tsconfig.base.json",
+ "compilerOptions": {
+ "outDir": "dist",
+ "rootDir": "src",
+ "types": ["@types/bun"]
+ },
+ "include": ["src/**/*.ts"],
+ "exclude": ["node_modules", "dist", "tests"]
+}
diff --git a/packages/core/vitest.config.ts b/packages/core/vitest.config.ts
new file mode 100644
index 0000000..ba60b3c
--- /dev/null
+++ b/packages/core/vitest.config.ts
@@ -0,0 +1,14 @@
+import { defineConfig } from "vitest/config";
+
+export default defineConfig({
+ test: {
+ include: ["tests/**/*.test.ts"],
+ server: {
+ deps: {
+ // Force inline resolution for packages that break under Bun's
+ // .bun/ symlink layout in Docker environments
+ inline: ["zod"],
+ },
+ },
+ },
+});
diff --git a/packages/frontend/index.html b/packages/frontend/index.html
new file mode 100644
index 0000000..32b56aa
--- /dev/null
+++ b/packages/frontend/index.html
@@ -0,0 +1,12 @@
+<!doctype html>
+<html lang="en">
+ <head>
+ <meta charset="UTF-8" />
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+ <title>Dispatch</title>
+ </head>
+ <body>
+ <div id="app"></div>
+ <script type="module" src="/src/main.ts"></script>
+ </body>
+</html>
diff --git a/packages/frontend/package.json b/packages/frontend/package.json
new file mode 100644
index 0000000..acb23ad
--- /dev/null
+++ b/packages/frontend/package.json
@@ -0,0 +1,25 @@
+{
+ "name": "@dispatch/frontend",
+ "version": "0.0.1",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "preview": "vite preview",
+ "test": "vitest run",
+ "test:watch": "vitest",
+ "typecheck": "svelte-check --tsconfig ./tsconfig.json"
+ },
+ "dependencies": {
+ "svelte": "^5.0.0"
+ },
+ "devDependencies": {
+ "@sveltejs/vite-plugin-svelte": "^5.0.0",
+ "@tailwindcss/vite": "^4.0.0",
+ "daisyui": "^5.0.0",
+ "svelte-check": "^4.0.0",
+ "tailwindcss": "^4.0.0",
+ "vite": "^6.0.0"
+ }
+}
diff --git a/packages/frontend/src/App.svelte b/packages/frontend/src/App.svelte
new file mode 100644
index 0000000..038fb09
--- /dev/null
+++ b/packages/frontend/src/App.svelte
@@ -0,0 +1,32 @@
+<script lang="ts">
+import { onMount } from "svelte";
+import ChatInput from "./lib/components/ChatInput.svelte";
+import ChatPanel from "./lib/components/ChatPanel.svelte";
+import Header from "./lib/components/Header.svelte";
+import { wsClient } from "./lib/ws.svelte.js";
+
+const STORAGE_KEY = "dispatch-theme";
+
+onMount(() => {
+ // Apply saved theme
+ const saved = localStorage.getItem(STORAGE_KEY);
+ if (saved) {
+ document.documentElement.setAttribute("data-theme", saved);
+ }
+
+ // Connect WebSocket
+ wsClient.connect();
+
+ return () => {
+ wsClient.disconnect();
+ };
+});
+</script>
+
+<div class="flex flex-col h-screen overflow-hidden bg-base-100 text-base-content">
+ <Header />
+ <div class="flex-1 overflow-hidden">
+ <ChatPanel />
+ </div>
+ <ChatInput />
+</div>
diff --git a/packages/frontend/src/app.css b/packages/frontend/src/app.css
new file mode 100644
index 0000000..5602e1f
--- /dev/null
+++ b/packages/frontend/src/app.css
@@ -0,0 +1,4 @@
+@import "tailwindcss";
+@plugin "daisyui" {
+ themes: light, dark, dracula, night, nord, sunset, cyberpunk, forest, cmyk, coffee, caramellatte;
+}
diff --git a/packages/frontend/src/lib/chat.svelte.ts b/packages/frontend/src/lib/chat.svelte.ts
new file mode 100644
index 0000000..54216ec
--- /dev/null
+++ b/packages/frontend/src/lib/chat.svelte.ts
@@ -0,0 +1,274 @@
+import { config } from "./config.js";
+import type { AgentEvent, ChatMessage, DebugInfo, ToolCallDisplay } from "./types.js";
+import { wsClient } from "./ws.svelte.js";
+
+function generateId() {
+ return Math.random().toString(36).slice(2, 11);
+}
+
+function makeDebugInfo(overrides: Partial<DebugInfo> = {}): DebugInfo {
+ return {
+ timestamp: new Date().toISOString(),
+ model: "deepseek-v4-flash-free",
+ apiBase: config.apiBase,
+ connectionStatus: wsClient.connectionStatus,
+ ...overrides,
+ };
+}
+
+function formatConversation(msgs: ChatMessage[]): string {
+ const lines: string[] = [];
+ lines.push("=== Dispatch Conversation ===");
+ lines.push(`Exported: ${new Date().toISOString()}`);
+ lines.push("");
+
+ for (const msg of msgs) {
+ const role = msg.role === "user" ? "User" : "Assistant";
+ lines.push(`--- ${role} ---`);
+ lines.push(msg.content);
+
+ if (msg.toolCalls && msg.toolCalls.length > 0) {
+ for (const tc of msg.toolCalls) {
+ lines.push(` [Tool: ${tc.name}]`);
+ lines.push(` Args: ${JSON.stringify(tc.arguments)}`);
+ if (tc.result !== undefined) {
+ const prefix = tc.isError ? " Error: " : " Result: ";
+ lines.push(`${prefix}${tc.result}`);
+ }
+ }
+ }
+
+ if (msg.debugInfo) {
+ lines.push(" [Debug Info]");
+ lines.push(` Timestamp: ${msg.debugInfo.timestamp}`);
+ if (msg.debugInfo.error) lines.push(` Error: ${msg.debugInfo.error}`);
+ if (msg.debugInfo.model) lines.push(` Model: ${msg.debugInfo.model}`);
+ if (msg.debugInfo.apiBase) lines.push(` API Base: ${msg.debugInfo.apiBase}`);
+ if (msg.debugInfo.connectionStatus)
+ lines.push(` Connection: ${msg.debugInfo.connectionStatus}`);
+ if (msg.debugInfo.agentStatus) lines.push(` Agent Status: ${msg.debugInfo.agentStatus}`);
+ if (msg.debugInfo.httpStatus) lines.push(` HTTP Status: ${msg.debugInfo.httpStatus}`);
+ if (msg.debugInfo.httpBody) lines.push(` HTTP Body: ${msg.debugInfo.httpBody}`);
+ if (msg.debugInfo.rawEvent)
+ lines.push(` Raw Event: ${JSON.stringify(msg.debugInfo.rawEvent)}`);
+ }
+
+ lines.push("");
+ }
+
+ return lines.join("\n");
+}
+
+function createChatStore() {
+ let messages: ChatMessage[] = $state([]);
+ let agentStatus: "idle" | "running" | "error" = $state("idle");
+ let isConnected = $state(false);
+ let currentAssistantId: string | null = null;
+
+ wsClient.onEvent((event) => {
+ const connected = wsClient.connectionStatus === "connected";
+ if (connected !== isConnected) {
+ isConnected = connected;
+ }
+ handleEvent(event);
+ });
+
+ $effect.root(() => {
+ $effect(() => {
+ isConnected = wsClient.connectionStatus === "connected";
+ });
+ });
+
+ function getCurrentAssistantMessage(): ChatMessage | null {
+ if (!currentAssistantId) return null;
+ return messages.find((m) => m.id === currentAssistantId) ?? null;
+ }
+
+ function ensureCurrentAssistantMessage(): ChatMessage {
+ let msg = getCurrentAssistantMessage();
+ if (!msg) {
+ const id = generateId();
+ currentAssistantId = id;
+ const newMsg: ChatMessage = {
+ id,
+ role: "assistant",
+ content: "",
+ toolCalls: [],
+ isStreaming: true,
+ };
+ messages = [...messages, newMsg];
+ msg = newMsg;
+ }
+ return msg;
+ }
+
+ function handleEvent(event: AgentEvent) {
+ switch (event.type) {
+ case "status": {
+ agentStatus = event.status;
+ if (event.status === "idle" || event.status === "error") {
+ currentAssistantId = null;
+ }
+ break;
+ }
+ case "text-delta": {
+ ensureCurrentAssistantMessage();
+ messages = messages.map((m) => {
+ if (m.id === currentAssistantId) {
+ return {
+ ...m,
+ content: m.content + event.delta,
+ isStreaming: true,
+ };
+ }
+ return m;
+ });
+ break;
+ }
+ case "tool-call": {
+ ensureCurrentAssistantMessage();
+ const toolCall: ToolCallDisplay = {
+ id: event.toolCall.id,
+ name: event.toolCall.name,
+ arguments: event.toolCall.arguments,
+ isExpanded: false,
+ };
+ messages = messages.map((m) => {
+ if (m.id === currentAssistantId) {
+ return { ...m, toolCalls: [...(m.toolCalls ?? []), toolCall] };
+ }
+ return m;
+ });
+ break;
+ }
+ case "tool-result": {
+ messages = messages.map((m) => {
+ if (m.id === currentAssistantId) {
+ return {
+ ...m,
+ toolCalls: (m.toolCalls ?? []).map((tc) => {
+ if (tc.id === event.toolResult.toolCallId) {
+ return {
+ ...tc,
+ result: event.toolResult.result,
+ isError: event.toolResult.isError,
+ };
+ }
+ return tc;
+ }),
+ };
+ }
+ return m;
+ });
+ break;
+ }
+ case "done": {
+ messages = messages.map((m) => {
+ if (m.id === currentAssistantId) {
+ return {
+ ...m,
+ content: event.message.content,
+ isStreaming: false,
+ };
+ }
+ return m;
+ });
+ currentAssistantId = null;
+ break;
+ }
+ case "error": {
+ const errMsg: ChatMessage = {
+ id: generateId(),
+ role: "assistant",
+ content: `Error: ${event.error}`,
+ isStreaming: false,
+ debugInfo: makeDebugInfo({
+ error: event.error,
+ agentStatus,
+ rawEvent: event,
+ }),
+ };
+ messages = [...messages, errMsg];
+ currentAssistantId = null;
+ agentStatus = "error";
+ break;
+ }
+ }
+ }
+
+ async function sendMessage(text: string) {
+ const userMsg: ChatMessage = {
+ id: generateId(),
+ role: "user",
+ content: text,
+ };
+ messages = [...messages, userMsg];
+ currentAssistantId = null;
+
+ const url = `${config.apiBase}/chat`;
+ try {
+ const res = await fetch(url, {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ message: text }),
+ });
+ if (!res.ok) {
+ const body = await res.text();
+ const errMsg: ChatMessage = {
+ id: generateId(),
+ role: "assistant",
+ content: `Error: Failed to send message (HTTP ${res.status})`,
+ isStreaming: false,
+ debugInfo: makeDebugInfo({
+ error: `POST ${url} returned ${res.status}`,
+ agentStatus,
+ httpStatus: res.status,
+ httpBody: body,
+ }),
+ };
+ messages = [...messages, errMsg];
+ }
+ } catch (err) {
+ const errorText = err instanceof Error ? err.message : String(err);
+ const errMsg: ChatMessage = {
+ id: generateId(),
+ role: "assistant",
+ content: "Error: Could not reach the server",
+ isStreaming: false,
+ debugInfo: makeDebugInfo({
+ error: `POST ${url} failed: ${errorText}`,
+ agentStatus,
+ }),
+ };
+ messages = [...messages, errMsg];
+ }
+ }
+
+ function copyConversation(): string {
+ return formatConversation(messages);
+ }
+
+ function clear() {
+ messages = [];
+ currentAssistantId = null;
+ agentStatus = "idle";
+ }
+
+ return {
+ get messages() {
+ return messages;
+ },
+ get agentStatus() {
+ return agentStatus;
+ },
+ get isConnected() {
+ return isConnected;
+ },
+ sendMessage,
+ handleEvent,
+ copyConversation,
+ clear,
+ };
+}
+
+export const chatStore = createChatStore();
diff --git a/packages/frontend/src/lib/components/ChatInput.svelte b/packages/frontend/src/lib/components/ChatInput.svelte
new file mode 100644
index 0000000..b929923
--- /dev/null
+++ b/packages/frontend/src/lib/components/ChatInput.svelte
@@ -0,0 +1,45 @@
+<script lang="ts">
+import { chatStore } from "../chat.svelte.js";
+
+let inputEl: HTMLInputElement | undefined;
+let inputValue = $state("");
+const isDisabled = $derived(chatStore.agentStatus === "running");
+
+$effect(() => {
+ inputEl?.focus();
+});
+
+function handleKeydown(e: KeyboardEvent) {
+ if (e.key === "Enter" && !e.shiftKey) {
+ e.preventDefault();
+ submit();
+ }
+}
+
+function submit() {
+ const text = inputValue.trim();
+ if (!text || isDisabled) return;
+ inputValue = "";
+ chatStore.sendMessage(text);
+}
+</script>
+
+<div class="flex items-center gap-2 p-3 border-t border-base-300 bg-base-100">
+ <input
+ bind:this={inputEl}
+ bind:value={inputValue}
+ type="text"
+ placeholder={isDisabled ? "Agent is running..." : "Type a message..."}
+ class="input input-bordered flex-1"
+ disabled={isDisabled}
+ onkeydown={handleKeydown}
+ />
+ <button
+ type="button"
+ class="btn btn-primary"
+ disabled={isDisabled || !inputValue.trim()}
+ onclick={submit}
+ >
+ Send
+ </button>
+</div>
diff --git a/packages/frontend/src/lib/components/ChatMessage.svelte b/packages/frontend/src/lib/components/ChatMessage.svelte
new file mode 100644
index 0000000..447bb29
--- /dev/null
+++ b/packages/frontend/src/lib/components/ChatMessage.svelte
@@ -0,0 +1,26 @@
+<script lang="ts">
+import type { ChatMessage } from "../types.js";
+import ToolCallDisplay from "./ToolCallDisplay.svelte";
+
+const { message }: { message: ChatMessage } = $props();
+
+const isUser = $derived(message.role === "user");
+</script>
+
+<div class="chat {isUser ? 'chat-end' : 'chat-start'} mb-2">
+ <div class="chat-bubble {isUser ? 'chat-bubble-primary' : 'chat-bubble-secondary'} max-w-[80%] break-words">
+ {#if message.content}
+ <span>{message.content}</span>
+ {/if}
+ {#if message.isStreaming}
+ <span class="inline-block w-2 h-4 bg-current animate-pulse ml-0.5 align-middle">▌</span>
+ {/if}
+ {#if message.toolCalls && message.toolCalls.length > 0}
+ <div class="mt-2">
+ {#each message.toolCalls as toolCall (toolCall.id)}
+ <ToolCallDisplay {toolCall} />
+ {/each}
+ </div>
+ {/if}
+ </div>
+</div>
diff --git a/packages/frontend/src/lib/components/ChatPanel.svelte b/packages/frontend/src/lib/components/ChatPanel.svelte
new file mode 100644
index 0000000..44efb0b
--- /dev/null
+++ b/packages/frontend/src/lib/components/ChatPanel.svelte
@@ -0,0 +1,58 @@
+<script lang="ts">
+import { chatStore } from "../chat.svelte.js";
+import { wsClient } from "../ws.svelte.js";
+import ChatMessageComponent from "./ChatMessage.svelte";
+
+let messagesEl: HTMLDivElement | undefined;
+
+const statusColor = $derived(
+ wsClient.connectionStatus === "connected"
+ ? "bg-success"
+ : wsClient.connectionStatus === "connecting"
+ ? "bg-warning"
+ : "bg-error",
+);
+
+$effect(() => {
+ // Trigger on messages change to scroll
+ void chatStore.messages;
+ if (messagesEl) {
+ messagesEl.scrollTop = messagesEl.scrollHeight;
+ }
+});
+</script>
+
+<div class="flex flex-col h-full">
+ <!-- Status bar -->
+ <div class="flex items-center gap-3 px-4 py-2 bg-base-200 border-b border-base-300 text-xs">
+ <span class="flex items-center gap-1.5">
+ <span class="w-2 h-2 rounded-full {statusColor}"></span>
+ <span class="capitalize text-base-content/70">{wsClient.connectionStatus}</span>
+ </span>
+ <span class="text-base-content/50">|</span>
+ <span class="text-base-content/70">
+ Agent:
+ <span
+ class="font-semibold {chatStore.agentStatus === 'running'
+ ? 'text-warning'
+ : chatStore.agentStatus === 'error'
+ ? 'text-error'
+ : 'text-success'}"
+ >
+ {chatStore.agentStatus === "running" ? "running..." : chatStore.agentStatus}
+ </span>
+ </span>
+ </div>
+
+ <!-- Messages -->
+ <div bind:this={messagesEl} class="flex-1 overflow-y-auto p-4">
+ {#if chatStore.messages.length === 0}
+ <div class="flex items-center justify-center h-full text-base-content/40 text-sm">
+ Send a message to start a conversation
+ </div>
+ {/if}
+ {#each chatStore.messages as message (message.id)}
+ <ChatMessageComponent {message} />
+ {/each}
+ </div>
+</div>
diff --git a/packages/frontend/src/lib/components/Header.svelte b/packages/frontend/src/lib/components/Header.svelte
new file mode 100644
index 0000000..79d371c
--- /dev/null
+++ b/packages/frontend/src/lib/components/Header.svelte
@@ -0,0 +1,54 @@
+<script lang="ts">
+import { chatStore } from "../chat.svelte.js";
+import ThemeSwitcher from "./ThemeSwitcher.svelte";
+
+let showThemeSwitcher = $state(false);
+let copyLabel = $state("Copy");
+
+function resetCopyLabel() {
+ copyLabel = "Copy";
+}
+
+async function handleCopy() {
+ const text = chatStore.copyConversation();
+ try {
+ await navigator.clipboard.writeText(text);
+ copyLabel = "Copied";
+ setTimeout(resetCopyLabel, 1500);
+ } catch {
+ copyLabel = "Failed";
+ setTimeout(resetCopyLabel, 1500);
+ }
+}
+</script>
+
+<header class="navbar bg-base-200 border-b border-base-300 px-4 min-h-14 flex-shrink-0">
+ <div class="flex-1">
+ <span class="text-xl font-bold tracking-tight">Dispatch</span>
+ </div>
+ <div class="flex-none flex items-center gap-3">
+ <span class="text-xs text-base-content/60 hidden sm:block">
+ DeepSeek V4 Flash via OpenCode Go
+ </span>
+ <button
+ type="button"
+ class="btn btn-ghost btn-sm"
+ onclick={handleCopy}
+ aria-label="Copy conversation"
+ >
+ {copyLabel}
+ </button>
+ <button
+ type="button"
+ class="btn btn-ghost btn-sm"
+ onclick={() => (showThemeSwitcher = !showThemeSwitcher)}
+ aria-label="Switch theme"
+ >
+ Theme
+ </button>
+ </div>
+</header>
+
+{#if showThemeSwitcher}
+ <ThemeSwitcher onclose={() => (showThemeSwitcher = false)} />
+{/if}
diff --git a/packages/frontend/src/lib/components/ThemeSwitcher.svelte b/packages/frontend/src/lib/components/ThemeSwitcher.svelte
new file mode 100644
index 0000000..6984e3f
--- /dev/null
+++ b/packages/frontend/src/lib/components/ThemeSwitcher.svelte
@@ -0,0 +1,65 @@
+<script lang="ts">
+const THEMES = [
+ "light",
+ "dark",
+ "dracula",
+ "night",
+ "nord",
+ "sunset",
+ "cyberpunk",
+ "forest",
+ "cmyk",
+ "coffee",
+ "caramellatte",
+] as const;
+
+const STORAGE_KEY = "dispatch-theme";
+
+const { onclose }: { onclose: () => void } = $props();
+
+let currentTheme = $state(
+ (typeof localStorage !== "undefined" && localStorage.getItem(STORAGE_KEY)) || "dark",
+);
+
+function selectTheme(theme: string) {
+ currentTheme = theme;
+ document.documentElement.setAttribute("data-theme", theme);
+ localStorage.setItem(STORAGE_KEY, theme);
+ onclose();
+}
+</script>
+
+<!-- Backdrop -->
+<div
+ class="fixed inset-0 z-40 bg-black/40"
+ role="button"
+ tabindex="0"
+ onclick={onclose}
+ onkeydown={(e) => e.key === "Escape" && onclose()}
+ aria-label="Close theme switcher"
+></div>
+
+<!-- Modal -->
+<div
+ class="fixed top-16 right-4 z-50 bg-base-100 border border-base-300 rounded-xl shadow-xl p-4 w-56"
+ role="dialog"
+ aria-label="Theme switcher"
+>
+ <p class="text-sm font-semibold mb-3 text-base-content">Select Theme</p>
+ <ul class="space-y-1">
+ {#each THEMES as theme}
+ <li>
+ <button
+ type="button"
+ class="w-full text-left px-3 py-1.5 rounded-lg text-sm capitalize hover:bg-base-200 transition-colors {currentTheme ===
+ theme
+ ? 'bg-primary text-primary-content'
+ : ''}"
+ onclick={() => selectTheme(theme)}
+ >
+ {theme}
+ </button>
+ </li>
+ {/each}
+ </ul>
+</div>
diff --git a/packages/frontend/src/lib/components/ToolCallDisplay.svelte b/packages/frontend/src/lib/components/ToolCallDisplay.svelte
new file mode 100644
index 0000000..f8e1f38
--- /dev/null
+++ b/packages/frontend/src/lib/components/ToolCallDisplay.svelte
@@ -0,0 +1,50 @@
+<script lang="ts">
+import type { ToolCallDisplay } from "../types.js";
+
+const { toolCall }: { toolCall: ToolCallDisplay } = $props();
+
+let isExpanded = $state(toolCall.isExpanded);
+
+function toggle() {
+ isExpanded = !isExpanded;
+}
+</script>
+
+<div class="collapse collapse-arrow bg-base-200 my-1 rounded-lg border border-base-300">
+ <button
+ type="button"
+ class="collapse-title flex items-center gap-2 text-sm font-medium cursor-pointer w-full text-left"
+ onclick={toggle}
+ aria-expanded={isExpanded}
+ >
+ <span class="badge badge-neutral badge-sm">tool</span>
+ <span class="font-mono">{toolCall.name}</span>
+ {#if toolCall.result !== undefined}
+ {#if toolCall.isError}
+ <span class="badge badge-error badge-sm ml-auto">error</span>
+ {:else}
+ <span class="badge badge-success badge-sm ml-auto">done</span>
+ {/if}
+ {:else}
+ <span class="badge badge-warning badge-sm ml-auto">pending</span>
+ {/if}
+ </button>
+
+ {#if isExpanded}
+ <div class="collapse-content text-xs">
+ <div class="mt-2">
+ <p class="font-semibold text-base-content/70 mb-1">Arguments</p>
+ <pre class="bg-base-300 rounded p-2 overflow-auto max-h-40 whitespace-pre-wrap break-all">{JSON.stringify(toolCall.arguments, null, 2)}</pre>
+ </div>
+ {#if toolCall.result !== undefined}
+ <div class="mt-2">
+ <p class="font-semibold text-base-content/70 mb-1">Result</p>
+ <pre
+ class="rounded p-2 overflow-auto max-h-40 whitespace-pre-wrap break-all {toolCall.isError
+ ? 'bg-error/20 text-error'
+ : 'bg-base-300'}">{toolCall.result}</pre>
+ </div>
+ {/if}
+ </div>
+ {/if}
+</div>
diff --git a/packages/frontend/src/lib/config.ts b/packages/frontend/src/lib/config.ts
new file mode 100644
index 0000000..c22746c
--- /dev/null
+++ b/packages/frontend/src/lib/config.ts
@@ -0,0 +1,6 @@
+const API_BASE = import.meta.env.VITE_API_URL ?? "http://localhost:3000";
+
+export const config = {
+ apiBase: API_BASE,
+ wsUrl: `${API_BASE.replace(/^http/, "ws")}/ws`,
+} as const;
diff --git a/packages/frontend/src/lib/types.ts b/packages/frontend/src/lib/types.ts
new file mode 100644
index 0000000..6edb550
--- /dev/null
+++ b/packages/frontend/src/lib/types.ts
@@ -0,0 +1,57 @@
+export interface ToolCallDisplay {
+ id: string;
+ name: string;
+ arguments: Record<string, unknown>;
+ result?: string;
+ isError?: boolean;
+ isExpanded: boolean;
+}
+
+export interface DebugInfo {
+ timestamp: string;
+ error?: string;
+ model?: string;
+ apiBase?: string;
+ connectionStatus?: string;
+ agentStatus?: string;
+ rawEvent?: unknown;
+ httpStatus?: number;
+ httpBody?: string;
+}
+
+export interface ChatMessage {
+ id: string;
+ role: "user" | "assistant";
+ content: string;
+ toolCalls?: ToolCallDisplay[];
+ isStreaming?: boolean;
+ debugInfo?: DebugInfo;
+}
+
+export type ConnectionStatus = "connecting" | "connected" | "disconnected";
+
+export type AgentEvent =
+ | { type: "status"; status: "idle" | "running" | "error" }
+ | { type: "text-delta"; delta: string }
+ | {
+ type: "tool-call";
+ toolCall: {
+ id: string;
+ name: string;
+ arguments: Record<string, unknown>;
+ };
+ }
+ | {
+ type: "tool-result";
+ toolResult: { toolCallId: string; result: string; isError: boolean };
+ }
+ | { type: "error"; error: string }
+ | {
+ type: "done";
+ message: {
+ role: string;
+ content: string;
+ toolCalls?: unknown[];
+ toolResults?: unknown[];
+ };
+ };
diff --git a/packages/frontend/src/lib/ws.svelte.ts b/packages/frontend/src/lib/ws.svelte.ts
new file mode 100644
index 0000000..76c7ef5
--- /dev/null
+++ b/packages/frontend/src/lib/ws.svelte.ts
@@ -0,0 +1,89 @@
+import { config } from "./config.js";
+import type { AgentEvent, ConnectionStatus } from "./types.js";
+
+type EventCallback = (event: AgentEvent) => void;
+
+function createWebSocketClient(url: string) {
+ let connectionStatus: ConnectionStatus = $state("disconnected");
+ let ws: WebSocket | null = null;
+ let reconnectDelay = 1000;
+ let reconnectTimer: ReturnType<typeof setTimeout> | null = null;
+ let manualDisconnect = false;
+ const callbacks: EventCallback[] = [];
+
+ function connect() {
+ if (ws && (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING)) {
+ return;
+ }
+ manualDisconnect = false;
+ connectionStatus = "connecting";
+ ws = new WebSocket(url);
+
+ ws.onopen = () => {
+ connectionStatus = "connected";
+ reconnectDelay = 1000;
+ };
+
+ ws.onmessage = (event: MessageEvent) => {
+ try {
+ const data = JSON.parse(event.data as string) as AgentEvent;
+ for (const cb of callbacks) {
+ cb(data);
+ }
+ } catch {
+ // ignore malformed messages
+ }
+ };
+
+ ws.onclose = () => {
+ connectionStatus = "disconnected";
+ ws = null;
+ if (!manualDisconnect) {
+ scheduleReconnect();
+ }
+ };
+
+ ws.onerror = () => {
+ ws?.close();
+ };
+ }
+
+ function scheduleReconnect() {
+ if (reconnectTimer) clearTimeout(reconnectTimer);
+ reconnectTimer = setTimeout(() => {
+ reconnectTimer = null;
+ connect();
+ }, reconnectDelay);
+ reconnectDelay = Math.min(reconnectDelay * 2, 10000);
+ }
+
+ function disconnect() {
+ manualDisconnect = true;
+ if (reconnectTimer) {
+ clearTimeout(reconnectTimer);
+ reconnectTimer = null;
+ }
+ ws?.close();
+ ws = null;
+ connectionStatus = "disconnected";
+ }
+
+ function onEvent(callback: EventCallback) {
+ callbacks.push(callback);
+ return () => {
+ const idx = callbacks.indexOf(callback);
+ if (idx !== -1) callbacks.splice(idx, 1);
+ };
+ }
+
+ return {
+ get connectionStatus() {
+ return connectionStatus;
+ },
+ connect,
+ disconnect,
+ onEvent,
+ };
+}
+
+export const wsClient = createWebSocketClient(config.wsUrl);
diff --git a/packages/frontend/src/main.ts b/packages/frontend/src/main.ts
new file mode 100644
index 0000000..fd54362
--- /dev/null
+++ b/packages/frontend/src/main.ts
@@ -0,0 +1,9 @@
+import App from "./App.svelte";
+import "./app.css";
+import { mount } from "svelte";
+
+const app = mount(App, {
+ target: document.getElementById("app") as HTMLElement,
+});
+
+export default app;
diff --git a/packages/frontend/src/vite-env.d.ts b/packages/frontend/src/vite-env.d.ts
new file mode 100644
index 0000000..4078e74
--- /dev/null
+++ b/packages/frontend/src/vite-env.d.ts
@@ -0,0 +1,2 @@
+/// <reference types="svelte" />
+/// <reference types="vite/client" />
diff --git a/packages/frontend/svelte.config.js b/packages/frontend/svelte.config.js
new file mode 100644
index 0000000..f77d881
--- /dev/null
+++ b/packages/frontend/svelte.config.js
@@ -0,0 +1,5 @@
+import { vitePreprocess } from "@sveltejs/vite-plugin-svelte";
+
+export default {
+ preprocess: vitePreprocess(),
+};
diff --git a/packages/frontend/tests/chat-store.test.ts b/packages/frontend/tests/chat-store.test.ts
new file mode 100644
index 0000000..de988a2
--- /dev/null
+++ b/packages/frontend/tests/chat-store.test.ts
@@ -0,0 +1,261 @@
+import { beforeEach, describe, expect, it } from "vitest";
+import type { AgentEvent } from "../src/lib/types.js";
+
+// We test the logic inline since runes require svelte compilation context.
+// The chat store logic is tested via a plain reimplementation of the same logic.
+
+function generateId() {
+ return Math.random().toString(36).slice(2, 11);
+}
+
+interface ToolCallDisplay {
+ id: string;
+ name: string;
+ arguments: Record<string, unknown>;
+ result?: string;
+ isError?: boolean;
+ isExpanded: boolean;
+}
+
+interface ChatMessage {
+ id: string;
+ role: "user" | "assistant";
+ content: string;
+ toolCalls?: ToolCallDisplay[];
+ isStreaming?: boolean;
+}
+
+// Plain JS version of the chat store logic (no runes) for unit testing
+function createTestStore() {
+ let messages: ChatMessage[] = [];
+ let agentStatus: "idle" | "running" | "error" = "idle";
+ let currentAssistantId: string | null = null;
+
+ function getCurrentAssistantMessage(): ChatMessage | null {
+ if (!currentAssistantId) return null;
+ return messages.find((m) => m.id === currentAssistantId) ?? null;
+ }
+
+ function ensureCurrentAssistantMessage(): ChatMessage {
+ let msg = getCurrentAssistantMessage();
+ if (!msg) {
+ const id = generateId();
+ currentAssistantId = id;
+ const newMsg: ChatMessage = {
+ id,
+ role: "assistant",
+ content: "",
+ toolCalls: [],
+ isStreaming: true,
+ };
+ messages = [...messages, newMsg];
+ msg = newMsg;
+ }
+ return msg;
+ }
+
+ function handleEvent(event: AgentEvent) {
+ switch (event.type) {
+ case "status": {
+ agentStatus = event.status;
+ if (event.status === "idle" || event.status === "error") {
+ currentAssistantId = null;
+ }
+ break;
+ }
+ case "text-delta": {
+ ensureCurrentAssistantMessage();
+ messages = messages.map((m) => {
+ if (m.id === currentAssistantId) {
+ return {
+ ...m,
+ content: m.content + event.delta,
+ isStreaming: true,
+ };
+ }
+ return m;
+ });
+ break;
+ }
+ case "tool-call": {
+ ensureCurrentAssistantMessage();
+ const toolCall: ToolCallDisplay = {
+ id: event.toolCall.id,
+ name: event.toolCall.name,
+ arguments: event.toolCall.arguments,
+ isExpanded: false,
+ };
+ messages = messages.map((m) => {
+ if (m.id === currentAssistantId) {
+ return { ...m, toolCalls: [...(m.toolCalls ?? []), toolCall] };
+ }
+ return m;
+ });
+ break;
+ }
+ case "tool-result": {
+ messages = messages.map((m) => {
+ if (m.id === currentAssistantId) {
+ return {
+ ...m,
+ toolCalls: (m.toolCalls ?? []).map((tc) => {
+ if (tc.id === event.toolResult.toolCallId) {
+ return {
+ ...tc,
+ result: event.toolResult.result,
+ isError: event.toolResult.isError,
+ };
+ }
+ return tc;
+ }),
+ };
+ }
+ return m;
+ });
+ break;
+ }
+ case "done": {
+ messages = messages.map((m) => {
+ if (m.id === currentAssistantId) {
+ return { ...m, content: event.message.content, isStreaming: false };
+ }
+ return m;
+ });
+ currentAssistantId = null;
+ break;
+ }
+ case "error": {
+ messages = [
+ ...messages,
+ {
+ id: generateId(),
+ role: "assistant",
+ content: `Error: ${event.error}`,
+ isStreaming: false,
+ },
+ ];
+ currentAssistantId = null;
+ agentStatus = "error";
+ break;
+ }
+ }
+ }
+
+ function sendMessage(text: string) {
+ const userMsg: ChatMessage = {
+ id: generateId(),
+ role: "user",
+ content: text,
+ };
+ messages = [...messages, userMsg];
+ currentAssistantId = null;
+ }
+
+ function clear() {
+ messages = [];
+ currentAssistantId = null;
+ agentStatus = "idle";
+ }
+
+ return {
+ get messages() {
+ return messages;
+ },
+ get agentStatus() {
+ return agentStatus;
+ },
+ handleEvent,
+ sendMessage,
+ clear,
+ };
+}
+
+describe("chat store logic", () => {
+ let store: ReturnType<typeof createTestStore>;
+
+ beforeEach(() => {
+ store = createTestStore();
+ });
+
+ it("has correct initial state", () => {
+ expect(store.messages).toHaveLength(0);
+ expect(store.agentStatus).toBe("idle");
+ });
+
+ it("sendMessage adds a user message", () => {
+ store.sendMessage("hello");
+ expect(store.messages).toHaveLength(1);
+ expect(store.messages[0]?.role).toBe("user");
+ expect(store.messages[0]?.content).toBe("hello");
+ });
+
+ it("text-delta creates a streaming assistant message and appends deltas", () => {
+ store.handleEvent({ type: "text-delta", delta: "Hello" });
+ expect(store.messages).toHaveLength(1);
+ expect(store.messages[0]?.role).toBe("assistant");
+ expect(store.messages[0]?.content).toBe("Hello");
+ expect(store.messages[0]?.isStreaming).toBe(true);
+
+ store.handleEvent({ type: "text-delta", delta: " world" });
+ expect(store.messages[0]?.content).toBe("Hello world");
+ });
+
+ it("tool-call adds to current assistant message toolCalls", () => {
+ store.handleEvent({ type: "text-delta", delta: "Calling tool..." });
+ store.handleEvent({
+ type: "tool-call",
+ toolCall: { id: "tc1", name: "search", arguments: { query: "test" } },
+ });
+ const msg = store.messages[0];
+ expect(msg?.toolCalls).toHaveLength(1);
+ expect(msg?.toolCalls?.[0]?.name).toBe("search");
+ expect(msg?.toolCalls?.[0]?.id).toBe("tc1");
+ });
+
+ it("tool-result fills in result on matching tool call", () => {
+ store.handleEvent({ type: "text-delta", delta: "..." });
+ store.handleEvent({
+ type: "tool-call",
+ toolCall: { id: "tc1", name: "search", arguments: { query: "x" } },
+ });
+ store.handleEvent({
+ type: "tool-result",
+ toolResult: { toolCallId: "tc1", result: "found it", isError: false },
+ });
+ const tc = store.messages[0]?.toolCalls?.[0];
+ expect(tc?.result).toBe("found it");
+ expect(tc?.isError).toBe(false);
+ });
+
+ it("done finalizes the current assistant message", () => {
+ store.handleEvent({ type: "text-delta", delta: "partial" });
+ store.handleEvent({
+ type: "done",
+ message: { role: "assistant", content: "full content" },
+ });
+ expect(store.messages[0]?.content).toBe("full content");
+ expect(store.messages[0]?.isStreaming).toBe(false);
+ });
+
+ it("error event adds an error message and sets status to error", () => {
+ store.handleEvent({ type: "error", error: "something went wrong" });
+ expect(store.messages).toHaveLength(1);
+ expect(store.messages[0]?.content).toBe("Error: something went wrong");
+ expect(store.agentStatus).toBe("error");
+ });
+
+ it("status event updates agentStatus", () => {
+ store.handleEvent({ type: "status", status: "running" });
+ expect(store.agentStatus).toBe("running");
+ store.handleEvent({ type: "status", status: "idle" });
+ expect(store.agentStatus).toBe("idle");
+ });
+
+ it("clear resets all state", () => {
+ store.sendMessage("hi");
+ store.handleEvent({ type: "text-delta", delta: "hello" });
+ store.clear();
+ expect(store.messages).toHaveLength(0);
+ expect(store.agentStatus).toBe("idle");
+ });
+});
diff --git a/packages/frontend/tsconfig.json b/packages/frontend/tsconfig.json
new file mode 100644
index 0000000..386cd8c
--- /dev/null
+++ b/packages/frontend/tsconfig.json
@@ -0,0 +1,10 @@
+{
+ "extends": "../../tsconfig.base.json",
+ "compilerOptions": {
+ "declaration": false,
+ "declarationMap": false,
+ "types": ["vite/client"]
+ },
+ "include": ["src/**/*.ts", "src/**/*.svelte", "src/vite-env.d.ts"],
+ "exclude": ["node_modules", "dist"]
+}
diff --git a/packages/frontend/vite.config.ts b/packages/frontend/vite.config.ts
new file mode 100644
index 0000000..3604d5e
--- /dev/null
+++ b/packages/frontend/vite.config.ts
@@ -0,0 +1,13 @@
+import { svelte } from "@sveltejs/vite-plugin-svelte";
+import tailwindcss from "@tailwindcss/vite";
+import { defineConfig } from "vite";
+
+export default defineConfig({
+ plugins: [tailwindcss(), svelte()],
+ server: {
+ port: 5173,
+ },
+ test: {
+ include: ["tests/**/*.test.ts"],
+ },
+});
diff --git a/plan.md b/plan.md
new file mode 100644
index 0000000..dc672cb
--- /dev/null
+++ b/plan.md
@@ -0,0 +1,408 @@
+# Dispatch — Build Plan
+
+## Stack
+
+| Layer | Choice | Why |
+|---|---|---|
+| Runtime | TypeScript / Node.js | Rich LLM ecosystem, strong async, same language front+back |
+| LLM | Vercel AI SDK (`ai`) | Provider-agnostic, streaming, tool calling, 15+ providers |
+| API | Hono or Fastify | Lightweight, WebSocket support |
+| Persistence | better-sqlite3 + drizzle-orm | Embedded, no external DB dependency |
+| Config/Skills | gray-matter + yaml + chokidar | YAML frontmatter parsing, hot-reload on file changes |
+| Frontend | HTML/CSS/JS | Lightweight for MVP, no heavy framework |
+| Process mgmt | child_process + tree-kill | Subagent lifecycle management |
+| LSP (Phase 6) | vscode-languageserver-protocol | Standard LSP client library |
+
+## Project Structure
+
+```
+dispatch/
+ packages/
+ core/ # Agent runtime, LLM, tools, permissions, config
+ api/ # HTTP + WebSocket server
+ frontend/ # HTML/CSS/JS client
+ .skills/ # Project-level skills (dogfooding)
+ dispatch.yaml # Project config (dogfooding)
+```
+
+---
+
+## Phase 1: Single Agent + Basic UI
+
+**Goal:** Chat with one agent in a browser, watch it read and write files.
+
+**Effort:** 2-3 weeks
+
+### Backend
+
+- [ ] Project scaffolding (monorepo with packages/core, packages/api, packages/frontend)
+- [ ] Agent runtime: message -> LLM -> tool call -> result -> repeat loop
+- [ ] Vercel AI SDK integration with streaming responses
+- [ ] Single provider config (one API key, one model — hardcoded or env vars for now)
+- [ ] Basic tools:
+ - `read_file` — read file contents
+ - `write_file` — write/overwrite a file
+ - `list_files` — glob/list directory contents
+- [ ] HTTP API:
+ - `POST /chat` — send a message, get streaming response
+ - `GET /status` — agent status (idle, running, etc.)
+- [ ] WebSocket: stream agent output tokens and tool calls in real-time
+
+### Frontend
+
+- [ ] Single chat panel — text input field, send button
+- [ ] Streamed response rendering (tokens appear as they arrive)
+- [ ] Tool call display (collapsible: show tool name, arguments, result)
+- [ ] Model/provider indicator in header
+- [ ] Basic layout: chat takes full screen, clean and minimal
+
+### Done When
+
+Open a browser, type "read the contents of package.json and summarize it," see the agent call `read_file`, stream back a summary. Ask it to create a new file — it calls `write_file` and confirms.
+
+---
+
+## Phase 2: Shell Permissions + UI
+
+**Goal:** Agent can run shell commands with directory-scoped permission controls. Usable on real projects.
+
+**Effort:** 1-2 weeks
+
+### Backend
+
+- [ ] Shell tool:
+ - `run_shell` — execute arbitrary commands, capture stdout/stderr/exit code
+ - Streaming output for long-running commands
+ - Working directory parameter (defaults to project root)
+- [ ] Directory permission system:
+ - Current working directory + subdirectories: always allowed (read, write, execute)
+ - Auto-allow list loaded from config file
+ - All other directories: prompt user for permission before access
+- [ ] Permission grant types:
+ - Per-request — allow this one operation
+ - Per-session — allow this directory for the rest of the session
+ - Permanent — add to auto-allow list in config
+- [ ] Permission prompt flow:
+ - Agent calls a tool that touches an out-of-scope directory
+ - API holds the request open (agent pauses)
+ - WebSocket pushes a permission prompt to the frontend
+ - User responds (approve/deny/always-allow)
+ - API resolves, agent continues or gets a denial message
+- [ ] Basic config file loading (`dispatch.yaml`) for auto-allow list:
+ ```yaml
+ permissions:
+ auto_allow:
+ - /tmp
+ - ~/.config/dispatch
+ ```
+- [ ] Apply permission checks to existing file tools (`read_file`, `write_file`) as well
+
+### Frontend
+
+- [ ] Permission prompt modal:
+ - Agent name
+ - Target path
+ - Operation type (read / write / execute)
+ - Buttons: Approve / Deny / Always Allow
+- [ ] Permission log panel: scrollable history of grants and denials
+- [ ] Shell output display in chat: stdout/stderr with monospace formatting, exit code indicator
+- [ ] Visual distinction between tool calls (file ops vs shell commands)
+
+### Done When
+
+Ask the agent to "run the test suite." It executes `npm test` in the project dir (allowed). Then ask it to "check what's in /etc/hosts." Permission prompt appears. You approve. It reads the file and reports back. Next time it tries `/etc/`, it remembers your per-session grant.
+
+---
+
+## Phase 3: Config + Skills + Model Groups
+
+**Goal:** YAML-driven agent templates, skills auto-loading from directory structure, multi-provider model groups with key budgets, fallback chains, and wait-on-exhaustion.
+
+**Effort:** 2-3 weeks
+
+### Backend — Config System
+
+- [ ] Full `dispatch.yaml` config loader:
+ - Agent templates: name, description, system prompt, tools, permissions, model group
+ - Model definitions with tags
+ - Key definitions with budget limits
+ - Fallback order
+ - Permission auto-allow list (already from Phase 2, now in full config)
+- [ ] Config validation on load (clear errors for missing fields, bad references)
+- [ ] Hot-reload: watch `dispatch.yaml` for changes, apply without restart
+
+### Backend — Model Groups + Key Management
+
+- [ ] Model tag system: each model has a list of tags (`heavy`, `medium`, `light`, `coding`, `review`, etc.)
+- [ ] Tag resolution: agent requests a tag -> system finds the best available model matching that tag, respecting fallback order
+- [ ] Key budget tracking:
+ - Track token usage and/or cost per key
+ - Configurable budget limits (per-month, per-day, or total)
+- [ ] Key fallback chain:
+ - Use highest-priority key first
+ - On exhaustion, switch to next key in chain
+ - Log the switch
+- [ ] Key exhaustion wait:
+ - When ALL keys for an agent are exhausted, agent enters wait state
+ - Poll for key availability on configurable interval
+ - Resume with whichever key refreshes first
+ - Per-agent: other agents with available keys continue running
+ - Preserve full agent context across the wait
+- [ ] API endpoints:
+ - `GET /config` — current config state
+ - `GET /models` — available models, tags, key status, budget remaining
+ - `GET /models/resolve?tag=heavy` — which model would be selected for a tag right now
+
+### Backend — Skills System
+
+- [ ] Skills directory loader (both levels):
+ ```
+ ~/.skills/
+ default/ # Auto-loaded for all agents globally
+ agents/ # Agent-type mappings
+ project/ # Available to any project, manually activated
+
+ <project>/.skills/
+ default/ # Auto-loaded for agents in this project
+ agents/ # Agent-type mappings for this project
+ project/ # Available in this project, manually activated
+ ```
+- [ ] Markdown skill files with YAML frontmatter (name, description, tags)
+- [ ] Agent mapping files in `agents/`:
+ - `<name>.txt` — maps skills to a subagent type
+ - `<name>.o.txt` — maps skills to an orchestrator type
+ - File contents: list of skill filenames to activate
+- [ ] Loading order:
+ 1. Global `default/` skills
+ 2. Project `default/` skills
+ 3. Agent-specific skills from `agents/` mappings (global then project)
+ 4. Manually activated `project/` skills on demand
+- [ ] Scope disambiguation: `global:skill-name` vs `project:skill-name` when both exist
+- [ ] Hot-reload: watch skills directories for changes via chokidar
+- [ ] API endpoints:
+ - `GET /skills` — all loaded skills, organized by scope and directory
+ - `GET /skills/:name` — skill content
+
+### Backend — Task List Tool
+
+- [ ] `task_list` tool available to all agents:
+ - `add(title, description)` — returns task ID
+ - `update(task_id, status)` — status: pending, in_progress, done, blocked
+ - `list()` — returns current task state
+ - `get(task_id)` — returns task details
+- [ ] Task list persists with agent state (survives context compaction and key exhaustion waits)
+- [ ] Parent agents can read child agent task lists
+
+### Frontend
+
+- [ ] Config viewer panel:
+ - Agent templates: name, description, model group, permissions
+ - Model groups: which models have which tags
+ - Key status: active / exhausted / waiting, budget used / remaining
+- [ ] Key/model status visualization:
+ - Per-key budget bar (used / remaining)
+ - Current fallback position indicator
+ - "Waiting for refresh" state with estimated time if known
+- [ ] Skills browser:
+ - Tree view organized by scope (global / project) and directory (default / agents / project)
+ - Click a skill to see its content
+ - Show which skills are mapped to which agent types
+- [ ] Hot-reload indicator: visual flash when config or skills change on disk
+- [ ] Task list view: show current agent's task list with status indicators
+
+### Done When
+
+You have a `dispatch.yaml` with two API keys (Anthropic + OpenAI), model groups tagged `heavy` and `light`, and a $5 budget on each key. Skills are loading from `.skills/default/`. You chat with the agent — it uses the Anthropic key until the budget drains, switches to OpenAI, drains that, then shows "waiting for key refresh" in the UI. You leave it overnight. In the morning, the key has refreshed and the task completed.
+
+---
+
+## Phase 4: Agent Spawning + Tree UI
+
+**Goal:** Agents can spawn child agents with defined context, model, and permissions. Full hierarchy visible in real-time. User can message any agent.
+
+**Effort:** 2-3 weeks
+
+### Backend
+
+- [ ] `summon_agent` tool:
+ - Parameters: task description, context (text and/or skill names), model tag or specific model, permission set, `detached` flag
+ - Returns an agent handle (ID, status)
+ - Parent can summon multiple children concurrently
+ - Child inherits project working directory but gets its own conversation context
+ - **Detached mode** (`detached: true`):
+ - Child agent gets a direct user-facing conversation channel
+ - It can ask the user questions, request clarification, and wait for input
+ - Child may spawn its own subagents (leaf workers, not further detached)
+ - Child reports results back to parent when its task is complete
+ - Parent continues running while detached child is active
+ - User sees detached child as a separate conversation thread in the UI
+- [ ] Permission enforcement:
+ - Agent can only use `summon_agent` if it has `summon_subagents` permission
+ - Child agent's permissions cannot exceed parent's permissions
+ - Parent defines child's permissions explicitly at spawn time
+- [ ] Agent tree data structure:
+ - Parent-child relationships
+ - Per-agent: status (running / waiting / done / error / waiting_for_key), model, permissions, task list
+ - Tree updates broadcast via WebSocket
+- [ ] Parent-child communication:
+ - Child results flow back to parent as tool call results
+ - Parent can read child's task list for progress without consuming full conversation
+ - Parent waits for child completion (or can check status asynchronously)
+- [ ] User-to-agent messaging:
+ - `POST /agents/:id/message` — queue a message for a specific agent
+ - Message delivered at the agent's next tool boundary
+ - Agent acknowledges and incorporates the message
+- [ ] Agent lifecycle management:
+ - Running: actively processing
+ - Waiting: blocked on child agents or user input
+ - Waiting for key: all keys exhausted, polling for refresh
+ - Done: completed, results available to parent
+ - Error: failed, error details available
+ - Cleanup: terminate child processes on completion or error
+- [ ] Conflict prevention:
+ - Not enforced by the system — this is the orchestrator agent's responsibility
+ - Orchestrator skills should instruct the agent to assign non-overlapping file scopes to children
+ - The system provides the tools; the skills provide the discipline
+
+### Frontend
+
+- [ ] Agent tree panel (sidebar or split view):
+ - Collapsible tree showing full hierarchy
+ - Per-agent: name/task summary, status icon, model badge
+ - Real-time updates (new agent appears, status changes, agent completes)
+ - Click any agent to view its chat stream
+- [ ] Agent detail view:
+ - Chat/output stream for the selected agent
+ - Metadata: model, permissions, parent agent, loaded skills, detached status
+ - Task list for this agent
+ - "Send message" input for user-to-agent injection (always available for detached agents, available for any agent via message routing)
+- [ ] Detached orchestrator support:
+ - Detached agents appear as separate conversation threads alongside the main dispatch thread
+ - User can switch between the dispatch conversation and any active detached orchestrator
+ - Notifications when a detached orchestrator is waiting for user input
+ - When a detached orchestrator completes, results flow back to the parent and the thread becomes read-only
+- [ ] Permission prompts now show which specific agent is requesting access
+- [ ] Tree-level status summary: total agents, running, waiting, done, errors
+- [ ] Visual indicators for key exhaustion: which agents are waiting for keys vs actively running
+
+### Done When
+
+You tell the dispatch agent: "Plan the authentication system for this project." The dispatch agent spawns a planning orchestrator in **detached** mode. The orchestrator opens its own conversation thread in the UI. It asks you: "Should this support OAuth, JWT, or both?" You answer. It asks about session duration. You clarify. Once it has enough input, it writes the plan, reports back to the dispatch agent, and its thread becomes read-only. Meanwhile you were still chatting with the dispatch agent about other things.
+
+You tell the dispatch agent: "Research how authentication works in this codebase and write a summary." The agent (given orchestration skills) spawns a research subagent to search the code and a writing subagent to draft the summary. You see both appear in the tree panel. Click into the research agent — watch it grep files. Click into the writer — it's waiting for the researcher to finish. Researcher completes, results flow to the orchestrator, orchestrator hands context to the writer, writer produces the summary, orchestrator delivers it back to you. Send a message to the writer mid-task: "focus on OAuth specifically." It acknowledges and adjusts.
+
+---
+
+## Phase 5: Session Management
+
+**Goal:** Full session persistence. Close the browser, come back tomorrow, pick up where you left off. Fork conversations to try different approaches.
+
+**Effort:** 1-2 weeks
+
+### Backend
+
+- [ ] SQLite schema:
+ - Sessions: id, project path, created_at, updated_at, metadata
+ - Messages: session_id, agent_id, role, content, tool_calls, timestamp
+ - Agent snapshots: session_id, agent_id, parent_id, config, status, task_list
+- [ ] Auto-save: persist every message and tool result as it happens
+- [ ] Resume: load a session, restore conversation context for the dispatch agent
+ - Note: child agents are NOT resumed (they completed or were terminated)
+ - Conversation history is restored so the agent has full context
+- [ ] Fork: create a new session branching from any message in an existing session
+ - Copies conversation up to the fork point
+ - New session diverges from there
+- [ ] Model switching: change the model for any agent mid-session
+ - Context preserved, next LLM call uses the new model
+- [ ] Session search: query by date range, project, content keywords
+- [ ] API endpoints:
+ - `GET /sessions` — list sessions with metadata
+ - `GET /sessions/:id` — full session data
+ - `POST /sessions/:id/resume` — resume a session
+ - `POST /sessions/:id/fork?at=message_id` — fork from a point
+ - `PATCH /agents/:id/model` — switch model for an agent
+ - `GET /sessions/search?q=...` — search sessions
+
+### Frontend
+
+- [ ] Session sidebar:
+ - List of past sessions with metadata (date, project, message count, cost)
+ - Search/filter bar
+ - "New session" button
+- [ ] Resume: click a past session to load and continue
+- [ ] Fork: right-click or button on any message -> "Fork from here"
+ - Opens a new session tab branching from that point
+- [ ] Model switcher: dropdown per agent to change models
+- [ ] Session cost summary: total tokens, estimated cost, breakdown by key/provider
+- [ ] Active session indicator: which session you're currently in
+
+### Done When
+
+You've been working on a task for an hour. Close the browser tab. Open it again. Click the session in the sidebar — full conversation loads, you continue from where you left off. Go back to message #5, click "Fork," try a completely different approach without losing the original.
+
+---
+
+## Phase 6: LSP Integration
+
+**Goal:** Agents can access real compiler/linter diagnostics via Language Server Protocol.
+
+**Effort:** 1-2 weeks
+
+### Backend
+
+- [ ] LSP client manager:
+ - Spawn language server processes (e.g., `typescript-language-server --stdio`)
+ - Manage lifecycle: start, initialize, monitor, restart on crash
+ - One server per language per project, shared across all agents
+- [ ] Auto-detection: inspect project files to determine language(s)
+ - `tsconfig.json` / `package.json` -> TypeScript
+ - `pyproject.toml` / `setup.py` -> Python
+ - `go.mod` -> Go
+ - etc.
+- [ ] Manual config overrides in `dispatch.yaml`:
+ ```yaml
+ lsp:
+ servers:
+ typescript:
+ command: typescript-language-server
+ args: [--stdio]
+ python:
+ command: pylsp
+ ```
+- [ ] `diagnostics` tool for agents:
+ - `get_diagnostics(file?)` — returns current errors/warnings, optionally filtered to a file
+ - `get_diagnostics_summary()` — count of errors/warnings across workspace
+- [ ] File sync: notify LSP when agents modify files (via `textDocument/didChange` or `textDocument/didOpen`)
+- [ ] API endpoints:
+ - `GET /lsp/status` — which servers are running
+ - `GET /lsp/diagnostics` — current diagnostics
+
+### Frontend
+
+- [ ] Diagnostics panel:
+ - List of current errors/warnings grouped by file
+ - Severity indicators (error / warning / info)
+ - Click to see full diagnostic message
+- [ ] Per-agent diagnostic context: show which errors an agent was given to work on
+- [ ] LSP server status: indicator showing which language servers are running/healthy
+
+### Done When
+
+An agent edits a TypeScript file and introduces a type error. You see the error appear in the diagnostics panel. Another agent (or the same one) calls `get_diagnostics()` and gets the error. It fixes the issue. The diagnostic disappears.
+
+---
+
+## Summary
+
+| Phase | Scope | Effort | Cumulative |
+|---|---|---|---|
+| 1. Single Agent + UI | One agent, chat in browser | 2-3w | 2-3w |
+| 2. Shell Permissions | Safe shell access, permission prompts | 1-2w | 3-5w |
+| 3. Config + Skills + Models | YAML config, skills dirs, model groups, key fallback | 2-3w | 5-8w |
+| 4. Spawning + Tree | Multi-agent hierarchy, tree UI, user messaging | 2-3w | 7-11w |
+| 5. Sessions | Persistence, fork, resume, model switch | 1-2w | 8-13w |
+| 6. LSP | Compiler diagnostics for agents | 1-2w | 9-15w |
+
+After Phase 2: usable on real projects.
+After Phase 4: full vision working.
+After Phase 6: feature-complete MVP.
diff --git a/requirements.md b/requirements.md
new file mode 100644
index 0000000..96b2fb8
--- /dev/null
+++ b/requirements.md
@@ -0,0 +1,353 @@
+# Dispatch - AI Agent Harness Requirements
+
+## Overview
+
+Dispatch is a multi-layered AI agent orchestration harness. The user interacts with a top-level **dispatch** layer, which spawns background **orchestrators** for high-level tasks. Orchestrators in turn spawn parallel **subagents** to execute atomic units of work.
+
+The goal is to enable complex, multi-step software engineering workflows (planning, research, implementation, review) through composable, config-driven agent hierarchies.
+
+## Architecture
+
+### Agent Hierarchy (Emergent, Not Rigid)
+
+The hierarchy is not a fixed three-layer structure enforced in code. Instead, it **emerges naturally from agent permissions and context**. Every agent is the same primitive -- what distinguishes a "dispatch agent" from an "orchestrator" from a "leaf worker" is the permissions and skills it was given when spawned.
+
+```
+User <-> Dispatch Agent (top-level, has all permissions)
+ |
+ +---> Agent A (given orchestration skills + summon permission)
+ | |-- Agent A1 (given task skills, no summon permission)
+ | |-- Agent A2 (given task skills, no summon permission)
+ |
+ +---> Agent B (given orchestration skills + summon permission)
+ |-- Agent B1 (given task skills + summon permission)
+ |-- Agent B1a (given narrow task skills, no summon)
+```
+
+**When an agent spawns a subagent, the parent defines:**
+- **Context**: What information and skills the child receives
+- **Model/pool**: Which model or model group tag the child should use (e.g., `heavy`, `coding`)
+- **Permissions**: What the child is allowed to do -- run shell commands, summon its own subagents, access specific directories, etc.
+
+This means:
+- An "orchestrator" is just an agent with the `summon_subagents` permission and a skill file that teaches it how to decompose and delegate work
+- A "leaf worker" is just an agent without `summon_subagents` permission
+- Hierarchy depth is unlimited and determined by the agents themselves, not hardcoded
+- The dispatch layer is simply the first agent in the tree, with full permissions
+
+### Communication Model
+
+- **Strict hierarchy**: Subagents report only to the agent that spawned them. No peer-to-peer communication between sibling agents.
+- Each agent communicates with its parent (upward) and its children (downward).
+- The specific transport mechanism (filesystem, IPC, message queue) is an implementation detail left open.
+
+### Detached Orchestrators
+
+The dispatch agent can spawn orchestrators in **detached mode**. A detached orchestrator:
+
+- Runs independently from the dispatch conversation
+- Has its own **direct communication channel to the user** — it can ask questions, request clarification, and wait for user input
+- The user interacts with the detached orchestrator as if it were its own conversation thread
+- The orchestrator may spawn its own subagents (which follow strict hierarchy — reporting only to the orchestrator)
+- When the orchestrator completes its task, it reports results back to the dispatch agent
+
+**Use case:** The dispatch agent spawns a planning orchestrator. The orchestrator opens a conversation with the user, asks clarifying questions about requirements, iterates on the plan with user feedback, and when the plan is finalized, hands it back to the dispatch agent for execution.
+
+A detached orchestrator is simply an agent spawned with `detached: true` — it receives a user-facing channel in addition to its parent channel.
+
+### User-to-Agent Messaging
+
+The user can send messages to any running agent at any time, regardless of where that agent is in its execution. Messages are delivered through tool interfaces -- any tool invocation point doubles as a message reception point.
+
+**Message types:**
+- **Instructions**: Direct the agent to change approach or focus
+- **Corrections**: Fix a misunderstanding or wrong assumption
+- **Context**: Provide additional information the agent lacks
+- **Data**: Supply concrete values, file contents, references, etc.
+
+**Requirements:**
+- Messages can target any agent in the hierarchy (dispatch, orchestrator, or subagent)
+- Delivery must not require the agent to finish its current tool call first -- the message is available on the next tool boundary
+- The agent must acknowledge and incorporate the message into its ongoing work
+- The dispatch layer provides a mechanism for the user to see which agents are active and route messages to them
+
+### Conflict Prevention
+
+When multiple subagents operate on code, the orchestrator must assign **non-overlapping scopes** (e.g., distinct files or file regions) to each subagent before dispatching them. Orchestrators are responsible for partitioning work to avoid merge conflicts.
+
+## Configuration
+
+### Config-Driven Orchestrators
+
+Orchestrators are defined through configuration files, not hardcoded. A configuration defines:
+
+- **Name and description** of the orchestrator type
+- **System prompt / instructions** for the orchestrator's LLM context
+- **Allowed tool set** for the orchestrator itself
+- **Subagent templates** -- what types of subagents this orchestrator can spawn, with their own tool scopes and prompts
+- **Concurrency limits** -- max parallel subagents
+- **Checkpoint rules** -- which stages require human approval (if any)
+
+### Role-Scoped Tooling
+
+Tools available to each agent are scoped by role:
+- Research subagents: web search, file read, documentation fetch
+- Coding subagents: file read/write, shell execution, code analysis
+- Review subagents: file read, test execution, linting
+- Custom roles define their own tool sets via config
+
+## Skills System
+
+Skills are markdown files (`.md`) containing specialized instructions, context, or workflows that are injected into an agent's context. Skills are organized in a standardized directory structure at two levels: **global** (home directory) and **project-level**.
+
+### Directory Structure
+
+```
+~/.skills/
+ default/ # .md skills auto-loaded for ALL agents globally
+ agents/ # Agent-type mappings (which skills activate for which agent)
+ project/ # .md skills available to any project (manually activated)
+
+<project>/.skills/
+ default/ # .md skills auto-loaded for agents working in this project
+ agents/ # Agent-type mappings specific to this project
+ project/ # .md skills available in this project (manually activated)
+```
+
+### Directories Explained
+
+| Directory | Scope | Loading |
+|-----------|-------|---------|
+| `default/` | All agents at that level | **Auto-loaded** -- always injected into agent context |
+| `project/` | Agents at that level | **Available** -- must be explicitly activated or referenced |
+| `agents/` | Specific agent types | **Mapped** -- defines which skills load for which agent type |
+
+### Agent Mapping Files (`agents/`)
+
+Files in the `agents/` directory map skills to specific agent types. The filename encodes the agent name and tier:
+
+- `<name>.txt` -- maps to a **subagent** of that name
+- `<name>.o.txt` -- maps to an **orchestrator** of that name
+
+File contents list skill filenames (from `default/` or `project/`) to activate for that agent type.
+
+**Examples:**
+```
+# agents/coding.txt (subagent)
+git-conventions.md
+code-style.md
+
+# agents/research.o.txt (orchestrator)
+search-strategy.md
+source-evaluation.md
+```
+
+### Scope and Precedence
+
+- Global skills (`~/.skills/`) are available to all projects.
+- Project skills (`<project>/.skills/`) are available only within that project.
+- When a skill with the same name exists at both levels, **both are retained and distinguishable by scope**. References can disambiguate using a scope prefix (e.g., `global:code-style` vs `project:code-style`).
+- `default/` skills at both levels stack: global defaults and project defaults are both auto-loaded.
+
+### Loading Order
+
+1. Global `default/` skills are loaded first
+2. Project `default/` skills are loaded next
+3. Agent-specific skills from `agents/` mappings are loaded (global then project)
+4. Manually activated `project/` skills are loaded on demand
+
+## LSP Integration
+
+Agents have access to Language Server Protocol diagnostics for the project they are operating in.
+
+### Capabilities
+
+- **Primary use case**: Real-time compiler/linter diagnostics and errors. Agents receive ground-truth error information from the language toolchain rather than inferring errors from output or guessing.
+- Agents can query the LSP for diagnostics on specific files or the entire workspace.
+- Diagnostics are available as a tool that any agent with appropriate scope can invoke.
+
+### Configuration
+
+- **Auto-detection**: The system detects the project language(s) and starts appropriate LSP servers automatically (similar to how an IDE discovers and launches language servers).
+- **Manual overrides**: A project-level config file can specify custom LSP server commands, initialization options, and settings. Manual config takes precedence over auto-detected defaults.
+- LSP servers are managed as long-lived background processes, shared across agents operating on the same project.
+
+## Filesystem and Shell Access
+
+### General Shell Access
+
+All agents have access to a general-purpose shell for running commands. This is not restricted to a predefined set of tools -- agents can execute arbitrary shell commands.
+
+### Directory Permissions
+
+- Agents may freely read and write within the **current working directory** (the project root) and its subdirectories.
+- **Any access to directories outside the current working directory requires explicit user permission.** When an agent attempts to read, write, or execute in an external directory, the system prompts the user for approval.
+- **Auto-allow list**: A configurable list of directories that are pre-approved for access without prompting. Defined in the project or global config.
+
+```
+# Example config
+permissions:
+ auto_allow:
+ - /tmp
+ - ~/.config/dispatch
+ - /usr/local/share/data
+```
+
+- Permission prompts include: the agent requesting access, the target path, and the operation (read/write/execute).
+- Permissions can be granted per-request, per-session, or permanently (added to auto-allow).
+
+## Session Management
+
+### Chat Forking
+
+The user can fork the current dispatch conversation at any point, creating a new branch from that moment in the chat history. Forking applies to the **dispatch-level conversation only** -- active orchestrators and subagents are not duplicated into the fork. The original session continues unaffected.
+
+### Model Switching
+
+The user can switch the LLM model for **any active agent** in the hierarchy mid-session:
+- Switch the dispatch agent's model during a conversation
+- Switch an orchestrator's or subagent's model while it is running
+- The agent continues with its existing context under the new model
+
+Model switches take effect immediately. Prior context is preserved and passed to the new model.
+
+### Chat History and Resumption
+
+All dispatch-level conversations are persisted and can be loaded later to continue where the user left off. Loading an old chat restores the **conversation history only** -- background orchestrators and subagents from the original session are not resumed.
+
+Loaded chats can be:
+- Continued with new messages
+- Forked from any point in the history
+- Searched/filtered by date, topic, or content
+
+## Human-in-the-Loop
+
+The system supports **configurable checkpoints** where execution pauses for human approval. Examples:
+
+- Approve a generated plan before implementation begins
+- Review proposed code changes before they are written
+- Confirm destructive operations (file deletions, large refactors)
+
+Checkpoints are configurable per orchestrator type. They can be enabled, disabled, or set to auto-approve with a timeout.
+
+## State Persistence
+
+The system persists state across sessions:
+
+- **Plans**: Generated plans are saved and can be resumed
+- **Research artifacts**: Research findings are stored for reuse
+- **Session state**: Interrupted orchestrators can be resumed from their last checkpoint
+- **History**: Past dispatches and their outcomes are queryable
+
+## Observability
+
+Basic logging is required:
+- Agent activity logs (what each agent did and when)
+- Error reporting with context
+- Optional: token usage tracking, cost estimates, decision traces
+
+Observability is a secondary concern -- basic logging is sufficient initially, with hooks for richer tracing later.
+
+## LLM Provider
+
+The system is **provider-agnostic**. It defines an abstract LLM interface that can be backed by any provider (Anthropic, OpenAI, local models, OpenRouter, etc.). Provider selection is configurable per agent or globally.
+
+### Key and Model Hierarchy
+
+Multiple API keys and models can be configured with a **fallback hierarchy**. When a key's quota or budget is exhausted, the system automatically falls through to the next key or model in the hierarchy.
+
+**Fallback triggers:**
+- API key quota or budget exhausted (daily, monthly, or total spend limits)
+
+**Configuration:**
+- Each API key has a configurable budget/quota limit. When reached, the system moves to the next key in the fallback chain.
+- Fallback chains are ordered lists: the system tries the first key/model, and on exhaustion moves to the second, and so on.
+- Fallback can cross providers (e.g., exhaust an Anthropic key, fall back to an OpenAI key).
+
+### Model Groups and Tags
+
+Models are organized into **groups** using tags. Tags allow orchestrators and subagents to request a model by capability rather than by name.
+
+**Built-in group tiers:**
+- `heavy` -- largest, most capable models (e.g., Claude Opus, GPT-4.5)
+- `medium` -- balanced capability/cost (e.g., Claude Sonnet, GPT-4o)
+- `light` -- fast, cheap models for simple tasks (e.g., Claude Haiku, GPT-4o-mini)
+
+**Task-specific tags:**
+- `coding` -- models best suited for code generation and editing
+- `review` -- models suited for code review and analysis
+- `research` -- models suited for search and synthesis
+- Custom tags can be defined freely in config
+
+**Resolution:** A model can have multiple tags (e.g., a model tagged `heavy, coding`). When an agent requests a tag, the system resolves it to the best available model matching that tag, respecting the key fallback hierarchy.
+
+```
+# Example config
+models:
+ keys:
+ - provider: anthropic
+ key: ${ANTHROPIC_KEY_1}
+ budget: $50/month
+ models:
+ claude-opus-4:
+ tags: [heavy, coding, review]
+ claude-sonnet-4:
+ tags: [medium, coding, research]
+ - provider: openai
+ key: ${OPENAI_KEY_1}
+ budget: $30/month
+ models:
+ gpt-4.5:
+ tags: [heavy, coding]
+ gpt-4o:
+ tags: [medium, research]
+ gpt-4o-mini:
+ tags: [light]
+
+ fallback_order:
+ - ${ANTHROPIC_KEY_1}
+ - ${OPENAI_KEY_1}
+```
+
+### Key Exhaustion Behavior
+
+When all configured keys for an agent's task are exhausted, the agent **does not fail**. Instead it enters a **wait state**, polling until any of its configured keys become available again, then resumes automatically from where it left off.
+
+**Behavior:**
+- Keys are used in priority order (highest priority first)
+- When the active key is exhausted, the system immediately falls through to the next key
+- When ALL configured keys are exhausted, the agent sleeps and polls for the first key to refresh
+- Whichever key refreshes first is used to resume -- priority order applies again from that point
+- Waiting is **per-agent**: other agents in the tree that still have available keys continue running unaffected
+- The agent's context and state are preserved across the wait -- resumption is seamless
+
+**Use case:** A complex overnight task drains all keys. The system sleeps until a rate window resets (e.g., a 5-hour cooldown expires), then picks up automatically. The user wakes up to a completed task.
+
+## Interface
+
+The system is **API-first** (HTTP + WebSocket) with an HTML frontend built alongside the backend from day one. The frontend is the primary testing and interaction surface.
+
+The core exposes a programmatic API that additional interfaces can be built on top of later:
+- Interactive CLI (REPL)
+- Command-based CLI
+- TUI
+- Desktop app
+
+## Language/Runtime
+
+**TypeScript / Node.js.** Chosen for:
+- Rich LLM SDK ecosystem (Vercel AI SDK, pi-ai, cline/llms)
+- Strong async/streaming support
+- Large pool of AI tooling libraries
+- Same language for backend and frontend
+
+**Library strategy:** Use existing battle-tested libraries heavily (Vercel AI SDK for LLM, etc.). Focus custom work on the novel parts -- hierarchy, orchestration, skills, permissions.
+
+## Key Design Principles
+
+1. **Emergent hierarchy** -- Agents are a single primitive. "Orchestrators" and "workers" emerge from the permissions and skills given at spawn time
+2. **Composability** -- Agent templates and skills are building blocks, combined via config
+3. **Parallelism** -- Subagents run concurrently by default; parent agents manage fan-out/fan-in
+4. **Isolation** -- Each agent operates in a scoped context with scoped tools and permissions
+5. **Resumability** -- Work can be interrupted and resumed, including across key exhaustion waits
+6. **Extensibility** -- New agent types, tool sets, and providers added via config, not code changes
diff --git a/research/ai-coding-assistants-established.md b/research/ai-coding-assistants-established.md
new file mode 100644
index 0000000..7833798
--- /dev/null
+++ b/research/ai-coding-assistants-established.md
@@ -0,0 +1,345 @@
+# Subagent Report: Established AI Coding Assistants (Aider, OpenHands, SWE-agent)
+
+## Research Summary
+
+This report evaluates three open-source AI coding assistants — **Aider**, **OpenHands** (formerly OpenDevin), and **SWE-agent** — against the Dispatch agent harness requirements. Aider is a mature, single-agent CLI tool focused on AI pair programming within git repos. OpenHands is a comprehensive SDK and platform for building coding agents, offering the richest feature set (skills system, security policies, state persistence, provider abstraction). SWE-agent is an academic research tool optimized for autonomous GitHub issue resolution on SWE-bench, now superseded by mini-SWE-agent. None of the three frameworks natively support the three-layer dispatch->orchestrator->subagent hierarchy that Dispatch requires, but OpenHands' SDK architecture provides the most extensible foundation for building such a system.
+
+---
+
+## Findings
+
+## 1. Aider
+
+### Core Architecture
+
+Aider is a **single-agent** AI pair programming tool that operates as an interactive CLI. It supports no multi-agent hierarchy — the user talks to one instance, which uses one LLM (or two in architect mode). Its architecture consists of a `Coder` class that manages file editing, a `Model` class for LLM interaction, and an `InputOutput` class for user I/O. Aider runs in the user's terminal connected to their local git repository.
+
+- Language: **Python** (80%), with CSS, Shell, Tree-sitter Query, JavaScript, HTML
+- GitHub: **45k stars**, 4.4k forks, **13,135 commits**, 93 releases (latest v0.86.0, Aug 9, 2025)
+- Primary use case: AI pair programming in the terminal — edit code through natural language conversations
+- Architect mode: Uses a two-model sequential pipeline (architect proposes changes, editor applies them), but this is not a hierarchy — it's a sequential two-step within a single session
+- [Source: Aider GitHub repo](https://github.com/Aider-AI/aider)
+
+### Key Features Identified
+
+- **Provider-agnostic LLM**: Supports OpenAI, Anthropic, Gemini, DeepSeek, OpenRouter, Ollama, local models, and 15+ more through model aliases and `--model` flag
+- **Git integration**: Automatically commits all AI changes with sensible commit messages, supports `/undo`
+- **Repository map**: Builds a compressed map of the codebase for better context in larger projects
+- **Lint/test integration**: Runs linters (per-language configurable via `--lint-cmd`) and tests after edits; automatically fixes detected errors
+- **Chat modes**: `code`, `ask`, `architect`, `help`, `context` modes for different interaction styles
+- **In-chat commands**: 30+ slash commands including `/model`, `/editor-model`, `/add`, `/drop`, `/clear`, `/reset`, `/run`, `/test`, `/save`
+- **IDE integration**: `--watch-files` mode watches for AI-prefixed comments in any editor
+- **Conventions system**: Markdown files loaded as read-only context (e.g., `CONVENTIONS.md`) to guide LLM behavior
+- **Scripting**: Python API via `Coder.create()` + `coder.run()` and CLI with `--message` flag
+- **Model switching mid-conversation**: `/model` and `/editor-model` commands allow switching LLMs mid-session
+
+### Checklist Evaluation
+
+| # | Requirement | Status | Evidence |
+|---|-------------|--------|----------|
+| 1 | **Three-layer hierarchy** | Not supported | Aider is a single-agent system. Architect mode uses 2 models sequentially, not as a hierarchy. No dispatch/orchestrator/subagent layers exist. |
+| 2 | **Config-driven orchestrators** | Not supported | Orchestrators do not exist. Configuration (`.aider.conf.yml`) is for tool-level settings, not agent type definitions. |
+| 3 | **Parallel subagent execution** | Not supported | Single agent, single-threaded. No concept of subagents or parallel execution. |
+| 4 | **Strict hierarchy communication** | Not supported | No hierarchy exists. Communication is user<->aider only. |
+| 5 | **User-to-agent messaging mid-execution** | Partial | The user can interrupt with Ctrl-C and send new messages, but cannot route messages to specific agents in a hierarchy (no hierarchy exists). Messages are delivered immediately to the single active agent. |
+| 6 | **Conflict prevention** | Not supported | Single-agent design — no parallel agents to conflict with. |
+| 7 | **Role-scoped tooling** | Not supported | All agents (single instance) have the same tool set. No role-based tool differentiation. |
+| 8 | **Skills system** | Partial | Conventions files (`CONVENTIONS.md`) provide markdown instructions loaded into context, but there is no formal directory-based organizational system (no `~/.skills/` or `<project>/.skills/` structure). Skills are manual file references. |
+| 9 | **LSP integration** | Partial | Aider has linting via `--lint-cmd` (per-language linters) but this is shell-based linting, not LSP protocol integration. No Language Server Protocol support for real-time diagnostics. [Source: Linting and testing docs](https://aider.chat/docs/usage/lint-test.html) |
+| 10 | **Shell access with directory permissions** | Not supported | `/run` command provides shell access but with no directory-level permission controls. No auto-allow lists or out-of-scope prompts. |
+| 11 | **Session management** | Partial | `/model` and `/editor-model` allow model switching mid-conversation. `/save` saves file list. But no chat forking, no loading/resuming old chats. History is ephemeral per session. |
+| 12 | **Human-in-the-loop checkpoints** | Partial | `--yes` flag auto-accepts all confirmation. `/undo` reverts changes. But no configurable checkpoints (e.g., "pause after planning for approval"). |
+| 13 | **State persistence** | Not supported | Git commits persist code changes. But session state, conversation history, and plans are NOT persisted across restarts. |
+| 14 | **Provider-agnostic LLM** | Full | Supports OpenAI, Anthropic, Gemini, DeepSeek, OpenRouter, Ollama, 15+ others via `--model` flag and model aliases. Abstract model interface. [Source: LLMs docs](https://aider.chat/docs/llms.html) |
+| 15 | **Multiple interfaces** | Partial | CLI only. Can be scripted via Python API and `--message` flag for non-interactive use, but no API server, no TUI (beyond the terminal prompt), no web UI. |
+
+### Key Quotes
+
+> "Aider lets you pair program with LLMs to start a new project or build on your existing codebase." [Source: Aider README](https://github.com/Aider-AI/aider)
+
+> "Aider can connect to almost any LLM, including local models." [Source: Aider README](https://github.com/Aider-AI/aider)
+
+---
+
+## 2. OpenHands (formerly OpenDevin)
+
+### Core Architecture
+
+OpenHands has evolved from a monolithic application into a **four-package SDK architecture**. The Software Agent SDK (`openhands.sdk`) is the foundation, providing core agent framework, LLM abstraction, tool system, workspace management, conversation state, skills system, and security analysis. On top of this sit the OpenHands CLI, GUI (React), Cloud, and Enterprise offerings.
+
+The architecture is: User Interface (CLI/GUI/Cloud) -> Conversation -> Agent (reasoning-action loop) -> Tools/LLM. The system is **stateless and event-driven**, with each agent step being atomic and interruptible.
+
+- Language: **Python (62.3%)** + TypeScript (35.9%) for frontend
+- GitHub: **74.1k stars**, 9.4k forks, **6,737 commits**, 102 releases (latest v1.7.0, May 1, 2026)
+- Primary use case: AI-driven development platform for building and running coding agents at scale
+- Four packages: `openhands.sdk` (core), `openhands.tools` (pre-built tools), `openhands.workspace` (Docker/remote exec), `openhands.agent_server` (FastAPI + WebSocket server)
+- Two deployment modes: Local (in-process) and Production (containerized with agent-server)
+- [Source: OpenHands GitHub repo](https://github.com/OpenHands/OpenHands)
+- [Source: SDK Architecture docs](https://docs.openhands.dev/sdk/arch/overview)
+
+### Key Features Identified
+
+- **Skills system**: Sophisticated three-type system — Repository skills (always-active, from `AGENTS.md`), Knowledge skills (keyword-triggered), Task skills (triggered with structured inputs). Supports MCP tool integration and dynamic content via inline shell commands. Skills are markdown files with YAML frontmatter. [Source: Skill docs](https://docs.openhands.dev/sdk/arch/skill)
+- **Provider-agnostic LLM**: Via LiteLLM, supporting 100+ providers. Configurable via environment variables, JSON, or programmatic. Supports both Chat Completions and OpenAI Responses API. [Source: LLM docs](https://docs.openhands.dev/sdk/arch/llm)
+- **Security system**: Pluggable security analyzers (NoOp or LLM-based) with risk levels (LOW/MEDIUM/HIGH/UNKNOWN) and configurable confirmation policies (AlwaysConfirm, NeverConfirm, ConfirmRisky). Actions can be blocked, require confirmation, or auto-execute based on risk. [Source: Security docs](https://docs.openhands.dev/sdk/arch/security)
+- **Custom tools**: Typed Action/Observation/Executor pattern for creating custom tools. Factory functions support shared executors across tools. [Source: Custom Tools docs](https://docs.openhands.dev/sdk/guides/custom-tools)
+- **State persistence**: Auto-save and resume with debounced writes and incremental events. [Source: Conversation docs](https://docs.openhands.dev/sdk/arch/conversation)
+- **Conversation management**: Two conversation types — LocalConversation (in-process) and RemoteConversation (via HTTP/WebSocket). Factory pattern auto-selects based on workspace type.
+- **Event-driven architecture**: Typed event framework with ActionEvent, ObservationEvent, MessageEvent, StateUpdateEvent. Immutable append-only event log.
+- **Context management**: Condenser system for compressing conversation history when token limits are approached.
+- **MCP integration**: Model Context Protocol servers can be spawned and managed through repository skills.
+
+### Checklist Evaluation
+
+| # | Requirement | Status | Evidence |
+|---|-------------|--------|----------|
+| 1 | **Three-layer hierarchy** | Not supported | OpenHands has a single Conversation->Agent->Tool pipeline, not a dispatch->orchestrator->subagent hierarchy. The README mentions "Major tasks that involve multiple agents, like refactors and rewrites" as a use case, but this must be built manually using the SDK — it is not a native framework feature. |
+| 2 | **Config-driven orchestrators** | Partial | Agents are defined programmatically in Python via the SDK (code-driven, not config-driven). However, the config template file (`config.template.toml`) provides some structure. Orchestrators as config-defined entities do not exist. |
+| 3 | **Parallel subagent execution** | Not supported | The SDK executes one agent step at a time per conversation. No native mechanism for spawning parallel subagents. Would need to be built on top of the SDK. |
+| 4 | **Strict hierarchy communication** | Not supported | No hierarchy enforced. The SDK's event system could be used to build one, but it's not a native constraint. |
+| 5 | **User-to-agent messaging mid-execution** | Full | The Conversation system supports `send_message()` at any time. In the GUI, users can inject messages mid-execution. The RemoteConversation uses WebSocket for real-time communication. The agent's step() loop checks for pending messages on each iteration. [Source: Agent architecture](https://docs.openhands.dev/sdk/arch/agent) |
+| 6 | **Conflict prevention** | Not supported | No native mechanism for assigning non-overlapping file scopes to parallel agents. Would need custom implementation. |
+| 7 | **Role-scoped tooling** | Full | Each agent instance is created with its own tool set (`Agent(llm=llm, tools=[...])`). Different agents can have entirely different tools. Factory functions and shared executors support complex tool topologies. [Source: Custom Tools docs](https://docs.openhands.dev/sdk/guides/custom-tools) |
+| 8 | **Skills system** | Full | Rich skills system with three skill types (Repository/Knowledge/Task), YAML frontmatter in markdown files, keyword triggers, dynamic content execution, MCP integration. Supports `AGENTS.md`, `.cursorrules`, and `.agents/skills/*.md` formats. However, the directory structure (`~/.skills/`, `<project>/.skills/`) differs from the Dispatch spec — OpenHands uses `AGENTS.md` at repo root and `.agents/skills/` directory. [Source: Skill docs](https://docs.openhands.dev/sdk/arch/skill) |
+| 9 | **LSP integration** | Not supported | No LSP integration found in the SDK documentation. The tools package includes `FileEditorTool` and `BashTool` but no LSP-based diagnostic tools. |
+| 10 | **Shell access with directory permissions** | Partial | Full shell access via `BashTool` and `TerminalTool`. SecurityAnalyzer provides risk-based action validation (LOW/MEDIUM/HIGH risk levels with configurable confirmation thresholds). However, this is a general action risk system, NOT a directory-based permission system (no auto-allow lists for specific paths, no prompting for out-of-scope directories). [Source: Security docs](https://docs.openhands.dev/sdk/arch/security) |
+| 11 | **Session management** | Partial | State persistence with auto-save and resume exists. Model switching is configurable at agent creation but there is no `/model` command for mid-conversation switching. No chat forking feature documented. |
+| 12 | **Human-in-the-loop checkpoints** | Full | ConfirmationPolicy supports AlwaysConfirm, NeverConfirm, and ConfirmRisky modes. The Agent step() loop checks for pending confirmations before executing actions. SecurityAnalyzer provides risk assessment for each action. Configurable thresholds. [Source: Agent architecture](https://docs.openhands.dev/sdk/arch/agent) |
+| 13 | **State persistence** | Full | Persistence service with auto-save and resume. Debounced writes, incremental events. Conversation state, event history, and workspace state are persisted. [Source: Conversation docs](https://docs.openhands.dev/sdk/arch/conversation) |
+| 14 | **Provider-agnostic LLM** | Full | Via LiteLLM backend supporting 100+ providers. LLM class with environment variable, JSON, and programmatic configuration. Dual API support (Chat Completions + Responses API). Telemetry and cost tracking. [Source: LLM docs](https://docs.openhands.dev/sdk/arch/llm) |
+| 15 | **Multiple interfaces** | Full | CLI (OpenHands CLI), GUI (React single-page app), Cloud (hosted deployment), Enterprise (self-hosted VPC), SDK (Python + REST API via agent-server). WebSocket support for real-time communication. [Source: OpenHands README](https://github.com/OpenHands/OpenHands) |
+
+### Key Quotes
+
+> "The SDK is a composable Python library that contains all of our agentic tech. It's the engine that powers everything else below." [Source: OpenHands README](https://github.com/OpenHands/OpenHands)
+
+> "You can use the OpenHands Software Agent SDK for: ... Major tasks that involve multiple agents, like refactors and rewrites." [Source: SDK docs](https://docs.openhands.dev/sdk)
+
+> "The Software Agent SDK serves as the source of truth for agents in OpenHands." [Source: SDK Architecture docs](https://docs.openhands.dev/sdk/arch/overview)
+
+---
+
+## 3. SWE-agent
+
+### Core Architecture
+
+SWE-agent is a **single-agent** system designed for autonomous resolution of GitHub issues. Its architecture: `sweagent` CLI -> `Agent` (reasoning loop with LLM) + `SWEEnv` (environment manager wrapping SWE-ReX). SWE-ReX manages a Docker container running a shell session with custom tool implementations. The agent prompts an LLM, the LLM outputs tool calls, which are parsed and executed in the Docker sandbox.
+
+The tool system is organized as "tool bundles" — directories containing executable scripts, a `config.yaml`, and an `install.sh`. Tools are configured via YAML config files.
+
+- Language: **Python (94.8%)**
+- GitHub: **19.2k stars**, 2.1k forks, **2,158 commits**, 10 releases (latest v1.1.0, May 22, 2025)
+- Primary use case: Academic research benchmark for autonomous SWE-bench issue resolution
+- **Superseded**: The project now recommends mini-SWE-agent (65% on SWE-bench verified in 100 lines of Python)
+- [Source: SWE-agent GitHub repo](https://github.com/SWE-agent/SWE-agent)
+- [Source: Architecture docs](https://swe-agent.com/latest/background/architecture/)
+
+### Key Features Identified
+
+- **YAML-driven configuration**: Single config file defines tools, prompts, history processors, model settings, environment. Multiple config files can be merged with `--config`.
+- **Tool bundles**: Extensible tool system. Tools are executables in a `bin/` directory with a `config.yaml` defining signatures, arguments, and docstrings. State commands return JSON after each action.
+- **History processors**: Plugable context compression, including `cache_control` (last N messages) and `image_parsing` for multimodal support.
+- **Batch mode**: `sweagent run-batch` with `--num_workers` for parallel execution across multiple instances. Supports SWE-bench, HuggingFace, file-based, and expert instance sources.
+- **Multimodal support**: Config for processing images from GitHub issues, extended observation lengths, web browsing tools.
+- **Demonstrations**: Pre-recorded trajectories can guide agent behavior.
+- **Template system**: Three template types — system_template (initial prompt), instance_template (per-task context), next_step_template (per-turn prompt). Templates use Jinja-like syntax.
+- **SWE-ReX integration**: Separate package for managing remote execution environments (Docker, Modal, AWS).
+
+### Checklist Evaluation
+
+| # | Requirement | Status | Evidence |
+|---|-------------|--------|----------|
+| 1 | **Three-layer hierarchy** | Not supported | Single agent resolving one issue at a time. No dispatch/orchestrator/subagent layers. |
+| 2 | **Config-driven orchestrators** | Not supported | SWE-agent is configured via YAML but configures a single agent type. Orchestrators as dispatch-defined entities do not exist. |
+| 3 | **Parallel subagent execution** | Partial | Batch mode (`sweagent run-batch --num_workers N`) runs multiple independent agent instances in parallel on separate issues. This is horizontal parallelism of independent tasks, NOT hierarchical subagent parallelism. [Source: Batch mode docs](https://swe-agent.com/latest/usage/batch_mode/) |
+| 4 | **Strict hierarchy communication** | Not supported | No hierarchy exists. Each agent operates independently in its own Docker container. |
+| 5 | **User-to-agent messaging mid-execution** | Not supported | SWE-agent is designed for autonomous execution. No mechanism for injecting user messages to a running agent mid-task. |
+| 6 | **Conflict prevention** | Not supported | Single agent per task. Batch mode tasks are independent (different GitHub issues), so no file conflicts. |
+| 7 | **Role-scoped tooling** | Not supported | Single agent type per config file. All agents in a batch use the same tool set. |
+| 8 | **Skills system** | Partial | Template system supports system prompts, instance prompts, and next-step prompts defined in YAML config. Demonstrations provide trajectory-based guidance. However, there is NO directory-based markdown skills system with auto-loading (no `~/.skills/` or `<project>/.skills/`). [Source: Templates docs](https://swe-agent.com/latest/config/templates/) |
+| 9 | **LSP integration** | Not supported | No LSP integration found. Tools are shell-based (file viewer, editor, bash). No compiler diagnostic integration. |
+| 10 | **Shell access with directory permissions** | Not supported | Full shell access inside Docker container via bash tool. No directory-level permission system. Docker provides container-level isolation but not fine-grained directory permissions. |
+| 11 | **Session management** | Not supported | No chat forking, no model switching mid-conversation, no loading/resuming old conversations. Trajectories are saved as output files for post-hoc analysis. |
+| 12 | **Human-in-the-loop checkpoints** | Not supported | Designed for fully autonomous execution. No configurable checkpoints for user approval. |
+| 13 | **State persistence** | Partial | Trajectories and predictions are saved to files (`preds.json`, trajectory files). But no formal state persistence for resuming interrupted sessions. The `sweagent merge-preds` utility can recover partial batch results. |
+| 14 | **Provider-agnostic LLM** | Full | Model configured in YAML (`agent.model.name`). Supports any LM via config. Model config includes per-instance cost limits, temperature, and other parameters. [Source: Config docs](https://swe-agent.com/latest/config/config/) |
+| 15 | **Multiple interfaces** | Partial | CLI only (`sweagent run`, `sweagent run-batch`, `sweagent merge-preds`). No API, no TUI, no web UI. |
+
+### Key Quotes
+
+> "SWE-agent takes a GitHub issue and tries to automatically fix it, using your LM of choice." [Source: SWE-agent README](https://github.com/SWE-agent/SWE-agent)
+
+> "Most of our current development effort is on mini-swe-agent, which has superseded SWE-agent." [Source: SWE-agent README](https://github.com/SWE-agent/SWE-agent)
+
+> "Configurable & fully documented: Governed by a single yaml file." [Source: SWE-agent README](https://github.com/SWE-agent/SWE-agent)
+
+---
+
+## Key Questions
+
+### 1. What is each framework's core architecture? How many layers of agent hierarchy does it support?
+
+- **Aider**: Single-layer (User <-> Agent). No hierarchy. The "architect mode" uses two models sequentially but is still a single-agent session.
+- **OpenHands**: Single-layer (Conversation -> Agent -> Tools). The SDK can be used to build multi-agent systems programmatically, but no hierarchy is natively enforced. The four-package architecture provides building blocks.
+- **SWE-agent**: Single-layer (CLI -> Agent + Environment). One agent per instance. Batch mode runs independent agents in parallel but no hierarchy.
+
+### 2. How extensible/configurable is each framework without modifying source code?
+
+- **Aider**: Modular via command-line flags, YAML config file, environment variables, and `.env` files. Can be scripted via Python API. Adding new LLM providers requires no code changes (model aliases). No plugin system for tools or new behaviors.
+- **OpenHands**: Highly extensible via the SDK. Custom tools (typed Action/Observation/Executor pattern), custom agents, custom security analyzers, MCP server integration, workspace implementations. Requires Python code for configuration but has clean extension points. Config template file provides some non-code configuration.
+- **SWE-agent**: Extensible via YAML config files (tools, prompts, models, environments, history processors). Custom tools are executables in tool bundles with a config YAML. Adding tools requires creating executable scripts but no modification of core source code. Now in maintenance-only mode.
+
+### 3. What is the primary use case each framework was designed for?
+
+- **Aider**: Interactive AI pair programming — a developer chatting with an LLM to edit code in a git repository.
+- **OpenHands**: General-purpose AI-driven development platform — from simple one-off tasks to complex multi-agent workflows, with CLI, GUI, Cloud, and Enterprise deployments.
+- **SWE-agent**: Academic research benchmark for autonomous SWE-bench issue resolution. Designed to evaluate LLMs on real-world GitHub issues.
+
+### 4. How active is each project?
+
+| Metric | Aider | OpenHands | SWE-agent |
+|--------|-------|-----------|-----------|
+| GitHub Stars | 45k | 74.1k | 19.2k |
+| Forks | 4.4k | 9.4k | 2.1k |
+| Commits | 13,135 | 6,737 | 2,158 |
+| Releases | 93 | 102 | 10 |
+| Latest Release | v0.86.0 (Aug 2025) | v1.7.0 (May 2026) | v1.1.0 (May 2025) |
+| Status | **Active development** | **Active development** | **Maintenance-only** (superseded by mini-SWE-agent) |
+
+### 5. What language is each framework written in?
+
+- **Aider**: Python (80%), CSS, Shell, Tree-sitter Query, JavaScript, HTML
+- **OpenHands**: Python (62.3%), TypeScript (35.9%), Go Template, Jinja, Makefile, CSS
+- **SWE-agent**: Python (94.8%), JavaScript, CSS, Shell, C++, Perl
+
+---
+
+## Summary Comparison Table
+
+| # | Requirement | Aider | OpenHands | SWE-agent |
+|---|-------------|-------|-----------|-----------|
+| 1 | Three-layer hierarchy | ❌ Not supported | ❌ Not supported | ❌ Not supported |
+| 2 | Config-driven orchestrators | ❌ Not supported | ⚠️ Partial (SDK is code-driven) | ❌ Not supported |
+| 3 | Parallel subagent execution | ❌ Not supported | ❌ Not supported | ⚠️ Partial (batch mode parallelism) |
+| 4 | Strict hierarchy communication | ❌ Not supported | ❌ Not supported | ❌ Not supported |
+| 5 | User-to-agent messaging mid-execution | ⚠️ Partial (single agent) | ✅ Full (WebSocket, send_message) | ❌ Not supported |
+| 6 | Conflict prevention | ❌ Not supported | ❌ Not supported | ❌ Not supported |
+| 7 | Role-scoped tooling | ❌ Not supported | ✅ Full (per-agent tool sets) | ❌ Not supported |
+| 8 | Skills system | ⚠️ Partial (conventions files) | ✅ Full (3 skill types, triggers, MCP) | ⚠️ Partial (templates + demonstrations) |
+| 9 | LSP integration | ⚠️ Partial (shell linting) | ❌ Not supported | ❌ Not supported |
+| 10 | Shell access w/ directory permissions | ❌ Not supported | ⚠️ Partial (risk-based security) | ❌ Not supported |
+| 11 | Session management | ⚠️ Partial (/model, /save) | ⚠️ Partial (persistence, no forking) | ❌ Not supported |
+| 12 | Human-in-the-loop checkpoints | ⚠️ Partial (--yes, /undo) | ✅ Full (ConfirmationPolicy) | ❌ Not supported |
+| 13 | State persistence | ❌ Not supported | ✅ Full (auto-save & resume) | ⚠️ Partial (trajectory files) |
+| 14 | Provider-agnostic LLM | ✅ Full (15+ providers) | ✅ Full (100+ via LiteLLM) | ✅ Full (model config in YAML) |
+| 15 | Multiple interfaces | ⚠️ Partial (CLI + Python API) | ✅ Full (CLI, GUI, Cloud, API) | ⚠️ Partial (CLI only) |
+
+**Scoring:**
+- **Aider**: 0 Full / 5 Partial / 10 Not supported
+- **OpenHands**: 7 Full / 4 Partial / 4 Not supported
+- **SWE-agent**: 1 Full / 4 Partial / 10 Not supported
+
+---
+
+## Source List
+
+| # | Source | Type |
+|---|--------|------|
+| 1 | [Aider GitHub Repository](https://github.com/Aider-AI/aider) | GitHub |
+| 2 | [Aider Configuration Docs](https://aider.chat/docs/config.html) | Official docs |
+| 3 | [Aider Chat Modes Docs](https://aider.chat/docs/usage/modes.html) | Official docs |
+| 4 | [Aider In-Chat Commands](https://aider.chat/docs/usage/commands.html) | Official docs |
+| 5 | [Aider Linting and Testing](https://aider.chat/docs/usage/lint-test.html) | Official docs |
+| 6 | [Aider IDE Integration](https://aider.chat/docs/usage/watch.html) | Official docs |
+| 7 | [Aider Scripting API](https://aider.chat/docs/scripting.html) | Official docs |
+| 8 | [Aider Coding Conventions](https://aider.chat/docs/usage/conventions.html) | Official docs |
+| 9 | [OpenHands GitHub Repository](https://github.com/OpenHands/OpenHands) | GitHub |
+| 10 | [OpenHands SDK Overview](https://docs.openhands.dev/sdk) | Official docs |
+| 11 | [OpenHands SDK Architecture](https://docs.openhands.dev/sdk/arch/overview) | Official docs |
+| 12 | [OpenHands Agent Architecture](https://docs.openhands.dev/sdk/arch/agent) | Official docs |
+| 13 | [OpenHands Conversation Architecture](https://docs.openhands.dev/sdk/arch/conversation) | Official docs |
+| 14 | [OpenHands LLM Architecture](https://docs.openhands.dev/sdk/arch/llm) | Official docs |
+| 15 | [OpenHands Skill System](https://docs.openhands.dev/sdk/arch/skill) | Official docs |
+| 16 | [OpenHands Security System](https://docs.openhands.dev/sdk/arch/security) | Official docs |
+| 17 | [OpenHands Custom Tools Guide](https://docs.openhands.dev/sdk/guides/custom-tools) | Official docs |
+| 18 | [OpenHands Hello World](https://docs.openhands.dev/sdk/guides/hello-world) | Official docs |
+| 19 | [SWE-agent GitHub Repository](https://github.com/SWE-agent/SWE-agent) | GitHub |
+| 20 | [SWE-agent Architecture](https://swe-agent.com/latest/background/architecture/) | Official docs |
+| 21 | [SWE-agent Config Files](https://swe-agent.com/latest/config/config/) | Official docs |
+| 22 | [SWE-agent Templates](https://swe-agent.com/latest/config/templates/) | Official docs |
+| 23 | [SWE-agent Tools](https://swe-agent.com/latest/config/tools/) | Official docs |
+| 24 | [SWE-agent Batch Mode](https://swe-agent.com/latest/usage/batch_mode/) | Official docs |
+
+---
+
+## Verbatim Quotes
+
+- "aider is AI pair programming in your terminal" — [Source: Aider README](https://github.com/Aider-AI/aider)
+- "Aider can connect to almost any LLM, including local models." — [Source: Aider README](https://github.com/Aider-AI/aider)
+- "The SDK is a composable Python library that contains all of our agentic tech. It's the engine that powers everything else below." — [Source: OpenHands README](https://github.com/OpenHands/OpenHands)
+- "The Software Agent SDK serves as the source of truth for agents in OpenHands." — [Source: SDK Architecture docs](https://docs.openhands.dev/sdk/arch/overview)
+- "You can use the OpenHands Software Agent SDK for: ... Major tasks that involve multiple agents, like refactors and rewrites." — [Source: SDK docs](https://docs.openhands.dev/sdk)
+- "SWE-agent takes a GitHub issue and tries to automatically fix it, using your LM of choice." — [Source: SWE-agent README](https://github.com/SWE-agent/SWE-agent)
+- "Most of our current development effort is on mini-swe-agent, which has superseded SWE-agent." — [Source: SWE-agent README](https://github.com/SWE-agent/SWE-agent)
+- "Configurable & fully documented: Governed by a single yaml file." — [Source: SWE-agent README](https://github.com/SWE-agent/SWE-agent)
+- "OpenHands is also the leading open source framework for coding agents. It's MIT-licensed, and can work with any LLM." — [Source: SDK docs](https://docs.openhands.dev/sdk)
+- "The agent operates through a single-step execution model where each step() call processes one reasoning cycle." — [Source: Agent Architecture](https://docs.openhands.dev/sdk/arch/agent)
+
+---
+
+## Source Quality Flags
+
+- Source 5, 6, 7, 8 (Aider docs): Official documentation — high quality, maintained by project maintainers.
+- Source 10-18 (OpenHands docs): Official SDK documentation — comprehensive, well-structured, with architecture diagrams and runnable code examples.
+- Source 20-24 (SWE-agent docs): Official documentation — includes architecture diagrams and configuration references. However, the project is now in maintenance-only mode with recommendation to use mini-SWE-agent.
+
+---
+
+## Confidence: High
+
+All information was gathered from primary sources (GitHub repositories, official documentation sites). No AI-generated summaries or marketing materials were used. The frameworks' architectures and capabilities are well-documented and verifiable.
+
+---
+
+## Gaps and Open Questions
+
+1. **Multi-agent hierarchy in OpenHands**: The OpenHands SDK README mentions "Major tasks that involve multiple agents" as a use case, but the public SDK documentation does not yet show concrete multi-agent orchestration examples. The multi-agent example directory (`examples/02_multi_agent_hello_world/`) was attempted but returned a 404, suggesting it may not exist yet. A follow-up investigation should check whether multi-agent orchestration patterns exist in the SDK source code.
+
+2. **OpenHands SDK vs Application distinction**: The OpenHands ecosystem has been restructured. The monolithic GitHub repo (`OpenHands/OpenHands`) now points to the SDK, CLI, and GUI as separate packages. The relationship between the legacy OpenDevin application repo and the new SDK was not fully explored and could affect feature availability.
+
+3. **SWE-agent mini-SWE-agent relationship**: SWE-agent now recommends mini-SWE-agent, which achieves comparable performance in "100 lines of Python." This research covered SWE-agent 1.0, but mini-SWE-agent may have a substantially different architecture worth evaluating separately.
+
+4. **None of the three frameworks natively support the three-layer hierarchy Dispatch requires.** The closest foundation is OpenHands' SDK, which provides the building blocks (per-agent tool scoping, event system, security policies, skills system, state persistence) but lacks the hierarchical orchestration layer. Building Dispatch on top of OpenHands SDK would require implementing the orchestrator and dispatcher layers as custom Python code.
+
+---
+
+## Tool Calls Made
+
+1. `webfetch` https://github.com/Aider-AI/aider
+2. `webfetch` https://github.com/All-Hands-AI/OpenHands
+3. `webfetch` https://github.com/princeton-nlp/SWE-agent
+4. `webfetch` https://aider.chat/docs/config.html
+5. `webfetch` https://aider.chat/docs/usage.html
+6. `webfetch` https://swe-agent.com/latest/
+7. `webfetch` https://docs.openhands.dev/sdk
+8. `webfetch` https://swe-agent.com/latest/background/architecture/
+9. `webfetch` https://docs.openhands.dev/sdk/arch/overview
+10. `webfetch` https://aider.chat/docs/usage/modes.html
+11. `webfetch` https://swe-agent.com/latest/config/config/
+12. `webfetch` https://docs.openhands.dev/sdk/arch/agent
+13. `webfetch` https://docs.openhands.dev/sdk/arch/skill
+14. `webfetch` https://aider.chat/docs/scripting.html
+15. `webfetch` https://swe-agent.com/latest/usage/batch_mode/
+16. `webfetch` https://aider.chat/docs/usage/lint-test.html
+17. `webfetch` https://aider.chat/docs/usage/watch.html
+18. `webfetch` https://docs.openhands.dev/sdk/arch/conversation
+19. `webfetch` https://docs.openhands.dev/sdk/arch/llm
+20. `webfetch` https://aider.chat/docs/usage/conventions.html
+21. `webfetch` https://docs.openhands.dev/sdk/arch/security
+22. `webfetch` https://swe-agent.com/latest/config/tools/
+23. `webfetch` https://aider.chat/docs/usage/commands.html
+24. `webfetch` https://docs.openhands.dev/sdk/guides/hello-world
+25. `webfetch` https://swe-agent.com/latest/config/templates/
+26. `webfetch` https://raw.githubusercontent.com/OpenHands/software-agent-sdk/main/examples/02_multi_agent_hello_world/01_multi_agent_basic.py
+27. `webfetch` https://docs.openhands.dev/sdk/guides/custom-tools
+28. `webfetch` https://github.com/SWE-agent/SWE-agent?tab=readme-ov-file
diff --git a/research/ai-coding-assistants-newer.md b/research/ai-coding-assistants-newer.md
new file mode 100644
index 0000000..932e244
--- /dev/null
+++ b/research/ai-coding-assistants-newer.md
@@ -0,0 +1,451 @@
+# Subagent Report: AI Coding Assistants — Goose, Mentat, Cline, Continue
+
+## Research summary
+
+This report evaluates four open-source AI coding assistants — Goose (Block/AAIF), Mentat (AbanteAI, archived), Cline, and Continue — against a 15-point requirements checklist focused on agent hierarchy, configurability, parallelism, security, and integration capabilities. Goose and Cline are the most full-featured frameworks, with multi-agent support, provider-agnostic LLM interfaces, plugin/skills systems, and human-in-the-loop controls. Continue has pivoted from an IDE assistant to a CI-focused "AI checks" product. Mentat (the original AbanteAI CLI tool) has been archived since January 2025 and is no longer maintained. Confidence is high for Goose, Cline, and Continue based on current docs and repos; Mentat information reflects its archived state.
+
+---
+
+## Findings
+
+### 1. Goose (by Block / AAIF)
+
+**GitHub**: [github.com/aaif-goose/goose](https://github.com/aaif-goose/goose) — 45.5k stars, 4,541 commits
+**Language**: Rust (49.8%) + TypeScript (44.6%)
+**Latest release**: v1.34.1 (May 15, 2026)
+**License**: Apache 2.0
+**Governance**: Now under Agentic AI Foundation (AAIF) at Linux Foundation
+
+**Core Architecture**: Goose has a three-component architecture: (1) **Interface** (desktop app, CLI, API), (2) **Agent** (core loop managing LLM interaction), and (3) **Extensions** (MCP-based tools). It supports spawning **subagents** — independent agent instances that execute tasks with process isolation, running sequentially or in parallel. Subagents inherit extensions from the parent but can be restricted.
+
+Key features:
+- Desktop app + CLI + API modes
+- 15+ LLM providers via abstract interface (Anthropic, OpenAI, Google, Ollama, OpenRouter, Azure, Bedrock, etc.)
+- MCP (Model Context Protocol) extension system with 70+ extensions
+- ACP (Agent Client Protocol) support for interoperability
+- Subagent system with parallel/sequential execution, recipe-based reusable configs, and external subagents (Codex, Claude Code)
+- Skills system: `~/.agents/skills/` and `.agents/skills/` directories with `SKILL.md` files in named subdirectories; also compatible with `.claude/skills/`
+- `.goosehints` files for project context (global at `~/.config/goose/.goosehints`, local per-directory, hierarchical)
+- Config-driven via YAML config files (`config.yaml`, `permission.yaml`, `secrets.yaml`)
+- Session management: start, resume, search sessions; smart context management with auto-compaction
+- Permission modes: auto, approve, chat, smart_approve
+- Prompt injection detection, adversary mode, sandboxing for desktop app
+- Extension allowlist for access control
+
+**Primary Use Case**: General-purpose AI agent for code, research, writing, automation, data analysis.
+
+**Activity**: Very active — 4,541 commits, 134 releases, moved to AAIF in April 2026.
+
+---
+
+### 2. Mentat (by AbanteAI)
+
+**Original GitHub**: [github.com/AbanteAI/archive-old-cli-mentat](https://github.com/AbanteAI/archive-old-cli-mentat) — 2.6k stars (archived)
+**Language**: Python (87.9%) + TypeScript (8.4%)
+**Status**: **Archived January 7, 2025** — no longer maintained or supported
+
+The original Mentat was a command-line AI coding assistant with the following characteristics:
+
+- Single-agent CLI tool (no multi-agent hierarchy)
+- Used GPT-4 models via OpenAI API (later supported alternatives via litellm proxy)
+- Worked within git repositories, editing multiple files
+- Had a VS Code extension (`mentat-vscode/`)
+- Supported auto-context via RAG (retrieval-augmented generation) using universal-ctags
+- File exclusion via glob patterns
+- Configuration via config files
+- Used Python SDK's OpenAI client under the hood
+
+**Key limitations for requirements checklist**:
+- No multi-agent hierarchy (single agent, no orchestrator/subagent concept)
+- No config-driven orchestrators
+- No parallel execution
+- No hierarchical communication restrictions
+- No user-to-agent mid-execution messaging
+- No conflict prevention for parallel agents
+- No role-scoped tooling
+- No skills system (basic context via auto-context only)
+- No LSP integration
+- Shell access without directory permissions
+- Basic session management (no forking, model switching mid-conversation)
+- No human-in-the-loop checkpoints
+- State persistence limited to git context
+- Provider-agnostic via litellm proxy workaround, not native abstract interface
+- CLI only (no TUI, no API mode documented)
+
+**Note**: The name "Mentat" is now used by a different project (an AI-powered GitHub bot at mentat.ai), but this report evaluates the original AbanteAI Mentat per the task scope.
+
+**Activity**: Archived project with 560 commits, last release v1.0.18 (Apr 2024), last PyPI release v1.0.19 (Jan 2025 — marked as archived).
+
+---
+
+### 3. Cline (formerly Claude Dev)
+
+**GitHub**: [github.com/cline/cline](https://github.com/cline/cline) — 62k stars, 5,919 commits
+**Language**: TypeScript (97.7%)
+**Latest CLI release**: v3.0.7 (May 18, 2026)
+**License**: Apache 2.0
+**Organization**: Cline Bot Inc.
+
+**Core Architecture**: Cline is built on a layered SDK architecture:
+- `@cline/sdk` — Public SDK surface
+- `@cline/core` — Node runtime for sessions, built-in tools, persistence, hub support, automation
+- `@cline/agents` — Browser-compatible stateless agent execution loop
+- `@cline/llms` — Provider gateway and model catalogs
+- `@cline/shared` — Types, schemas, tool helpers
+
+Cline operates in multiple form factors: **CLI** (terminal with interactive or headless modes), **VS Code Extension**, **JetBrains Plugin**, and **Kanban** (web-based multi-agent task board).
+
+Key features:
+- **Multi-agent teams**: Coordinator agent breaks work into subtasks and delegates to specialist agents, each with their own tools and context. Team state persists across sessions.
+- **Kanban**: Run many agents in parallel from a web-based task board; each card gets its own worktree and auto-commit
+- **Rules system**: `.clinerules` files for project-specific guidance
+- **Skills system**: `.agents/skills/` directory with SKILL.md files (compatible with Goose/Claude skill format)
+- **Plugin system**: `AgentPlugin` with hooks into lifecycle stages (beforeRun, afterRun, beforeTool, afterTool, etc.)
+- **MCP support**: Load MCP settings through runtime/config extension path
+- **Provider-agnostic**: 200+ models via OpenRouter, plus Anthropic, OpenAI, Google, AWS Bedrock, Azure, Ollama, LM Studio, and any OpenAI-compatible API
+- **Tool policies**: Per-tool auto-approve/require-approval/disable
+- **Checkpoints and state persistence**: Sessions persist across restarts; snapshot/restore capability
+- **CLI commands**: `cline auth`, `cline config`, `cline mcp`, `cline history`, `cline schedule`, `cline hub`, `cline kanban`
+- **Scheduled agents**: Cron-based recurring automations
+- **Headless mode**: For CI/CD with JSON output and auto-approve
+- **Human-in-the-loop**: Approval required per action (configurable); Plan mode vs Act mode
+- **Chat integrations**: Slack, Telegram, Discord, Google Chat, WhatsApp, Linear
+- **Enterprise features**: SSO, RBAC, observability (OpenTelemetry, Datadog, Grafana)
+
+**Primary Use Case**: AI coding agent in IDE and terminal; also used as SDK for building custom agent applications.
+
+**Activity**: Very active — 5,919 commits, 260 releases, 62k stars, 3.3k dependents.
+
+---
+
+### 4. Continue (Continue Dev, Inc.)
+
+**GitHub**: [github.com/continuedev/continue](https://github.com/continuedev/continue) — 33.3k stars, 21,498 commits
+**Language**: TypeScript (84.4%) + Kotlin (3.8%) + Python (2.2%)
+**Latest release**: v1.2.22-vscode (Mar 27, 2026)
+**License**: Apache 2.0
+
+**Note**: Continue has undergone a significant pivot. The project originally was an open-source IDE code assistant (VS Code + JetBrains) with chat, edit, autocomplete, and agent modes. It has now pivoted to **Source-controlled AI checks, enforceable in CI** — a CI-focused product where checks are defined as markdown files in `.continue/checks/` and run as GitHub status checks on every PR.
+
+**Current Architecture** (post-pivot):
+- Checks defined as markdown files with YAML frontmatter in `.continue/checks/` or `.agents/checks/`
+- Each check file has `name`, `description`, optional `model`, and a markdown body prompt
+- CLI (`cn`) runs checks locally and in CI
+- GitHub integration for PR status checks
+- "Agents" for event-triggered automation (schedules, new issues, webhooks)
+- Cloud agent gallery with pre-configured agents
+
+**Legacy IDE extension** (still available):
+- VS Code Agent, Chat, Edit, Autocomplete modes
+- JetBrains plugin
+- MCP support
+- Multiple LLM provider support (via config)
+
+**Primary Use Case (current)**: AI-powered code review checks in CI/CD pipelines.
+
+**Activity**: Active — 21,498 commits (the highest commit count), 822 releases. However, recent focus appears to be on the CI/checks product rather than the agent framework.
+
+---
+
+## Requirements Checklist
+
+### Requirement 1: Three-layer hierarchy (dispatch -> orchestrator -> subagent)
+
+| Framework | Support | Explanation |
+|-----------|---------|-------------|
+| **Goose** | **Partial** | Supports subagents (spawned by main agent) but no formal three-layer hierarchy. Has main agent → subagent (1 level deep). Subagents cannot spawn further subagents. Recipes enable reusable subagent configs. External subagents (Codex, Claude Code) supported via MCP. |
+| **Mentat** | **Not at all** | Single-agent CLI tool. No notion of orchestrator or subagents. |
+| **Cline** | **Partial** | Supports multi-agent teams: coordinator agent delegates to specialist agents. CLI supports `--team-name` flag. Kanban enables parallel agent execution. SDK enables building custom multi-agent systems. However, no formal three-layer dispatch→orchestrator→subagent hierarchy documented. |
+| **Continue** | **Not at all** | Current product is single-agent checks per PR. No multi-agent hierarchy. Each check runs independently. |
+
+### Requirement 2: Config-driven orchestrators
+
+| Framework | Support | Explanation |
+|-----------|---------|-------------|
+| **Goose** | **Yes** | Recipes (YAML files) define subagent behavior: instructions, extensions, parameters, prompts. `GOOSE_RECIPE_PATH` env var configures recipe locations. Subagent settings configurable via natural language, env vars, or recipes. |
+| **Mentat** | **Not at all** | No orchestrator concept. Basic config file for model settings. |
+| **Cline** | **Partial** | Orchestrator behavior is primarily code-driven via SDK (TypeScript). Plugin system allows packaging configuration. CLI flags (`--team-name`, `--plan`, `--auto-approve`) provide some config-driven behavior. No dedicated YAML orchestration config. |
+| **Continue** | **Not at all** | No orchestrator concept. Check files are markdown with YAML frontmatter, but these are individual check configs, not orchestrator definitions. |
+
+### Requirement 3: Parallel subagent execution
+
+| Framework | Support | Explanation |
+|-----------|---------|-------------|
+| **Goose** | **Yes** | Subagents can run in parallel using trigger keywords ("parallel", "simultaneously", "concurrently", "at the same time"). Parallel subagents supported natively. |
+| **Mentat** | **Not at all** | Single-threaded CLI, no parallel execution. |
+| **Cline** | **Yes** | Kanban enables parallel agent execution from a web-based task board. SDK multi-agent example demonstrates parallel agents streaming to web UI. Multi-agent teams with coordinator delegating work. |
+| **Continue** | **Not at all** | Checks run sequentially as part of CI pipeline. No parallel subagent execution. |
+
+### Requirement 4: Strict hierarchy communication
+
+| Framework | Support | Explanation |
+|-----------|---------|-------------|
+| **Goose** | **Yes** | Subagents have restricted operations: cannot spawn further subagents (prevents infinite recursion), cannot manage extensions, cannot manage schedules. Communication flows through the parent agent. |
+| **Mentat** | **Not at all** | No hierarchy exists. |
+| **Cline** | **Partial** | Multi-agent team communication pattern: coordinator → specialist agents. SDK plugin hooks enable lifecycle-based communication control. No explicit peer-to-peer blocking documented but the architecture implies parent-mediated communication. |
+| **Continue** | **Not at all** | No hierarchy exists. |
+
+### Requirement 5: User-to-agent messaging mid-execution
+
+| Framework | Support | Explanation |
+|-----------|---------|-------------|
+| **Goose** | **Yes** | Sessions are continuous conversations. Users can interrupt and provide input during execution. In-session actions allow sharing information mid-session. Subagent activity is visible in real-time. |
+| **Mentat** | **Partial** | Interactive CLI sessions allow text input during conversation, but no structured mid-execution message injection. |
+| **Cline** | **Yes** | Interactive CLI mode and VS Code extension support continuous chat. Users can interrupt agent execution. `ask_question` tool allows agent to request user input mid-execution. Plan/Act mode switching. |
+| **Continue** | **Not at all** | Checks run autonomously in CI. No user interaction mid-execution. The legacy VS Code extension supports chat but the current CI product does not. |
+
+### Requirement 6: Conflict prevention (non-overlapping file scopes)
+
+| Framework | Support | Explanation |
+|-----------|---------|-------------|
+| **Goose** | **Partial** | Subagents operate with process isolation. Sandbox for desktop app controls file access. No explicit file-scoping mechanism for parallel subagents documented. |
+| **Mentat** | **Not at all** | No parallel execution, no conflict prevention needed. |
+| **Cline** | **Partial** | Kanban gives each agent its own worktree (Git worktree) with auto-commit. This provides file-level isolation. No explicit lock-based conflict prevention. |
+| **Continue** | **Not at all** | No parallel execution. |
+
+### Requirement 7: Role-scoped tooling
+
+| Framework | Support | Explanation |
+|-----------|---------|-------------|
+| **Goose** | **Yes** | Subagents can be given specific extension sets (e.g., "Create a subagent with only the developer extension"). Extension control via recipes and natural language prompts. `available_tools` field in extension config filters tools. |
+| **Mentat** | **Not at all** | No role-based tool differentiation. |
+| **Cline** | **Yes** | Plugin system allows registering tools per agent. Multi-agent teams: specialist agents get their own tools and context. Tool policies per agent (`autoApprove`, `enabled`). SDK enables fine-grained tool assignment. |
+| **Continue** | **Not at all** | Each check gets the same toolset (PR diff reading). No role-scoped tooling. |
+
+### Requirement 8: Skills system (injectable markdown/text instructions per agent type)
+
+| Framework | Support | Explanation |
+|-----------|---------|-------------|
+| **Goose** | **Yes** | Skills stored in `~/.agents/skills/<name>/SKILL.md` (global) or `.agents/skills/<name>/SKILL.md` (project-level). YAML frontmatter with `name` and `description`. Supports supporting files (scripts, templates). Also compatible with `.claude/skills/`. Skill Marketplace available. |
+| **Mentat** | **Not at all** | No skills system. Auto-context uses RAG to find relevant code snippets. |
+| **Cline** | **Yes** | Skills in `.agents/skills/` directory with SKILL.md files (same format as Goose). `.clinerules` files for project rules. Plugin system allows bundling rules. Skills system mentioned in built-in tools list. |
+| **Continue** | **Not at all** | Check files are similar to skills in format (markdown + frontmatter) but serve a different purpose (CI review prompts, not injectable agent instructions). No skills directory concept. |
+
+### Requirement 9: LSP integration
+
+| Framework | Support | Explanation |
+|-----------|---------|-------------|
+| **Goose** | **Not at all** | No LSP integration documented. Uses shell commands for compilation checks. |
+| **Mentat** | **Not at all** | No LSP integration. |
+| **Cline** | **Yes** | VS Code extension integrates with editor LSP for compiler diagnostics. Monitors linter and compiler errors as it works. SDK plugin examples include TypeScript LSP tools. Clinerules can reference LSP diagnostics. |
+| **Continue** | **Partial** | Legacy VS Code extension integrates with editor LSP. Current CI-focused product does not use LSP (analyzes PR diffs, not live diagnostics). |
+
+### Requirement 10: Shell access with directory permissions
+
+| Framework | Support | Explanation |
+|-----------|---------|-------------|
+| **Goose** | **Yes** | Extension allowlist for controlling which extensions/tools are permitted. Sandbox for Desktop app (macOS sandbox). Permission modes: auto, approve, chat, smart_approve. `GOOSE_ALLOWLIST` env var. Prompt injection detection. Adversary mode for monitoring. |
+| **Mentat** | **Not at all** | Run commands directly via shell. No permission controls beyond git tracking. |
+| **Cline** | **Yes** | `CLINE_COMMAND_PERMISSIONS` env var with allow/deny lists for shell commands. Tool policies per tool (autoApprove/require approval/disable). Auto-approve can be toggled. VS Code extension every action requires explicit approval by default. |
+| **Continue** | **Not at all** | No shell access in current CI product. Legacy extension uses VS Code sandbox. |
+
+### Requirement 11: Session management (forking, model switching, loading/resuming)
+
+| Framework | Support | Explanation |
+|-----------|---------|-------------|
+| **Goose** | **Yes** | Session management with start, resume (`-r`), search, and history. Smart context management with auto-compaction. Sessions are single continuous conversations. Model switching supported via multi-model config. |
+| **Mentat** | **Partial** | Basic CLI sessions. No session forking, no model switching mid-conversation, no resume documented (beyond git context). |
+| **Cline** | **Yes** | `cline history` command for managing task history. Session state persists across restarts (snapshot/restore). Model can be overridden per run with `-m` flag. Task history queryable. |
+| **Continue** | **Not at all** | No session concept in current CI product. Each check run is stateless. |
+
+### Requirement 12: Human-in-the-loop checkpoints
+
+| Framework | Support | Explanation |
+|-----------|---------|-------------|
+| **Goose** | **Yes** | Permission modes: "approve" mode requires user approval for each action, "smart_approve" for selective approval. Configuration via `GOOSE_MODE`. Session-level approval flow. |
+| **Mentat** | **Not at all** | No checkpoint/approval system. Mentat edits files directly. |
+| **Cline** | **Yes** | Each file edit and terminal command requires approval by default. Plan mode vs Act mode. Auto-approve can be toggled. Checkpoints track all changes for easy undo. VS Code extension shows diffs for review. |
+| **Continue** | **Not at all** | Runs autonomously in CI. No human-in-the-loop during execution. Results are reviewed after the fact. |
+
+### Requirement 13: State persistence across restarts
+
+| Framework | Support | Explanation |
+|-----------|---------|-------------|
+| **Goose** | **Yes** | Sessions can be resumed across restarts (`goose session -r`). Configuration persists in `config.yaml`. Session history maintained with smart context management. OpenTelemetry for observability. |
+| **Mentat** | **No** | No session persistence. Each session starts fresh (though git provides code history). |
+| **Cline** | **Yes** | Session state persists across restarts. Snapshot/restore capability via SDK. Schedules persist across restarts. `cline history` for past sessions. Config in `.clinerules` and plugin configs. |
+| **Continue** | **Not at all** | Each check run is stateless. Check files are persistent (in git repo) but execution state is not. |
+
+### Requirement 14: Provider-agnostic LLM
+
+| Framework | Support | Explanation |
+|-----------|---------|-------------|
+| **Goose** | **Yes** | 15+ providers: Anthropic, OpenAI, Google, Ollama, OpenRouter, Azure, Bedrock, SageMaker, etc. ACP providers (Claude Code, Codex). Provider abstraction in config. Multi-model configuration supported. |
+| **Mentat** | **Partial** | Primarily OpenAI GPT-4. Alternative models via litellm proxy (workaround, not native abstraction). |
+| **Cline** | **Yes** | Provider gateway via `@cline/llms` package. Anthropic, OpenAI, Google, OpenRouter (200+ models), AWS Bedrock, Azure, GCP Vertex, Cerebras, Groq, Ollama, LM Studio, any OpenAI-compatible API. Provider catalog with `getAllProviders()`, `getModelsForProvider()`. |
+| **Continue** | **Yes** | Multiple providers supported via config. Uses `@continuedev/openai-adapters` for provider abstraction. Supports Anthropic, OpenAI, Google, AWS Bedrock, Ollama, etc. Model configurable per check file. |
+
+### Requirement 15: Multiple interfaces (CLI, TUI, API)
+
+| Framework | Support | Explanation |
+|-----------|---------|-------------|
+| **Goose** | **Yes** | Desktop app (macOS, Linux, Windows), CLI, API (via ACP server mode). `goose acp` starts as ACP server. Multiple interfaces documented. |
+| **Mentat** | **Partial** | CLI only. Had a VS Code extension (mentat-vscode) but it's part of the archived project. No API, no TUI beyond basic CLI. |
+| **Cline** | **Yes** | CLI (interactive + headless), VS Code Extension, JetBrains Plugin, Kanban (web-based), SDK for custom integrations. ACP mode for other editors (Neovim, Zed). |
+| **Continue** | **Partial** | CLI (`cn`), VS Code Extension, JetBrains Plugin. API is cloud-based (continue.dev). No TUI. Current focus is CLI + CI integration. |
+
+---
+
+## Source list
+
+| # | Source | Type |
+|---|--------|------|
+| 1 | [Goose GitHub Repository](https://github.com/aaif-goose/goose) | official (GitHub) |
+| 2 | [Goose Architecture Docs](https://goose-docs.ai/docs/goose-architecture/) | official (docs) |
+| 3 | [Goose Subagents Guide](https://goose-docs.ai/docs/guides/context-engineering/subagents) | official (docs) |
+| 4 | [Goose Skills Guide](https://goose-docs.ai/docs/guides/context-engineering/using-skills) | official (docs) |
+| 5 | [Goose Goosehints Guide](https://goose-docs.ai/docs/guides/context-engineering/using-goosehints) | official (docs) |
+| 6 | [Goose Config Files Guide](https://goose-docs.ai/docs/guides/config-files) | official (docs) |
+| 7 | [Goose Sessions Guide](https://goose-docs.ai/docs/guides/sessions/) | official (docs) |
+| 8 | [Goose Security Guide](https://goose-docs.ai/docs/guides/security/) | official (docs) |
+| 9 | [Mentat Archived Repository](https://github.com/AbanteAI/archive-old-cli-mentat) | official (GitHub, archived) |
+| 10 | [Mentat PyPI Page](https://pypi.org/project/mentat/) | official (PyPI) |
+| 11 | [Mentat Web Archive README (Jan 2024)](https://web.archive.org/web/20240105225309/https://github.com/AbanteAI/mentat) | official (archived) |
+| 12 | [Cline GitHub Repository](https://github.com/cline/cline) | official (GitHub) |
+| 13 | [Cline SDK Overview Docs](https://docs.cline.bot/sdk/overview) | official (docs) |
+| 14 | [Cline SDK Architecture Docs](https://docs.cline.bot/sdk/architecture/overview) | official (docs) |
+| 15 | [Cline CLI Overview Docs](https://docs.cline.bot/usage/cli-overview) | official (docs) |
+| 16 | [Cline Tools Docs](https://docs.cline.bot/sdk/tools) | official (docs) |
+| 17 | [Cline Plugins Docs](https://docs.cline.bot/sdk/plugins) | official (docs) |
+| 18 | [Cline Building an Agent Guide](https://docs.cline.bot/sdk/guides/building-an-agent) | official (docs) |
+| 19 | [Cline Creating Custom Tools Guide](https://docs.cline.bot/sdk/guides/creating-custom-tools) | official (docs) |
+| 20 | [Continue GitHub Repository](https://github.com/continuedev/continue) | official (GitHub) |
+| 21 | [Continue Docs — What is Continue](https://docs.continue.dev/) | official (docs) |
+| 22 | [Continue Check File Reference](https://docs.continue.dev/checks/reference) | official (docs) |
+| 23 | [Continue Beyond Checks — Agents](https://docs.continue.dev/mission-control/beyond-checks) | official (docs) |
+| 24 | [Continue core/package.json](https://github.com/continuedev/continue/blob/main/core/package.json) | official (source) |
+
+---
+
+## Verbatim quotes
+
+- "goose is an open source, extensible AI agent that goes beyond code suggestions - install, execute, edit, and test with any LLM" — [Goose GitHub](https://github.com/aaif-goose/goose)
+- "Subagents are independent instances that execute tasks while keeping your main conversation clean and focused." — [Goose Subagents Guide](https://goose-docs.ai/docs/guides/context-engineering/subagents)
+- "Skills are reusable sets of instructions and resources that teach goose how to perform specific tasks." — [Goose Skills Guide](https://goose-docs.ai/docs/guides/context-engineering/using-skills)
+- "⚠️ ARCHIVED PROJECT ⚠️ This repository contains an archived version of an old command-line tool that is no longer maintained or supported." — [Mentat Archived Repository](https://github.com/AbanteAI/archive-old-cli-mentat)
+- "Cline is an AI coding agent that lives in your editor and your terminal. It can read and write files, run terminal commands, use a browser, and help you build features through natural conversation." — [Cline Docs](https://docs.cline.bot/)
+- "The Cline SDK is an open source framework for building agentic applications, and is the same harness used in the Cline IDE extensions and CLI." — [Cline SDK Docs](https://docs.cline.bot/sdk/overview)
+- "Coordinate multiple agents working together on complex tasks. A coordinator agent breaks the work into subtasks and delegates to specialist agents." — [Cline GitHub README](https://github.com/cline/cline)
+- "Source-controlled AI checks, enforceable in CI. Powered by the open-source Continue CLI." — [Continue GitHub](https://github.com/continuedev/continue)
+- "Continue runs AI checks on every pull request. Each check is a markdown file in your repo that shows up as a GitHub status check." — [Continue Docs](https://docs.continue.dev/)
+
+---
+
+## Source quality flags
+
+- Source 10 (Mentat PyPI): The project is marked as archived. Useful for confirming status but not for evaluating current capabilities.
+- Source 11 (Mentat Wayback Machine): Archived snapshot from Jan 2024; reflects pre-archive state. Useful context but outdated.
+- Source 22-23 (Continue Docs): Primarily documents the new CI/checks product. The legacy VS Code extension features are not well documented in current docs.
+
+---
+
+## Confidence: High
+
+All four frameworks were researched from primary sources (GitHub repos, official documentation). Goose and Cline documentation is current and comprehensive. Continue's documentation accurately reflects its pivot to CI checks. Mentat's status as archived is confirmed by both GitHub and PyPI. The key limitation is that some features (especially edge cases around hierarchy and conflict prevention) were inferred from published capabilities rather than explicit documentation, but the overall assessment is reliable.
+
+---
+
+## Gaps and open questions
+
+1. **Cline three-layer hierarchy**: Cline's multi-agent teams imply a coordinator→specialist pattern, but whether this supports full 3+ layer nesting (dispatch→orchestrator→subagent→sub-subagent) is unclear from available docs.
+2. **Goose conflict prevention**: Whether Goose provides explicit file-locking or scope-assignment mechanisms for parallel subagents is not documented beyond process isolation and worktree separation.
+3. **Continue agent features**: Continue's "Agents" (beyond checks) are cloud-based and poorly documented for self-hosted use. What local agent capabilities remain from the original IDE assistant is unclear.
+4. **New Mentat (mentat.ai)**: The task specified "Mentat (by AbanteAI)" which refers to the archived CLI tool. The current Mentat at mentat.ai is a different product (AI GitHub bot) and was not evaluated. If this is what the lead researcher intended, follow-up investigation is needed.
+5. **LSP integration depth**: Cline's LSP integration is primarily through VS Code extension host. Whether SDK-level LSP tools exist outside the IDE is unclear.
+
+---
+
+## Summary Comparison Table
+
+| Requirement | Goose | Mentat (archived) | Cline | Continue |
+|---|---|---|---|---|
+| **1. Three-layer hierarchy** | Partial | Not at all | Partial | Not at all |
+| **2. Config-driven orchestrators** | Yes | Not at all | Partial | Not at all |
+| **3. Parallel subagent execution** | Yes | Not at all | Yes | Not at all |
+| **4. Strict hierarchy communication** | Yes | Not at all | Partial | Not at all |
+| **5. User-to-agent mid-execution** | Yes | Partial | Yes | Not at all |
+| **6. Conflict prevention** | Partial | Not at all | Partial | Not at all |
+| **7. Role-scoped tooling** | Yes | Not at all | Yes | Not at all |
+| **8. Skills system** | Yes | Not at all | Yes | Not at all |
+| **9. LSP integration** | Not at all | Not at all | Yes | Partial |
+| **10. Shell with dir permissions** | Yes | Not at all | Yes | Not at all |
+| **11. Session management** | Yes | Partial | Yes | Not at all |
+| **12. Human-in-the-loop checkpoints** | Yes | Not at all | Yes | Not at all |
+| **13. State persistence** | Yes | No | Yes | Not at all |
+| **14. Provider-agnostic LLM** | Yes | Partial | Yes | Yes |
+| **15. Multiple interfaces** | Yes (Desktop, CLI, API) | Partial (CLI only) | Yes (CLI, IDE, Kanban, SDK) | Partial (CLI, IDE) |
+
+---
+
+## Key Questions Summary
+
+### What is each framework's core architecture? How many layers of agent hierarchy?
+- **Goose**: Interface → Agent → Extensions (3-component). Subagents: main agent → subagent (1 level deep, cannot nest further).
+- **Mentat**: Single agent CLI. No hierarchy.
+- **Cline**: Layered SDK (shared → llms → agents → core). Multi-agent teams: coordinator → specialists. SDK allows custom hierarchies.
+- **Continue**: Single-agent check runner. No hierarchy.
+
+### How extensible/configurable without modifying source code?
+- **Goose**: Very — YAML config files, recipes, skills, extensions/MCP, env vars, custom distributions.
+- **Mentat**: Minimal — basic config file for model settings and file exclusions.
+- **Cline**: Very — SDK plugins, custom tools, hooks, rules, MCP servers, config CLI.
+- **Continue**: Limited — check files are markdown with YAML frontmatter; config via CLI/env.
+
+### Primary use case?
+- **Goose**: General-purpose AI agent (code, automation, research, writing)
+- **Mentat (archived)**: CLI-based AI coding assistant (context-aware code editing)
+- **Cline**: AI coding agent in IDE/terminal + SDK for custom agent apps
+- **Continue**: Source-controlled AI checks in CI/CD (pivoted from IDE assistant)
+
+### Activity level?
+- **Goose**: Very active — 45.5k stars, 4,541 commits, 134 releases, moved to Linux Foundation AAIF
+- **Mentat**: Archived — 2.6k stars, 560 commits, last code Jan 2024, archived Jan 2025
+- **Cline**: Very active — 62k stars, 5,919 commits, 260 releases, CLI v3.0.7 (May 18, 2026)
+- **Continue**: Active — 33.3k stars, 21,498 commits, 822 releases (but pivoted focus)
+
+### Language?
+- **Goose**: Rust + TypeScript
+- **Mentat (archived)**: Python (TypeScript for VS Code extension)
+- **Cline**: TypeScript (some Rust)
+- **Continue**: TypeScript (some Kotlin for JetBrains, Python)
+
+---
+
+## Tool calls made
+
+1. `webfetch` — github.com/block/goose (README)
+2. `webfetch` — github.com/AbanteAI/mentat (404)
+3. `webfetch` — github.com/cline/cline (README)
+4. `webfetch` — github.com/continuedev/continue (README)
+5. `webfetch` — github.com/AbanteAI/mentat (second attempt, 404)
+6. `webfetch` — github.com/continuedev/continue/blob/main/extensions/vscode/README.md
+7. `webfetch` — goose-docs.ai/docs/quickstart
+8. `webfetch` — docs.cline.bot (overview)
+9. `webfetch` — docs.cline.bot/sdk/overview
+10. `webfetch` — goose-docs.ai/docs/category/architecture-overview
+11. `webfetch` — docs.continue.dev (what is continue)
+12. `webfetch` — goose-docs.ai/docs/goose-architecture/
+13. `webfetch` — docs.cline.bot/sdk/architecture/overview
+14. `webfetch` — github.com/continuedev/continue/blob/main/core/README.md (404)
+15. `webfetch` — pypi.org/project/mentat/
+16. `webfetch` — npmjs.com/package/mentat (403)
+17. `webfetch` — goose-docs.ai/docs/guides/context-engineering/subagents
+18. `webfetch` — docs.cline.bot/sdk/tools
+19. `webfetch` — goose-docs.ai/docs/guides/context-engineering/using-skills
+20. `webfetch` — docs.cline.bot/sdk/plugins
+21. `webfetch` — goose-docs.ai/docs/guides/config-files
+22. `webfetch` — docs.continue.dev/checks/quickstart
+23. `webfetch` — goose-docs.ai/docs/guides/security/
+24. `webfetch` — docs.cline.bot/usage/cli-overview
+25. `webfetch` — github.com/AbanteAI/archive-old-cli-mentat
+26. `webfetch` — docs.cline.bot/sdk/guides/building-an-agent
+27. `webfetch` — docs.cline.bot/sdk/guides/creating-custom-tools
+28. `webfetch` — github.com/continuedev/continue/blob/main/core/package.json
+29. `webfetch` — raw.githubusercontent.com/AbanteAI/archive-old-cli-mentat/main/README.md
+30. `webfetch` — docs.continue.dev/mission-control/beyond-checks
+31. `webfetch` — raw.githubusercontent.com/AbanteAI/archive-old-cli-mentat/main/mentat/__init__.py
+32. `webfetch` — raw.githubusercontent.com/AbanteAI/archive-old-cli-mentat/main/pyproject.toml
+33. `webfetch` — web.archive.org/web/20240105225309/https://github.com/AbanteAI/mentat
+34. `webfetch` — mentat.ai (transport error)
+35. `webfetch` — docs.cline.bot/getting-started/authorizing-with-cline
+36. `webfetch` — raw.githubusercontent.com/AbanteAI/archive-old-cli-mentat/main/docs/overview.md (404)
diff --git a/research/emerging-specialized.md b/research/emerging-specialized.md
new file mode 100644
index 0000000..6be8f18
--- /dev/null
+++ b/research/emerging-specialized.md
@@ -0,0 +1,473 @@
+# Subagent Report: Emerging & Specialized AI Agent Harnesses
+
+## Research summary
+
+This report evaluates **OpenCode** (now **Crush**), **Plandex**, **GPT-Engineer**, **Claude Code**, and several other notable open-source AI coding agents against a 15-point requirements checklist for a multi-layered agent orchestration harness. Of all frameworks evaluated, **Claude Code** comes closest to the full requirements but is not fully open source and lacks a native three-layer dispatch->orchestrator->subagent hierarchy. **Plandex** is strong for large-scale plan-then-execute workflows but is single-agent. **Crush** (OpenCode's successor) is a well-architected terminal agent with LSP/MCP/skills but lacks hierarchy. **GPT-Engineer** is archived and unsuitable. **Bolt.diy** is a web app builder, not an agent harness. **Sweep** has pivoted to a JetBrains plugin.
+
+---
+
+## Findings
+
+### 1. OpenCode (archived) → Crush (successor)
+
+**GitHub**: [opencode-ai/opencode](https://github.com/opencode-ai/opencode) (archived Sep 18, 2025) → [charmbracelet/crush](https://github.com/charmbracelet/crush) (24.4k stars, active)
+
+**Language**: Go
+
+**Architecture**: OpenCode was a Go-based CLI/TUI coding agent built with Charm's Bubble Tea framework. It was archived in September 2025 and the project continued as **Crush** by the Charm team. Crush is actively maintained (v0.70.0, May 18, 2026) with 3,380+ commits.
+
+Crush's architecture is a **single-agent loop** with tool-calling capabilities. It supports spawning sub-tasks via the `agent` tool but does not have a built-in multi-layer hierarchy. It provides:
+
+- Interactive TUI + CLI + non-interactive prompt mode
+- Multi-provider LLM support (Anthropic, OpenAI, Google, Groq, OpenRouter, AWS Bedrock, Azure, local models)
+- LSP integration with configurable language server support
+- MCP protocol support (stdio, http, sse)
+- Skills via the Agent Skills open standard
+- Session management (save/load/switch sessions, SQLite-backed)
+- Plugin system through MCP and skills
+- Hooks (preliminary support)
+- Configurable permissions (`allowed_tools`, tool allow/deny)
+- Desktop notifications
+- Provider auto-updates from Catwalk database
+
+**Notable features for Dispatch comparison**: Crush reads `.claude/skills/` and `.cursor/skills/` directories for compatibility with Claude Code skills. It supports `.crushignore` files. It has `initialize_as` option to create AGENTS.md/CRUSH.md context files.
+
+**Confidence**: High — well-documented, active project, extensive README.
+
+---
+
+### 2. Plandex
+
+**GitHub**: [plandex-ai/plandex](https://github.com/plandex-ai/plandex) (15.4k stars, active, 1,483 commits)
+
+**Language**: Go (93.4%)
+
+**Architecture**: Plandex is a terminal-based AI development tool with a **plan-and-execute** workflow. It is a single-agent system with a cumulative diff review sandbox. Its architecture is:
+
+- **Plan branch**: AI proposes changes in a sandbox, kept separate from project files
+- **Apply phase**: User reviews and applies the cumulative diff
+- **Autonomous loop**: Can auto-execute commands, detect failures, debug, and retry
+
+Plandex handles up to 2M tokens of effective context via selective loading. It uses tree-sitter for project map generation (30+ languages). It has built-in version control for plans (branches, diffs). It integrates with git for commit message generation.
+
+**Key details**:
+- CLI + REPL with fuzzy auto-complete
+- Multi-provider: Anthropic, OpenAI, Google, OpenRouter, open source models
+- Context caching for all major providers
+- Configurable autonomy levels (full auto → step-by-step review)
+- Automated debugging of terminal commands and browser apps
+- Cloud hosted mode is winding down; self-hosted/local Docker mode is primary
+- No API mode, no TUI (terminal REPL only)
+
+**Confidence**: High — well-documented, active community, recent releases.
+
+---
+
+### 3. GPT-Engineer
+
+**GitHub**: [AntonOsika/gpt-engineer](https://github.com/AntonOsika/gpt-engineer) (55.2k stars, **archived** Apr 22, 2026)
+
+**Language**: Python (98.8%)
+
+**Architecture**: GPT-Engineer was a CLI platform for code generation experimentation. It used a simple single-agent prompt→generate loop. The project was archived in April 2026 and the last release was v0.3.1 (June 6, 2024). The README explicitly directs users to "aider" for a well-maintained CLI or "lovable.dev" for the commercial evolution.
+
+**Capabilities**:
+- Single-agent, one-shot code generation from natural language prompts
+- Support for OpenAI, Anthropic, and open source models
+- Benchmark support (APPS, MBPP)
+- Pre-prompt overrides for agent identity
+- Vision support via image inputs
+- Docker support
+
+**Confidence**: High — project is archived, low relevance for a new harness build.
+
+---
+
+### 4. Claude Code (Anthropic)
+
+**GitHub**: [anthropics/claude-code](https://github.com/anthropics/claude-code) (125k stars, very active, 627 commits)
+
+**Language**: Shell 47.1%, Python 29.2%, TypeScript 17.7% (Note: this repo contains the installer, plugins, examples, and documentation; the **core agent engine is proprietary** and not fully open source.)
+
+**Architecture**: Claude Code is a sophisticated agentic coding tool with a layered architecture:
+
+1. **Main session**: The primary agent loop that the user interacts with
+2. **Subagents**: Specialized agents spawned from the main session, each with:
+ - Its own context window
+ - Custom system prompts via YAML frontmatter in Markdown files
+ - Restricted/permissions-scoped tool sets
+ - Configurable models (sonnet/opus/haiku or full model IDs)
+ - Independent permission modes (default, acceptEdits, auto, dontAsk, bypassPermissions, plan)
+ - Persistent memory (user/project/local scope)
+ - Preloaded skills
+ - Optional git worktree isolation
+3. **Agent teams** (experimental, behind feature flag): Multiple full Claude Code sessions that coordinate via a shared task list, with peer-to-peer messaging via mailbox system
+
+**Surface area**: Terminal CLI, VS Code extension, JetBrains plugin, Desktop app, Web (claude.ai/code), Slack integration, GitHub Actions, GitLab CI/CD, iOS app
+
+**Key subsystems**:
+- **Skills system**: Full Agent Skills open standard support, directory-based (`.claude/skills/`), personal/project/plugin/managed scopes, YAML frontmatter with description/tools/context/agent fields, dynamic context injection via `` !`command` ``, argument substitution
+- **Memory system**: CLAUDE.md files (project/user/managed/org scopes), `.claude/rules/` for path-scoped instructions, auto memory (Claude writes learnings across sessions)
+- **Hooks**: PreToolUse, PostToolUse, SubagentStart/Stop, TeammateIdle, TaskCreated, TaskCompleted — shell commands at lifecycle events
+- **Permissions**: Tiered system (allow/ask/deny rules), wildcard matching, Bash/Read/Edit/WebFetch/MCP scoping, sandboxing (OS-level isolation), auto mode (ML classifier)
+- **MCP**: Model Context Protocol for external tool integration
+- **Agent SDK**: Python and TypeScript SDKs for building custom agents with Claude Code's tools and agent loop
+
+**Provider support**: Primarily uses Claude models but supports Amazon Bedrock, Google Vertex AI, Microsoft Azure AI Foundry as third-party backends.
+
+---
+
+### 5. Bolt.new / Bolt.diy
+
+**GitHub**: [stackblitz/bolt.new](https://github.com/stackblitz/bolt.new) (16.4k stars) / [stackblitz-labs/bolt.diy](https://github.com/stackblitz-labs/bolt.diy) (19.4k stars)
+
+**Language**: TypeScript
+
+**Architecture**: Bolt.new is a web-based AI-powered full-stack application generator using StackBlitz's WebContainers. bolt.diy is the community fork that supports any LLM. Both are **web application builders**, not agent harnesses or orchestrators.
+
+- Single-agent prompt→generate→preview loop
+- No agent hierarchy
+- No orchestrator or subagent concepts
+- Primarily browser-based (with Electron desktop app)
+- Supports 19+ LLM providers in bolt.diy
+- File locking system to prevent conflicts
+- Git integration for clone/import/deploy
+- MCP support
+- Diff view for AI changes
+
+**Confidence**: High — well-documented, but not applicable as an agent harness.
+
+---
+
+### 6. Sweep
+
+**GitHub**: [sweepai/sweep](https://github.com/sweepai/sweep) (7.7k stars)
+
+**Language**: Python, Jupyter Notebook, TypeScript
+
+**Note**: Sweep was originally a GitHub app for automated PR creation from issues. It has since pivoted to an AI coding assistant for JetBrains IDEs. The open source repo is largely dormant. Not applicable as a general agent harness.
+
+---
+
+### 7. Other Notable Frameworks Not Found
+
+**Kortix Suna**: Could not locate an active GitHub repository under sunaify/suna or similar names.
+
+**Devon (entelligence-ai/devon)**: Could not locate — the GitHub org/user was not found (404).
+
+---
+
+## Requirements Checklist
+
+### 1. Three-layer hierarchy (dispatch → orchestrator → subagent)
+
+| Framework | Status | Notes |
+|-----------|--------|-------|
+| **OpenCode → Crush** | ❌ Not supported | Single-agent loop; has `agent` tool for sub-tasks but no orchestrator layer |
+| **Plandex** | ❌ Not supported | Single-agent plan→execute loop |
+| **GPT-Engineer** | ❌ Not supported | Single-agent prompt→generate |
+| **Claude Code** | ⚠️ Partial | 2 layers: main agent → subagents. Agent teams (experimental) add peer coordination but not 3-tier hierarchy |
+| **Bolt.diy** | ❌ Not supported | Single-agent web app builder |
+| **Sweep** | ❌ Not supported | Single-agent GitHub PR generator (now JetBrains plugin) |
+
+### 2. Config-driven orchestrators
+
+| Framework | Status | Notes |
+|-----------|--------|-------|
+| **OpenCode → Crush** | ❌ Not supported | No orchestrator concept; config is for providers/model/agents |
+| **Plandex** | ❌ Not supported | Config is limited to model providers and autonomy level |
+| **GPT-Engineer** | ❌ Not supported | Pre-prompt override only |
+| **Claude Code** | ⚠️ Partial | Subagents defined via YAML frontmatter in `.md` files with tools/models/prompts/permissions. No subagent template spawning from orchestrator config |
+| **Bolt.diy** | ❌ Not supported | Provider config only |
+| **Sweep** | ❌ Not supported | — |
+
+### 3. Parallel subagent execution
+
+| Framework | Status | Notes |
+|-----------|--------|-------|
+| **OpenCode → Crush** | ❌ Not supported | Sub-agent tool is sequential |
+| **Plandex** | ❌ Not supported | Single-threaded plan→execute |
+| **GPT-Engineer** | ❌ Not supported | Single-threaded |
+| **Claude Code** | ✅ Supported | Subagents run in parallel; multiple background agents can run concurrently. Agent teams spawn multiple independent sessions |
+| **Bolt.diy** | ❌ Not supported | Single prompt→response |
+| **Sweep** | ❌ Not supported | — |
+
+### 4. Strict hierarchy communication (subagents only talk to parent)
+
+| Framework | Status | Notes |
+|-----------|--------|-------|
+| **OpenCode → Crush** | ⚠️ Partial | Agent tool calls return results; no peer-to-peer, but also no multi-layer parent chain |
+| **Plandex** | N/A | No subagents |
+| **GPT-Engineer** | N/A | No subagents |
+| **Claude Code** | ✅ Supported | Subagents report to parent only; no peer-to-peer for subagents. Note: Agent teams (experimental) intentionally allow peer-to-peer messaging |
+| **Bolt.diy** | N/A | No subagents |
+| **Sweep** | N/A | — |
+
+### 5. User-to-agent messaging mid-execution
+
+| Framework | Status | Notes |
+|-----------|--------|-------|
+| **OpenCode → Crush** | ❌ Not supported | User types at the main session; cannot message a sub-agent mid-task |
+| **Plandex** | ⚠️ Partial | User can interrupt and redirect during plan step, but not while AI is executing |
+| **GPT-Engineer** | ❌ Not supported | Batch process |
+| **Claude Code** | ✅ Supported | Users can interact with subagents directly via Shift+Down in in-process mode; can message agent team teammates directly |
+| **Bolt.diy** | ❌ Not supported | Single prompt-response |
+| **Sweep** | ❌ Not supported | — |
+
+### 6. Conflict prevention (non-overlapping file scopes)
+
+| Framework | Status | Notes |
+|-----------|--------|-------|
+| **OpenCode → Crush** | ❌ Not supported | No scope assignment |
+| **Plandex** | ⚠️ Partial | Cumulative diff sandbox prevents conflicts by keeping AI changes separate until user applies them |
+| **GPT-Engineer** | ❌ Not supported | — |
+| **Claude Code** | ⚠️ Partial | Git worktrees for subagent isolation; file locking in agent teams. No orchestrator-driven scope assignment |
+| **Bolt.diy** | ✅ Supported | File locking system prevents concurrent edits during AI code generation |
+| **Sweep** | ❌ Not supported | — |
+
+### 7. Role-scoped tooling
+
+| Framework | Status | Notes |
+|-----------|--------|-------|
+| **OpenCode → Crush** | ✅ Supported | Different agents can have different tool sets via `agents` config with `tools` field |
+| **Plandex** | ❌ Not supported | Single agent with all tools |
+| **GPT-Engineer** | ❌ Not supported | Single agent |
+| **Claude Code** | ✅ Supported | Full role-scoped tooling via `tools` and `disallowedTools` frontmatter in subagent definitions |
+| **Bolt.diy** | ❌ Not supported | Single agent |
+| **Sweep** | ❌ Not supported | — |
+
+### 8. Skills system (injectable markdown instructions, directory-based)
+
+| Framework | Status | Notes |
+|-----------|--------|-------|
+| **OpenCode → Crush** | ✅ Supported | Supports Agent Skills open standard. Reads from `.crush/skills/`, `.claude/skills/`, `.agents/skills/`, `.cursor/skills/`, plus user-level paths |
+| **Plandex** | ❌ Not supported | No skills system |
+| **GPT-Engineer** | ⚠️ Partial | Has pre-prompt override files but not a structured skills system |
+| **Claude Code** | ✅ Supported | Full Agent Skills open standard support, YAML frontmatter, personal/project/plugin/managed scopes, dynamic context injection, argument substitution, preloading into subagents |
+| **Bolt.diy** | ❌ Not supported | No skills system |
+| **Sweep** | ❌ Not supported | — |
+
+### 9. LSP integration
+
+| Framework | Status | Notes |
+|-----------|--------|-------|
+| **OpenCode → Crush** | ✅ Supported | LSP integration with multi-language support via configurable language server commands |
+| **Plandex** | ⚠️ Partial | Tree-sitter for syntax validation and project maps, but no LSP diagnostics tool |
+| **GPT-Engineer** | ❌ Not supported | — |
+| **Claude Code** | ✅ Supported | LSP through IDE integrations (VS Code, JetBrains); diagnostics tool available to agents |
+| **Bolt.diy** | ❌ Not supported | — |
+| **Sweep** | ❌ Not supported | — |
+
+### 10. Shell access with directory permissions
+
+| Framework | Status | Notes |
+|-----------|--------|-------|
+| **OpenCode → Crush** | ⚠️ Partial | Has `allowed_tools` permission allowlist but no directory-based permission scoping. Crush has `--yolo` flag to bypass |
+| **Plandex** | ❌ Not supported | Has shell access but no permission system |
+| **GPT-Engineer** | ❌ Not supported | Shell execution without permissions |
+| **Claude Code** | ✅ Supported | Full permission system: allow/ask/deny rules, wildcard matching, absolute/project-relative/home-relative path patterns, sandboxing (OS-level filesystem/network isolation), auto mode with ML classifier |
+| **Bolt.diy** | ❌ Not supported | — |
+| **Sweep** | ❌ Not supported | — |
+
+### 11. Session management (forking, model switching, resume)
+
+| Framework | Status | Notes |
+|-----------|--------|-------|
+| **OpenCode → Crush** | ✅ Supported | Session save/load/switch, model switching mid-session, session persistence via SQLite |
+| **Plandex** | ✅ Supported | Plan version control with branching; model switching supported |
+| **GPT-Engineer** | ❌ Not supported | No session persistence |
+| **Claude Code** | ✅ Supported | Full session management: resume (`-r`, `-c`), fork (`/fork`), model switching (`/model`), chat history persistence, auto memory, `/compact` for context management |
+| **Bolt.diy** | ⚠️ Partial | Chat history via file system, no forking or model switching |
+| **Sweep** | ❌ Not supported | — |
+
+### 12. Human-in-the-loop checkpoints
+
+| Framework | Status | Notes |
+|-----------|--------|-------|
+| **OpenCode → Crush** | ❌ Not supported | No checkpoint system; permission prompts are per-tool |
+| **Plandex** | ✅ Supported | Configurable autonomy from full-auto to step-by-step approval; user reviews cumulative diff before applying |
+| **GPT-Engineer** | ❌ Not supported | No HITL |
+| **Claude Code** | ✅ Supported | Permission prompts, permission modes (default/acceptEdits/plan/auto), plan mode for review-before-edit. Checkpoints via hooks for custom approval workflows |
+| **Bolt.diy** | ❌ Not supported | Auto-approves all AI changes |
+| **Sweep** | ❌ Not supported | — |
+
+### 13. State persistence (sessions, plans, artifacts across restarts)
+
+| Framework | Status | Notes |
+|-----------|--------|-------|
+| **OpenCode → Crush** | ✅ Supported | SQLite-based session persistence; project-specific context files |
+| **Plandex** | ✅ Supported | Plans persist and can be resumed; cumulative diff sandbox stores pending changes |
+| **GPT-Engineer** | ❌ Not supported | No persistence |
+| **Claude Code** | ✅ Supported | Sessions persist in `~/.claude/projects/`; auto memory persists across sessions; CLAUDE.md files are disk-based |
+| **Bolt.diy** | ⚠️ Partial | Project snapshots via browser storage and file system |
+| **Sweep** | ❌ Not supported | — |
+
+### 14. Provider-agnostic LLM
+
+| Framework | Status | Notes |
+|-----------|--------|-------|
+| **OpenCode → Crush** | ✅ Supported | 15+ providers including Anthropic, OpenAI, Google, Groq, OpenRouter, AWS Bedrock, Azure, local models, Ollama, LM Studio |
+| **Plandex** | ✅ Supported | Anthropic, OpenAI, Google, OpenRouter, open source providers; OpenRouter as primary gateway |
+| **GPT-Engineer** | ✅ Supported | OpenAI, Anthropic, open source/local models |
+| **Claude Code** | ⚠️ Partial | Primarily Claude models; supports Amazon Bedrock, Google Vertex AI, Microsoft Azure as third-party backends |
+| **Bolt.diy** | ✅ Supported | 19+ providers including OpenAI, Anthropic, Google, Ollama, OpenRouter, DeepSeek, Groq, etc. |
+| **Sweep** | ❌ Not applicable | — |
+
+### 15. Multiple interfaces (CLI, TUI, API)
+
+| Framework | Status | Notes |
+|-----------|--------|-------|
+| **OpenCode → Crush** | ✅ Supported | Interactive TUI, CLI (non-interactive mode with `-p`), scripting support |
+| **Plandex** | ⚠️ Partial | REPL (TUI-like interactive mode) and CLI commands. No API mode |
+| **GPT-Engineer** | ⚠️ Partial | CLI only |
+| **Claude Code** | ✅ Supported | Terminal CLI (interactive + `-p`), VS Code extension, JetBrains plugin, Desktop app, Web UI, Slack, Agent SDK (Python/TypeScript API), GitHub Actions, GitLab CI/CD |
+| **Bolt.diy** | ⚠️ Partial | Web UI, Electron desktop app, no API |
+| **Sweep** | ❌ Not applicable | — |
+
+---
+
+## Summary Comparison Table
+
+| Requirement | OpenCode→Crush | Plandex | GPT-Engineer | Claude Code | Bolt.diy |
+|---|---|---|---|---|---|
+| **1. Three-layer hierarchy** | ❌ | ❌ | ❌ | ⚠️ Partial | ❌ |
+| **2. Config-driven orchestrators** | ❌ | ❌ | ❌ | ⚠️ Partial | ❌ |
+| **3. Parallel subagent execution** | ❌ | ❌ | ❌ | ✅ | ❌ |
+| **4. Strict hierarchy communication** | N/A | N/A | N/A | ✅ | N/A |
+| **5. Mid-execution user messaging** | ❌ | ⚠️ | ❌ | ✅ | ❌ |
+| **6. Conflict prevention** | ❌ | ⚠️ | ❌ | ⚠️ | ✅ |
+| **7. Role-scoped tooling** | ✅ | ❌ | ❌ | ✅ | ❌ |
+| **8. Skills system** | ✅ | ❌ | ⚠️ | ✅ | ❌ |
+| **9. LSP integration** | ✅ | ⚠️ | ❌ | ✅ | ❌ |
+| **10. Shell + directory permissions** | ⚠️ | ❌ | ❌ | ✅ | ❌ |
+| **11. Session management** | ✅ | ✅ | ❌ | ✅ | ⚠️ |
+| **12. HITL checkpoints** | ❌ | ✅ | ❌ | ✅ | ❌ |
+| **13. State persistence** | ✅ | ✅ | ❌ | ✅ | ⚠️ |
+| **14. Provider-agnostic LLM** | ✅ | ✅ | ✅ | ⚠️ | ✅ |
+| **15. Multiple interfaces** | ✅ | ⚠️ | ⚠️ | ✅ | ⚠️ |
+| **Open source** | ✅ (MIT) | ✅ (MIT) | ✅ (MIT) | ⚠️ (Partial) | ✅ (MIT) |
+| **Language** | Go | Go | Python | Multi | TypeScript |
+| **Activity** | ✅ Very Active | ✅ Active | ❌ Archived | ✅ Very Active | ✅ Active |
+| **Stars** | 24.4k (Crush) | 15.4k | 55.2k | 125k | 19.4k |
+
+---
+
+## Key Questions Answered
+
+### 1. Core architecture and agent hierarchy depth
+
+- **Crush**: Single-agent TUI/CLI with sub-task spawning (2 layers max). No built-in orchestrator concept.
+- **Plandex**: Single-agent plan→execute loop. No hierarchy.
+- **GPT-Engineer**: Single-agent prompt→generate. No hierarchy.
+- **Claude Code**: Main agent → subagents (2 layers). Experimental agent teams add peer-level coordination. No native 3-tier dispatch→orchestrator→subagent.
+- **Bolt.diy**: Single-agent web app builder. No hierarchy.
+
+### 2. Extensibility without source modification
+
+- **Crush**: JSON config files, skills via markdown, MCP servers, LSP config. Moderate extensibility.
+- **Plandex**: Model/provider config, autonomy level settings. Limited extensibility.
+- **GPT-Engineer**: Pre-prompt overrides only. Very limited.
+- **Claude Code**: Most extensible — YAML frontmatter subagents, skills, hooks, MCP, permission rules via JSON config files, managed settings for org-wide policy, plugin system. No source modification needed.
+- **Bolt.diy**: Provider config, some environment variables. Limited.
+
+### 3. Primary use case
+
+- **Crush**: General-purpose AI coding assistant for terminal users
+- **Plandex**: Large-scale, multi-step coding tasks requiring plan review and diff sandboxing
+- **GPT-Engineer**: (Archived) Experimental code generation platform
+- **Claude Code**: Production AI coding assistant with enterprise features, team collaboration, and multi-surface support
+- **Bolt.diy**: Rapid full-stack web application prototyping in the browser
+
+### 4. Project activity
+
+- **Crush**: Very active — v0.70.0 (May 18, 2026), 24.4k stars, 3,380+ commits, Charm ecosystem backing
+- **Plandex**: Active — v2.2.1 (Jul 16, 2025), 15.4k stars, 1,483 commits, active Discord
+- **GPT-Engineer**: Archived — last release v0.3.1 (Jun 6, 2024), 55.2k stars, read-only
+- **Claude Code**: Very active — 125k stars, 627 commits, active development, Anthropic backing
+- **Bolt.diy**: Active — v1.0.0 (May 12, 2025), 19.4k stars, community-driven
+
+### 5. Language
+
+- **Crush**: Go
+- **Plandex**: Go
+- **GPT-Engineer**: Python
+- **Claude Code**: Shell/Python/TypeScript (installer + plugins); core engine is proprietary binary
+- **Bolt.diy**: TypeScript
+
+### 6. Other notable open-source agent harnesses (2024-2025)
+
+- **Crush** (charmbracelet/crush) — The most notable new entrant, as successor to OpenCode. Go-based, well-architected, Charm ecosystem.
+- **Bolt.diy** (stackblitz-labs/bolt.diy) — Significant as a community fork enabling any LLM with Bolt.new's WebContainer architecture.
+- **Claude Code Agent SDK** — Released mid-2025, enables building custom agents with Claude Code's tool set. Available in Python and TypeScript.
+
+Could not locate active repositories for **Kortix Suna** or **Devon** as of May 2026.
+
+---
+
+## Source list
+
+| # | Source | Type |
+|---|--------|------|
+| 1 | [OpenCode GitHub](https://github.com/opencode-ai/opencode) | GitHub |
+| 2 | [Crush GitHub](https://github.com/charmbracelet/crush) | GitHub |
+| 3 | [Plandex GitHub](https://github.com/plandex-ai/plandex) | GitHub |
+| 4 | [GPT-Engineer GitHub](https://github.com/AntonOsika/gpt-engineer) | GitHub |
+| 5 | [Claude Code GitHub](https://github.com/anthropics/claude-code) | GitHub |
+| 6 | [Claude Code Overview](https://code.claude.com/docs/en/overview) | Official docs |
+| 7 | [Claude Code Subagents](https://code.claude.com/docs/en/sub-agents) | Official docs |
+| 8 | [Claude Code Skills](https://code.claude.com/docs/en/skills) | Official docs |
+| 9 | [Claude Code Memory](https://code.claude.com/docs/en/memory) | Official docs |
+| 10 | [Claude Code Agent SDK](https://code.claude.com/docs/en/agent-sdk/overview) | Official docs |
+| 11 | [Claude Code Settings](https://code.claude.com/docs/en/settings) | Official docs |
+| 12 | [Claude Code Permissions](https://code.claude.com/docs/en/permissions) | Official docs |
+| 13 | [Claude Code Agent Teams](https://code.claude.com/docs/en/agent-teams) | Official docs |
+| 14 | [Claude Code Quickstart](https://code.claude.com/docs/en/quickstart) | Official docs |
+| 15 | [Bolt.new GitHub](https://github.com/stackblitz/bolt.new) | GitHub |
+| 16 | [Bolt.diy GitHub](https://github.com/stackblitz-labs/bolt.diy) | GitHub |
+| 17 | [Sweep GitHub](https://github.com/sweepai/sweep) | GitHub |
+| 18 | [Dispatch Requirements](file:///home/tradam/projects/dispatch/requirements.md) | Project file |
+
+---
+
+## Verbatim quotes
+
+- "This repository is no longer maintained and has been archived for provenance. The project has continued under the name Crush" — [OpenCode GitHub](https://github.com/opencode-ai/opencode)
+- "Claude Code is an agentic coding tool that lives in your terminal, understands your codebase, and helps you code faster" — [Claude Code GitHub](https://github.com/anthropics/claude-code)
+- "Subagents are specialized AI assistants that handle specific types of tasks." — [Claude Code Subagents](https://code.claude.com/docs/en/sub-agents)
+- "Agent teams let you coordinate multiple Claude Code instances working together. One session acts as the team lead, coordinating work, assigning tasks, and synthesizing results." — [Claude Code Agent Teams](https://code.claude.com/docs/en/agent-teams)
+- "Plandex is a terminal-based AI development tool that can plan and execute large coding tasks that span many steps and touch dozens of files." — [Plandex GitHub](https://github.com/plandex-ai/plandex)
+- "Crush: Your new coding bestie, now available in your favourite terminal. Your tools, your code, and your workflows, wired into your LLM of choice." — [Crush GitHub](https://github.com/charmbracelet/crush)
+- "Claude Code supports fine-grained permissions so that you can specify exactly what the agent is allowed to do and what it cannot." — [Claude Code Permissions](https://code.claude.com/docs/en/permissions)
+- "GPT-Engineer lets you: Specify software in natural language, Sit back and watch as an AI writes and executes the code" — [GPT-Engineer GitHub](https://github.com/AntonOsika/gpt-engineer)
+- "This repository was archived by the owner on Apr 22, 2026. It is now read-only." — [GPT-Engineer GitHub](https://github.com/AntonOsika/gpt-engineer)
+- "Skills extend what Claude can do. Create a SKILL.md file with instructions, and Claude adds it to its toolkit." — [Claude Code Skills](https://code.claude.com/docs/en/skills)
+
+---
+
+## Source quality flags
+
+- **Claude Code GitHub repo**: The repository (anthropics/claude-code) contains primarily installer scripts, plugins, examples, and documentation — the core agent engine is proprietary and distributed via `npm`/native installers. The docs at code.claude.com are official Anthropic documentation.
+- **GPT-Engineer**: Archived project; README explicitly directs users to other tools. Not representative of current capabilities.
+- **Sweep**: README indicates project has pivoted to a JetBrains plugin. The open source repo is not actively maintained for the use case being evaluated.
+
+---
+
+## Confidence: High
+
+All major frameworks evaluated have been thoroughly researched via their GitHub repositories and official documentation. Claude Code's proprietary core limits the depth of source-code-level analysis, but its public documentation is extensive and detailed. Frameworks like Kortix Suna and Devon could not be located, indicating they may have been renamed, deprecated, or are not publicly available under those names.
+
+---
+
+## Gaps and open questions
+
+1. **Kortix Suna** and **Devon** could not be located — they may exist under different GitHub orgs, have been renamed, or are not publicly available.
+2. **Claude Code's core architecture** is not fully open source; the proprietary agent engine cannot be audited for architectural details. The analysis is based on publicly documented behavior.
+3. **Claude Code's Agent SDK** (released mid-2025) represents a new category — it provides programmatic access to Claude Code's agent loop. A follow-up investigation could evaluate it as an alternative to building from scratch.
+4. **Crush** is still in v0.x (v0.70.0) — some features like hooks are marked as "preliminary" and may not be production-ready.
+5. **None of the evaluated frameworks** provide a native three-layer (dispatch→orchestrator→subagent) architecture. This appears to be a novel design not present in existing open-source tools.
+
+---
+
+## Tool calls made
+
+Web fetches: 20 (OpenCode, Plandex, GPT-Engineer, Claude Code, Sweep, Devon attempted, Bolt.new, Plandex docs, Claude Code overview, sub-agents, skills, memory, Agent SDK, settings, permissions, agent teams, quickstart, Crush, Bolt.diy, Sweep README)
diff --git a/research/general-agent-platforms.md b/research/general-agent-platforms.md
new file mode 100644
index 0000000..a7f1bd4
--- /dev/null
+++ b/research/general-agent-platforms.md
@@ -0,0 +1,299 @@
+# Subagent Report: General Agent Platforms (SuperAGI, Agency Swarm, Semantic Kernel)
+
+## Research summary
+
+This report evaluates three open-source general AI agent platforms — **SuperAGI**, **Agency Swarm**, and **Microsoft Semantic Kernel** — against 15 specific requirements for an agent harness architecture. SuperAGI is a single-agent autonomous agent framework with a GUI and tool marketplace, not designed for hierarchical multi-agent orchestration. Agency Swarm is a Python multi-agent orchestration framework built on the OpenAI Agents SDK, supporting structured communication flows with parallel execution. Microsoft Semantic Kernel is an enterprise-grade SDK (C#/Python/Java) for building AI agents and multi-agent systems, now succeeded by Microsoft Agent Framework. None of the three fully support all 15 requirements; Agency Swarm comes closest for hierarchical orchestration needs, while Semantic Kernel provides the richest SDK for plugin-extensible agent systems.
+
+**Confidence**: High — all findings are based on official GitHub repositories, documentation sites, and framework README files.
+
+---
+
+## Findings
+
+### 1. SuperAGI
+
+#### Core Architecture
+
+SuperAGI is a **dev-first open-source autonomous AI agent framework** written in Python. It is designed for building, managing, and running single autonomous agents with tool augmentation. The architecture uses a Celery task queue, PostgreSQL database, Redis for caching, and multiple vector store options (Pinecone, Weaviate, Chroma, Qdrant). Agents operate as independent ReAct (Reasoning + Acting) loops with goals, instructions, constraints, and tools.
+
+- **Language**: Python
+- **GitHub stars**: ~17.5k, 2,342 commits
+- **Latest release**: v0.0.11 (appears stale — README states "Under Development!")
+- **Primary use case**: Running individual autonomous agents with tool augmentation
+- **Architecture diagrams** show a single agent workflow with tool integration, not multi-agent hierarchy
+
+Key architectural components visible in the codebase:
+- `superagi/agent/` — single-agent execution loop with prompt building, tool execution, task queue
+- `config_template.yaml` — YAML-based configuration for system settings (API keys, model, DB, storage)
+- `gui/` — React-based graphical user interface
+- `tools.json` — tool definitions
+
+[Source: SuperAGI GitHub README](https://github.com/TransformerOptimus/SuperAGI)
+[Source: SuperAGI config_template.yaml](https://raw.githubusercontent.com/TransformerOptimus/SuperAGI/main/config_template.yaml)
+
+#### Requirements Checklist
+
+| # | Requirement | Status | Explanation |
+|---|-------------|--------|-------------|
+| 1 | **Three-layer hierarchy** | **Not supported** | SuperAGI is a single-agent framework. No orchestrator-subagent hierarchy exists. Multiple agents can run but are independent. |
+| 2 | **Config-driven orchestrators** | **Not supported** | System config (API keys, model, DB) is YAML-driven, but agent definitions, tools, and workflows are defined in Python code, not config files. |
+| 3 | **Parallel subagent execution** | **Partially supported** | README states "You can run concurrent agents seamlessly" but these are independent agent runs, not orchestrated subagents. No parent-child coordination of parallelism. |
+| 4 | **Strict hierarchy communication** | **Not supported** | No inter-agent communication at all. Each agent is independent. |
+| 5 | **User-to-agent messaging mid-execution** | **Partially supported** | "Action Console" allows user interaction by giving agents input and permissions, but this is at the GUI level, not arbitrary injection to running agents. |
+| 6 | **Conflict prevention** | **Not supported** | No mechanism for assigning non-overlapping file scopes to parallel agents. |
+| 7 | **Role-scoped tooling** | **Partially supported** | Agents can be configured with different toolkits from the marketplace, but this is done through the GUI/database, not through a code-level role system. |
+| 8 | **Skills system (markdown instructions)** | **Partially supported** | Uses prompt templates with variable substitution (`{goals}`, `{instructions}`, `{constraints}`), but not a markdown file-based skills directory system. |
+| 9 | **LSP integration** | **Not supported** | No Language Server Protocol integration. |
+| 10 | **Shell access with permissions** | **Not supported** | No built-in shell tool. Tools are pre-built plugins from the marketplace. |
+| 11 | **Session management** | **Partially supported** | "Agent Memory Storage" for learning/adaptation, "Performance Telemetry" for insights. No chat forking or model switching mid-conversation. |
+| 12 | **Human-in-the-loop checkpoints** | **Partially supported** | Action Console provides some user input/permissions, but no configurable checkpoint mechanism. |
+| 13 | **State persistence** | **Partially supported** | Uses PostgreSQL for agent state and file storage for workspace artifacts (`RESOURCES_OUTPUT_ROOT_DIR`). Sessions persist via DB. |
+| 14 | **Provider-agnostic LLM** | **Supported** | Supports OpenAI, Google PaLM, Replicate, HuggingFace. Configurable via `config_template.yaml`. Local LLM support via Text Generation Web UI. |
+| 15 | **Multiple interfaces** | **Supported** | Has GUI (React web interface), CLI (`cli2.py`), and REST API (via FastAPI/Postman docs). |
+
+---
+
+### 2. Agency Swarm
+
+#### Core Architecture
+
+Agency Swarm is a **Python multi-agent orchestration framework** built on the OpenAI Agents SDK. It organizes agents into structured communication flows that mirror real-world organizational structures. Agents are defined with roles, instructions (from markdown files), tools, and MCP server support. Communication flows are directional tuples that define which agents can message which.
+
+- **Language**: Python (97.8%), some JavaScript/TypeScript for TUI
+- **GitHub stars**: ~4.4k, 2,412 commits
+- **Latest release**: v1.9.8 (May 6, 2026 — very active)
+- **Primary use case**: Multi-agent orchestration with structured communication patterns
+- **Python**: 3.12+
+
+Key features from the documentation:
+- **Communication flows**: Directional paths using `(sender, receiver)` tuples. Supports Handoff and Orchestrator-Worker patterns.
+- **Parallel execution**: "the CEO can assign tasks to both Developer and Virtual Assistant, so they will run in parallel in different threads and come back with their results to the CEO"
+- **Per-agent configuration**: Each agent has its own `instructions.md`, `files/`, `tools/`, `schemas/` directories
+- **State persistence**: `load_threads_callback` and `save_threads_callback` for thread persistence
+- **Multiple run modes**: TUI, CopilotKit/AG-UI web interface, FastAPI HTTP API, async programmatic
+
+Key architecture code pattern:
+```python
+agency = Agency(
+ ceo, dev, # Entry points
+ communication_flows=[
+ (ceo, dev), # Director can delegate to developer
+ (ceo, va), # Director can delegate to virtual assistant
+ ],
+ shared_instructions="./agency_manifesto.md",
+ shared_tools=[get_current_time],
+)
+```
+
+[Source: Agency Swarm GitHub README](https://github.com/VRSEN/agency-swarm)
+[Source: Agency Swarm Docs - Overview](https://agency-swarm.ai/core-framework/agencies/overview)
+[Source: Agency Swarm Docs - Communication Flows](https://agency-swarm.ai/core-framework/agencies/communication-flows)
+
+#### Requirements Checklist
+
+| # | Requirement | Status | Explanation |
+|---|-------------|--------|-------------|
+| 1 | **Three-layer hierarchy** | **Partially supported** | Supports 2-layer hierarchy (entry point orchestrator → workers). The Orchestrator-Worker pattern has an orchestrator delegating to workers. A 3rd layer would require nesting agencies or custom coding — not natively supported. |
+| 2 | **Config-driven orchestrators** | **Not supported** | Agents and communication flows are defined in Python code. Instructions can be loaded from markdown files, but the orchestration structure itself is code-defined, not config-driven. |
+| 3 | **Parallel subagent execution** | **Supported** | Explicitly supported: "the CEO can assign tasks to both Developer and Virtual Assistant, so they will run in parallel in different threads and come back with their results to the CEO." [Source](https://agency-swarm.ai/core-framework/agencies/communication-flows) |
+| 4 | **Strict hierarchy communication** | **Supported** | Communication flows are directional tuples. By defining only parent→child flows and omitting peer flows, communication is restricted to parent-child only. |
+| 5 | **User-to-agent messaging mid-execution** | **Partially supported** | TUI supports `@mentions` to direct messages to specific agents. FastAPI supports `recipient_agent` parameter. However, mid-execution injection to running agents is not documented. |
+| 6 | **Conflict prevention** | **Not supported** | No built-in mechanism for assigning non-overlapping file scopes to parallel agents. |
+| 7 | **Role-scoped tooling** | **Supported** | Each agent has its own `tools=[]`, `tools_folder`, `schemas_folder`. Tools are role-specific. Shared tools can be applied via `shared_tools`. |
+| 8 | **Skills system (markdown instructions)** | **Partially supported** | Instructions can be loaded from markdown files (`instructions.md`). Shared instructions via `shared_instructions` (file path). Per-agent folder structure includes `tools/`, `files/`, `schemas/`. However, it's not a directory-based skills system with global + project level override. |
+| 9 | **LSP integration** | **Not supported** | No Language Server Protocol integration. |
+| 10 | **Shell access with permissions** | **Not supported** | No native shell execution tool. Relies on OpenAI function calling for tool execution. |
+| 11 | **Session management** | **Partially supported** | Thread persistence via `load_threads_callback`/`save_threads_callback`. `/cost` command in TUI. No chat forking or model switching mid-conversation documented. |
+| 12 | **Human-in-the-loop checkpoints** | **Partially supported** | Has input and output guardrails that can check/modify messages. No explicit configurable checkpoint mechanism for pausing execution. |
+| 13 | **State persistence** | **Partially supported** | Thread state can be persisted via callbacks. User context accessible by agents. No built-in plan/artifact persistence mechanism. |
+| 14 | **Provider-agnostic LLM** | **Supported** | Native OpenAI (GPT-5, GPT-4o). Via LiteLLM router: Anthropic, Google Gemini, Grok, Azure OpenAI, OpenRouter. |
+| 15 | **Multiple interfaces** | **Supported** | TUI (`agency.tui()`, `npx @vrsen/agentswarm`), CopilotKit/AG-UI web interface (`agency.copilot_demo()`), FastAPI HTTP API, async/sync programmatic API. |
+
+---
+
+### 3. Microsoft Semantic Kernel
+
+#### Core Architecture
+
+Semantic Kernel is an **enterprise-grade SDK** for integrating LLMs into applications and building AI agents and multi-agent systems. It is written in C# (primary), Python, and Java. The kernel acts as a Dependency Injection container that manages AI services and plugins. The Agent Framework (within SK) provides agent types and orchestration patterns. **Important**: Semantic Kernel has been succeeded by **Microsoft Agent Framework** (MAF), which is the enterprise-ready successor at v1.0.
+
+- **Languages**: C# (66.8%), Python (31.3%), Java
+- **GitHub stars**: ~27.9k, 4,991 commits
+- **Latest release**: python-1.42.0 (May 14, 2026 — very active)
+- **Primary use case**: Building AI-powered applications with LLM integration, plugin extensibility, and multi-agent orchestration
+- **Python**: 3.10+, .NET: .NET 10.0+, Java: JDK 17+
+
+Key architectural components:
+- **Kernel**: Central DI container holding services (AI, logging, HTTP) and plugins
+- **Plugins**: Collections of functions with semantic descriptions, supporting native code, OpenAPI, and MCP
+- **Agent Framework**: `ChatCompletionAgent`, `OpenAIAssistantAgent`, `AzureAIAgent`, `OpenAIResponsesAgent`
+- **Orchestration Framework** (experimental): `ConcurrentOrchestration`, `SequentialOrchestration`, `HandoffOrchestration`, `GroupChatOrchestration`, `MagenticOrchestration`
+
+[Source: Semantic Kernel GitHub README](https://github.com/microsoft/semantic-kernel)
+[Source: SK Agent Framework Docs](https://learn.microsoft.com/en-us/semantic-kernel/frameworks/agent/)
+[Source: SK Orchestration Docs](https://learn.microsoft.com/en-us/semantic-kernel/frameworks/agent/agent-orchestration/)
+
+#### Requirements Checklist
+
+| # | Requirement | Status | Explanation |
+|---|-------------|--------|-------------|
+| 1 | **Three-layer hierarchy** | **Not supported** | Orchestration patterns support 2 layers (orchestrator ↔ agents). GroupChat supports collaborative patterns but not a strict 3-tier dispatch→orchestrator→subagent chain. The experimental orchestration framework is flat. |
+| 2 | **Config-driven orchestrators** | **Not supported** | Semantic Kernel is code-first SDK. Orchestration patterns are instantiated via code (e.g., `ConcurrentOrchestration(members=[...])`). Prompt templates can be loaded from files but orchestration structure is not config-driven. |
+| 3 | **Parallel subagent execution** | **Supported** | `ConcurrentOrchestration` runs multiple agents in parallel on the same task. Results are collected independently. [Source](https://learn.microsoft.com/en-us/semantic-kernel/frameworks/agent/agent-orchestration/concurrent) |
+| 4 | **Strict hierarchy communication** | **Not supported** | Agents in GroupChat/Orchestration all communicate via shared history. No mechanism to restrict communication to parent-child only. |
+| 5 | **User-to-agent messaging mid-execution** | **Partially supported** | Messages can be added to `ChatHistory` at any point, but not designed for injecting into a running orchestration mid-execution. |
+| 6 | **Conflict prevention** | **Not supported** | No built-in file scope assignment mechanism. |
+| 7 | **Role-scoped tooling** | **Supported** | Each agent can be configured with different plugins/function tools. Plugins can be per-agent using the kernel's DI system. |
+| 8 | **Skills system (markdown instructions)** | **Partially supported** | Has a powerful plugin system with semantic kernel functions. Prompt templates support variable substitution. Not a markdown/text-based skills directory system with global/project override. |
+| 9 | **LSP integration** | **Not supported** | No Language Server Protocol integration. |
+| 10 | **Shell access with permissions** | **Not supported** | No built-in shell execution tool. Code execution would need a custom plugin. |
+| 11 | **Session management** | **Not supported** | No built-in session management, chat forking, or model switching. ChatHistory is in-memory. |
+| 12 | **Human-in-the-loop checkpoints** | **Partially supported** | Human-in-the-loop is mentioned for "task automation functions" but there is no explicit configurable checkpoint mechanism in the agent framework. |
+| 13 | **State persistence** | **Not supported** | No built-in state persistence. ChatHistory, plans, artifacts are in-memory. Requires custom implementation for persistence. |
+| 14 | **Provider-agnostic LLM** | **Supported** | Built-in support for OpenAI, Azure OpenAI, Hugging Face, NVIDIA. Local deployment with Ollama, LMStudio, ONNX. |
+| 15 | **Multiple interfaces** | **Not supported** | SDK only. No CLI, TUI, or built-in API server. Must build your own interfaces. |
+
+---
+
+## Comparison Summary Table
+
+| # | Requirement | SuperAGI | Agency Swarm | Semantic Kernel |
+|---|-------------|----------|--------------|-----------------|
+| 1 | Three-layer hierarchy | ❌ Not supported | ⚠️ Partial (2-layer) | ❌ Not supported |
+| 2 | Config-driven orchestrators | ❌ Not supported | ❌ Not supported | ❌ Not supported |
+| 3 | Parallel subagent execution | ⚠️ Partial (independent runs) | ✅ Supported | ✅ Supported |
+| 4 | Strict hierarchy communication | ❌ Not supported | ✅ Supported | ❌ Not supported |
+| 5 | User-to-agent messaging mid-execution | ⚠️ Partial (Action Console) | ⚠️ Partial (@mentions, recipient_agent) | ⚠️ Partial (ChatHistory injection) |
+| 6 | Conflict prevention | ❌ Not supported | ❌ Not supported | ❌ Not supported |
+| 7 | Role-scoped tooling | ⚠️ Partial (toolkit marketplace) | ✅ Supported | ✅ Supported |
+| 8 | Skills system (markdown instructions) | ⚠️ Partial (prompt templates) | ⚠️ Partial (instructions.md, shared_instructions) | ⚠️ Partial (plugin system) |
+| 9 | LSP integration | ❌ Not supported | ❌ Not supported | ❌ Not supported |
+| 10 | Shell access with permissions | ❌ Not supported | ❌ Not supported | ❌ Not supported |
+| 11 | Session management | ⚠️ Partial (memory storage, telemetry) | ⚠️ Partial (thread callbacks) | ❌ Not supported |
+| 12 | Human-in-the-loop checkpoints | ⚠️ Partial (Action Console) | ⚠️ Partial (guardrails) | ⚠️ Partial (mentioned for plugins) |
+| 13 | State persistence | ⚠️ Partial (PostgreSQL, file storage) | ⚠️ Partial (thread callbacks, user context) | ❌ Not supported |
+| 14 | Provider-agnostic LLM | ✅ Supported | ✅ Supported | ✅ Supported |
+| 15 | Multiple interfaces | ✅ Supported (GUI, CLI, API) | ✅ Supported (TUI, Web, API, programmatic) | ❌ Not supported (SDK only) |
+
+---
+
+## Key Questions Answered
+
+### 1. What is each framework's core architecture? How many layers of agent hierarchy does it support?
+
+- **SuperAGI**: Single autonomous agent architecture. No hierarchy — each agent is independent. Maximum layers: 1.
+- **Agency Swarm**: Multi-agent orchestration with directional communication flows. Supports 2 layers (entry point orchestrator → worker agents). Can be stretched by nesting but not natively supported.
+- **Semantic Kernel**: SDK for building AI agents with orchestration patterns. Supports 2 layers (orchestrator → participating agents). Orchestration framework is experimental.
+
+### 2. How extensible/configurable is each framework without modifying source code?
+
+- **SuperAGI**: System-level YAML config for API keys, models, DB. Tools via marketplace/JSON. Agent prompt templates with variable substitution. Limited config without modifying Python code.
+- **Agency Swarm**: Instructions from markdown files. Tools from folder discovery. MCP server support. Communication flows via code. No config-file-based agent definition.
+- **Semantic Kernel**: Plugin system via native code, OpenAPI specs, or MCP servers. Prompt templates. Everything requires code (C#, Python, Java).
+
+### 3. What is the primary use case each framework was designed for?
+
+- **SuperAGI**: Running individual autonomous AI agents with tool augmentation for task completion.
+- **Agency Swarm**: Multi-agent collaboration with structured organizational patterns (CEO, developer, virtual assistant roles).
+- **Semantic Kernel**: Integrating LLMs into enterprise .NET/Python applications with plugin extensibility and multi-agent orchestration.
+
+### 4. How active is each project?
+
+- **SuperAGI**: Stale. v0.0.11 release, README states "Under Development!" with 182 open issues. Community: Discord, Reddit. Creator active on Twitter.
+- **Agency Swarm**: Very active. v1.9.8 (May 6, 2026). 61 releases. 2,412 commits. Active maintainer (VRSEN). Discord community. YouTube channel.
+- **Semantic Kernel**: Very active. python-1.42.0 (May 14, 2026). 269 releases. 4,991 commits. Microsoft-maintained. Discord community. Note: now superseded by Microsoft Agent Framework.
+
+### 5. What language is each framework written in?
+
+- **SuperAGI**: Python
+- **Agency Swarm**: Python (primary), JavaScript/TypeScript (TUI UI)
+- **Semantic Kernel**: C# (primary, 66.8%), Python (31.3%), Java
+
+---
+
+## Source list
+
+| # | Source | Type |
+|---|--------|------|
+| 1 | [SuperAGI GitHub Repository](https://github.com/TransformerOptimus/SuperAGI) | GitHub / code |
+| 2 | [SuperAGI config_template.yaml](https://raw.githubusercontent.com/TransformerOptimus/SuperAGI/main/config_template.yaml) | Config file |
+| 3 | [SuperAGI agent_prompt_builder.py](https://github.com/TransformerOptimus/SuperAGI/blob/main/superagi/agent/agent_prompt_builder.py) | Source code |
+| 4 | [Agency Swarm GitHub Repository](https://github.com/VRSEN/agency-swarm) | GitHub / code |
+| 5 | [Agency Swarm Docs - Overview](https://agency-swarm.ai/core-framework/agencies/overview) | Official docs |
+| 6 | [Agency Swarm Docs - Communication Flows](https://agency-swarm.ai/core-framework/agencies/communication-flows) | Official docs |
+| 7 | [Agency Swarm Docs - Running an Agency](https://agency-swarm.ai/core-framework/agencies/running-agency) | Official docs |
+| 8 | [Agency Swarm Docs - Agents Overview](https://agency-swarm.ai/core-framework/agents/overview) | Official docs |
+| 9 | [Agency Swarm Docs - Observability](https://agency-swarm.ai/additional-features/observability) | Official docs |
+| 10 | [Agency Swarm Docs - FastAPI Integration](https://agency-swarm.ai/additional-features/fastapi-integration) | Official docs |
+| 11 | [Semantic Kernel GitHub Repository](https://github.com/microsoft/semantic-kernel) | GitHub / code |
+| 12 | [SK Agent Framework Docs](https://learn.microsoft.com/en-us/semantic-kernel/frameworks/agent/) | Official docs (Microsoft Learn) |
+| 13 | [SK Orchestration Docs](https://learn.microsoft.com/en-us/semantic-kernel/frameworks/agent/agent-orchestration/) | Official docs (Microsoft Learn) |
+| 14 | [SK Concurrent Orchestration Docs](https://learn.microsoft.com/en-us/semantic-kernel/frameworks/agent/agent-orchestration/concurrent) | Official docs (Microsoft Learn) |
+| 15 | [SK Kernel Concepts Docs](https://learn.microsoft.com/en-us/semantic-kernel/concepts/kernel) | Official docs (Microsoft Learn) |
+| 16 | [SK Plugins Docs](https://learn.microsoft.com/en-us/semantic-kernel/concepts/plugins/) | Official docs (Microsoft Learn) |
+
+---
+
+## Verbatim quotes
+
+- "A dev-first open source autonomous AI agent framework enabling developers to build, manage & run useful autonomous agents. You can run concurrent agents seamlessly" — [SuperAGI GitHub README](https://github.com/TransformerOptimus/SuperAGI)
+- "The CEO can assign tasks to both Developer and Virtual Assistant, so they will run in parallel in different threads and come back with their results to the CEO" — [Agency Swarm Docs - Communication Flows](https://agency-swarm.ai/core-framework/agencies/communication-flows)
+- "Communication flows are defined using tuples in the communication_flows parameter: (sender, receiver) defines a directional communication path" — [Agency Swarm Docs - Communication Flows](https://agency-swarm.ai/core-framework/agencies/communication-flows)
+- "Concurrent orchestration enables multiple agents to work on the same task in parallel. Each agent processes the input independently, and their results are collected and aggregated" — [SK Concurrent Orchestration Docs](https://learn.microsoft.com/en-us/semantic-kernel/frameworks/agent/agent-orchestration/concurrent)
+- "Semantic Kernel is now Microsoft Agent Framework! Microsoft Agent Framework (MAF) is the enterprise‑ready successor to Semantic Kernel" — [Semantic Kernel GitHub README](https://github.com/microsoft/semantic-kernel)
+- "Agent Orchestration features in the Agent Framework are in the experimental stage" — [SK Orchestration Docs](https://learn.microsoft.com/en-us/semantic-kernel/frameworks/agent/agent-orchestration/)
+
+---
+
+## Source quality flags
+
+- Source 3 (SuperAGI agent_prompt_builder.py): source code file, high quality for understanding architecture but shows framework is single-agent focused
+- Source 1 (SuperAGI README): marketing language — "dev-first", "build, manage & run useful autonomous agents" — but backed by code
+- Source 2 (SuperAGI config_template.yaml): primary source showing config capabilities
+- Sources 5-10 (Agency Swarm docs): official documentation, well-maintained, comprehensive
+- Sources 12-16 (SK Microsoft Learn): official Microsoft documentation, high quality
+- Source 11 (SK GitHub): official Microsoft repo, highly active
+
+---
+
+## Confidence: High
+
+All findings are based on direct examination of official GitHub repositories, official documentation sites, source code files, and configuration files. No marketing or third-party summaries were relied upon.
+
+---
+
+## Gaps and open questions
+
+1. **SuperAGI's current maintenance status**: The README says "Under Development!" and the last release tag appears to be v0.0.11, but the commit history shows recent activity. Confirming the exact current maintenance pace would require deeper investigation.
+2. **Agency Swarm's 3rd layer**: Whether nesting `Agency` instances or using custom code could achieve a 3-layer dispatch→orchestrator→subagent hierarchy is not documented. This would need experimental verification.
+3. **Semantic Kernel's successor relationship**: Microsoft Agent Framework (MAF) is now the recommended path. It's unclear how long Semantic Kernel will receive updates. MAF may address some gaps listed here.
+4. **None of the three frameworks support**: LSP integration, shell access with directory permissions, chat forking/model switching, or explicit conflict prevention. These would need custom implementation regardless of framework choice.
+5. **Config-driven orchestration**: None of the three frameworks support defining orchestrator types via config files (not code). This appears to be a gap unique to the current landscape of agent frameworks.
+
+---
+
+## Tool calls made
+
+1. `webfetch` — https://github.com/TransformerOptimus/SuperAGI (GitHub README)
+2. `webfetch` — https://github.com/VRSEN/agency-swarm (GitHub README)
+3. `webfetch` — https://github.com/microsoft/semantic-kernel (GitHub README)
+4. `webfetch` — https://superagi.com/docs/ (failed — transport error)
+5. `webfetch` — https://agency-swarm.ai/core-framework/agencies/overview
+6. `webfetch` — https://learn.microsoft.com/en-us/semantic-kernel/frameworks/agent/
+7. `webfetch` — https://agency-swarm.ai/core-framework/agencies/communication-flows
+8. `webfetch` — https://learn.microsoft.com/en-us/semantic-kernel/frameworks/agent/agent-orchestration/
+9. `webfetch` — https://superagi.com/docs/architecture/ (failed — transport error)
+10. `webfetch` — https://github.com/TransformerOptimus/SuperAGI/blob/main/README.MD
+11. `webfetch` — https://agency-swarm.ai/additional-features/observability
+12. `webfetch` — https://learn.microsoft.com/en-us/semantic-kernel/frameworks/agent/agent-orchestration/concurrent
+13. `webfetch` — https://raw.githubusercontent.com/TransformerOptimus/SuperAGI/main/config_template.yaml
+14. `webfetch` — https://agency-swarm.ai/core-framework/agents/overview
+15. `webfetch` — https://agency-swarm.ai/core-framework/agencies/running-agency
+16. `webfetch` — https://github.com/TransformerOptimus/SuperAGI/blob/main/superagi/agent/agent_prompt_builder.py
+17. `webfetch` — https://learn.microsoft.com/en-us/semantic-kernel/concepts/kernel
+18. `webfetch` — https://agency-swarm.ai/additional-features/fastapi-integration
+19. `webfetch` — https://learn.microsoft.com/en-us/semantic-kernel/concepts/plugins/
+20. `webfetch` — https://raw.githubusercontent.com/microsoft/semantic-kernel/main/FEATURE_MATRIX.md
+21. `webfetch` — https://github.com/VRSEN/agency-swarm/blob/main/AGENTS.md
+22. `webfetch` — https://github.com/TransformerOptimus/SuperAGI/tree/main/superagi/agent
+23. `webfetch` — https://learn.microsoft.com/en-us/semantic-kernel/concepts/prompt-engineering/ (failed — 404)
diff --git a/research/multi-agent-orchestration.md b/research/multi-agent-orchestration.md
new file mode 100644
index 0000000..6c5c5f5
--- /dev/null
+++ b/research/multi-agent-orchestration.md
@@ -0,0 +1,235 @@
+# Subagent Report: Multi-Agent Orchestration Frameworks
+
+## Research summary
+This report evaluates three open-source multi-agent orchestration frameworks — **CrewAI**, **Microsoft AutoGen (AG2)**, and **LangGraph** — against a 15-point requirements checklist for an AI agent harness. CrewAI is the most opinionated with a built-in hierarchy and YAML-driven configuration, LangGraph is the lowest-level graph-based orchestration runtime with the most flexible persistence model, and AutoGen is a maintenance-mode framework with a layered design and no-code GUI. No framework fully satisfies all 15 requirements; each has gaps in the areas of LSP integration, shell access with directory permissions, and skills systems as defined.
+
+---
+
+## Findings
+
+### 1. CrewAI
+
+#### Core Architecture
+
+CrewAI is an open-source Python framework for orchestrating role-based AI agents. It is built entirely from scratch — independent of LangChain. It provides two complementary paradigms: **Crews** (autonomous agent teams with role-based collaboration) and **Flows** (event-driven, stateful workflows). Version 1.14.5 (latest as of May 2026). Language: Python (99%+). Stars: ~51.7k on GitHub. Commits: ~2,414. Recent releases: 191 total, active.
+
+**Architecture layers:**
+- **Agent** — role, goal, backstory, tools, LLM config
+- **Crew** — orchestrates a team of agents with a Process (sequential, hierarchical, or hybrid)
+- **Flow** — higher-level orchestration with `@start`, `@listen`, `@router` decorators, state management, persistence
+- **Config** — YAML files for agents and tasks (recommended approach)
+
+**Primary use case:** General-purpose multi-agent automation for enterprise workflows. Not specifically focused on coding — more general business process automation.
+
+**Project status:** Very active. Backed by a company (CrewAI Inc.). 100k+ certified developers in the community.
+
+#### Requirements Checklist
+
+| # | Requirement | Status | Detail |
+|---|------------|--------|--------|
+| 1 | **Three-layer hierarchy** | **Partial** | CrewAI has a native hierarchical process where a manager agent coordinates sub-agents. However, it's a 2-level hierarchy (manager → agent), not 3-level (dispatch → orchestrator → subagent). Flows can chain multiple crews, achieving multi-level composition programmatically but not as a built-in dispatch architecture. |
+| 2 | **Config-driven orchestrators** | **Fully** | Agents and tasks are defined in YAML (`agents.yaml`, `tasks.yaml`) loaded via `@CrewBase` decorators. This is the recommended approach. Source: CrewAI docs "YAML Configuration (Recommended)" |
+| 3 | **Parallel subagent execution** | **Fully** | Tasks support `async_execution=True` for parallel execution. Multiple tasks can run concurrently when using the context mechanism for dependency management. Source: CrewAI Tasks docs on Asynchronous Execution |
+| 4 | **Strict hierarchy communication** | **Partial** | The hierarchical process assigns a manager that delegates tasks and validates results, providing structured parent-child communication. However, there is no built-in mechanism to prevent peer-to-peer agent messaging when delegation is enabled (`allow_delegation`). |
+| 5 | **User-to-agent messaging mid-execution** | **Partial** | CrewAI supports `@human_feedback` decorator (v1.8.0+) for human-in-the-loop in Flows, and `human_input=True` on tasks. However, this is for configured approval points, not arbitrary mid-execution injection to any running agent. |
+| 6 | **Conflict prevention** | **Not at all** | No built-in mechanism for assigning non-overlapping file scopes to parallel agents. Code execution is deprecated (uses external sandboxes like E2B). |
+| 7 | **Role-scoped tooling** | **Fully** | Agents can have different tool sets based on role. Tools are assigned per-agent via the `tools` parameter. Tasks can also override tools. Source: CrewAI Agents documentation on tools |
+| 8 | **Skills system** | **Partial** | Supports custom system templates, prompt templates, and response templates per agent. The project template uses `agents.yaml` for defining agent behaviors. Has a "skills" feature via MCP/skills.sh for AI coding assistants, but no directory-based markdown instruction system for agent definition. |
+| 9 | **LSP integration** | **Not at all** | No Language Server Protocol integration for compiler diagnostics. |
+| 10 | **Shell access with directory permissions** | **Not at all** | Code execution is deprecated in favor of external sandboxes. No shell access with permission controls. |
+| 11 | **Session management** | **Partial** | Flows support `@persist` decorator for state persistence across restarts using SQLite. Supports "fork" via `restore_from_state_id`. No chat forking or model switching mid-conversation in the traditional sense. |
+| 12 | **Human-in-the-loop checkpoints** | **Fully** | `@human_feedback` decorator on Flow methods pauses execution and collects feedback. `human_input=True` on tasks enables human review. Source: CrewAI Flows docs on human_feedback |
+| 13 | **State persistence** | **Fully** | `@persist` decorator on Flows persists state to SQLite automatically. Supports resume and fork patterns. Source: CrewAI Flows persistence docs |
+| 14 | **Provider-agnostic LLM** | **Fully** | Supports OpenAI, Azure, Anthropic, Ollama, Gemini, and many more via LiteLLM integration. The `llm` parameter accepts model strings or `LLM` instances. Source: CrewAI docs "Connecting Your Crew to a Model" |
+| 15 | **Multiple interfaces** | **Partial** | Has a CLI (`crewai create`, `crewai run`, `crewai flow kickoff`) and a Python API. No native TUI or API server in the open-source version (CrewAI AMP provides enterprise management console). |
+
+---
+
+### 2. Microsoft AutoGen (AG2)
+
+#### Core Architecture
+
+AutoGen is a Python framework for building multi-agent AI applications. Developed by Microsoft Research. Currently in **maintenance mode** — no new features, community-managed only. Latest release: `python-v0.7.5` (Sep 2025). Stars: ~58.2k. Forks: ~8.8k. Commits: ~3,782. Microsoft recommends migrating to **Microsoft Agent Framework** for new projects.
+
+**Architecture layers (3-tier design):**
+- **Core API** — message passing, event-driven agents, distributed runtime, cross-language (Python + .NET)
+- **AgentChat API** — higher-level opinionated API for rapid prototyping with agents, teams, group chats
+- **Extensions API** — model clients, tools, code execution backends
+
+**Team patterns:** RoundRobinGroupChat, SelectorGroupChat, Swarm, MagenticOneGroupChat, GraphFlow
+
+**Primary use case:** Conversational multi-agent AI applications, research, prototyping. Magentic-One for web/file tasks.
+
+**Project status:** Maintenance mode. No new features. Users directed to Microsoft Agent Framework.
+
+#### Requirements Checklist
+
+| # | Requirement | Status | Detail |
+|---|------------|--------|--------|
+| 1 | **Three-layer hierarchy** | **Partial** | AutoGen has a flat agent model with "teams" orchestrating agents. It supports Swarm and SelectorGroupChat for multi-agent coordination, and GraphFlow for workflows. No native 3-layer dispatch → orchestrator → subagent hierarchy exists. Subordination must be implemented manually via `AgentTool` wrapping. |
+| 2 | **Config-driven orchestrators** | **Partial** | AutoGen Studio provides a no-code GUI for prototyping. The framework itself is code-first — teams, agents, and termination conditions are defined in Python code. Component serialization exists (`.dump_component()`) but is not a YAML-based config system. |
+| 3 | **Parallel subagent execution** | **Partial** | The `AgentTool` pattern allows one agent to call another as a tool, but this is sequential delegation, not parallel execution. RoundRobinGroupChat is sequential (turn-based). No native parallel agent execution within a team. |
+| 4 | **Strict hierarchy communication** | **Partial** | In SelectorGroupChat and Swarm, speaker selection is controlled. `AgentTool` wrapping creates a tool-call boundary. However, no strict parent-child communication restriction mechanism is built in. |
+| 5 | **User-to-agent messaging mid-execution** | **Fully** | `UserProxyAgent` allows injecting user input during team execution (blocking). `ExternalTermination` can stop teams mid-execution. `HandoffTermination` enables handoff to user. Source: AutoGen Human-in-the-Loop docs |
+| 6 | **Conflict prevention** | **Not at all** | No built-in mechanism for non-overlapping file scopes in parallel agents. |
+| 7 | **Role-scoped tooling** | **Fully** | Each `AssistantAgent` can have its own set of tools. Agent descriptions define their role for the selector. Source: AutoGen AgentChat docs on agents and tools |
+| 8 | **Skills system** | **Not at all** | No skills system for injecting markdown/text instructions per agent type. Agents are configured via `system_message` string and `description` string in code. |
+| 9 | **LSP integration** | **Not at all** | No Language Server Protocol integration. |
+| 10 | **Shell access with directory permissions** | **Not at all** | Code execution requires external sandboxes or MCP tools. No built-in shell access with permissions. |
+| 11 | **Session management** | **Partial** | Teams support `save_state()` and `load_state()` for persisting conversation state. State can be serialized to JSON. No chat forking or model switching mid-conversation. |
+| 12 | **Human-in-the-loop checkpoints** | **Fully** | `UserProxyAgent` for inline feedback, `HandoffTermination` for async feedback, `max_turns` for turn-based pausing. Source: AutoGen Human-in-the-Loop tutorial |
+| 13 | **State persistence** | **Fully** | `save_state()` / `load_state()` on agents and teams. State dictionaries can be serialized to file or database. Source: AutoGen Managing State docs |
+| 14 | **Provider-agnostic LLM** | **Fully** | Supports OpenAI, Azure OpenAI, Azure AI Foundry, Anthropic (experimental), Ollama (experimental), Gemini (via API), Llama API, plus Semantic Kernel adapter for even more providers. Source: AutoGen Models docs |
+| 15 | **Multiple interfaces** | **Fully** | Python API, CLI (`autogenstudio ui`), AutoGen Studio (no-code GUI web app), and FastAPI/ChainLit/Streamlit integration samples. Source: AutoGen README and FastAPI sample |
+
+---
+
+### 3. LangGraph
+
+#### Core Architecture
+
+LangGraph is a low-level orchestration framework for building stateful, long-running agents. Developed by LangChain Inc. Built as a graph-based runtime inspired by Google's Pregel and Apache Beam. Latest release: `langgraph==1.2.0` (May 2026). Stars: ~32.4k. Commits: ~6,862. Active development (534 releases total).
+
+**Architecture:**
+- **StateGraph** — defines state schema (TypedDict/Pydantic), nodes (functions), edges (conditional/static)
+- **Subgraphs** — graphs used as nodes in other graphs (supports multi-agent patterns)
+- **Checkpointer** — persistence layer for state snapshots at every super-step
+- **Store** — cross-thread memory for long-term knowledge
+- **Persistence modes:** per-invocation (default), per-thread, stateless
+
+**Primary use case:** Low-level agent orchestration for complex, stateful, long-running workflows. Used by Klarna, Replit, Elastic, Uber, J.P. Morgan. Higher-level abstraction available via Deep Agents and LangChain agents.
+
+**Project status:** Very active. Backed by LangChain Inc with commercial LangSmith platform.
+
+#### Requirements Checklist
+
+| # | Requirement | Status | Detail |
+|---|------------|--------|--------|
+| 1 | **Three-layer hierarchy** | **Fully** | LangGraph's subgraph architecture supports arbitrary nesting. A parent graph can contain subgraphs, which can contain further subgraphs. `Command(goto=..., graph=Command.PARENT)` enables navigation between levels. Each level has its own state schema. Source: LangGraph Subgraphs documentation |
+| 2 | **Config-driven orchestrators** | **Not at all** | LangGraph is purely code-defined — graphs, nodes, edges, and state schemas are all Python code. No YAML or config file support for defining orchestrator types. LangSmith Studio provides a UI but generates code. |
+| 3 | **Parallel subagent execution** | **Fully** | Multiple outgoing edges from a single node execute in parallel (same super-step). `Send()` API enables map-reduce patterns with dynamic fan-out. Subgraphs can run in parallel. Source: LangGraph Graph API docs on edges and Send |
+| 4 | **Strict hierarchy communication** | **Fully** | Subgraphs can have private state schemas invisible to the parent graph. When a subgraph is invoked via a node function, the parent only sees what the node function returns. State isolation is achieved via separate state schemas. Source: LangGraph Subgraphs docs on different state schemas |
+| 5 | **User-to-agent messaging mid-execution** | **Fully** | `interrupt()` function pauses graph execution and returns control to the caller. The caller can inspect state and resume with `Command(resume=...)`. Supports multiple simultaneous interrupts. Source: LangGraph Interrupts documentation |
+| 6 | **Conflict prevention** | **Not at all** | No built-in mechanism for file scope conflict prevention. |
+| 7 | **Role-scoped tooling** | **Fully** | Each agent node can have its own set of tools. In multi-agent patterns, subgraphs/agents have independent tool configurations. Tools are LangChain-compatible. |
+| 8 | **Skills system** | **Not at all** | No skills system for injecting markdown instructions per agent type. No directory-based instruction organization. Agents use system prompts defined in code. |
+| 9 | **LSP integration** | **Not at all** | No Language Server Protocol integration. |
+| 10 | **Shell access with directory permissions** | **Not at all** | No built-in shell access. Code execution relies on external tools or LangChain tool integrations. |
+| 11 | **Session management** | **Partial** | Checkpointer-based threads provide conversation history via `get_state_history()`. Time-travel debugging via replay from checkpoints. `update_state()` for editing state. No explicit chat forking or model switching mid-conversation. |
+| 12 | **Human-in-the-loop checkpoints** | **Fully** | `interrupt()` function for dynamic pausing. Static breakpoints via `interrupt_before`/`interrupt_after` at compile time. Supports approval workflows, review-and-edit, and validation loops. Source: LangGraph Interrupts documentation |
+| 13 | **State persistence** | **Fully** | Multiple checkpointers: InMemorySaver, SqliteSaver, PostgresSaver, Azure CosmosDB. Checkpoints at every super-step. Cross-thread memory via Store. Encryption support. Source: LangGraph Persistence documentation |
+| 14 | **Provider-agnostic LLM** | **Fully** | LangGraph can use any LangChain-compatible model provider (OpenAI, Anthropic, Google, Ollama, AWS Bedrock, Azure, etc.) plus standalone models without LangChain. The `Runtime` context can pass model configuration. |
+| 15 | **Multiple interfaces** | **Partial** | Python API primarily. LangSmith Studio for visual prototyping. LangGraph API for deployment. No native CLI or TUI. Deep Agents SDK provides a higher-level interface. |
+
+---
+
+## Summary Comparison Table
+
+| # | Requirement | CrewAI | AutoGen (AG2) | LangGraph |
+|---|------------|--------|---------------|-----------|
+| 1 | **Three-layer hierarchy** | Partial (2-level natively, chaining via Flows) | Partial (flat agent teams, Swarm/GraphFlow) | **Fully** (arbitrary nesting via subgraphs) |
+| 2 | **Config-driven orchestrators** | **Fully** (YAML agents.yaml/tasks.yaml) | Partial (code-first, Studio GUI, component serialization) | Not at all (purely code-defined) |
+| 3 | **Parallel subagent execution** | **Fully** (async_execution tasks) | Partial (sequential team patterns, AgentTool is blocking) | **Fully** (Send API, parallel edges, map-reduce) |
+| 4 | **Strict hierarchy communication** | Partial (manager delegates but no P2P prevention) | Partial (SelectorGroupChat controls turns, AgentTool boundary) | **Fully** (private state schemas, subgraph isolation) |
+| 5 | **User-to-agent messaging mid-execution** | Partial (@human_feedback at configured points) | **Fully** (UserProxyAgent, ExternalTermination, HandoffTermination) | **Fully** (interrupt()/Command(resume=...) anywhere) |
+| 6 | **Conflict prevention** | Not at all | Not at all | Not at all |
+| 7 | **Role-scoped tooling** | **Fully** (per-agent tools, task override) | **Fully** (per-agent tools) | **Fully** (per-node tools, LangChain-compatible) |
+| 8 | **Skills system** | Partial (agent templates, prompt customization) | Not at all | Not at all |
+| 9 | **LSP integration** | Not at all | Not at all | Not at all |
+| 10 | **Shell access with directory permissions** | Not at all | Not at all | Not at all |
+| 11 | **Session management** | Partial (Flow persist/fork) | Partial (save_state/load_state, JSON serialization) | Partial (checkpointer history, time travel, update_state) |
+| 12 | **Human-in-the-loop checkpoints** | **Fully** (@human_feedback, human_input on tasks) | **Fully** (UserProxyAgent, HandoffTermination, max_turns) | **Fully** (interrupt(), static breakpoints, approval patterns) |
+| 13 | **State persistence** | **Fully** (@persist with SQLite, resume/fork) | **Fully** (save_state/load_state to file or DB) | **Fully** (checkpointers: Memory, SQLite, Postgres, CosmosDB) |
+| 14 | **Provider-agnostic LLM** | **Fully** (many providers via LiteLLM) | **Fully** (many providers via Extensions API + SK adapter) | **Fully** (all LangChain providers, plus standalone mode) |
+| 15 | **Multiple interfaces** | Partial (CLI + Python API, AMP enterprise console) | **Fully** (Python API, CLI, Studio web GUI, FastAPI/Streamlit) | Partial (Python API, LangSmith Studio, LangGraph API) |
+
+---
+
+## Overall Assessment
+
+### CrewAI
+**Strength:** Most opinionated framework for role-based agents with YAML-driven configuration. Excellent for enterprise automation where you want to define agents declaratively. Strong community (100k+ certified developers), active development, and a commercial offering (CrewAI AMP).
+
+**Key Gaps for this harness:** No LSP, no shell permissions, no 3-layer dispatch hierarchy natively, limited mid-execution user injection.
+
+### AutoGen (AG2)
+**Strength:** Best GUI/studio support for prototyping. Most flexible human-in-the-loop with `UserProxyAgent` and handoff patterns. Multiple interface options (Python, CLI, Studio web app). Provider-agnostic model support with Semantic Kernel adapter.
+
+**Key Gaps:** **Maintenance mode** — no new features, users directed to Microsoft Agent Framework. No parallel execution, no config-driven setup, flat agent model.
+
+### LangGraph
+**Strength:** Most architecturally flexible — arbitrary graph topologies, arbitrary subgraph nesting, the richest persistence model (multiple checkpointers + cross-thread store), durable execution, and the most sophisticated interrupt system. Best for complex, long-running, stateful workflows.
+
+**Key Gaps:** Code-only configuration (no YAML), no built-in skills system, no CLI/TUI, steeper learning curve due to low-level nature. No file scope conflict prevention.
+
+---
+
+## Source list
+
+| # | Source | Type |
+|---|--------|------|
+| 1 | [CrewAI GitHub Repository](https://github.com/crewAIInc/crewAI) | official |
+| 2 | [CrewAI Documentation - Agents](https://docs.crewai.com/concepts/agents) | official |
+| 3 | [CrewAI Documentation - Tasks](https://docs.crewai.com/concepts/tasks) | official |
+| 4 | [CrewAI Documentation - Flows](https://docs.crewai.com/concepts/flows) | official |
+| 5 | [CrewAI Documentation - Memory](https://docs.crewai.com/concepts/memory) | official |
+| 6 | [CrewAI Documentation - Processes](https://docs.crewai.com/core-concepts/Processes/) | official |
+| 7 | [AutoGen GitHub Repository](https://github.com/microsoft/autogen) | official |
+| 8 | [AutoGen Documentation - Teams](https://microsoft.github.io/autogen/stable/user-guide/agentchat-user-guide/tutorial/teams.html) | official |
+| 9 | [AutoGen Documentation - Human-in-the-Loop](https://microsoft.github.io/autogen/stable/user-guide/agentchat-user-guide/tutorial/human-in-the-loop.html) | official |
+| 10 | [AutoGen Documentation - Managing State](https://microsoft.github.io/autogen/stable/user-guide/agentchat-user-guide/tutorial/state.html) | official |
+| 11 | [AutoGen Documentation - Models](https://microsoft.github.io/autogen/stable/user-guide/agentchat-user-guide/tutorial/models.html) | official |
+| 12 | [AutoGen Documentation - Selector Group Chat](https://microsoft.github.io/autogen/stable/user-guide/agentchat-user-guide/selector-group-chat.html) | official |
+| 13 | [LangGraph GitHub Repository](https://github.com/langchain-ai/langgraph) | official |
+| 14 | [LangGraph Documentation - Overview](https://docs.langchain.com/oss/python/langgraph/overview) | official |
+| 15 | [LangGraph Documentation - Graph API](https://docs.langchain.com/oss/python/langgraph/graph-api) | official |
+| 16 | [LangGraph Documentation - Subgraphs](https://docs.langchain.com/oss/python/langgraph/use-subgraphs) | official |
+| 17 | [LangGraph Documentation - Interrupts](https://docs.langchain.com/oss/python/langgraph/interrupts) | official |
+| 18 | [LangGraph Documentation - Persistence](https://docs.langchain.com/oss/python/langgraph/persistence) | official |
+
+---
+
+## Verbatim quotes
+
+- "CrewAI is a lean, lightning-fast Python framework built entirely from scratch—completely independent of LangChain or other agent frameworks." — [CrewAI GitHub README](https://github.com/crewAIInc/crewAI)
+- "Using YAML configuration provides a cleaner, more maintainable way to define agents. We strongly recommend using this approach in your CrewAI projects." — [CrewAI Agents docs](https://docs.crewai.com/concepts/agents)
+- "AutoGen is now in maintenance mode. It will not receive new features or enhancements and is community managed going forward." — [AutoGen GitHub README](https://github.com/microsoft/autogen)
+- "New users should start with Microsoft Agent Framework. Existing users are encouraged to migrate." — [AutoGen GitHub README](https://github.com/microsoft/autogen)
+- "LangGraph is a low-level orchestration framework for building, managing, and deploying long-running, stateful agents." — [LangGraph GitHub README](https://github.com/langchain-ai/langgraph)
+- "Subgraphs are useful for building multi-agent systems, reusing a set of nodes in multiple graphs, and distributing development." — [LangGraph Subgraphs docs](https://docs.langchain.com/oss/python/langgraph/use-subgraphs)
+- "Interrupts allow you to pause graph execution at specific points and wait for external input before continuing." — [LangGraph Interrupts docs](https://docs.langchain.com/oss/python/langgraph/interrupts)
+- "UserProxyAgent is a special built-in agent that acts as a proxy for a user to provide feedback to the team." — [AutoGen HITL docs](https://microsoft.github.io/autogen/stable/user-guide/agentchat-user-guide/tutorial/human-in-the-loop.html)
+
+---
+
+## Source quality flags
+
+- No significant quality issues found. All three sources are primary (official GitHub repositories, official documentation sites).
+- AutoGen's documentation is comprehensive but the project is in maintenance mode.
+
+---
+
+## Confidence: High
+
+All claims are sourced from official GitHub repositories and official documentation sites for each framework (CrewAI, AutoGen, LangGraph). The information reflects the current state as of May 2026.
+
+## Gaps and open questions
+
+- **LSP integration**: None of the three frameworks support Language Server Protocol integration. This would need to be built as a custom extension for any chosen framework.
+- **Shell access with directory permissions**: None support this natively. Shell access would require wrapping shell tools with permission checks manually in any framework.
+- **Skills system**: Only CrewAI has anything close (prompt templates, agent configuration files), but none have the directory-based markdown instruction system described in the requirements.
+- **Conflict prevention for file scopes**: No framework has built-in mechanisms for this. Would need to be implemented as a custom tool wrapper or middleware.
+- **Session management (chat forking, model switching)**: All three have basic state persistence but not the full session management features (chat forking, model switching mid-conversation).
+- **CrewAI's Allow Delegation**: When `allow_delegation=True`, agents can delegate tasks to other agents, which could include peer agents — creating implicit P2P communication. The strictness of hierarchy enforcement depends on configuration.
+- **AutoGen's successor**: Microsoft Agent Framework (MAF) is the recommended successor and may address many gaps, but it was outside this research scope.
+
+---
+
+## Tool calls made
+
+1. `webfetch` - 3 GitHub README pages (CrewAI, AutoGen, LangGraph)
+2. `webfetch` - CrewAI Processes page, Crews page
+3. `webfetch` - LangGraph overview, AutoGen tutorial, CrewAI agents, tasks
+4. `webfetch` - CrewAI flows, AutoGen HITL, LangGraph interrupts, LangGraph subgraphs (404)
+5. `webfetch` - LangGraph persistence, AutoGen state management, AutoGen selector chat, LangGraph graph API
+6. `webfetch` - LangGraph subgraphs, CrewAI memory, AutoGen models
diff --git a/research/multi-agent-roleplay.md b/research/multi-agent-roleplay.md
new file mode 100644
index 0000000..72686a7
--- /dev/null
+++ b/research/multi-agent-roleplay.md
@@ -0,0 +1,439 @@
+# Subagent Report: Multi-Agent Role-Playing Frameworks (MetaGPT, ChatDev, CAMEL)
+
+## Research summary
+
+This report evaluates three open-source multi-agent simulation/role-playing frameworks — **MetaGPT**, **ChatDev**, and **CAMEL** — against 15 specific AI agent harness requirements. MetaGPT is the most mature for software-development workflows (68.1k stars) but has a flat role-based architecture, not a deep hierarchy. ChatDev 2.0 (released Jan 2026) has pivoted to a zero-code YAML-driven DAG orchestration platform with strong config-driven design and parallel execution. CAMEL is the most general-purpose research framework (17k stars, 210+ releases) with the broadest model provider support and excellent tooling abstractions, but lacks strict hierarchy enforcement and production-oriented features like session management or shell permissions. None of the three frameworks fully satisfy all 15 requirements; each has distinct gaps in hierarchy depth, user-to-agent messaging, LSP integration, and shell permission controls.
+
+---
+
+## Findings
+
+### 1. MetaGPT
+
+#### Core Architecture
+
+MetaGPT is a Python-based multi-agent framework designed to simulate a software company. Its core philosophy is `Code = SOP(Team)` — Standard Operating Procedures materialized into a team of LLM-based agents. The architecture has three main abstractions:
+
+- **Role**: An agent with specific actions, memory, and watch/observe patterns
+- **Action**: A discrete unit of work (LLM-powered or code-only)
+- **Team**: A collection of roles operating within an Environment
+
+Agents communicate by publishing messages to the Environment, which other agents observe. The communication model is a publish-subscribe pattern (agents publish to the environment, others observe based on watched Action types).
+
+**Key metrics**: 68.1k stars, 8.7k forks, 6,367 commits, Python 3.9+ (Python 97.5%). Latest release v0.8.1 (April 2024). The project has been relatively quiet in terms of releases since April 2024, though the repo continues to receive commits. The team has shifted focus to MGX ([mgx.dev](https://mgx.dev/)) — a commercial natural language programming product.
+
+[Source: GitHub README](https://github.com/geekan/MetaGPT)
+
+#### Hierarchy Depth
+
+MetaGPT supports a **flat role pool** within a single Team/Environment. Agents are peers — there is no orchestrator/subagent distinction. The `Team` class manages a set of roles, but it does not support recursive nesting (a sub-team within a team). Communication is broadcast-based: all messages go to the Environment, and roles subscribe to specific message types via `_watch()`. There is no built-in three-layer hierarchy.
+
+[Source: MultiAgent 101](https://docs.deepwisdom.ai/main/en/guide/tutorials/multi_agent_101.html)
+
+#### Config System
+
+MetaGPT has a YAML config file (`~/.metagpt/config2.yaml`) that supports:
+- LLM provider settings (api_type, base_url, api_key, model)
+- Per-role LLM overrides
+- Search, browser, Redis, S3 configurations
+- Experience pool and memory settings
+
+However, orchestrator types, agent behaviors, and workflow logic are **defined in Python code**, not in configuration files. The config covers infrastructure and LLM settings but not orchestration logic.
+
+[Source: config2.example.yaml](https://github.com/geekan/MetaGPT/blob/main/config/config2.example.yaml)
+
+#### Parallel Execution
+
+MetaGPT's `Team` executes roles **sequentially in a round-robin fashion**. The `n_round` parameter controls how many rounds of interaction occur. There is no built-in parallel subagent execution — agents take turns within each round. The async implementation (`asyncio`) exists in the codebase but is used for single-agent action execution, not parallel multi-agent execution.
+
+[Source: MultiAgent 101](https://docs.deepwisdom.ai/main/en/guide/tutorials/multi_agent_101.html)
+
+#### Communication Restrictions
+
+MetaGPT uses a **broadcast-based** communication model. When a Role publishes a message, it goes to all agents via the Environment. There is a `send_to` field on messages, but the default sends to `["<all>"]`. There is no built-in mechanism to enforce that subagents only talk to their parent orchestrator — all agents observe all messages.
+
+[Source: Agent communication](https://docs.deepwisdom.ai/main/en/guide/in_depth_guides/agent_communication.html)
+
+#### User-to-Agent Messaging Mid-Execution
+
+MetaGPT supports **human engagement** via setting `is_human=True` on a Role. This causes the terminal to prompt for user input when it's that role's turn. However, this is a **replacement** of an agent role with a human, not arbitrary message injection to any running agent at any time. The human takes over a specific role in the SOP.
+
+[Source: Human Engagement](https://docs.deepwisdom.ai/main/en/guide/tutorials/human_engagement.html)
+
+#### Conflict Prevention (File Scopes)
+
+MetaGPT has **no built-in mechanism** for assigning non-overlapping file scopes to parallel agents. The generated code is written to a shared workspace directory. There is no file-locking, scope assignment, or conflict detection.
+
+#### Role-Scoped Tooling
+
+MetaGPT supports **per-role tool assignment** through the `Role` class and its `set_actions()` method. Each role can be equipped with different actions (tools). The config file supports per-role LLM configuration. However, tool configuration is done in Python code, not via config files.
+
+[Source: Agent 101](https://docs.deepwisdom.ai/main/en/guide/tutorials/agent_101.html)
+
+#### Skills System
+
+MetaGPT does **not** have a directory-based skills system with markdown/text instructions per agent type. It has an "experience pool" (`exp_pool`) feature that stores past experiences, but this is not a skills directory system with global + project-level organization.
+
+[Source: config2.example.yaml](https://github.com/geekan/MetaGPT/blob/main/config/config2.example.yaml)
+
+#### LSP Integration
+
+MetaGPT has **no LSP integration**. Generated code is written to disk, but there is no built-in Language Server Protocol client for compiler diagnostics or code validation.
+
+#### Shell Access with Directory Permissions
+
+MetaGPT's `SimpleRunCode` action executes Python code via `subprocess.run()`. There is **no permission control system** — no auto-allow lists, no prompt-for-out-of-scope directories. Code execution is unrestricted within the subprocess.
+
+[Source: Agent 101 - SimpleRunCode](https://docs.deepwisdom.ai/main/en/guide/tutorials/agent_101.html)
+
+#### Session Management
+
+MetaGPT supports **serialization and breakpoint recovery** via `--recover_path`. It serializes team state (roles, memories, actions) to a JSON file and can resume execution. However, there is **no support for chat forking, model switching mid-conversation, or loading/resuming old chats** as separate sessions.
+
+[Source: Serialization & Breakpoint Recovery](https://docs.deepwisdom.ai/main/en/guide/in_depth_guides/breakpoint_recovery.html)
+
+#### Human-in-the-Loop Checkpoints
+
+MetaGPT has **no configurable checkpoint system** for pausing execution at predefined points. The human engagement feature (`is_human=True`) pauses at each role turn, but this replaces the agent rather than providing a checkpoint/approval mechanism. There are no approval gates or pause/resume hooks.
+
+#### State Persistence
+
+Yes — MetaGPT serializes team, environment, roles, and actions to JSON files in a `./workspace/storage` directory. This enables recovery after crashes or Ctrl-C. The serialization captures memory, role state, action progress, and environment history.
+
+[Source: Serialization & Breakpoint Recovery](https://docs.deepwisdom.ai/main/en/guide/in_depth_guides/breakpoint_recovery.html)
+
+#### Provider-Agnostic LLM
+
+Yes — MetaGPT supports multiple LLM providers via a configurable `api_type` field. Options include OpenAI, Azure, Ollama, Groq, Gemini, and others. Each role can have a different LLM provider.
+
+[Source: Config example](https://github.com/geekan/MetaGPT/blob/main/config/config2.example.yaml)
+
+#### Multiple Interfaces
+
+MetaGPT supports:
+- **CLI**: `metagpt "Create a 2048 game"` command
+- **Python library**: Import and use as a Python package
+- No dedicated TUI or API mode
+
+[Source: README](https://github.com/geekan/MetaGPT)
+
+---
+
+### 2. ChatDev (2.0 / DevAll)
+
+#### Core Architecture
+
+ChatDev has undergone a major transformation. **ChatDev 2.0 (DevAll)** — released Jan 7, 2026 — is a **zero-code multi-agent orchestration platform**. It is no longer focused solely on software development but positions itself as a platform for "Developing Everything" through configurable DAG-based workflows.
+
+The architecture is:
+- **YAML-defined workflows** describing DAGs of agent nodes
+- **Node types**: `agent` (LLM), `python`, `human`, `subgraph`, `passthrough`, `literal`, `loop_counter`, `loop_timer`
+- **Web UI** (Vue 3) for visual workflow design + execution dashboard
+- **Python SDK / PyPI package** (`chatdev`) for programmatic execution
+- **FastAPI backend** for the server component
+
+**Key metrics**: 33.1k stars, 4.1k forks, Python 68.7% / Vue 28.5%. Latest release v2.2.0 (March 23, 2026). Very active development — 190 commits on main branch for 2.0. The legacy ChatDev 1.x is preserved on the `chatdev1.0` branch.
+
+[Source: ChatDev README](https://github.com/OpenBMB/ChatDev)
+
+#### Hierarchy Depth
+
+ChatDev 2.0 supports **multi-level nesting** through its `subgraph` node type. A workflow can reference another workflow file or inline graph, enabling recursive hierarchy. Additionally, its "Tree Mode" (in dynamic execution) provides a natural fan-out/reduce pattern. The DAG structure with start/end nodes enables clear orchestration flows. However, this is a **directed acyclic graph** structure, not a strict three-level dispatch->orchestrator->subagent tree. The hierarchy is defined by edge topology, not by enforced levels.
+
+[Source: Workflow Authoring Guide](https://github.com/OpenBMB/ChatDev/blob/main/docs/user_guide/en/workflow_authoring.md)
+
+#### Config-Driven Orchestrators
+
+**Yes** — this is ChatDev 2.0's core strength. Entire workflows, including agent types, models, prompts, tools, edge conditions, and execution logic, are defined in YAML configuration files under `yaml_instance/`. No Python code changes are needed to create new orchestrator types. The `DesignConfig` structure with `version`, `vars`, and `graph` keys provides a clean configuration schema.
+
+[Source: Workflow Authoring Guide](https://github.com/OpenBMB/ChatDev/blob/main/docs/user_guide/en/workflow_authoring.md)
+
+#### Parallel Subagent Execution
+
+**Yes** — ChatDev 2.0 supports parallel execution through its **dynamic execution** feature. Both **Map Mode** (`type: map`, fan-out) and **Tree Mode** (`type: tree`, fan-out with recursive reduce) are supported. The `max_parallel` parameter controls concurrency limits. This enables true parallel subagent execution.
+
+[Source: Workflow Authoring Guide - Dynamic Execution](https://github.com/OpenBMB/ChatDev/blob/main/docs/user_guide/en/workflow_authoring.md)
+
+#### Strict Hierarchy Communication
+
+ChatDev 2.0's DAG-based execution **enforces communication along defined edges**. Nodes only receive messages from their upstream nodes and send to downstream nodes. However, there is no parent-child trust boundary — all nodes within a workflow are at the same trust level. The `subgraph` node type provides some isolation (child graph has its own nodes), but there is no mechanism to enforce that sub-nodes can _only_ communicate with their parent orchestrator.
+
+**Partial support** — edges define communication paths, but strict hierarchical communication with parent-only routing is not enforced.
+
+[Source: Workflow Authoring Guide - Edges](https://github.com/OpenBMB/ChatDev/blob/main/docs/user_guide/en/workflow_authoring.md)
+
+#### User-to-Agent Messaging Mid-Execution
+
+ChatDev 2.0 supports this via the **`human` node type**. When execution reaches a `human` node, the workflow pauses and waits for user input in the Web UI. However, messaging is constrained to the node level — the user interacts at predefined points in the DAG, not with arbitrary running agents at any time. The `human` node must be explicitly placed in the workflow DAG.
+
+**Partial support** — user can inject messages at designated human nodes, but not to any arbitrary agent mid-execution.
+
+[Source: Workflow Authoring Guide - Node Types](https://github.com/OpenBMB/ChatDev/blob/main/docs/user_guide/en/workflow_authoring.md)
+
+#### Conflict Prevention
+
+ChatDev 2.0 has **no built-in conflict prevention** mechanism for non-overlapping file scopes. The system does not assign or track file write scopes. The `python` node type executes scripts sharing a `code_workspace/` directory, but there is no file-locking or scope isolation.
+
+#### Role-Scoped Tooling
+
+**Yes** — the `AgentConfig.tooling` field lets different agent nodes have different tool sets. Tools are configured per-node in the YAML workflow. The MCP (Model Context Protocol) is also supported for tool integration. Tooling is defined at the node level, providing role-scoped access.
+
+[Source: Workflow Authoring Guide - Agent Node Advanced Features](https://github.com/OpenBMB/ChatDev/blob/main/docs/user_guide/en/workflow_authoring.md)
+
+#### Skills System
+
+ChatDev 2.0 has a **`.agents/skills`** directory with a directory-based skill organization. Currently contains `greeting-demo`, `python-scratchpad`, and `rest-api-caller` skills. The skills system is designed for injectable markdown/text instructions per agent type, with both global and project-level organization. However, the documentation does not detail how skills are loaded/scoped to specific agent nodes.
+
+[Source: .agents/skills directory](https://github.com/OpenBMB/ChatDev/tree/main/.agents/skills)
+
+#### LSP Integration
+
+ChatDev has **no LSP integration** documented. The platform focuses on workflow orchestration and multi-agent collaboration, not on providing compiler diagnostics or language server features.
+
+#### Shell Access with Directory Permissions
+
+ChatDev 2.0 has **no documented shell permission controls**. The `python` node type executes scripts, and Docker support is available for safe execution (noted in legacy 1.x), but there is no auto-allow list, prompt-for-scope, or directory-based permission system in the current documentation.
+
+#### Session Management
+
+ChatDev 2.0 supports **session management** through the Web UI — workflows can be launched, monitored, and inspected. Context snapshots are saved to `WareHouse/<session>/context.json`. There is HTTP API support (`POST /api/workflow/execute`) and CLI execution (`python run.py`). However, there is **no support for chat forking, model switching mid-conversation, or loading/resuming old chats** in the documented features.
+
+[Source: Workflow Authoring Guide - CLI/API Execution Paths](https://github.com/OpenBMB/ChatDev/blob/main/docs/user_guide/en/workflow_authoring.md)
+
+#### Human-in-the-Loop Checkpoints
+
+**Yes** — the `human` node type provides explicit checkpoints where the workflow pauses for user input. Conditions on edges (e.g., `keyword` condition checking for "ACCEPT") can create approval gates. The workflow only continues when the user provides the expected input.
+
+[Source: Workflow Authoring Guide - Conditions](https://github.com/OpenBMB/ChatDev/blob/main/docs/user_guide/en/workflow_authoring.md)
+
+#### State Persistence
+
+ChatDev 2.0 saves workflow session data to `WareHouse/` directories, including context snapshots. The SQLite database (`sync` command) stores workflow definitions. However, there is **no documented mechanism for full state persistence across restarts** that would allow resuming a workflow from where it left off after a crash (like MetaGPT's breakpoint recovery).
+
+**Partial support** — artifacts and logs persist, but workflow execution state recovery is not documented.
+
+[Source: Workflow Authoring Guide - Debugging Tips](https://github.com/OpenBMB/ChatDev/blob/main/docs/user_guide/en/workflow_authoring.md)
+
+#### Provider-Agnostic LLM
+
+**Yes** — ChatDev 2.0 supports multiple LLM providers configured per-node via `provider` field (e.g., `openai`, `gemini`, etc.). The config uses `${VAR}` syntax for API keys and base URLs. The `globals.default_provider` sets a fallback. Providers can be mixed within a single workflow.
+
+[Source: Workflow Authoring Guide - Providers](https://github.com/OpenBMB/ChatDev/blob/main/docs/user_guide/en/workflow_authoring.md)
+
+#### Multiple Interfaces
+
+ChatDev 2.0 supports:
+- **Web UI / TUI**: Vue 3 frontend at port 5173 with visual workflow canvas and execution dashboard
+- **CLI**: `python run.py --path yaml_instance/demo.yaml --name test_run`
+- **HTTP API**: `POST /api/workflow/execute` with session management
+- **Python SDK**: `chatdev` PyPI package for programmatic execution
+
+[Source: ChatDev README](https://github.com/OpenBMB/ChatDev)
+
+---
+
+### 3. CAMEL
+
+#### Core Architecture
+
+CAMEL is a Python-based research framework focused on "finding the scaling laws of agents." It is the most general-purpose and academically oriented of the three frameworks. The architecture includes:
+
+- **ChatAgent**: Core agent abstraction with LLM, tools, and memory
+- **Agent Societies**: Coordination layers including `RolePlaying`, `BabyAGI`, and `Workforce`
+- **Workforce**: A hierarchical agent orchestration system with task decomposition and worker assignment
+- **ModelFactory**: Abstract LLM interface supporting 45+ model platforms
+- **Toolkits**: 70+ tool integrations (search, browser, code execution, MCP, etc.)
+- **Memory & Storage**: Persistent state management
+- **Interpreters**: Code/command execution backends
+- **Human-in-the-Loop**: Tool approval and interactive components
+
+**Key metrics**: 17k stars, 1.9k forks, 2,208 commits, Python 95.9% / TypeScript 2.1%. Latest release v0.2.90 (March 22, 2026). Extremely active — 210 releases and very frequent commits. Large community with Discord, WeChat, and Reddit presence. Backed by Eigen AI research collective with 100+ researchers.
+
+[Source: CAMEL README](https://github.com/camel-ai/camel)
+
+#### Hierarchy Depth
+
+CAMEL's **Workforce** module provides a hierarchical multi-agent system. The `Workforce` class acts as an orchestrator that receives a task, decomposes it into subtasks, assigns workers (single agents or role-playing pairs), and aggregates results. The workforce supports **multiple levels** — a worker can itself be a `Workforce` (recursive nesting), enabling arbitrary hierarchy depth. The `RolePlaying` society creates two-agent task-solving pairs. However, workforce configuration is done via Python code, not config files, and the hierarchy is defined programmatically.
+
+[Source: CAMEL societies/workforce](https://github.com/camel-ai/camel/tree/master/camel/societies/workforce)
+
+#### Config-Driven Orchestrators
+
+CAMEL is **primarily Python-code-driven**. The `ModelFactory.create()` method accepts programmatic configuration. While `create_from_yaml()` and `create_from_json()` methods exist for model configuration, the orchestration logic (workforce setup, task decomposition, worker assignment) is **defined in Python code**. There is no YAML-based workflow definition system like ChatDev 2.0.
+
+**Partial support** — model config can be loaded from YAML/JSON files, but orchestration logic cannot.
+
+[Source: model_factory.py](https://github.com/camel-ai/camel/blob/master/camel/models/model_factory.py)
+
+#### Parallel Subagent Execution
+
+CAMEL's Workforce **does not appear to have built-in parallel execution** of subagents. The workforce processes tasks sequentially through its worker pool. While individual agents can make async LLM calls, the workforce orchestration itself is not designed for parallel fan-out execution. The documentation emphasizes scalability to "millions of agents" as a design principle but does not describe current parallel execution in the workforce implementation.
+
+**Not supported** in the current documented and released architecture.
+
+[Source: CAMEL societies/workforce](https://github.com/camel-ai/camel/tree/master/camel/societies/workforce)
+
+#### Strict Hierarchy Communication
+
+CAMEL's Workforce uses a **centralized orchestration** pattern — the workforce assigns tasks to workers and collects results. Workers do not communicate directly with each other; they receive tasks from and return results to the workforce. The `RolePlaying` society, however, involves direct agent-to-agent communication. The framework does not explicitly enforce a "subagents only talk to parent" policy — the architecture enables this pattern through the workforce abstraction by design, but there is no system-level enforcement mechanism.
+
+**Partial support** — Workforce architecture naturally restricts communication to parent-child, but no formal enforcement mechanism exists.
+
+[Source: CAMEL Workforce codebase](https://github.com/camel-ai/camel/tree/master/camel/societies/workforce)
+
+#### User-to-Agent Messaging Mid-Execution
+
+CAMEL has a **Human toolkit** (`human_toolkit.py`) and documented **Human-in-the-Loop** features including tool approval. The framework supports interactive components for human oversight and intervention. However, these are primarily **tool approval** mechanisms (approving tool calls before execution), not free-form message injection into any running agent at any time. The messaging pattern requires explicit setup in code.
+
+**Partial support** — human-in-the-loop tool approval exists, but mid-execution arbitrary message injection is not a built-in feature.
+
+[Source: CAMEL README - Human-in-the-Loop](https://github.com/camel-ai/camel)
+
+#### Conflict Prevention
+
+CAMEL has **no built-in conflict prevention** mechanism for non-overlapping file scopes. The framework provides code execution via `Interpreters` and toolkits like `code_execution.py`, but there is no file-locking, scope assignment, or conflict detection for parallel agents writing to the same filesystem.
+
+#### Role-Scoped Tooling
+
+**Yes** — CAMEL has an extensive toolkit system with **70+ toolkits** (search, browser, code execution, GitHub, Gmail, Slack, MCP, etc.). Tools are assigned to individual agents at creation time via the `tools` parameter of `ChatAgent`. Different agents can have completely different tool sets. The skill system also provides role-scoped capabilities.
+
+[Source: CAMEL toolkits directory](https://github.com/camel-ai/camel/tree/master/camel/toolkits)
+
+#### Skills System
+
+CAMEL has a **`.camel/skills`** directory with a directory-based skill organization. Currently contains `docs-incremental-update` and `skill-creator` skills. The `skill_toolkit.py` provides programmatic access to skills. Skills can be loaded and assigned to agents. However, the system does not appear to support both global and project-level skill directories — the skills are in a single project-level `.camel/skills` directory.
+
+[Source: .camel/skills directory](https://github.com/camel-ai/camel/tree/master/.camel/skills)
+
+#### LSP Integration
+
+CAMEL has **no LSP integration** documented. The framework focuses on multi-agent research and task automation, not on providing compiler diagnostics or language server features.
+
+#### Shell Access with Directory Permissions
+
+CAMEL provides shell access through its `TerminalToolkit` (`terminal_toolkit/`), `CodeExecution` toolkit, and `Interpreters`. However, there is **no documented permission control system** — no auto-allow lists, no prompt-for-out-of-scope directories, no directory-based sandboxing.
+
+[Source: CAMEL toolkits - terminal_toolkit](https://github.com/camel-ai/camel/tree/master/camel/toolkits/terminal_toolkit)
+
+#### Session Management
+
+CAMEL supports **session and conversation management** through its `Memory` module and `Storage` module, which provide persistent context layers for chat history and tool outputs. The framework logs model request/response to JSON files when `CAMEL_MODEL_LOG_ENABLED=true`. However, there is **no support for chat forking, model switching mid-conversation, or loading/resuming old chats** as a built-in feature.
+
+[Source: CAMEL README - Model Logging](https://github.com/camel-ai/camel)
+
+#### Human-in-the-Loop Checkpoints
+
+CAMEL supports **Human-in-the-Loop** features including tool approval (approving tool calls before execution) and interactive components. The `human_toolkit.py` provides human interaction capabilities. However, there is **no checkpoint system** for pausing execution at configurable predefined points — the human involvement is primarily about tool-call approval, not workflow-level pause/resume.
+
+**Partial support** — tool approval exists, but configurable execution checkpoints do not.
+
+[Source: CAMEL README - Human-in-the-Loop](https://github.com/camel-ai/camel)
+
+#### State Persistence
+
+CAMEL provides **stateful memory** as a core design principle. Agents maintain stateful memory enabling multi-step interactions. The `Memory` and `Storage` modules provide persistent context. However, there is **no documented mechanism for full workflow/execution state persistence across restarts** (like MetaGPT's breakpoint recovery). The logging system captures request/response data, but this is not a recovery mechanism.
+
+**Partial support** — agent memory persists within a session, but full execution state recovery across restarts is not documented.
+
+[Source: CAMEL README - Statefulness](https://github.com/camel-ai/camel)
+
+#### Provider-Agnostic LLM
+
+**Yes** — this is CAMEL's strongest area. The `ModelFactory` supports **45+ model platforms** including OpenAI, Azure, Anthropic, Gemini, Mistral, Cohere, Ollama, vLLM, SGLang, Groq, DeepSeek, Qwen, and many more. The `ModelFactory.create()` method provides a clean abstract interface, and models can also be created from YAML/JSON config files via `create_from_yaml()` and `create_from_json()`.
+
+[Source: model_factory.py](https://github.com/camel-ai/camel/blob/master/camel/models/model_factory.py)
+
+#### Multiple Interfaces
+
+CAMEL supports:
+- **Python library**: Primary interface as a Python package (`pip install camel-ai`)
+- **CLI**: Via environment variable configuration and example scripts
+- **API/Server**: Apps directory contains server applications (FastAPI-based)
+- No dedicated TUI or web UI (unlike ChatDev's Vue 3 frontend)
+
+[Source: CAMEL README](https://github.com/camel-ai/camel)
+
+---
+
+## Comparison Table
+
+| Requirement | MetaGPT | ChatDev 2.0 | CAMEL |
+|---|---|---|---|
+| **1. Three-layer hierarchy** | Not supported (flat role pool) | Partial (DAG + subgraph nesting) | Partial (recursive Workforce, code-configured) |
+| **2. Config-driven orchestrators** | Partial (infra config only, logic in code) | **Yes** (full YAML workflow definitions) | Partial (model config from YAML, logic in code) |
+| **3. Parallel subagent execution** | Not supported (sequential rounds) | **Yes** (Map/Tree modes, max_parallel) | Not supported (sequential workforce) |
+| **4. Strict hierarchy communication** | Not supported (broadcast) | Partial (edge-routed but no parent-only enforcement) | Partial (Workforce centralizes but no enforcement) |
+| **5. User-to-agent messaging mid-execution** | Partial (is_human role replacement) | Partial (human nodes in DAG) | Partial (tool approval, not free injection) |
+| **6. Conflict prevention** | Not supported | Not supported | Not supported |
+| **7. Role-scoped tooling** | **Yes** (per-role actions in code) | **Yes** (per-node tooling in YAML) | **Yes** (per-agent toolkits) |
+| **8. Skills system (markdown dirs)** | Not supported | Partial (.agents/skills dir exists) | Partial (.camel/skills dir exists) |
+| **9. LSP integration** | Not supported | Not supported | Not supported |
+| **10. Shell access with permissions** | Not supported (unrestricted subprocess) | Not supported (Docker for safety only) | Not supported (no permission system) |
+| **11. Session management** | Partial (breakpoint recovery) | Partial (context snapshots) | Partial (memory storage) |
+| **12. Human-in-the-loop checkpoints** | Not supported | **Yes** (human nodes + edge conditions) | Partial (tool approval only) |
+| **13. State persistence** | **Yes** (JSON serialization + recovery) | Partial (artifacts persist, no recovery) | Partial (memory/storage, no recovery) |
+| **14. Provider-agnostic LLM** | **Yes** (multiple providers via config) | **Yes** (per-node provider config) | **Yes** (45+ platforms, broadest support) |
+| **15. Multiple interfaces** | Partial (CLI + Python lib) | **Yes** (Web UI, CLI, HTTP API, Python SDK) | Partial (Python lib + server apps) |
+
+---
+
+## Source list
+
+| # | Source | Type |
+|---|--------|------|
+| 1 | [MetaGPT GitHub](https://github.com/geekan/MetaGPT) | GitHub |
+| 2 | [MetaGPT Documentation - Concepts](https://docs.deepwisdom.ai/main/en/guide/tutorials/concepts.html) | Official docs |
+| 3 | [MetaGPT Documentation - Agent 101](https://docs.deepwisdom.ai/main/en/guide/tutorials/agent_101.html) | Official docs |
+| 4 | [MetaGPT Documentation - MultiAgent 101](https://docs.deepwisdom.ai/main/en/guide/tutorials/multi_agent_101.html) | Official docs |
+| 5 | [MetaGPT Documentation - Human Engagement](https://docs.deepwisdom.ai/main/en/guide/tutorials/human_engagement.html) | Official docs |
+| 6 | [MetaGPT Documentation - Breakpoint Recovery](https://docs.deepwisdom.ai/main/en/guide/in_depth_guides/breakpoint_recovery.html) | Official docs |
+| 7 | [MetaGPT config2.example.yaml](https://github.com/geekan/MetaGPT/blob/main/config/config2.example.yaml) | GitHub source |
+| 8 | [ChatDev GitHub](https://github.com/OpenBMB/ChatDev) | GitHub |
+| 9 | [ChatDev Workflow Authoring Guide](https://github.com/OpenBMB/ChatDev/blob/main/docs/user_guide/en/workflow_authoring.md) | Official docs |
+| 10 | [ChatDev .agents/skills](https://github.com/OpenBMB/ChatDev/tree/main/.agents/skills) | GitHub source |
+| 11 | [CAMEL GitHub](https://github.com/camel-ai/camel) | GitHub |
+| 12 | [CAMEL model_factory.py](https://github.com/camel-ai/camel/blob/master/camel/models/model_factory.py) | GitHub source |
+| 13 | [CAMEL societies/workforce](https://github.com/camel-ai/camel/tree/master/camel/societies/workforce) | GitHub source |
+| 14 | [CAMEL .camel/skills](https://github.com/camel-ai/camel/tree/master/.camel/skills) | GitHub source |
+| 15 | [CAMEL toolkits directory](https://github.com/camel-ai/camel/tree/master/camel/toolkits) | GitHub source |
+| 16 | [CAMEL Documentation](https://docs.camel-ai.org/) | Official docs |
+
+---
+
+## Verbatim quotes
+
+- "MetaGPT takes a one line requirement as input and outputs user stories / competitive analysis / requirements / data structures / APIs / documents" — [Source 1](https://github.com/geekan/MetaGPT)
+- "Code = SOP(Team) is the core philosophy. We materialize SOP and apply it to teams composed of LLMs." — [Source 1](https://github.com/geekan/MetaGPT)
+- "ChatDev has evolved from a specialized software development multi-agent system into a comprehensive multi-agent orchestration platform." — [Source 8](https://github.com/OpenBMB/ChatDev)
+- "ChatDev 2.0 (DevAll) is a Zero-Code Multi-Agent Platform for 'Developing Everything'. It empowers users to rapidly build and customize multi-agent systems through simple configuration. No coding is required." — [Source 8](https://github.com/OpenBMB/ChatDev)
+- "CAMEL is an open-source community dedicated to finding the scaling laws of agents." — [Source 11](https://github.com/camel-ai/camel)
+- "The framework enables multi-agent systems to continuously evolve by generating data and interacting with environments." — [Source 11](https://github.com/camel-ai/camel) (Evolvability principle)
+- "The framework is designed to support systems with millions of agents, ensuring efficient coordination, communication, and resource management at scale." — [Source 11](https://github.com/camel-ai/camel) (Scalability principle)
+
+---
+
+## Source quality flags
+
+- Source 5 (MetaGPT Human Engagement): mentions that the current interaction is "through terminal input, which is inconvenient for multi-line or structured writeup" — the feature is acknowledged as limited by maintainers
+- Source 8 (ChatDev GitHub): marketing language in the README — "World's first AI agent development team", "Zero-Code Multi-Agent Platform" — these are product positioning claims
+- Source 11 (CAMEL GitHub): marketing language — "the first and the best multi-agent framework" — this is self-promotion, not an objective claim
+
+---
+
+## Confidence: Medium
+
+The information is drawn directly from GitHub repositories, official documentation, and source code. Confidence is medium because some features (especially those related to hierarchy depth, parallel execution, and session management) required interpretation of architecture and code structure rather than explicit documentation. ChatDev 2.0 is very new (released Jan 2026) and its documentation is still evolving, so some features may be present but undocumented.
+
+---
+
+## Gaps and open questions
+
+1. **Hierarchy depth**: None of the three frameworks explicitly implements a three-layer dispatch->orchestrator->subagent architecture. ChatDev's subgraph nesting comes closest but is DAG-based, not tree-based. CAMEL's recursive Workforce is close but configured in code. A follow-up investigation should assess whether any framework could be extended to support this pattern without major refactoring.
+2. **Conflict prevention**: No framework addresses file-scope conflict prevention. This would likely require custom implementation regardless of framework choice.
+3. **LSP integration**: No framework has LSP support. This would be a novel feature to add.
+4. **Shell permissions**: No framework has directory-based shell permission controls. This is a significant gap for any production deployment that needs sandboxing.
+5. **MetaGPT release cadence**: The latest release (v0.8.1) is from April 2024 — over 13 months old. The project may be entering maintenance mode as the team focuses on MGX. This should be verified.
+6. **ChatDev 2.0 maturity**: ChatDev 2.0 was released in January 2026 and the documentation is still being written. Several module documentation pages (e.g., skills, tooling) are marked as "Chinese for now" or return 404. The framework's long-term stability is unproven.
+7. **CAMEL's parallel execution**: The workforce module's parallel execution capabilities need deeper investigation. The design principles mention scalability to "millions of agents" but current implementation appears sequential.
+
+
diff --git a/research/pi-dev-harness.md b/research/pi-dev-harness.md
new file mode 100644
index 0000000..8fb94b9
--- /dev/null
+++ b/research/pi-dev-harness.md
@@ -0,0 +1,322 @@
+# Subagent Report: pi.dev (Pi) — Super-Customizable AI Agent Harness
+
+## Research summary
+
+Pi (pi.dev / `@earendil-works/pi-coding-agent`) is a minimal, aggressively **extensible** terminal-based coding agent harness built in **TypeScript** by Mario Zechner (Earendil Inc.). It is **not** a multi-agent orchestration framework — it is a **single-agent CLI** (like Claude Code, opencode, or Codex) designed to be customized via TypeScript extensions, markdown skills, prompt templates, and themes. It has excellent built-in support for provider-agnostic LLMs, session management (tree-structured with branching), state persistence, user-to-agent messaging mid-execution, and multiple interfaces (TUI, CLI, RPC, SDK). However, **multi-agent hierarchy, config-driven orchestrators, LSP integration, conflict prevention, and role-scoped tooling are all absent by design** — the project philosophy is "primitives, not features." These would need to be built from scratch as extensions. Confidence: **high** — the source, docs, and blog posts are extensive and transparent about what Pi does and does not include.
+
+---
+
+## Findings
+
+### 1. Overview: What Pi Is and Is Not
+
+**What exactly IS Pi?** Pi is a monorepo containing five npm packages:
+
+| Package | Purpose |
+|---|---|
+| `@earendil-works/pi-ai` | Unified multi-provider LLM API (15+ providers: Anthropic, OpenAI, Google, Azure, Bedrock, Mistral, Groq, Cerebras, xAI, Hugging Face, OpenRouter, etc.) |
+| `@earendil-works/pi-agent-core` | Agent runtime with tool calling, state management, event streaming, compaction |
+| `@earendil-works/pi-coding-agent` | The main CLI: interactive TUI, print mode, JSON mode, RPC mode, SDK |
+| `@earendil-works/pi-tui` | Terminal UI library with differential rendering |
+| `@earendil-works/pi-web-ui` | Web components for AI chat interfaces |
+
+Pi is **not** a multi-agent orchestration framework like LangGraph or CrewAI. It is a **single-agent coding assistant CLI** that you run in a terminal. The "super customizable" claim refers to its extension system — you can add tools, commands, UI components, and behaviors via TypeScript extensions without forking the codebase.
+
+[Source: GitHub monorepo README](https://github.com/earendil-works/pi)
+
+**Language & Tech Stack:**
+
+- **TypeScript** (96.5% of the repo), JavaScript (2.9%), CSS (0.4%), Shell (0.2%)
+- Runtime: Node.js >= 22.19.0
+- Key dependencies: `jiti` (TypeScript loader for extensions), `typebox` (schema validation), `chalk`, `yaml`, `diff`, `undici`, `cross-spawn`
+- Packaging: npm ecosystem, published as `@earendil-works/pi-coding-agent`
+- Custom-built TUI framework (`pi-tui`) — not React/Ink-based
+
+[Source: package.json](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/package.json)
+
+**License:** MIT — fully open source.
+
+[Source: LICENSE file](https://github.com/earendil-works/pi/blob/main/LICENSE)
+
+**Activity & Community:** Extremely active.
+
+| Metric | Value |
+|---|---|
+| GitHub Stars | **51.4k** |
+| Forks | **6.1k** |
+| Commits | **4,188** |
+| Releases | **219** (latest: v0.75.3, May 18, 2026) |
+| Open Issues | 28 |
+| Open PRs | 6 |
+| Discord Community | Yes (linked from site) |
+| Maintainer | Mario Zechner (badlogicgames) / Earendil Inc. |
+
+Note: New contributor issues/PRs are auto-closed by default; maintainers review them daily.
+
+[Source: GitHub repo](https://github.com/earendil-works/pi)
+
+**Philosophy (from the maintainer's blog):**
+> "If I don't need it, it won't be built. And I don't need a lot of things."
+> "Pi is aggressively extensible so it doesn't have to dictate your workflow."
+> "Features that other tools bake in can be built with extensions, skills, or installed from third-party pi packages."
+
+Pi deliberately ships **without**: sub-agents, plan mode, permission popups, MCP support, background bash, and to-do lists. The maintainer believes these should be built as extensions or handled externally (tmux, containers, file-based plans).
+
+[Source: maintainer blog post](https://mariozechner.at/posts/2025-11-30-pi-coding-agent/)
+
+---
+
+### 2. Architecture Deep-Dive
+
+#### Core Architecture (Layered)
+
+```
+┌────────────────────────────────────────────────┐
+│ pi-coding-agent (CLI/TUI) │
+│ Session management, extensions, skills, │
+│ themes, prompt templates, commands, UI │
+├────────────────────────────────────────────────┤
+│ pi-agent-core │
+│ Agent loop, tool execution, state management │
+│ Event streaming, compaction, transport layer │
+├────────────────────────────────────────────────┤
+│ pi-ai │
+│ Unified LLM API: OpenAI, Anthropic, Google, │
+│ and 12+ more providers. Tool calling, │
+│ streaming, thinking/reasoning, context handoff │
+├────────────────────────────────────────────────┤
+│ pi-tui / pi-web-ui │
+│ Terminal UI framework / Web components │
+└────────────────────────────────────────────────┘
+```
+
+[Source: GitHub README](https://github.com/earendil-works/pi)
+
+#### Agent Creation & Management
+
+Pi's agent model is **single-agent per session**. Key classes:
+
+- **`AgentSession`** — Manages one agent's lifecycle, message history, model state, streaming. Created via `createAgentSession()`.
+- **`Agent`** (from `@earendil-works/pi-agent-core`) — The core loop: processes user messages, executes tool calls, feeds results back to LLM, repeats until done.
+- **`AgentSessionRuntime`** — Wraps `AgentSession` with session replacement capabilities (new/resume/fork/clone).
+- **SessionManager** — Handles persistence as tree-structured JSONL files.
+
+The agent loop is minimal: no max-steps limit, no sub-agent spawning. It loops until the model produces a non-tool-call response.
+
+#### Extension System (Plugin Model)
+
+Extensions are **TypeScript modules** auto-discovered from well-known directories:
+
+| Location | Scope |
+|---|---|
+| `~/.pi/agent/extensions/*.ts` | Global (all projects) |
+| `~/.pi/agent/extensions/*/index.ts` | Global (subdirectory) |
+| `.pi/extensions/*.ts` | Project-local |
+| `.pi/extensions/*/index.ts` | Project-local (subdirectory) |
+
+Extensions export a default factory function receiving `ExtensionAPI`:
+
+```typescript
+import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
+
+export default function (pi: ExtensionAPI) {
+ // Register tools
+ pi.registerTool({ name: "my_tool", ... });
+
+ // Register commands
+ pi.registerCommand("mycmd", { handler: async (args, ctx) => { ... } });
+
+ // Register keyboard shortcuts
+ pi.registerShortcut("ctrl+x", { handler: async (ctx) => { ... } });
+
+ // Register CLI flags
+ pi.registerFlag("my-flag", { description: "..." });
+
+ // Hook events
+ pi.on("tool_call", async (event, ctx) => { ... });
+ pi.on("session_start", async (event, ctx) => { ... });
+ pi.on("before_agent_start", async (event, ctx) => { ... });
+ // ... 30+ event types
+}
+```
+
+**What extensions can do:**
+- Custom tools (or replace built-in tools entirely)
+- Intercept/block/modify tool calls via `tool_call` event
+- Custom compaction and summarization
+- Permission gates, path protection
+- Custom UI components, editors, status bars, overlays
+- SSH and sandbox execution
+- MCP server integration (via custom tool wrapping)
+- Sub-agents (spawn child `pi` processes via bash)
+- Session persistence via `pi.appendEntry()`
+- Custom providers and models
+
+Extensions are loaded via `jiti` (TypeScript compilation on-the-fly). Async factories are supported. Pi packages bundle extensions, skills, prompts, and themes for sharing via npm or git.
+
+**50+ extension examples** in the repo, including: subagent, plan-mode, permission-gate, protected-paths, sandbox, ssh, custom-compaction, snake game, Doom.
+
+[Source: Extensions documentation](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/extensions.md)
+
+#### Skills System
+
+Pi implements the [Agent Skills standard](https://agentskills.io). Skills are markdown files with YAML frontmatter stored in directories:
+
+```
+~/.pi/agent/skills/my-skill/SKILL.md
+~/.agents/skills/
+.pi/skills/
+.agents/skills/ (in cwd and ancestor directories)
+```
+
+Skills follow progressive disclosure: only descriptions are always in the context; the full SKILL.md content loads on-demand when triggered via `/skill:name` command or when the agent decides to read it.
+
+#### Event Lifecycle
+
+Pi exposes ~30 events across the lifecycle:
+- **Resource events**: `resources_discover`
+- **Session events**: `session_start`, `session_before_switch`, `session_before_fork`, `session_before_compact`, `session_compact`, `session_shutdown`
+- **Agent events**: `before_agent_start`, `agent_start`, `agent_end`, `turn_start`, `turn_end`, `message_start/update/end`
+- **Tool events**: `tool_call` (can block), `tool_result` (can modify), `tool_execution_start/update/end`
+- **Model events**: `model_select`, `thinking_level_select`
+- **Input events**: `input` (can intercept/transform)
+- **User bash events**: `user_bash`
+
+[Source: Extensions documentation (event reference)](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/extensions.md)
+
+---
+
+### 3. Requirements Checklist Evaluation
+
+For each requirement, two ratings are given:
+- **Built-in Support**: Is it present in the core today?
+- **Ease of Adding**: How hard to build on top of Pi's extension system?
+
+| # | Requirement | Built-in Support | Ease of Adding | Assessment |
+|---|---|---|---|---|
+| 1 | **Three-layer hierarchy** (dispatch→orchestrator→subagent) | **Not supported** — Pi is a single-agent system. No parent-child agent relationships. | **Hard** — No architectural concept of agent hierarchy exists. The subagent extension spawns child `pi` processes via bash, but has no orchestration layer, routing, or lifecycle management. Building a full 3-layer hierarchy with dispatch and orchestrator would require creating the entire system from scratch as an extension. | The architecture is flat: one agent session, one agent loop. Adding hierarchy means building a new abstraction layer atop the existing single-agent runtime, not extending an existing one. |
+| 2 | **Config-driven orchestrators** (orchestrator types defined via YAML/JSON config) | **Not supported** — No concept of "orchestrator types" or orchestration configs. | **Hard** — Pi uses JSON files for its own settings (settings.json, models.json), but there is no orchestrator abstraction. Would need to build a config schema and runtime interpreter for orchestrator definitions from scratch. | Pi's `settings.json` is for Pi configuration, not agent orchestration. A new config format and execution engine would be needed. |
+| 3 | **Parallel subagent execution** | **Not supported** built-in. The subagent example extension supports parallel execution (up to 8 tasks, 4 concurrent). | **Moderate** — The subagent extension already exists as a working example and supports parallel `pi` process spawning. However, it spawns child processes via bash (not in-process agents), has a 50KB output cap per task, and limited concurrency. Would need significant enhancement for production use. | Example exists but is a demo, not production-grade. Architecture of spawning subprocesses works but has limitations. |
+| 4 | **Strict hierarchy communication** (subagents only talk to parent orchestrator, no peer-to-peer) | **Not supported** — No communication framework between agents. Subagents (when used) are independent processes. | **Moderate** — Could build a message-passing protocol on top of the subagent extension or RPC mode. But Pi has no built-in mechanism for parent-child routing or peer-to-peer blocking. | Would need to implement a communication protocol and enforce routing rules. |
+| 5 | **User-to-agent messaging mid-execution** | **Supported** — Built-in message queuing. `Enter` queues a "steer" message (delivered after current tool call, interrupts remaining tools). `Alt+Enter` queues a "follow-up" (delivered when agent finishes). Escape aborts and restores queued messages. | N/A (already built-in) | **Strong feature.** Configurable delivery modes: `one-at-a-time` (default) or `all`. Also available programmatically via `session.steer()` and `session.followUp()` methods in the SDK. |
+| 6 | **Conflict prevention** (non-overlapping file scopes for parallel agents) | **Not supported** — Pi runs in YOLO mode with full filesystem access. No concept of file scope assignment. | **Hard** — Goes against Pi's core philosophy of unrestricted access. Could be approximated by running each agent in a different directory/container, but there's no built-in file-scope assignment or enforcement mechanism. | Pi's design philosophy explicitly rejects this kind of restriction. Working around it would be fighting the architecture. |
+| 7 | **Role-scoped tooling** (different agents get different tool sets based on role) | **Partial** — Pi's `--tools` flag can restrict which tools are available globally. Extensions can register custom tools. But there's no role-based assignment system. | **Moderate** — Could use the `before_agent_start` event to dynamically modify the toolset based on context. But there's no built-in concept of "agent roles" or tool-to-role mapping. | Single-agent system means no role differentiation. Would need to build role management into whatever multi-agent layer you add. |
+| 8 | **Skills system** (injectable markdown instructions per agent type, with specific directory structure) | **Partial** — Pi has a full skills system following the Agent Skills standard. Skills are markdown files with YAML frontmatter. They are discovered from `~/.pi/agent/skills/`, `~/.agents/skills/`, `.pi/skills/`, and ancestor `.agents/skills/` directories. However, the directory structure does NOT match the required `default/`, `agents/`, `project/` subdirectory scheme. | **Easy** — The skills system is mature and extensible. The directory structure is configurable through the `DefaultResourceLoader`. Adding support for additional directory conventions would be straightforward. | Skills system is one of Pi's strongest extensibility points. The directory convention difference is minor. |
+| 9 | **LSP integration** (Language Server Protocol for compiler/linter diagnostics) | **Not supported** — No LSP client. Pi has no compiler, linter, or language server integration. | **Hard** — Would need to build an LSP client as an extension, handling stdio JSON-RPC protocol, file synchronization, diagnostics display, etc. No existing LSP primitives exist in the codebase. | This is a significant feature to build, but not architecturally impossible — just no existing support. |
+| 10 | **Shell access with directory permissions** (auto-allow lists, prompt for out-of-scope directories) | **Not supported** — Pi has full unrestricted bash access by design ("YOLO mode"). There IS a `permission-gate.ts` extension example that prompts before dangerous commands, and a `protected-paths.ts` extension example. | **Moderate** — The tool_call event can intercept bash commands. Directory awareness could be added. But the permission model would need to be built from scratch (auto-allow lists, scope checking). | The extension event model makes this possible, but Pi's philosophy is deliberately against permission systems. |
+| 11 | **Session management** (chat forking, model switching mid-conversation, loading/resuming old chats) | **Supported** — Excellent built-in session management: tree-structured JSONL files, `/tree` navigation to any previous point, `/fork` (new session from user message), `/clone` (duplicate branch), `/resume` (pick from past sessions), `/model` to switch models mid-session, HTML export, share via GitHub gist. Auto-save on every message. | N/A (already built-in) | **Strong feature.** Sessions persist to `~/.pi/agent/sessions/` organized by working directory. Continue with `pi -c`. |
+| 12 | **Human-in-the-loop checkpoints** (execution pauses at configurable points for user approval) | **Partial** — No built-in checkpoint system. But the extension event model allows blocking on any tool_call event. The permission-gate extension demonstrates this pattern. | **Easy** — Extensions can block tool execution via `{ block: true, reason: "..." }` return from `tool_call` event handler. Can show confirmation dialogs via `ctx.ui.confirm()`. Configurable checkpoint logic can be implemented entirely in an extension. | The `tool_call` event's blocking capability is exactly designed for this. |
+| 13 | **State persistence** (sessions, plans, artifacts persist across restarts) | **Supported** — Sessions auto-save to disk as JSONL files. Everything persists: full message history, model state, compaction metadata, tool results. `pi -c` continues the most recent session. Sessions survive process restarts. | N/A (already built-in) | **Strong feature.** Tree structure with branching means no information is lost — old branches remain accessible via `/tree`. |
+| 14 | **Provider-agnostic LLM** (multiple providers through abstract interface) | **Supported** — 15+ built-in providers (Anthropic, OpenAI, Google, Azure, Bedrock, Mistral, Groq, Cerebras, xAI, Hugging Face, OpenRouter, Together AI, Fireworks, DeepSeek, Kimi, MiniMax, etc.), custom providers via `models.json`, custom providers via extensions with full OAuth flows. Model switching mid-session. | N/A (already built-in) | **Strong feature.** Cross-provider context handoff is built into `@earendil-works/pi-ai` — models can be switched mid-conversation. |
+| 15 | **Multiple interfaces** (CLI, TUI, API modes) | **Supported** — 4 built-in modes: **Interactive** (full TUI), **Print** (`-p` for scripts), **JSON** (`--mode json` for structured output), **RPC** (`--mode rpc` for stdin/stdout JSONL protocol). Plus an **SDK** for embedding Pi in Node.js apps. Plus a **web-ui** package for web interfaces. | N/A (already built-in) | **Strong feature.** The SDK exports `InteractiveMode`, `runPrintMode`, and `runRpcMode` utilities for building custom interfaces on top. |
+
+---
+
+### 4. Strengths and Weaknesses as a Base for the Dispatch Harness
+
+#### Strengths
+
+1. **Exceptional session management** — Tree-structured sessions with branching, forking, cloning, resume, and model switching mid-conversation. This provides a robust foundation for state persistence.
+
+2. **Excellent provider-agnostic LLM layer** — 15+ providers, cross-provider context handoff, and a clean abstraction (`@earendil-works/pi-ai`). This could directly power the LLM layer of a dispatch system.
+
+3. **Powerful extension system** — TypeScript extensions with full access to lifecycle events, tool registration, UI components, and state management. The `tool_call` event's blocking capability is ideal for human-in-the-loop checkpoints. The `before_agent_start` event allows dynamic system prompt modification. These are the primitives needed for orchestration logic.
+
+4. **Message queuing mid-execution** — Built-in steer/follow-up messaging provides the foundation for user-to-agent communication during execution.
+
+5. **Multiple interface modes** — TUI, CLI, JSON, RPC, and SDK mean the system can be used interactively, programmatically, or embedded.
+
+6. **Mature skills system** — Follows the Agent Skills standard, with progressive disclosure. Easily adaptable to different directory conventions.
+
+7. **MIT license** — No restrictions on use or modification.
+
+8. **Active community and maintenance** — 219 releases, very active development. The project is not abandoned.
+
+#### Weaknesses
+
+1. **No multi-agent architecture** — Pi is fundamentally single-agent. There is no concept of agent hierarchy, orchestrator, dispatch, or subagent management. Building a 3-layer hierarchy (dispatch→orchestrator→subagent) means creating an entirely new architectural layer on top of Pi, not extending an existing one. This is the single biggest gap.
+
+2. **No config-driven orchestration** — Pi has no YAML/JSON-based orchestrator definitions, no workflow DSL, no task routing. This would need to be built from scratch.
+
+3. **No role-scoped tooling** — Pi's tool model is flat. There's no concept of "this agent type gets these tools." Role-based tool assignment would need to be built.
+
+4. **"YOLO by design" philosophy** — The maintainer explicitly rejects permission gates, file-scope restrictions, and safety rails. While extensions can add some of these, the architecture and philosophy push against them. Conflict prevention for parallel agents (requirement 6) is particularly at odds with Pi's design.
+
+5. **Subagent implementation is ad-hoc** — The subagent extension spawns child `pi` processes via bash, not in-process agents. This means: separate process overhead, no shared state, limited communication, 50KB output cap. Production-grade subagent management would need significant rework.
+
+6. **No LSP integration** — Building LSP support from scratch is a significant undertaking.
+
+7. **No built-in WebSocket/server mode** — While RPC mode exists, there's no persistent server/API mode that could serve as a dispatch endpoint. The SDK can be embedded, but you'd need to build the server layer.
+
+8. **Node.js-only** — The entire stack is TypeScript/Node.js. If the Dispatch system needs polyglot support, Pi cannot provide it.
+
+#### Summary Verdict
+
+Pi is **not a suitable base framework** for the Dispatch requirements as stated, primarily because it lacks any multi-agent architecture. It would require building the entire hierarchy, orchestration, routing, and role systems from scratch. What Pi does provide (session management, provider abstraction, extension system, state persistence, messaging) are valuable **components that could be used within** a Dispatch-like system, but Pi itself is the wrong substrate.
+
+A better approach would be to use `@earendil-works/pi-ai` and `@earendil-works/pi-agent-core` as **libraries** in a custom-built orchestration system, rather than trying to extend the Pi CLI into something it was never designed to be.
+
+---
+
+## Source list
+
+| # | Source | Type |
+|---|--------|------|
+| 1 | [pi.dev website](https://pi.dev) | Official website |
+| 2 | [GitHub monorepo](https://github.com/earendil-works/pi) | Source code |
+| 3 | [Coding Agent README](https://github.com/earendil-works/pi/tree/main/packages/coding-agent) | Official docs |
+| 4 | [Extensions documentation](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/extensions.md) | Official docs |
+| 5 | [Skills documentation](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/skills.md) | Official docs |
+| 6 | [SDK documentation](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/sdk.md) | Official docs |
+| 7 | [Subagent extension example](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/examples/extensions/subagent/README.md) | Example code |
+| 8 | [Maintainer's blog post](https://mariozechner.at/posts/2025-11-30-pi-coding-agent/) | Blog post |
+| 9 | [package.json (coding-agent)](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/package.json) | Source metadata |
+| 10 | [package.json (agent-core)](https://github.com/earendil-works/pi/blob/main/packages/agent/package.json) | Source metadata |
+| 11 | [Permission gate extension example](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/examples/extensions/permission-gate.ts) | Example code |
+| 12 | [Subagent extension source](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/examples/extensions/subagent/index.ts) | Example code |
+
+---
+
+## Verbatim quotes
+
+- "Pi is a minimal terminal coding harness. Adapt Pi to your workflows, not the other way around." — [pi.dev](https://pi.dev)
+- "Pi ships with powerful defaults but skips features like sub-agents and plan mode. Ask Pi to build what you want, or install a package that does it your way." — [pi.dev](https://pi.dev)
+- "No sub-agents. There's many ways to do this. Spawn pi instances via tmux, or build your own with extensions, or install a package that does it your way." — [Coding Agent README](https://github.com/earendil-works/pi/tree/main/packages/coding-agent)
+- "If I don't need it, it won't be built. And I don't need a lot of things." — [Mario Zechner's blog](https://mariozechner.at/posts/2025-11-30-pi-coding-agent/)
+- "pi runs in full YOLO mode and assumes you know what you're doing. It has unrestricted access to your filesystem and can execute any command without permission checks or safety rails." — [Mario Zechner's blog](https://mariozechner.at/posts/2025-11-30-pi-coding-agent/)
+- "Spawning multiple sub-agents to implement various features in parallel is an anti-pattern in my book and doesn't work, unless you don't care if your codebase devolves into a pile of garbage." — [Mario Zechner's blog](https://mariozechner.at/posts/2025-11-30-pi-coding-agent/)
+- "Extensions are TypeScript modules that extend pi's behavior. They can subscribe to lifecycle events, register custom tools callable by the LLM, add commands, and more." — [Extensions documentation](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/extensions.md)
+- "Pi does not and will not have a built-in plan mode." — [Mario Zechner's blog](https://mariozechner.at/posts/2025-11-30-pi-coding-agent/)
+- "pi's system prompt and tool definitions together come in below 1000 tokens." — [Mario Zechner's blog](https://mariozechner.at/posts/2025-11-30-pi-coding-agent/)
+- "Submit messages while the agent works. Enter sends a steering message (delivered after current tool, interrupts remaining tools). Alt+Enter sends a follow-up (waits until the agent finishes)." — [pi.dev](https://pi.dev)
+- "I prefer Claude Code for most of my work... Over the past few months, Claude Code has turned into a spaceship with 80% of functionality I have no use for." — [Mario Zechner's blog](https://mariozechner.at/posts/2025-11-30-pi-coding-agent/)
+
+---
+
+## Source quality flags
+
+- Source 6 (Maintainer's blog): **Personal blog post** — strong authority on Pi's design philosophy and rationale, but represents one person's opinion. Explicitly states design decisions that may not align with all use cases. The benchmark claims are specific to Pi and useful for comparison but should be taken as one data point.
+- Source 1 (pi.dev): **Marketing website** — the landing page is promotional, but the actual content is technically accurate and links to verifiable source code. Not marketing hype in the traditional sense, but does emphasize features positively.
+- No AI-generated summaries or paid content were used.
+
+---
+
+## Confidence: High
+
+Comprehensive primary source data was available: the full source code (GitHub), extensive documentation (extensions.md at 94KB, sdk.md at 32KB), the maintainer's detailed technical blog post, and the package.json metadata. The project is transparent about what it does and doesn't do. No conflicting information was found across sources.
+
+---
+
+## Gaps and open questions
+
+1. **Real-world multi-agent usage**: No evidence was found of anyone successfully building a production multi-agent orchestration layer on top of Pi. The subagent extension is explicitly labeled as an example/demo. It's unknown how well it holds up under production loads.
+
+2. **Performance with deeply hierarchical systems**: Since Pi's architecture is single-agent, there's no data on how it performs when one Pi instance orchestrates many child Pi instances. The subagent example caps at 8 tasks/4 concurrent.
+
+3. **LSP integration**: No community extensions or discussions about LSP support were found. The feasibility of building an LSP extension is theoretical.
+
+4. **Conflict prevention approaches**: While Pi's philosophy rejects permissions, there may be creative approaches using containers, directory-restricted `pi` instances, or RPC-level routing that were not explored in this research.
+
+5. **Community ecosystem size**: The Discord server exists and packages are listed on npm, but no hard data was found on how many third-party extensions/packages exist or how active the community is beyond the core maintainer.
+
+6. **Comparison with opencode**: The maintainer mentions opencode in his blog (using their models.dev data), and Pi was partly inspired by frustrations with Claude Code. But no direct architectural comparison with opencode was found — this would be valuable for evaluating which framework is a better base for the Dispatch requirements.
diff --git a/tsconfig.base.json b/tsconfig.base.json
new file mode 100644
index 0000000..a0b8514
--- /dev/null
+++ b/tsconfig.base.json
@@ -0,0 +1,20 @@
+{
+ "compilerOptions": {
+ "strict": true,
+ "target": "ESNext",
+ "module": "ESNext",
+ "moduleResolution": "bundler",
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "forceConsistentCasingInFileNames": true,
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "declaration": true,
+ "declarationMap": true,
+ "sourceMap": true,
+ "noUncheckedIndexedAccess": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "verbatimModuleSyntax": true
+ }
+}
diff --git a/vitest.config.ts b/vitest.config.ts
new file mode 100644
index 0000000..e74aab6
--- /dev/null
+++ b/vitest.config.ts
@@ -0,0 +1,7 @@
+import { defineConfig } from "vitest/config";
+
+export default defineConfig({
+ test: {
+ projects: ["packages/*"],
+ },
+});