Add continuous improvement workflow for spawn matrix (#5)

- manifest.json: tracks agents x clouds matrix with metadata
- CLAUDE.md: instructions for Claude Code to fill gaps and discover new agents/clouds
- improve.sh: loop script that launches Claude Code to expand the matrix

Current matrix: 3 agents (claude, openclaw, nanoclaw) x 2 clouds (sprite, hetzner)
with 2 gaps remaining (hetzner/openclaw, hetzner/nanoclaw).

Co-authored-by: Sprite <noreply@sprite.dev>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
L 2026-02-07 08:47:21 -08:00 committed by GitHub
parent bd30565d86
commit 6002e6c7f7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 391 additions and 0 deletions

114
CLAUDE.md Normal file
View file

@ -0,0 +1,114 @@
# Spawn
Spawn is a matrix of **agents x clouds**. Every script provisions a cloud server, installs an agent, injects OpenRouter credentials, and drops the user into an interactive session.
## The Matrix
`manifest.json` is the source of truth. It tracks:
- **agents** — coding agents / AI tools (Claude Code, OpenClaw, NanoClaw, ...)
- **clouds** — cloud providers to run them on (Sprite, Hetzner, ...)
- **matrix** — which `cloud/agent` combinations are `"implemented"` vs `"missing"`
## How to Improve Spawn
When run via `./improve.sh`, your job is to pick ONE of these tasks and execute it:
### 1. Fill a missing matrix entry
Look at `manifest.json``matrix` for any `"missing"` entry. To implement it:
- Find the **cloud's** `lib/common.sh` — it has all the provider-specific primitives (create server, run command, upload file, interactive session)
- Find the **agent's** existing script on another cloud — it shows the install steps, config files, env vars, and launch command
- Combine them: use the cloud's primitives to execute the agent's setup steps
- The script goes at `{cloud}/{agent}.sh`
**Pattern for every script:**
```
1. Source {cloud}/lib/common.sh (local or remote fallback)
2. Authenticate with cloud provider
3. Provision server/VM
4. Wait for readiness
5. Install the agent
6. Get OpenRouter API key (env var or OAuth)
7. Inject env vars into shell config
8. Write agent-specific config files
9. Launch interactive session
```
**OpenRouter injection is mandatory.** Every agent script MUST:
- Set `OPENROUTER_API_KEY` in the shell environment
- Set provider-specific env vars (e.g., `ANTHROPIC_BASE_URL=https://openrouter.ai/api`)
- These come from the agent's `env` field in `manifest.json`
### 2. Add a new agent
Research coding agents, AI CLI tools, or AI-powered dev tools. To add one:
1. Add an entry to `manifest.json``agents` with: name, description, url, install command, launch command, and env vars needed for OpenRouter
2. Add `"missing"` entries to the matrix for every existing cloud
3. Implement the script for at least one cloud
4. Update `README.md`
**Where to find new agents:**
- GitHub trending in AI/coding categories
- OpenRouter's ecosystem
- HuggingFace agent frameworks
- CLI tools that accept `ANTHROPIC_API_KEY` or `OPENAI_API_KEY` (these work with OpenRouter via base URL override)
### 3. Add a new cloud provider
Research cloud providers with API-based provisioning. To add one:
1. Create `{cloud}/lib/common.sh` with the provider's primitives:
- Auth/token management (env var → config file → prompt)
- Server creation (API call or CLI)
- SSH/exec connectivity
- File upload
- Interactive session
- Server destruction
2. Add an entry to `manifest.json``clouds`
3. Add `"missing"` entries to the matrix for every existing agent
4. Implement at least one agent script
5. Update `README.md`
**Good candidate clouds** have:
- REST API or simple CLI for provisioning
- SSH access to the created server
- Cloud-init or similar userdata support
- Pay-per-hour pricing (so users can destroy after use)
### 4. Extend tests
`test/run.sh` contains the test harness. When adding a new cloud or agent:
- Add mock functions for the cloud's CLI/API calls
- Add per-script assertions matching the agent's setup steps
- Run `bash test/run.sh` to verify
## File Structure Convention
```
spawn/
{cloud}/
lib/common.sh # Cloud-specific shared functions
{agent}.sh # One script per agent
manifest.json # The matrix (source of truth)
improve.sh # Run this to trigger one improvement cycle
test/run.sh # Test harness
README.md # User-facing docs
```
## Script Conventions
- `#!/bin/bash` + `set -e`
- Source `lib/common.sh` with local-first, remote-fallback pattern
- Use `OPENROUTER_API_KEY` env var to skip OAuth when set
- All env vars documented in README.md under the relevant section
- Remote fallback URL: `https://raw.githubusercontent.com/OpenRouterTeam/spawn/main/{path}`
- Scripts must be runnable via: `bash <(curl -fsSL https://openrouter.ai/lab/spawn/{cloud}/{agent}.sh)`
## After Each Change
1. Update `manifest.json` matrix status to `"implemented"`
2. Update `README.md` with usage instructions
3. Run `bash test/run.sh` if tests exist for the cloud
4. Commit with a descriptive message

172
improve.sh Executable file
View file

@ -0,0 +1,172 @@
#!/bin/bash
# Continuous improvement loop for spawn
#
# Each iteration:
# 1. Reads manifest.json to find gaps
# 2. Launches Claude Code to fill one gap (or discover new agents/clouds)
# 3. Commits the result
# 4. Repeats
#
# Usage:
# ./improve.sh # run one cycle
# ./improve.sh --loop # run continuously until matrix is full
# ./improve.sh --discover # focus on discovering new agents/clouds
set -uo pipefail
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
MANIFEST="$REPO_ROOT/manifest.json"
MODE="${1:-once}"
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
log_info() { echo -e "${GREEN}[improve]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[improve]${NC} $1"; }
log_error() { echo -e "${RED}[improve]${NC} $1"; }
# Check prerequisites
if ! command -v claude &>/dev/null; then
log_error "Claude Code is required. Install: curl -fsSL https://claude.ai/install.sh | bash"
exit 1
fi
if ! command -v python3 &>/dev/null; then
log_error "python3 is required for manifest parsing"
exit 1
fi
if [[ ! -f "$MANIFEST" ]]; then
log_error "manifest.json not found at $MANIFEST"
exit 1
fi
# Get the next missing matrix entry
get_next_gap() {
python3 -c "
import json
m = json.load(open('$MANIFEST'))
for key, status in m.get('matrix', {}).items():
if status == 'missing':
print(key)
break
"
}
# Count missing entries
count_gaps() {
python3 -c "
import json
m = json.load(open('$MANIFEST'))
print(sum(1 for v in m.get('matrix', {}).values() if v == 'missing'))
"
}
# Build the prompt for Claude Code
build_fill_prompt() {
local gap="$1"
local cloud="${gap%%/*}"
local agent="${gap##*/}"
cat <<EOF
Read CLAUDE.md and manifest.json to understand the project.
Your task: implement the missing script "$cloud/$agent.sh"
This means creating a spawn script that provisions a $cloud server and installs $agent on it with OpenRouter credentials.
Steps:
1. Read the existing $cloud/lib/common.sh to understand the cloud primitives available
2. Read an existing $agent.sh on another cloud (check sprite/$agent.sh or hetzner/$agent.sh) to understand the agent's install/config steps
3. Write $cloud/$agent.sh combining the two
4. Update manifest.json to mark "$cloud/$agent" as "implemented"
5. Update README.md with usage instructions for this combination
6. Commit the changes
Follow the patterns in CLAUDE.md exactly. OpenRouter environment injection is mandatory.
EOF
}
build_discover_prompt() {
cat <<EOF
Read CLAUDE.md and manifest.json to understand the project.
Your task: discover and add ONE new agent or cloud provider to the spawn matrix.
For a new AGENT:
- Search for popular open-source AI coding agents, CLI tools, or dev assistants
- It must be installable via a single command (curl, npm, pip, etc.)
- It must accept API keys via environment variables (so we can inject OpenRouter)
- Add it to manifest.json with full metadata
- Add "missing" matrix entries for all existing clouds
- Implement it on at least one cloud
- Update README.md
For a new CLOUD:
- Look for cloud providers with simple REST APIs or CLIs for server provisioning
- Must support SSH access and cloud-init (or equivalent userdata)
- Must have pay-per-hour or pay-per-minute pricing
- Create the cloud's lib/common.sh with all primitives
- Add it to manifest.json
- Add "missing" matrix entries for all existing agents
- Implement at least one agent on it
- Update README.md
Pick whichever (agent or cloud) you think adds the most value. Commit when done.
EOF
}
run_cycle() {
local gap=$(get_next_gap)
local gaps=$(count_gaps)
if [[ -n "$gap" && "$MODE" != "--discover" ]]; then
log_info "Found gap: $gap ($gaps total missing)"
log_warn "Launching Claude Code to implement $gap..."
echo ""
local prompt=$(build_fill_prompt "$gap")
(cd "$REPO_ROOT" && claude --print -p "$prompt")
return $?
elif [[ "$gaps" -eq 0 && "$MODE" != "--discover" ]]; then
log_info "Matrix is full! Switching to discovery mode."
local prompt=$(build_discover_prompt)
(cd "$REPO_ROOT" && claude --print -p "$prompt")
return $?
else
log_info "Discovery mode: looking for new agents or clouds..."
local prompt=$(build_discover_prompt)
(cd "$REPO_ROOT" && claude --print -p "$prompt")
return $?
fi
}
# Main
log_info "Spawn Improvement Loop"
log_info "Mode: $MODE"
log_info "Gaps: $(count_gaps) missing matrix entries"
echo ""
case "$MODE" in
--loop)
cycle=1
while true; do
log_info "=== Cycle $cycle ==="
run_cycle || {
log_error "Cycle $cycle failed, pausing 10s..."
sleep 10
}
((cycle++))
log_info "Pausing 5s before next cycle..."
sleep 5
done
;;
--discover)
run_cycle
;;
*)
run_cycle
;;
esac

105
manifest.json Normal file
View file

@ -0,0 +1,105 @@
{
"agents": {
"claude": {
"name": "Claude Code",
"description": "Anthropic's CLI coding agent",
"url": "https://claude.ai",
"install": "curl -fsSL https://claude.ai/install.sh | bash",
"launch": "claude",
"env": {
"ANTHROPIC_BASE_URL": "https://openrouter.ai/api",
"ANTHROPIC_AUTH_TOKEN": "${OPENROUTER_API_KEY}",
"ANTHROPIC_API_KEY": "",
"CLAUDE_CODE_SKIP_ONBOARDING": "1",
"CLAUDE_CODE_ENABLE_TELEMETRY": "0"
},
"config_files": {
"~/.claude/settings.json": {
"theme": "dark",
"permissions": {
"defaultMode": "bypassPermissions",
"dangerouslySkipPermissions": true
}
},
"~/.claude.json": {
"hasCompletedOnboarding": true,
"bypassPermissionsModeAccepted": true
}
}
},
"openclaw": {
"name": "OpenClaw",
"description": "OpenRouter's agent framework with gateway + TUI",
"url": "https://github.com/OpenRouterTeam/openclaw",
"install": "bun install -g openclaw",
"launch": "openclaw tui",
"pre_launch": "nohup openclaw gateway > /tmp/openclaw-gateway.log 2>&1 &",
"env": {
"OPENROUTER_API_KEY": "${OPENROUTER_API_KEY}",
"ANTHROPIC_API_KEY": "${OPENROUTER_API_KEY}",
"ANTHROPIC_BASE_URL": "https://openrouter.ai/api"
},
"interactive_prompts": {
"model_id": {
"prompt": "Enter model ID",
"default": "openrouter/auto"
}
}
},
"nanoclaw": {
"name": "NanoClaw",
"description": "WhatsApp-based AI agent",
"url": "https://github.com/gavrielc/nanoclaw",
"install": "git clone https://github.com/gavrielc/nanoclaw.git ~/nanoclaw && cd ~/nanoclaw && npm install && npm run build",
"launch": "cd ~/nanoclaw && npm run dev",
"deps": ["tsx"],
"env": {
"OPENROUTER_API_KEY": "${OPENROUTER_API_KEY}",
"ANTHROPIC_API_KEY": "${OPENROUTER_API_KEY}",
"ANTHROPIC_BASE_URL": "https://openrouter.ai/api"
},
"dotenv": {
"path": "~/nanoclaw/.env",
"values": {
"ANTHROPIC_API_KEY": "${OPENROUTER_API_KEY}"
}
},
"notes": "Requires WhatsApp QR code scan for authentication"
}
},
"clouds": {
"sprite": {
"name": "Sprite",
"description": "Sprites.dev managed VMs with CLI",
"url": "https://sprites.dev",
"type": "cli",
"auth": "sprite login",
"provision_method": "sprite create",
"exec_method": "sprite exec",
"interactive_method": "sprite exec -tty"
},
"hetzner": {
"name": "Hetzner Cloud",
"description": "Hetzner Cloud servers via REST API",
"url": "https://www.hetzner.com/cloud/",
"type": "api",
"auth": "HCLOUD_TOKEN",
"provision_method": "POST /v1/servers with cloud-init",
"exec_method": "ssh root@IP",
"interactive_method": "ssh -t root@IP",
"defaults": {
"server_type": "cx22",
"location": "fsn1",
"image": "ubuntu-24.04"
}
}
},
"matrix": {
"sprite/claude": "implemented",
"sprite/openclaw": "implemented",
"sprite/nanoclaw": "implemented",
"hetzner/claude": "implemented",
"hetzner/openclaw": "missing",
"hetzner/nanoclaw": "missing"
}
}