mirror of
https://github.com/OpenRouterTeam/spawn.git
synced 2026-04-28 03:49:31 +00:00
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:
parent
bd30565d86
commit
6002e6c7f7
3 changed files with 391 additions and 0 deletions
114
CLAUDE.md
Normal file
114
CLAUDE.md
Normal 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
172
improve.sh
Executable 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
105
manifest.json
Normal 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"
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue