From 950abfb6432e3320dfd1e8ea0654f8e6179740d6 Mon Sep 17 00:00:00 2001 From: johnnyfish Date: Mon, 9 Feb 2026 12:46:46 +0000 Subject: [PATCH] =?UTF-8?q?refactor:=20dynamic=20agent=20onboarding=20?= =?UTF-8?q?=E2=80=94=20replace=20hardcoded=20agent=20config=20with=20Conve?= =?UTF-8?q?x-driven=20model?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Move agent definitions from hardcoded AGENTS array to Convex database - Watcher now seeds defaults only when Convex is empty, reads agents from DB for cron setup - Add agentType, cronSchedule, model fields to agents schema and upsert mutation - Deduplicate workspace templates into base/lead and base/worker with envsubst - Add `clawe agent:onboard` CLI command for live agent addition (no rebuild) - Add build-time scripts: add-agent.sh, validate-agent.sh - Delete per-agent template duplication (clawe, inky, pixel, scout workspace files) - Document onboarding workflow in docs/AGENT_ONBOARDING.md --- README.md | 38 ++- apps/watcher/src/index.ts | 109 ++++--- docker-compose.yml | 2 + docker/openclaw/scripts/init-agents.sh | 90 ++++-- .../{workspaces/clawe => base/lead}/AGENTS.md | 13 +- .../clawe => base/lead}/BOOTSTRAP.md | 0 .../clawe => base/lead}/HEARTBEAT.md | 8 +- docker/openclaw/templates/base/lead/MEMORY.md | 26 ++ docker/openclaw/templates/base/lead/TOOLS.md | 18 ++ .../{workspaces/clawe => base/lead}/USER.md | 2 + .../inky => base/worker}/AGENTS.md | 10 +- .../templates/base/worker/HEARTBEAT.md | 43 +++ .../openclaw/templates/base/worker/MEMORY.md | 19 ++ .../openclaw/templates/base/worker/TOOLS.md | 20 ++ .../{workspaces/inky => base/worker}/USER.md | 2 + .../openclaw/templates/config.template.json | 8 +- .../templates/workspaces/clawe/MEMORY.md | 31 -- .../templates/workspaces/clawe/TOOLS.md | 16 - .../templates/workspaces/inky/HEARTBEAT.md | 41 --- .../templates/workspaces/inky/MEMORY.md | 23 -- .../templates/workspaces/inky/TOOLS.md | 11 - .../workspaces/{clawe => main}/SOUL.md | 0 .../templates/workspaces/pixel/AGENTS.md | 68 ---- .../templates/workspaces/pixel/HEARTBEAT.md | 41 --- .../templates/workspaces/pixel/MEMORY.md | 23 -- .../templates/workspaces/pixel/TOOLS.md | 15 - .../templates/workspaces/pixel/USER.md | 7 - .../templates/workspaces/scout/AGENTS.md | 68 ---- .../templates/workspaces/scout/HEARTBEAT.md | 41 --- .../templates/workspaces/scout/MEMORY.md | 23 -- .../templates/workspaces/scout/TOOLS.md | 15 - .../templates/workspaces/scout/USER.md | 7 - docs/AGENT_ONBOARDING.md | 149 +++++++++ packages/backend/convex/agents.ts | 9 + packages/backend/convex/schema.ts | 7 + packages/cli/src/commands/agent-onboard.ts | 298 ++++++++++++++++++ packages/cli/src/index.ts | 25 ++ scripts/add-agent.sh | 191 +++++++++++ scripts/validate-agent.sh | 159 ++++++++++ 39 files changed, 1131 insertions(+), 545 deletions(-) rename docker/openclaw/templates/{workspaces/clawe => base/lead}/AGENTS.md (87%) rename docker/openclaw/templates/{workspaces/clawe => base/lead}/BOOTSTRAP.md (100%) rename docker/openclaw/templates/{workspaces/clawe => base/lead}/HEARTBEAT.md (72%) create mode 100644 docker/openclaw/templates/base/lead/MEMORY.md create mode 100644 docker/openclaw/templates/base/lead/TOOLS.md rename docker/openclaw/templates/{workspaces/clawe => base/lead}/USER.md (77%) rename docker/openclaw/templates/{workspaces/inky => base/worker}/AGENTS.md (83%) create mode 100644 docker/openclaw/templates/base/worker/HEARTBEAT.md create mode 100644 docker/openclaw/templates/base/worker/MEMORY.md create mode 100644 docker/openclaw/templates/base/worker/TOOLS.md rename docker/openclaw/templates/{workspaces/inky => base/worker}/USER.md (56%) delete mode 100644 docker/openclaw/templates/workspaces/clawe/MEMORY.md delete mode 100644 docker/openclaw/templates/workspaces/clawe/TOOLS.md delete mode 100644 docker/openclaw/templates/workspaces/inky/HEARTBEAT.md delete mode 100644 docker/openclaw/templates/workspaces/inky/MEMORY.md delete mode 100644 docker/openclaw/templates/workspaces/inky/TOOLS.md rename docker/openclaw/templates/workspaces/{clawe => main}/SOUL.md (100%) delete mode 100644 docker/openclaw/templates/workspaces/pixel/AGENTS.md delete mode 100644 docker/openclaw/templates/workspaces/pixel/HEARTBEAT.md delete mode 100644 docker/openclaw/templates/workspaces/pixel/MEMORY.md delete mode 100644 docker/openclaw/templates/workspaces/pixel/TOOLS.md delete mode 100644 docker/openclaw/templates/workspaces/pixel/USER.md delete mode 100644 docker/openclaw/templates/workspaces/scout/AGENTS.md delete mode 100644 docker/openclaw/templates/workspaces/scout/HEARTBEAT.md delete mode 100644 docker/openclaw/templates/workspaces/scout/MEMORY.md delete mode 100644 docker/openclaw/templates/workspaces/scout/TOOLS.md delete mode 100644 docker/openclaw/templates/workspaces/scout/USER.md create mode 100644 docs/AGENT_ONBOARDING.md create mode 100644 packages/cli/src/commands/agent-onboard.ts create mode 100755 scripts/add-agent.sh create mode 100755 scripts/validate-agent.sh diff --git a/README.md b/README.md index ef6ede3..afc8fe0 100644 --- a/README.md +++ b/README.md @@ -183,28 +183,32 @@ Each agent has an isolated workspace with: ### Adding New Agents -1. Create workspace template in `docker/openclaw/templates/workspaces/{name}/` -2. Add agent to `docker/openclaw/templates/config.template.json` -3. Add agent to watcher's `AGENTS` array in `apps/watcher/src/index.ts` -4. Rebuild: `docker compose build && docker compose up -d` +**Live (no rebuild):** + +```bash +clawe agent:onboard coder Coder Developer --emoji 💻 --cron "15,45 * * * *" +``` + +**Build-time (persists across rebuilds):** + +```bash +./scripts/add-agent.sh coder Coder 💻 Developer "15,45 * * * *" +docker compose build --no-cache openclaw && docker compose up -d +clawe agent:onboard coder Coder Developer --emoji 💻 --cron "15,45 * * * *" +``` + +See [docs/AGENT_ONBOARDING.md](docs/AGENT_ONBOARDING.md) for the full guide. ### Changing Heartbeat Schedules -Edit the `AGENTS` array in `apps/watcher/src/index.ts`: - -```typescript -const AGENTS = [ - { - id: "main", - name: "Clawe", - emoji: "🦞", - role: "Squad Lead", - cron: "0 * * * *", - }, - // Add or modify agents here -]; +Heartbeat schedules are stored in Convex (per agent). Update them via the CLI: + +```bash +clawe agent:onboard --cron "new schedule" ``` +Or update the agent's `cronSchedule` field directly in Convex. Changes take effect on next watcher restart. + ## Development ```bash diff --git a/apps/watcher/src/index.ts b/apps/watcher/src/index.ts index 6bbec2a..79fdc29 100644 --- a/apps/watcher/src/index.ts +++ b/apps/watcher/src/index.ts @@ -1,8 +1,12 @@ /** * Clawe Notification Watcher * - * 1. On startup: ensures heartbeat crons are configured for all agents - * 2. Continuously: polls Convex for undelivered notifications and delivers them + * 1. On startup: registers default agents if none exist + * 2. On startup: ensures heartbeat crons are configured for all agents + * 3. Continuously: polls Convex for undelivered notifications and delivers them + * + * Agents are read from Convex — no hardcoded list. + * New agents can be added at runtime via CLI or API. * * Environment variables: * CONVEX_URL - Convex deployment URL @@ -26,14 +30,15 @@ validateEnv(); const convex = new ConvexHttpClient(config.convexUrl); -// Agent configuration -const AGENTS = [ +// Default agents — only used on first run when Convex is empty +const DEFAULT_AGENTS = [ { id: "main", name: "Clawe", emoji: "🦞", role: "Squad Lead", cron: "0,15,30,45 * * * *", + agentType: "lead" as const, }, { id: "inky", @@ -41,6 +46,7 @@ const AGENTS = [ emoji: "✍️", role: "Writer", cron: "3,18,33,48 * * * *", + agentType: "worker" as const, }, { id: "pixel", @@ -48,6 +54,7 @@ const AGENTS = [ emoji: "🎨", role: "Designer", cron: "7,22,37,52 * * * *", + agentType: "worker" as const, }, { id: "scout", @@ -55,6 +62,7 @@ const AGENTS = [ emoji: "🔍", role: "SEO", cron: "11,26,41,56 * * * *", + agentType: "worker" as const, }, ]; @@ -108,31 +116,25 @@ async function withRetry( } /** - * Register all agents in Convex (upsert - creates or updates) + * Register default agents in Convex (only if no agents exist yet) */ -async function registerAgents(): Promise { - console.log("[watcher] Registering agents in Convex..."); - console.log("[watcher] CONVEX_URL:", config.convexUrl); - - // Try to register first agent with retry (waits for Convex to be ready) - const firstAgent = AGENTS[0]; - if (firstAgent) { - await withRetry(async () => { - const sessionKey = `agent:${firstAgent.id}:main`; - await convex.mutation(api.agents.upsert, { - name: firstAgent.name, - role: firstAgent.role, - sessionKey, - emoji: firstAgent.emoji, - }); - console.log( - `[watcher] ✓ ${firstAgent.name} ${firstAgent.emoji} registered (${sessionKey})`, - ); - }, "Convex connection"); +async function registerDefaultAgents(): Promise { + console.log("[watcher] Checking for existing agents in Convex..."); + + const existingAgents = await withRetry(async () => { + return await convex.query(api.agents.list, {}); + }, "Convex connection"); + + if (existingAgents.length > 0) { + console.log( + `[watcher] Found ${existingAgents.length} agent(s) in Convex. Skipping default registration.`, + ); + return; } - // Register remaining agents (Convex is now ready) - for (const agent of AGENTS.slice(1)) { + console.log("[watcher] No agents found. Registering defaults..."); + + for (const agent of DEFAULT_AGENTS) { const sessionKey = `agent:${agent.id}:main`; try { @@ -141,6 +143,8 @@ async function registerAgents(): Promise { role: agent.role, sessionKey, emoji: agent.emoji, + agentType: agent.agentType, + cronSchedule: agent.cron, }); console.log( `[watcher] ✓ ${agent.name} ${agent.emoji} registered (${sessionKey})`, @@ -153,16 +157,21 @@ async function registerAgents(): Promise { } } - console.log("[watcher] Agent registration complete.\n"); + console.log("[watcher] Default agent registration complete.\n"); } /** - * Setup heartbeat crons for all agents (if not already configured) + * Setup heartbeat crons for all agents from Convex */ async function setupCrons(): Promise { - console.log("[watcher] Checking heartbeat crons..."); + console.log("[watcher] Syncing heartbeat crons..."); + + // Get agents from Convex + const agents = await withRetry(async () => { + return await convex.query(api.agents.list, {}); + }, "Convex agent list"); - // Retry getting cron list (waits for OpenClaw to be ready) + // Get existing crons from OpenClaw const result = await withRetry(async () => { const res = await cronList(); if (!res.ok) { @@ -175,26 +184,42 @@ async function setupCrons(): Promise { result.result.details.jobs.map((j: CronJob) => j.name), ); - for (const agent of AGENTS) { - const cronName = `${agent.id}-heartbeat`; + for (const agent of agents) { + // Extract agent ID from sessionKey (agent::main) + const agentId = agent.sessionKey.split(":")[1]; + if (!agentId) continue; + + const cronSchedule = agent.cronSchedule; + if (!cronSchedule) { + console.log( + `[watcher] ⏭ ${agent.name} ${agent.emoji || ""} — no cron schedule, skipping`, + ); + continue; + } + + const cronName = `${agentId}-heartbeat`; if (existingNames.has(cronName)) { - console.log(`[watcher] ✓ ${agent.name} ${agent.emoji} heartbeat exists`); + console.log( + `[watcher] ✓ ${agent.name} ${agent.emoji || ""} heartbeat exists`, + ); continue; } - console.log(`[watcher] Adding ${agent.name} ${agent.emoji} heartbeat...`); + console.log( + `[watcher] Adding ${agent.name} ${agent.emoji || ""} heartbeat...`, + ); const job: CronAddJob = { name: cronName, - agentId: agent.id, + agentId: agentId, enabled: true, - schedule: { kind: "cron", expr: agent.cron }, + schedule: { kind: "cron", expr: cronSchedule }, sessionTarget: "isolated", payload: { kind: "agentTurn", message: HEARTBEAT_MESSAGE, - model: "anthropic/claude-sonnet-4-20250514", + model: agent.model || "anthropic/claude-sonnet-4-20250514", timeoutSeconds: 600, }, delivery: { mode: "none" }, @@ -203,7 +228,7 @@ async function setupCrons(): Promise { const addResult = await cronAdd(job); if (addResult.ok) { console.log( - `[watcher] ✓ ${agent.name} ${agent.emoji} heartbeat: ${agent.cron}`, + `[watcher] ✓ ${agent.name} ${agent.emoji || ""} heartbeat: ${cronSchedule}`, ); } else { console.error( @@ -213,7 +238,7 @@ async function setupCrons(): Promise { } } - console.log("[watcher] Cron setup complete.\n"); + console.log("[watcher] Cron sync complete.\n"); } /** @@ -322,10 +347,10 @@ async function main(): Promise { console.log(`[watcher] OpenClaw: ${config.openclawUrl}`); console.log(`[watcher] Poll interval: ${POLL_INTERVAL_MS}ms\n`); - // Register agents in Convex - await registerAgents(); + // Register default agents if Convex is empty + await registerDefaultAgents(); - // Setup crons on startup + // Setup crons from Convex agent data await setupCrons(); console.log("[watcher] Starting notification delivery loop...\n"); diff --git a/docker-compose.yml b/docker-compose.yml index 00dd753..6d80ebf 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -29,6 +29,8 @@ services: - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY} - OPENAI_API_KEY=${OPENAI_API_KEY:-} - CONVEX_URL=${CONVEX_URL} + - OPENCLAW_DATA_DIR=/data + - CLAWE_TEMPLATES_DIR=/opt/clawe/templates volumes: - openclaw-data:/data healthcheck: diff --git a/docker/openclaw/scripts/init-agents.sh b/docker/openclaw/scripts/init-agents.sh index 7edfb88..9fa8042 100644 --- a/docker/openclaw/scripts/init-agents.sh +++ b/docker/openclaw/scripts/init-agents.sh @@ -2,9 +2,11 @@ set -e # Clawe Agent Initialization Script -# This script initializes agent workspaces and shared state +# Generates agent workspaces from base templates + agent-specific SOUL.md TEMPLATES_DIR="/opt/clawe/templates" +BASE_DIR="$TEMPLATES_DIR/base" +WORKSPACES_DIR="$TEMPLATES_DIR/workspaces" DATA_DIR="/data" echo "🦞 Initializing Clawe agents..." @@ -15,41 +17,61 @@ mkdir -p "$DATA_DIR/shared" cp -r "$TEMPLATES_DIR/shared/"* "$DATA_DIR/shared/" echo " ✓ Shared state initialized" -# Initialize Clawe (lead) workspace -echo " → Creating Clawe workspace..." -mkdir -p "$DATA_DIR/workspace/memory" -cp -r "$TEMPLATES_DIR/workspaces/clawe/"* "$DATA_DIR/workspace/" -ln -sf "$DATA_DIR/shared" "$DATA_DIR/workspace/shared" -echo " ✓ Clawe workspace initialized" - -# Initialize Inky (writer) workspace -echo " → Creating Inky workspace..." -mkdir -p "$DATA_DIR/workspace-inky/memory" -cp -r "$TEMPLATES_DIR/workspaces/inky/"* "$DATA_DIR/workspace-inky/" -ln -sf "$DATA_DIR/shared" "$DATA_DIR/workspace-inky/shared" -echo " ✓ Inky workspace initialized" - -# Initialize Pixel (designer) workspace -echo " → Creating Pixel workspace..." -mkdir -p "$DATA_DIR/workspace-pixel/memory" -mkdir -p "$DATA_DIR/workspace-pixel/assets" -cp -r "$TEMPLATES_DIR/workspaces/pixel/"* "$DATA_DIR/workspace-pixel/" -ln -sf "$DATA_DIR/shared" "$DATA_DIR/workspace-pixel/shared" -echo " ✓ Pixel workspace initialized" - -# Initialize Scout (SEO) workspace -echo " → Creating Scout workspace..." -mkdir -p "$DATA_DIR/workspace-scout/memory" -mkdir -p "$DATA_DIR/workspace-scout/research" -cp -r "$TEMPLATES_DIR/workspaces/scout/"* "$DATA_DIR/workspace-scout/" -ln -sf "$DATA_DIR/shared" "$DATA_DIR/workspace-scout/shared" -echo " ✓ Scout workspace initialized" +# ────────────────────────────────────────────── +# init_agent +# type: "lead" or "worker" +# ────────────────────────────────────────────── +init_agent() { + AGENT_ID="$1" + AGENT_NAME="$2" + AGENT_EMOJI="$3" + AGENT_ROLE="$4" + AGENT_TYPE="${5:-worker}" + + # Workspace path: lead uses /data/workspace, workers use /data/workspace- + if [ "$AGENT_TYPE" = "lead" ]; then + WS_DIR="$DATA_DIR/workspace" + else + WS_DIR="$DATA_DIR/workspace-$AGENT_ID" + fi + + echo " → Creating $AGENT_NAME $AGENT_EMOJI workspace..." + mkdir -p "$WS_DIR/memory" + + # Export variables for envsubst + export AGENT_ID AGENT_NAME AGENT_EMOJI AGENT_ROLE + + # Generate files from base templates + for template in "$BASE_DIR/$AGENT_TYPE"/*.md; do + filename=$(basename "$template") + envsubst '${AGENT_ID} ${AGENT_NAME} ${AGENT_EMOJI} ${AGENT_ROLE}' < "$template" > "$WS_DIR/$filename" + done + + # Copy agent-specific files (SOUL.md and any overrides) + if [ -d "$WORKSPACES_DIR/$AGENT_ID" ]; then + cp -r "$WORKSPACES_DIR/$AGENT_ID/"* "$WS_DIR/" + fi + + # Symlink shared directory + ln -sf "$DATA_DIR/shared" "$WS_DIR/shared" + + echo " ✓ $AGENT_NAME workspace initialized" +} + +# ────────────────────────────────────────────── +# Initialize all agents +# ────────────────────────────────────────────── + +init_agent "main" "Clawe" "🦞" "Squad Lead" "lead" +init_agent "inky" "Inky" "✍️" "Content Writer" "worker" +init_agent "pixel" "Pixel" "🎨" "Graphic Designer" "worker" +init_agent "scout" "Scout" "🔍" "SEO Specialist" "worker" echo "✅ Agent initialization complete!" echo "" echo "Squad:" -echo " 🦞 Clawe (Lead) → $DATA_DIR/workspace" -echo " ✍️ Inky (Writer) → $DATA_DIR/workspace-inky" -echo " 🎨 Pixel (Designer) → $DATA_DIR/workspace-pixel" -echo " 🔍 Scout (SEO) → $DATA_DIR/workspace-scout" +echo " 🦞 Clawe (Squad Lead) → $DATA_DIR/workspace" +echo " ✍️ Inky (Content Writer) → $DATA_DIR/workspace-inky" +echo " 🎨 Pixel (Graphic Designer) → $DATA_DIR/workspace-pixel" +echo " 🔍 Scout (SEO Specialist) → $DATA_DIR/workspace-scout" echo "" diff --git a/docker/openclaw/templates/workspaces/clawe/AGENTS.md b/docker/openclaw/templates/base/lead/AGENTS.md similarity index 87% rename from docker/openclaw/templates/workspaces/clawe/AGENTS.md rename to docker/openclaw/templates/base/lead/AGENTS.md index 6f07264..a7f2e96 100644 --- a/docker/openclaw/templates/workspaces/clawe/AGENTS.md +++ b/docker/openclaw/templates/base/lead/AGENTS.md @@ -21,14 +21,14 @@ Don't ask permission. Just do it. Use the `clawe` CLI for all squad communication. See `shared/CLAWE-CLI.md` for full documentation. -Your session key: `agent:main:main` +Your session key: `agent:${AGENT_ID}:main` Quick commands: ```bash -clawe notify agent:inky:main "message" --from agent:main:main -clawe task:create "title" --assign agent:inky:main --by agent:main:main -clawe check agent:main:main +clawe notify agent:inky:main "message" --from agent:${AGENT_ID}:main +clawe task:create "title" --assign agent:inky:main --by agent:${AGENT_ID}:main +clawe check agent:${AGENT_ID}:main clawe squad ``` @@ -85,9 +85,4 @@ When assigning work, be specific about: - Any deadlines or constraints **IMPORTANT: You are a coordinator, NOT a worker.** - -- Writing → Assign to Inky -- Research/SEO → Assign to Scout -- Design/Visuals → Assign to Pixel - If a specialist isn't responding or available, **tell your human** — don't do the work yourself. You delegate, you don't fill in. diff --git a/docker/openclaw/templates/workspaces/clawe/BOOTSTRAP.md b/docker/openclaw/templates/base/lead/BOOTSTRAP.md similarity index 100% rename from docker/openclaw/templates/workspaces/clawe/BOOTSTRAP.md rename to docker/openclaw/templates/base/lead/BOOTSTRAP.md diff --git a/docker/openclaw/templates/workspaces/clawe/HEARTBEAT.md b/docker/openclaw/templates/base/lead/HEARTBEAT.md similarity index 72% rename from docker/openclaw/templates/workspaces/clawe/HEARTBEAT.md rename to docker/openclaw/templates/base/lead/HEARTBEAT.md index 5a4d4ad..d179dc3 100644 --- a/docker/openclaw/templates/workspaces/clawe/HEARTBEAT.md +++ b/docker/openclaw/templates/base/lead/HEARTBEAT.md @@ -1,10 +1,10 @@ -# HEARTBEAT.md — Clawe's Check-in +# HEARTBEAT.md — ${AGENT_NAME}'s Check-in When you wake up, do the following: ## On Wake -1. Run: `clawe check agent:main:main` +1. Run: `clawe check agent:${AGENT_ID}:main` 2. Read `shared/WORKING.md` for team state 3. Check for tasks in "review" status @@ -31,7 +31,7 @@ When tasks are in "review" status: Create and assign tasks: ```bash -clawe task:create "Task title" --assign agent:inky:main --by agent:main:main +clawe task:create "Task title" --assign agent::main --by agent:${AGENT_ID}:main ``` ## If Nothing to Do @@ -40,4 +40,4 @@ Reply: `HEARTBEAT_OK` --- -**I am Clawe 🦞 — squad lead. Review, coordinate, unblock.** +**I am ${AGENT_NAME} ${AGENT_EMOJI} — squad lead. Review, coordinate, unblock.** diff --git a/docker/openclaw/templates/base/lead/MEMORY.md b/docker/openclaw/templates/base/lead/MEMORY.md new file mode 100644 index 0000000..0ace91f --- /dev/null +++ b/docker/openclaw/templates/base/lead/MEMORY.md @@ -0,0 +1,26 @@ +# MEMORY.md — ${AGENT_NAME} Long-term Memory + +Curated knowledge. Lessons learned. Stable facts. + +--- + +## Identity + +- **Name:** ${AGENT_NAME} ${AGENT_EMOJI} +- **Role:** Squad Lead + +## The Squad + +_Updated automatically on first heartbeat. Or update manually._ + +## Lessons Learned + +_Document important lessons here as you learn them._ + +## Key Decisions + +_Record significant decisions and their reasoning._ + +--- + +**Last updated:** Initial setup diff --git a/docker/openclaw/templates/base/lead/TOOLS.md b/docker/openclaw/templates/base/lead/TOOLS.md new file mode 100644 index 0000000..70bcda2 --- /dev/null +++ b/docker/openclaw/templates/base/lead/TOOLS.md @@ -0,0 +1,18 @@ +# TOOLS.md — Local Notes + +This file is for environment-specific notes — the stuff that's unique to your setup. + +## Clawe CLI + +```bash +clawe check agent:${AGENT_ID}:main # Check notifications +clawe tasks agent:${AGENT_ID}:main # List my tasks +clawe task:create "title" --assign # Create & assign task +clawe task:view # View task details +clawe squad # Squad status +clawe feed # Activity feed +``` + +--- + +Add whatever helps you do your job. This is your cheat sheet. diff --git a/docker/openclaw/templates/workspaces/clawe/USER.md b/docker/openclaw/templates/base/lead/USER.md similarity index 77% rename from docker/openclaw/templates/workspaces/clawe/USER.md rename to docker/openclaw/templates/base/lead/USER.md index 0da2118..7523750 100644 --- a/docker/openclaw/templates/workspaces/clawe/USER.md +++ b/docker/openclaw/templates/base/lead/USER.md @@ -10,6 +10,8 @@ _Fill this in as you learn about who you're helping._ What are they working on? What do they care about? +Check business context with `clawe business:get` for saved details. + --- _Update this as you learn more._ diff --git a/docker/openclaw/templates/workspaces/inky/AGENTS.md b/docker/openclaw/templates/base/worker/AGENTS.md similarity index 83% rename from docker/openclaw/templates/workspaces/inky/AGENTS.md rename to docker/openclaw/templates/base/worker/AGENTS.md index c2bb32d..a138243 100644 --- a/docker/openclaw/templates/workspaces/inky/AGENTS.md +++ b/docker/openclaw/templates/base/worker/AGENTS.md @@ -16,15 +16,15 @@ Don't ask permission. Just do it. Use the `clawe` CLI for all squad communication. See `shared/CLAWE-CLI.md` for full documentation. -Your session key: `agent:inky:main` +Your session key: `agent:${AGENT_ID}:main` Quick commands: ```bash -clawe check agent:inky:main -clawe tasks agent:inky:main -clawe task:status in_progress --by agent:inky:main -clawe notify agent:main:main "message" --from agent:inky:main +clawe check agent:${AGENT_ID}:main +clawe tasks agent:${AGENT_ID}:main +clawe task:status in_progress --by agent:${AGENT_ID}:main +clawe notify agent:main:main "message" --from agent:${AGENT_ID}:main ``` ## Memory diff --git a/docker/openclaw/templates/base/worker/HEARTBEAT.md b/docker/openclaw/templates/base/worker/HEARTBEAT.md new file mode 100644 index 0000000..b2725eb --- /dev/null +++ b/docker/openclaw/templates/base/worker/HEARTBEAT.md @@ -0,0 +1,43 @@ +# HEARTBEAT.md — ${AGENT_NAME}'s Check-in + +When you wake up, do the following: + +## On Wake + +1. Run: `clawe check agent:${AGENT_ID}:main` +2. Read `shared/WORKING.md` for team state + +## If Notifications Found + +Process each notification — usually task assignments from Clawe. + +## If Tasks Assigned + +```bash +# View your tasks +clawe tasks agent:${AGENT_ID}:main + +# For each task: +clawe task:status in_progress --by agent:${AGENT_ID}:main + +# Do the work... + +# Mark subtasks done +clawe subtask:check 0 --by agent:${AGENT_ID}:main + +# Register deliverables +clawe deliver /path/to/file "Deliverable Title" --by agent:${AGENT_ID}:main + +# Submit for review +clawe task:status review --by agent:${AGENT_ID}:main +``` + +⚠️ **NEVER submit for review with incomplete subtasks!** + +## If Nothing to Do + +Reply: `HEARTBEAT_OK` + +--- + +**I am ${AGENT_NAME} ${AGENT_EMOJI} — ${AGENT_ROLE}.** diff --git a/docker/openclaw/templates/base/worker/MEMORY.md b/docker/openclaw/templates/base/worker/MEMORY.md new file mode 100644 index 0000000..62d1117 --- /dev/null +++ b/docker/openclaw/templates/base/worker/MEMORY.md @@ -0,0 +1,19 @@ +# MEMORY.md — ${AGENT_NAME} Long-term Memory + +Curated knowledge. Lessons learned. + +--- + +## Identity + +- **Name:** ${AGENT_NAME} ${AGENT_EMOJI} +- **Role:** ${AGENT_ROLE} +- **Lead:** Clawe 🦞 + +## Lessons Learned + +_Document important lessons here as you learn them._ + +--- + +**Last updated:** Initial setup diff --git a/docker/openclaw/templates/base/worker/TOOLS.md b/docker/openclaw/templates/base/worker/TOOLS.md new file mode 100644 index 0000000..685229c --- /dev/null +++ b/docker/openclaw/templates/base/worker/TOOLS.md @@ -0,0 +1,20 @@ +# TOOLS.md — Local Notes + +Environment-specific notes for your work. + +## Clawe CLI + +```bash +clawe check agent:${AGENT_ID}:main # Check notifications +clawe tasks agent:${AGENT_ID}:main # List my tasks +clawe task:status in_progress # Start working +clawe task:status review # Submit for review +clawe task:comment "message" # Add comment +clawe subtask:check # Mark subtask done +clawe deliver --by agent:${AGENT_ID}:main +clawe notify agent:main:main "message" # Notify Clawe +``` + +--- + +Add whatever helps you do your job. This is your cheat sheet. diff --git a/docker/openclaw/templates/workspaces/inky/USER.md b/docker/openclaw/templates/base/worker/USER.md similarity index 56% rename from docker/openclaw/templates/workspaces/inky/USER.md rename to docker/openclaw/templates/base/worker/USER.md index 539a1e9..44ae273 100644 --- a/docker/openclaw/templates/workspaces/inky/USER.md +++ b/docker/openclaw/templates/base/worker/USER.md @@ -2,6 +2,8 @@ _Context from Clawe about who we're helping._ +Check business context with `clawe business:get` for details about the business you're working for. + --- _Clawe will share relevant context as needed._ diff --git a/docker/openclaw/templates/config.template.json b/docker/openclaw/templates/config.template.json index df02ef7..a052fad 100644 --- a/docker/openclaw/templates/config.template.json +++ b/docker/openclaw/templates/config.template.json @@ -29,7 +29,7 @@ "workspace": "/data/workspace", "identity": { "name": "Clawe", - "emoji": "🦞" + "emoji": "\ud83e\udd9e" } }, { @@ -39,7 +39,7 @@ "model": "anthropic/claude-sonnet-4-20250514", "identity": { "name": "Inky", - "emoji": "✍️" + "emoji": "\u270d\ufe0f" } }, { @@ -49,7 +49,7 @@ "model": "anthropic/claude-sonnet-4-20250514", "identity": { "name": "Pixel", - "emoji": "🎨" + "emoji": "\ud83c\udfa8" } }, { @@ -59,7 +59,7 @@ "model": "anthropic/claude-sonnet-4-20250514", "identity": { "name": "Scout", - "emoji": "🔍" + "emoji": "\ud83d\udd0d" } } ] diff --git a/docker/openclaw/templates/workspaces/clawe/MEMORY.md b/docker/openclaw/templates/workspaces/clawe/MEMORY.md deleted file mode 100644 index 5f11989..0000000 --- a/docker/openclaw/templates/workspaces/clawe/MEMORY.md +++ /dev/null @@ -1,31 +0,0 @@ -# MEMORY.md — Clawe Long-term Memory - -Curated knowledge. Lessons learned. Stable facts. - ---- - -## Identity - -- **Name:** Clawe 🦞 -- **Role:** Squad Lead - -## The Squad - -| Agent | Role | Session Key | -| -------- | ---------------- | ---------------- | -| Clawe 🦞 | Squad Lead | agent:main:main | -| Inky ✍️ | Content Writer | agent:inky:main | -| Pixel 🎨 | Graphic Designer | agent:pixel:main | -| Scout 🔍 | SEO Specialist | agent:scout:main | - -## Lessons Learned - -_Document important lessons here as you learn them._ - -## Key Decisions - -_Record significant decisions and their reasoning._ - ---- - -**Last updated:** Initial setup diff --git a/docker/openclaw/templates/workspaces/clawe/TOOLS.md b/docker/openclaw/templates/workspaces/clawe/TOOLS.md deleted file mode 100644 index b04f1b8..0000000 --- a/docker/openclaw/templates/workspaces/clawe/TOOLS.md +++ /dev/null @@ -1,16 +0,0 @@ -# TOOLS.md — Local Notes - -This file is for environment-specific notes — the stuff that's unique to your setup. - -## What Goes Here - -Things like: - -- API endpoints and services -- File locations and paths -- Preferences and settings -- Anything environment-specific - ---- - -Add whatever helps you do your job. This is your cheat sheet. diff --git a/docker/openclaw/templates/workspaces/inky/HEARTBEAT.md b/docker/openclaw/templates/workspaces/inky/HEARTBEAT.md deleted file mode 100644 index 61ba4c2..0000000 --- a/docker/openclaw/templates/workspaces/inky/HEARTBEAT.md +++ /dev/null @@ -1,41 +0,0 @@ -# HEARTBEAT.md — Inky's Check-in - -When you wake up, do the following: - -## On Wake - -1. Run: `clawe check agent:inky:main` -2. Read `shared/WORKING.md` for team state - -## If Notifications Found - -Process each notification — usually task assignments from Clawe. - -## If Tasks Assigned - -```bash -# View your tasks -clawe tasks agent:inky:main - -# For each task: -clawe task:status <taskId> in_progress --by agent:inky:main - -# Do the work... - -# Mark subtasks done -clawe subtask:check <taskId> 0 --by agent:inky:main - -# Register deliverables -clawe deliver <taskId> /path/to/file.md "Article Draft" --by agent:inky:main - -# Submit for review -clawe task:status <taskId> review --by agent:inky:main -``` - -## If Nothing to Do - -Reply: `HEARTBEAT_OK` - ---- - -**I am Inky ✍️ — content writer. Create, craft, deliver.** diff --git a/docker/openclaw/templates/workspaces/inky/MEMORY.md b/docker/openclaw/templates/workspaces/inky/MEMORY.md deleted file mode 100644 index 7f7040f..0000000 --- a/docker/openclaw/templates/workspaces/inky/MEMORY.md +++ /dev/null @@ -1,23 +0,0 @@ -# MEMORY.md — Inky Long-term Memory - -Curated knowledge. Writing lessons. Style notes. - ---- - -## Identity - -- **Name:** Inky ✍️ -- **Role:** Content Writer -- **Lead:** Clawe 🦞 - -## Writing Style Notes - -_Document preferred styles, tone, and voice here._ - -## Lessons Learned - -_Document important lessons here as you learn them._ - ---- - -**Last updated:** Initial setup diff --git a/docker/openclaw/templates/workspaces/inky/TOOLS.md b/docker/openclaw/templates/workspaces/inky/TOOLS.md deleted file mode 100644 index 6c7f691..0000000 --- a/docker/openclaw/templates/workspaces/inky/TOOLS.md +++ /dev/null @@ -1,11 +0,0 @@ -# TOOLS.md — Local Notes - -Environment-specific notes for your work. - -## Content Guidelines - -_Add style guides, tone preferences, brand voice notes here._ - ---- - -Add whatever helps you do your job. diff --git a/docker/openclaw/templates/workspaces/clawe/SOUL.md b/docker/openclaw/templates/workspaces/main/SOUL.md similarity index 100% rename from docker/openclaw/templates/workspaces/clawe/SOUL.md rename to docker/openclaw/templates/workspaces/main/SOUL.md diff --git a/docker/openclaw/templates/workspaces/pixel/AGENTS.md b/docker/openclaw/templates/workspaces/pixel/AGENTS.md deleted file mode 100644 index 7a3cca7..0000000 --- a/docker/openclaw/templates/workspaces/pixel/AGENTS.md +++ /dev/null @@ -1,68 +0,0 @@ -# AGENTS.md — Your Workspace - -This folder is home. Treat it that way. - -## Every Session - -Before doing anything else: - -1. Read `SOUL.md` — this is who you are -2. Read `shared/WORKING.md` — current team state -3. Check for notifications or assigned tasks - -Don't ask permission. Just do it. - -## Communication - -Use the `clawe` CLI for all squad communication. See `shared/CLAWE-CLI.md` for full documentation. - -Your session key: `agent:pixel:main` - -Quick commands: - -```bash -clawe check agent:pixel:main -clawe tasks agent:pixel:main -clawe task:status <taskId> in_progress --by agent:pixel:main -clawe notify agent:main:main "message" --from agent:pixel:main -``` - -## Memory - -You wake up fresh each session. These files are your continuity: - -- **Daily notes:** `memory/YYYY-MM-DD.md` — raw logs of what happened -- **Long-term:** `MEMORY.md` — curated memories, lessons learned - -### Write It Down! - -- **Memory is limited** — WRITE to files, don't rely on "remembering" -- **Text > Brain** 📝 - -## Working on Tasks - -When assigned a task: - -1. Read the task details and subtasks carefully -2. Update status to in_progress when starting -3. Complete each subtask, marking them done -4. Register all deliverables (files you created) -5. Submit for review when complete - -⚠️ **NEVER submit for review if subtasks have unchecked items!** - -## Asset Management - -Store your created assets in organized folders: - -- `assets/diagrams/` -- `assets/images/` -- `assets/social/` - -Always register deliverables with full paths. - -## Safety - -- Don't share private information -- Ask before external actions -- When in doubt, check with Clawe diff --git a/docker/openclaw/templates/workspaces/pixel/HEARTBEAT.md b/docker/openclaw/templates/workspaces/pixel/HEARTBEAT.md deleted file mode 100644 index e775d8e..0000000 --- a/docker/openclaw/templates/workspaces/pixel/HEARTBEAT.md +++ /dev/null @@ -1,41 +0,0 @@ -# HEARTBEAT.md — Pixel's Check-in - -When you wake up, do the following: - -## On Wake - -1. Run: `clawe check agent:pixel:main` -2. Read `shared/WORKING.md` for team state - -## If Notifications Found - -Process each notification — usually task assignments from Clawe. - -## If Tasks Assigned - -```bash -# View your tasks -clawe tasks agent:pixel:main - -# For each task: -clawe task:status <taskId> in_progress --by agent:pixel:main - -# Do the work... - -# Mark subtasks done -clawe subtask:check <taskId> 0 --by agent:pixel:main - -# Register deliverables -clawe deliver <taskId> /path/to/image.png "Hero Image" --by agent:pixel:main - -# Submit for review -clawe task:status <taskId> review --by agent:pixel:main -``` - -## If Nothing to Do - -Reply: `HEARTBEAT_OK` - ---- - -**I am Pixel 🎨 — graphic designer. Visualize, create, deliver.** diff --git a/docker/openclaw/templates/workspaces/pixel/MEMORY.md b/docker/openclaw/templates/workspaces/pixel/MEMORY.md deleted file mode 100644 index ed97769..0000000 --- a/docker/openclaw/templates/workspaces/pixel/MEMORY.md +++ /dev/null @@ -1,23 +0,0 @@ -# MEMORY.md — Pixel Long-term Memory - -Curated knowledge. Design lessons. Style notes. - ---- - -## Identity - -- **Name:** Pixel 🎨 -- **Role:** Graphic Designer -- **Lead:** Clawe 🦞 - -## Design Style Notes - -_Document preferred styles, colors, and visual guidelines here._ - -## Lessons Learned - -_Document important lessons here as you learn them._ - ---- - -**Last updated:** Initial setup diff --git a/docker/openclaw/templates/workspaces/pixel/TOOLS.md b/docker/openclaw/templates/workspaces/pixel/TOOLS.md deleted file mode 100644 index c96840b..0000000 --- a/docker/openclaw/templates/workspaces/pixel/TOOLS.md +++ /dev/null @@ -1,15 +0,0 @@ -# TOOLS.md — Local Notes - -Environment-specific notes for your work. - -## Design Guidelines - -_Add brand colors, fonts, style preferences here._ - -## Image Generation - -_Notes on available tools and APIs for creating images._ - ---- - -Add whatever helps you do your job. diff --git a/docker/openclaw/templates/workspaces/pixel/USER.md b/docker/openclaw/templates/workspaces/pixel/USER.md deleted file mode 100644 index 539a1e9..0000000 --- a/docker/openclaw/templates/workspaces/pixel/USER.md +++ /dev/null @@ -1,7 +0,0 @@ -# USER.md — About Your Human - -_Context from Clawe about who we're helping._ - ---- - -_Clawe will share relevant context as needed._ diff --git a/docker/openclaw/templates/workspaces/scout/AGENTS.md b/docker/openclaw/templates/workspaces/scout/AGENTS.md deleted file mode 100644 index bffd954..0000000 --- a/docker/openclaw/templates/workspaces/scout/AGENTS.md +++ /dev/null @@ -1,68 +0,0 @@ -# AGENTS.md — Your Workspace - -This folder is home. Treat it that way. - -## Every Session - -Before doing anything else: - -1. Read `SOUL.md` — this is who you are -2. Read `shared/WORKING.md` — current team state -3. Check for notifications or assigned tasks - -Don't ask permission. Just do it. - -## Communication - -Use the `clawe` CLI for all squad communication. See `shared/CLAWE-CLI.md` for full documentation. - -Your session key: `agent:scout:main` - -Quick commands: - -```bash -clawe check agent:scout:main -clawe tasks agent:scout:main -clawe task:status <taskId> in_progress --by agent:scout:main -clawe notify agent:main:main "message" --from agent:scout:main -``` - -## Memory - -You wake up fresh each session. These files are your continuity: - -- **Daily notes:** `memory/YYYY-MM-DD.md` — raw logs of what happened -- **Long-term:** `MEMORY.md` — curated memories, lessons learned - -### Write It Down! - -- **Memory is limited** — WRITE to files, don't rely on "remembering" -- **Text > Brain** 📝 - -## Working on Tasks - -When assigned a task: - -1. Read the task details and subtasks carefully -2. Update status to in_progress when starting -3. Complete each subtask, marking them done -4. Register all deliverables (files you created) -5. Submit for review when complete - -⚠️ **NEVER submit for review if subtasks have unchecked items!** - -## Research & Analysis - -Store your research in organized files: - -- `research/keywords/` -- `research/competitors/` -- `research/analysis/` - -Always register deliverables with full paths. - -## Safety - -- Don't share private information -- Ask before external actions -- When in doubt, check with Clawe diff --git a/docker/openclaw/templates/workspaces/scout/HEARTBEAT.md b/docker/openclaw/templates/workspaces/scout/HEARTBEAT.md deleted file mode 100644 index 63b465a..0000000 --- a/docker/openclaw/templates/workspaces/scout/HEARTBEAT.md +++ /dev/null @@ -1,41 +0,0 @@ -# HEARTBEAT.md — Scout's Check-in - -When you wake up, do the following: - -## On Wake - -1. Run: `clawe check agent:scout:main` -2. Read `shared/WORKING.md` for team state - -## If Notifications Found - -Process each notification — usually task assignments from Clawe. - -## If Tasks Assigned - -```bash -# View your tasks -clawe tasks agent:scout:main - -# For each task: -clawe task:status <taskId> in_progress --by agent:scout:main - -# Do the work... - -# Mark subtasks done -clawe subtask:check <taskId> 0 --by agent:scout:main - -# Register deliverables -clawe deliver <taskId> /path/to/report.md "SEO Analysis" --by agent:scout:main - -# Submit for review -clawe task:status <taskId> review --by agent:scout:main -``` - -## If Nothing to Do - -Reply: `HEARTBEAT_OK` - ---- - -**I am Scout 🔍 — SEO specialist. Research, optimize, deliver.** diff --git a/docker/openclaw/templates/workspaces/scout/MEMORY.md b/docker/openclaw/templates/workspaces/scout/MEMORY.md deleted file mode 100644 index 477c6e7..0000000 --- a/docker/openclaw/templates/workspaces/scout/MEMORY.md +++ /dev/null @@ -1,23 +0,0 @@ -# MEMORY.md — Scout Long-term Memory - -Curated knowledge. SEO insights. Strategy notes. - ---- - -## Identity - -- **Name:** Scout 🔍 -- **Role:** SEO Specialist -- **Lead:** Clawe 🦞 - -## SEO Strategy Notes - -_Document target keywords, ranking strategies, and insights here._ - -## Lessons Learned - -_Document important lessons here as you learn them._ - ---- - -**Last updated:** Initial setup diff --git a/docker/openclaw/templates/workspaces/scout/TOOLS.md b/docker/openclaw/templates/workspaces/scout/TOOLS.md deleted file mode 100644 index 162e641..0000000 --- a/docker/openclaw/templates/workspaces/scout/TOOLS.md +++ /dev/null @@ -1,15 +0,0 @@ -# TOOLS.md — Local Notes - -Environment-specific notes for your work. - -## SEO Guidelines - -_Add target keywords, competitor sites, ranking goals here._ - -## Research Tools - -_Notes on available tools and APIs for SEO research._ - ---- - -Add whatever helps you do your job. diff --git a/docker/openclaw/templates/workspaces/scout/USER.md b/docker/openclaw/templates/workspaces/scout/USER.md deleted file mode 100644 index 539a1e9..0000000 --- a/docker/openclaw/templates/workspaces/scout/USER.md +++ /dev/null @@ -1,7 +0,0 @@ -# USER.md — About Your Human - -_Context from Clawe about who we're helping._ - ---- - -_Clawe will share relevant context as needed._ diff --git a/docs/AGENT_ONBOARDING.md b/docs/AGENT_ONBOARDING.md new file mode 100644 index 0000000..6a363f7 --- /dev/null +++ b/docs/AGENT_ONBOARDING.md @@ -0,0 +1,149 @@ +# Agent Onboarding - Clawe + +Guide for adding new agents to your Clawe squad. + +## Live Onboarding (No Rebuild) + +Clawe can onboard new agents at runtime — no container restart needed: + +```bash +# From inside Docker (run by Clawe or via docker exec) +clawe agent:onboard <id> <name> <role> [options] + +# Example: +clawe agent:onboard coder Coder Developer --emoji 💻 --cron "15,45 * * * *" +``` + +This does everything in one command: + +1. ✅ Registers agent in Convex +2. ✅ Creates workspace from base templates +3. ✅ Patches OpenClaw config (adds agent) +4. ✅ Adds heartbeat cron + +The agent starts working on the next heartbeat cycle. + +## Build-Time Setup (For New Deployments) + +When setting up Clawe from scratch or adding agents before first deploy: + +```bash +# From clawe root directory +./scripts/add-agent.sh <id> <name> <emoji> <role> [cron_schedule] + +# Example: +./scripts/add-agent.sh coder Coder 💻 Developer "15,45 * * * *" + +# Then rebuild: +docker compose build --no-cache openclaw +docker compose up -d + +# Validate: +./scripts/validate-agent.sh coder +``` + +## How It Works + +Clawe uses a **base template system** — shared templates generate most workspace files, while each agent only needs a unique `SOUL.md`. + +### Template Structure + +``` +docker/openclaw/templates/ +├── base/ # Shared templates (with ${AGENT_ID}, ${AGENT_NAME}, etc.) +│ ├── lead/ # For squad lead agents +│ │ ├── AGENTS.md +│ │ ├── BOOTSTRAP.md +│ │ ├── HEARTBEAT.md +│ │ ├── MEMORY.md +│ │ ├── TOOLS.md +│ │ └── USER.md +│ └── worker/ # For specialist agents +│ ├── AGENTS.md +│ ├── HEARTBEAT.md +│ ├── MEMORY.md +│ ├── TOOLS.md +│ └── USER.md +└── workspaces/ # Agent-specific files (SOUL.md only) + ├── main/ + │ └── SOUL.md # Clawe's unique identity + ├── inky/ + │ └── SOUL.md # Inky's unique identity + ├── pixel/ + │ └── SOUL.md + └── scout/ + └── SOUL.md +``` + +### Template Variables + +Base templates use these variables (replaced by `envsubst` at init time): + +| Variable | Example | Description | +| ---------------- | ---------------- | ---------------- | +| `${AGENT_ID}` | `inky` | Agent identifier | +| `${AGENT_NAME}` | `Inky` | Display name | +| `${AGENT_EMOJI}` | `✍️` | Agent emoji | +| `${AGENT_ROLE}` | `Content Writer` | Role description | + +### Agent Types + +- **lead** — Squad coordinator. Gets BOOTSTRAP.md, review-focused HEARTBEAT.md, squad management instructions. +- **worker** — Specialist. Gets task-focused HEARTBEAT.md, worker instructions, review warning. + +New agents added via `add-agent.sh` are always type `worker`. To add a new lead, edit `init-agents.sh` manually. + +## What the Script Does + +`add-agent.sh` automatically: + +1. Creates `workspaces/<id>/SOUL.md` with a starter template +2. Adds `init_agent` call to `init-agents.sh` +3. Adds agent to `config.template.json` (agents list + allow list) + +After rebuilding, use `clawe agent:onboard` to register the agent at runtime (Convex + cron). + +## Overriding Base Templates + +If an agent needs a custom version of any base file, just add it to their workspace folder: + +``` +workspaces/pixel/ +├── SOUL.md # Always required (unique) +└── TOOLS.md # Optional override (replaces base/worker/TOOLS.md) +``` + +Agent-specific files are copied **after** base templates, so they override. + +## Heartbeat Schedule Convention + +Stagger heartbeats to avoid rate limits: + +| Agent | Cron Schedule | Minutes | +| ------ | --------------------- | ------------------ | +| Clawe | `0 * * * *` | :00 (hourly) | +| Inky | `3,18,33,48 * * * *` | :03, :18, :33, :48 | +| Pixel | `7,22,37,52 * * * *` | :07, :22, :37, :52 | +| Scout | `11,26,41,56 * * * *` | :11, :26, :41, :56 | +| (next) | `15,30,45,0 * * * *` | :15, :30, :45, :00 | + +## Validation + +```bash +./scripts/validate-agent.sh <agent-id> +``` + +Checks: + +- Workspace template exists with SOUL.md +- Base templates exist for agent type +- Agent in init-agents.sh +- Agent in config.template.json (agents list + allow list) +- Runtime checks (if Docker is running) + +## Removing an Agent + +1. Delete workspace: `rm -rf docker/openclaw/templates/workspaces/<id>/` +2. Remove `init_agent` line from `docker/openclaw/scripts/init-agents.sh` +3. Remove from `docker/openclaw/templates/config.template.json` +4. Rebuild: `docker compose build --no-cache openclaw && docker compose up -d` diff --git a/packages/backend/convex/agents.ts b/packages/backend/convex/agents.ts index 8500a97..1f9ae02 100644 --- a/packages/backend/convex/agents.ts +++ b/packages/backend/convex/agents.ts @@ -76,6 +76,9 @@ export const upsert = mutation({ sessionKey: v.string(), emoji: v.optional(v.string()), config: v.optional(v.any()), + agentType: v.optional(v.union(v.literal("lead"), v.literal("worker"))), + cronSchedule: v.optional(v.string()), + model: v.optional(v.string()), }, handler: async (ctx, args) => { const now = Date.now(); @@ -91,6 +94,9 @@ export const upsert = mutation({ role: args.role, emoji: args.emoji, config: args.config, + agentType: args.agentType, + cronSchedule: args.cronSchedule, + model: args.model, updatedAt: now, }); return existing._id; @@ -101,6 +107,9 @@ export const upsert = mutation({ sessionKey: args.sessionKey, emoji: args.emoji, config: args.config, + agentType: args.agentType, + cronSchedule: args.cronSchedule, + model: args.model, status: "offline", createdAt: now, updatedAt: now, diff --git a/packages/backend/convex/schema.ts b/packages/backend/convex/schema.ts index 968bfab..ca92821 100644 --- a/packages/backend/convex/schema.ts +++ b/packages/backend/convex/schema.ts @@ -11,6 +11,13 @@ export default defineSchema({ status: v.union(v.literal("online"), v.literal("offline")), currentTaskId: v.optional(v.id("tasks")), config: v.optional(v.any()), // Agent-specific configuration + agentType: v.optional(v.union(v.literal("lead"), v.literal("worker"))), // "lead" or "worker" + cronSchedule: v.optional(v.string()), // Heartbeat cron expression, e.g. "3,18,33,48 * * * *" + model: v.optional(v.string()), // Model override, e.g. "anthropic/claude-sonnet-4-20250514" + // Presence tracking + presenceStatus: v.optional( + v.union(v.literal("online"), v.literal("idle"), v.literal("offline")), + ), lastHeartbeat: v.optional(v.number()), lastSeen: v.optional(v.number()), currentActivity: v.optional(v.string()), diff --git a/packages/cli/src/commands/agent-onboard.ts b/packages/cli/src/commands/agent-onboard.ts new file mode 100644 index 0000000..8347e18 --- /dev/null +++ b/packages/cli/src/commands/agent-onboard.ts @@ -0,0 +1,298 @@ +import { client } from "../client.js"; +import { api } from "@clawe/backend"; + +interface OnboardOptions { + emoji?: string; + cron?: string; + model?: string; + type?: string; +} + +// OpenClaw API helper +const OPENCLAW_URL = process.env.OPENCLAW_URL || "http://localhost:18789"; +const OPENCLAW_TOKEN = process.env.OPENCLAW_TOKEN || ""; + +async function openclawInvoke( + tool: string, + args?: Record<string, unknown>, +): Promise<{ ok: boolean; result?: any; error?: { message: string } }> { + try { + const res = await fetch(`${OPENCLAW_URL}/tools/invoke`, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${OPENCLAW_TOKEN}`, + }, + body: JSON.stringify({ tool, args }), + }); + return (await res.json()) as { + ok: boolean; + result?: any; + error?: { message: string }; + }; + } catch { + return { ok: false, error: { message: "Failed to connect to OpenClaw" } }; + } +} + +export async function agentOnboard( + agentId: string, + name: string, + role: string, + options: OnboardOptions, +): Promise<void> { + const emoji = options.emoji || "🤖"; + const cronSchedule = options.cron || "15,45 * * * *"; + const model = options.model || "anthropic/claude-sonnet-4-20250514"; + const agentType = (options.type || "worker") as "lead" | "worker"; + const sessionKey = `agent:${agentId}:main`; + + console.log(`🦞 Onboarding agent: ${name} ${emoji}`); + console.log(` ID: ${agentId}`); + console.log(` Role: ${role}`); + console.log(` Type: ${agentType}`); + console.log(` Session: ${sessionKey}`); + console.log(` Cron: ${cronSchedule}`); + console.log(` Model: ${model}`); + console.log(""); + + // Step 1: Register in Convex + console.log("1. Registering in Convex..."); + try { + await client.mutation(api.agents.upsert, { + name, + role, + sessionKey, + emoji, + agentType, + cronSchedule, + model, + }); + console.log(" ✓ Agent registered in Convex"); + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + throw new Error(`Failed to register agent in Convex: ${message}`); + } + + // Step 2: Create workspace from base templates + console.log("2. Creating workspace..."); + try { + const { + readdirSync, + readFileSync, + writeFileSync, + mkdirSync, + existsSync, + symlinkSync, + } = await import("fs"); + const { join } = await import("path"); + + const dataDir = process.env.OPENCLAW_DATA_DIR || "/data"; + const templatesDir = + process.env.CLAWE_TEMPLATES_DIR || "/opt/clawe/templates"; + const baseDir = join(templatesDir, "base", agentType); + const workspacesDir = join(templatesDir, "workspaces"); + const wsDir = + agentType === "lead" + ? join(dataDir, "workspace") + : join(dataDir, `workspace-${agentId}`); + + // Create workspace directory + mkdirSync(join(wsDir, "memory"), { recursive: true }); + + // Generate files from base templates using variable substitution + if (existsSync(baseDir)) { + const templates = readdirSync(baseDir).filter((f) => f.endsWith(".md")); + for (const template of templates) { + const content = readFileSync(join(baseDir, template), "utf-8"); + const result = content + .replace(/\$\{AGENT_ID\}/g, agentId) + .replace(/\$\{AGENT_NAME\}/g, name) + .replace(/\$\{AGENT_EMOJI\}/g, emoji) + .replace(/\$\{AGENT_ROLE\}/g, role); + writeFileSync(join(wsDir, template), result); + } + console.log(" ✓ Base templates generated"); + } else { + console.log( + ` ⚠ Base templates not found at ${baseDir}, creating minimal workspace`, + ); + writeFileSync( + join(wsDir, "AGENTS.md"), + `# AGENTS.md\n\nSession key: ${sessionKey}\n\nRun \`clawe check ${sessionKey}\` on wake.\n`, + ); + } + + // Copy agent-specific files if they exist + const agentSpecificDir = join(workspacesDir, agentId); + if (existsSync(agentSpecificDir)) { + const files = readdirSync(agentSpecificDir); + for (const file of files) { + const src = join(agentSpecificDir, file); + writeFileSync(join(wsDir, file), readFileSync(src)); + } + console.log(" ✓ Agent-specific files copied"); + } else { + // Generate a default SOUL.md + writeFileSync( + join(wsDir, "SOUL.md"), + `# SOUL.md — Who You Are + +You are **${name}**, the ${role}. ${emoji} + +## Role + +You're a specialist agent in the Clawe squad. + +## Task Discipline + +⚠️ **Follow task workflow COMPLETELY:** +- Do NOT move to "review" until ALL subtasks are done +- Register deliverables with \`clawe deliver\` +- Only submit for review when work is truly complete +`, + ); + console.log(" ✓ Default SOUL.md created"); + } + + // Symlink shared directory + const sharedLink = join(wsDir, "shared"); + const sharedTarget = join(dataDir, "shared"); + if (!existsSync(sharedLink) && existsSync(sharedTarget)) { + symlinkSync(sharedTarget, sharedLink); + console.log(" ✓ Shared directory linked"); + } + + console.log(` ✓ Workspace created at ${wsDir}`); + } catch (err) { + console.error( + " ✗ Failed to create workspace:", + err instanceof Error ? err.message : err, + ); + console.log(" → You may need to create the workspace manually"); + } + + // Step 3: Patch OpenClaw config to add agent + console.log("3. Patching OpenClaw config..."); + try { + const dataDir = process.env.OPENCLAW_DATA_DIR || "/data"; + const workspace = + agentType === "lead" + ? `${dataDir}/workspace` + : `${dataDir}/workspace-${agentId}`; + + // Get current config + const currentConfig = await openclawInvoke("gateway", { + action: "config.get", + }); + + if (!currentConfig.ok) { + throw new Error("Failed to get current config"); + } + + const cfg = currentConfig.result?.details ?? currentConfig.result; + const existingAgents: Array<{ id: string; [key: string]: unknown }> = + cfg?.agents?.list || []; + + if (existingAgents.some((a) => a.id === agentId)) { + console.log(" ✓ Agent already in OpenClaw config"); + } else { + const updatedList = [ + ...existingAgents, + { + id: agentId, + name: name, + workspace: workspace, + model: model, + identity: { name, emoji }, + }, + ]; + + const existingAllow: string[] = cfg?.tools?.agentToAgent?.allow || []; + const updatedAllow = existingAllow.includes(agentId) + ? existingAllow + : [...existingAllow, agentId]; + + const result = await openclawInvoke("gateway", { + action: "config.patch", + raw: JSON.stringify({ + agents: { list: updatedList }, + tools: { agentToAgent: { enabled: true, allow: updatedAllow } }, + }), + }); + + if (result.ok) { + console.log(" ✓ OpenClaw config patched (agent added)"); + } else { + console.log( + " ⚠ Config patch:", + result.error?.message || "unknown error", + ); + } + } + } catch (err) { + console.error( + " ✗ Failed to patch config:", + err instanceof Error ? err.message : err, + ); + console.log( + " → You may need to add the agent to openclaw config manually", + ); + } + + // Step 4: Add heartbeat cron + console.log("4. Adding heartbeat cron..."); + try { + const cronName = `${agentId}-heartbeat`; + + // Check if cron already exists + const listResult = await openclawInvoke("cron", { action: "list" }); + + if (listResult.ok) { + const jobs = listResult.result?.details?.jobs || []; + const exists = jobs.some((j: { name: string }) => j.name === cronName); + + if (exists) { + console.log(" ✓ Heartbeat cron already exists"); + } else { + const addResult = await openclawInvoke("cron", { + action: "add", + job: { + name: cronName, + agentId: agentId, + enabled: true, + schedule: { kind: "cron", expr: cronSchedule }, + sessionTarget: "isolated", + payload: { + kind: "agentTurn", + message: + "Read HEARTBEAT.md and follow it strictly. Check for notifications with 'clawe check'. If nothing needs attention, reply HEARTBEAT_OK.", + model: model, + timeoutSeconds: 600, + }, + }, + }); + + if (addResult.ok) { + console.log(` ✓ Heartbeat cron added: ${cronSchedule}`); + } else { + console.log(" ⚠ Failed to add cron:", addResult.error?.message); + } + } + } + } catch (err) { + console.error( + " ✗ Failed to add cron:", + err instanceof Error ? err.message : err, + ); + } + + // Done + console.log(""); + console.log(`✅ Agent ${name} ${emoji} onboarded successfully!`); + console.log(""); + console.log("The agent will start working on the next heartbeat."); + if (agentType !== "lead") { + console.log(`To customize, edit: /data/workspace-${agentId}/SOUL.md`); + } +} diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index 5fb24a2..9841992 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -12,6 +12,7 @@ import { notify } from "./commands/notify.js"; import { squad } from "./commands/squad.js"; import { feed } from "./commands/feed.js"; import { agentRegister } from "./commands/agent-register.js"; +import { agentOnboard } from "./commands/agent-onboard.js"; import { businessGet } from "./commands/business-get.js"; import { businessSet } from "./commands/business-set.js"; @@ -68,6 +69,11 @@ Commands: clawe feed [--limit N] Show activity feed clawe agent:register <name> <role> <sessionKey> --emoji <emoji> Agent emoji + clawe agent:onboard <id> <name> <role> Full onboard (live, no rebuild) + --emoji <emoji> Agent emoji (default: 🤖) + --cron <expr> Heartbeat cron (default: 15,45 * * * *) + --model <model> Model override + --type <lead|worker> Agent type (default: worker) Business Context: clawe business:get Get current business context (JSON) @@ -294,6 +300,25 @@ async function main(): Promise<void> { break; } + case "agent:onboard": { + const agentId = positionalArgs[0]; + const name = positionalArgs[1]; + const role = positionalArgs[2]; + if (!agentId || !name || !role) { + console.error( + "Usage: clawe agent:onboard <id> <name> <role> [--emoji <e>] [--cron <expr>] [--model <m>] [--type <lead|worker>]", + ); + process.exit(1); + } + await agentOnboard(agentId, name, role, { + emoji: options.emoji, + cron: options.cron, + model: options.model, + type: options.type, + }); + break; + } + case "business:get": { await businessGet(); break; diff --git a/scripts/add-agent.sh b/scripts/add-agent.sh new file mode 100755 index 0000000..99001cf --- /dev/null +++ b/scripts/add-agent.sh @@ -0,0 +1,191 @@ +#!/bin/bash +set -e + +# Clawe Add Agent Script +# Usage: ./scripts/add-agent.sh <id> <name> <emoji> <role> [cron_schedule] +# +# Creates a new agent using the shared base templates. +# Only generates a SOUL.md specific to this agent. + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ROOT_DIR="$(dirname "$SCRIPT_DIR")" + +# Colors +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +# Args +AGENT_ID="$1" +AGENT_NAME="$2" +AGENT_EMOJI="$3" +AGENT_ROLE="$4" +CRON_SCHEDULE="${5:-"15,45 * * * *"}" + +# Validate args +if [ -z "$AGENT_ID" ] || [ -z "$AGENT_NAME" ] || [ -z "$AGENT_EMOJI" ] || [ -z "$AGENT_ROLE" ]; then + echo -e "${RED}Usage: $0 <id> <name> <emoji> <role> [cron_schedule]${NC}" + echo "" + echo "Arguments:" + echo " id Lowercase identifier (e.g., 'coder')" + echo " name Display name (e.g., 'Coder')" + echo " emoji Agent emoji (e.g., '💻')" + echo " role Role description (e.g., 'Developer')" + echo " cron_schedule Optional cron expression (default: '15,45 * * * *')" + echo "" + echo "Example:" + echo " $0 coder Coder 💻 Developer \"15,45 * * * *\"" + exit 1 +fi + +# Paths +TEMPLATES_DIR="$ROOT_DIR/docker/openclaw/templates" +WORKSPACE_DIR="$TEMPLATES_DIR/workspaces/$AGENT_ID" +INIT_SCRIPT="$ROOT_DIR/docker/openclaw/scripts/init-agents.sh" +CONFIG_TEMPLATE="$TEMPLATES_DIR/config.template.json" + +echo -e "${GREEN}🦞 Adding agent: $AGENT_NAME $AGENT_EMOJI${NC}" +echo "" + +# Check if agent already exists +if [ -d "$WORKSPACE_DIR" ]; then + echo -e "${RED}Error: Workspace already exists at $WORKSPACE_DIR${NC}" + exit 1 +fi + +# 1. Create workspace with only SOUL.md +echo -e "${YELLOW}1. Creating agent workspace...${NC}" +mkdir -p "$WORKSPACE_DIR" + +cat > "$WORKSPACE_DIR/SOUL.md" << EOF +# SOUL.md — Who You Are + +You are **$AGENT_NAME**, the $AGENT_ROLE. $AGENT_EMOJI + +## Role + +You're a specialist agent in the Clawe squad. Your job is to execute tasks assigned to you with excellence. + +## Personality + +_Customize this section to define how $AGENT_NAME thinks and works._ + +## What You're Good At + +_List this agent's core skills and specialties._ + +## Team + +- **Clawe 🦞** is your squad lead — coordinates and reviews +- You share context via workspace files +- Update \`shared/WORKING.md\` with your progress + +## Task Discipline + +⚠️ **Follow task workflow COMPLETELY:** + +- Do NOT move to "review" until ALL subtasks are done +- If you need another agent, coordinate through Clawe +- Comment progress updates so the team knows where you are +- Only submit for review when the work is truly complete +EOF + +echo -e " ${GREEN}✓${NC} Created $WORKSPACE_DIR/SOUL.md" +echo -e " ${YELLOW}→${NC} Other files (AGENTS.md, HEARTBEAT.md, etc.) generated from base templates" + +# 2. Update init-agents.sh — add init_agent call before the completion message +echo -e "${YELLOW}2. Updating init-agents.sh...${NC}" + +# Add new init_agent call before the "Agent initialization complete" line +sed -i "/echo \"✅ Agent initialization complete!\"/i\\init_agent \"$AGENT_ID\" \"$AGENT_NAME\" \"$AGENT_EMOJI\" \"$AGENT_ROLE\" \"worker\"" "$INIT_SCRIPT" + +# Add to the squad list at the end +sed -i "/^echo \"\"$/i\\echo \" $AGENT_EMOJI $AGENT_NAME ($AGENT_ROLE) → \$DATA_DIR/workspace-$AGENT_ID\"" "$INIT_SCRIPT" + +echo -e " ${GREEN}✓${NC} Updated init-agents.sh" + +# 3. Update config.template.json +echo -e "${YELLOW}3. Updating config.template.json...${NC}" + +# Use Python to safely parse and update the JSON template +python3 -c " +import json, re, sys + +with open('$CONFIG_TEMPLATE', 'r') as f: + content = f.read() + +# Handle envsubst placeholders that aren't valid JSON +placeholders = {} +counter = [0] + +def replace_quoted(m): + key = 'PLACEHOLDER_' + str(counter[0]) + placeholders[key] = m.group(1) + counter[0] += 1 + return '\"' + key + '\"' + +def replace_bare(m): + key = 'PLACEHOLDER_' + str(counter[0]) + placeholders[key] = m.group(0) + counter[0] += 1 + return '\"' + key + '\"' + +# First quoted: \"\\\${VAR}\", then bare \\\${VAR} +safe = re.sub(r'\"(\\\$\{\w+\})\"', replace_quoted, content) +safe = re.sub(r'\\\$\{\w+\}', replace_bare, safe) + +config = json.loads(safe) + +config['agents']['list'].append({ + 'id': '$AGENT_ID', + 'name': '$AGENT_NAME', + 'workspace': '/data/workspace-$AGENT_ID', + 'model': 'anthropic/claude-sonnet-4-20250514', + 'identity': { + 'name': '$AGENT_NAME', + 'emoji': '$AGENT_EMOJI' + } +}) + +if '$AGENT_ID' not in config['tools']['agentToAgent']['allow']: + config['tools']['agentToAgent']['allow'].append('$AGENT_ID') + +result = json.dumps(config, indent=2) + +for key, val in placeholders.items(): + result = result.replace('\"' + key + '\"', val) + +with open('$CONFIG_TEMPLATE', 'w') as f: + f.write(result + '\n') +" 2>/dev/null + +if [ $? -eq 0 ]; then + echo -e " ${GREEN}✓${NC} Updated config.template.json" +else + echo -e " ${YELLOW}⚠${NC} Could not auto-update config. Please manually add agent to config.template.json" +fi + +# Summary +echo "" +echo -e "${GREEN}✅ Agent $AGENT_NAME $AGENT_EMOJI added successfully!${NC}" +echo "" +echo "Files created:" +echo " $WORKSPACE_DIR/SOUL.md (customize this!)" +echo "" +echo "Base templates used (shared with all agents):" +echo " $TEMPLATES_DIR/base/worker/AGENTS.md" +echo " $TEMPLATES_DIR/base/worker/HEARTBEAT.md" +echo " $TEMPLATES_DIR/base/worker/MEMORY.md" +echo " $TEMPLATES_DIR/base/worker/TOOLS.md" +echo " $TEMPLATES_DIR/base/worker/USER.md" +echo "" +echo "Next steps:" +echo " 1. Customize SOUL.md: $WORKSPACE_DIR/SOUL.md" +echo " 2. Rebuild and deploy:" +echo "" +echo " docker compose build --no-cache openclaw" +echo " docker compose up -d" +echo "" +echo " 3. Register at runtime: clawe agent:onboard $AGENT_ID $AGENT_NAME \"$AGENT_ROLE\" --emoji $AGENT_EMOJI --cron \"$CRON_SCHEDULE\"" +echo " 4. Validate: ./scripts/validate-agent.sh $AGENT_ID" diff --git a/scripts/validate-agent.sh b/scripts/validate-agent.sh new file mode 100755 index 0000000..d5b0e1f --- /dev/null +++ b/scripts/validate-agent.sh @@ -0,0 +1,159 @@ +#!/bin/bash +set -e + +# Clawe Validate Agent Script +# Usage: ./scripts/validate-agent.sh <agent-id> + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ROOT_DIR="$(dirname "$SCRIPT_DIR")" + +# Colors +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +AGENT_ID="$1" + +if [ -z "$AGENT_ID" ]; then + echo -e "${RED}Usage: $0 <agent-id>${NC}" + echo "" + echo "Example: $0 coder" + exit 1 +fi + +echo -e "${GREEN}🦞 Validating agent: $AGENT_ID${NC}" +echo "" + +ERRORS=0 +WARNINGS=0 + +check_pass() { echo -e " ${GREEN}✓${NC} $1"; } +check_fail() { echo -e " ${RED}✗${NC} $1"; ERRORS=$((ERRORS + 1)); } +check_warn() { echo -e " ${YELLOW}⚠${NC} $1"; WARNINGS=$((WARNINGS + 1)); } + +# Paths +WORKSPACE_DIR="$ROOT_DIR/docker/openclaw/templates/workspaces/$AGENT_ID" +BASE_LEAD="$ROOT_DIR/docker/openclaw/templates/base/lead" +BASE_WORKER="$ROOT_DIR/docker/openclaw/templates/base/worker" +INIT_SCRIPT="$ROOT_DIR/docker/openclaw/scripts/init-agents.sh" +CONFIG_TEMPLATE="$ROOT_DIR/docker/openclaw/templates/config.template.json" + +# Detect agent type from init-agents.sh +if grep -q "\"$AGENT_ID\".*\"lead\"" "$INIT_SCRIPT" 2>/dev/null; then + AGENT_TYPE="lead" + BASE_DIR="$BASE_LEAD" +else + AGENT_TYPE="worker" + BASE_DIR="$BASE_WORKER" +fi + +# 1. Check workspace template +echo -e "${YELLOW}1. Workspace Template (type: $AGENT_TYPE)${NC}" + +if [ -d "$WORKSPACE_DIR" ]; then + check_pass "Directory exists: workspaces/$AGENT_ID/" +else + check_fail "Directory missing: workspaces/$AGENT_ID/" +fi + +# SOUL.md must be in the agent's own workspace +if [ -f "$WORKSPACE_DIR/SOUL.md" ]; then + check_pass "SOUL.md exists (agent-specific)" +else + check_fail "SOUL.md missing (must be in workspaces/$AGENT_ID/)" +fi + +# Other files should exist in base templates +for file in AGENTS.md HEARTBEAT.md MEMORY.md TOOLS.md USER.md; do + if [ -f "$WORKSPACE_DIR/$file" ]; then + check_pass "$file exists (agent override)" + elif [ -f "$BASE_DIR/$file" ]; then + check_pass "$file exists (from base/$AGENT_TYPE/)" + else + check_fail "$file missing (not in workspace or base)" + fi +done + +# Check SOUL.md has task discipline +if [ -f "$WORKSPACE_DIR/SOUL.md" ]; then + if grep -q "Task Discipline" "$WORKSPACE_DIR/SOUL.md"; then + check_pass "SOUL.md has Task Discipline section" + else + check_warn "SOUL.md missing Task Discipline section" + fi +fi + +# Check HEARTBEAT.md has the warning (check both locations) +HEARTBEAT_FILE="$WORKSPACE_DIR/HEARTBEAT.md" +[ ! -f "$HEARTBEAT_FILE" ] && HEARTBEAT_FILE="$BASE_DIR/HEARTBEAT.md" +if [ -f "$HEARTBEAT_FILE" ]; then + if grep -q "NEVER submit for review" "$HEARTBEAT_FILE"; then + check_pass "HEARTBEAT.md has review warning" + else + check_warn "HEARTBEAT.md missing review warning" + fi +fi + +# 2. Check init-agents.sh +echo "" +echo -e "${YELLOW}2. Init Script${NC}" + +if grep -q "\"$AGENT_ID\"" "$INIT_SCRIPT"; then + check_pass "Agent in init-agents.sh" +else + check_fail "Agent not in init-agents.sh" +fi + +# 3. Check config.template.json +echo "" +echo -e "${YELLOW}3. Config Template${NC}" + +if grep -q "\"id\": \"$AGENT_ID\"" "$CONFIG_TEMPLATE" 2>/dev/null || \ + grep -q "\"$AGENT_ID\"" "$CONFIG_TEMPLATE" 2>/dev/null; then + check_pass "Agent in config.template.json agents.list" +else + check_fail "Agent not in config.template.json agents.list" +fi + +if grep -A 100 "agentToAgent" "$CONFIG_TEMPLATE" 2>/dev/null | grep -q "\"$AGENT_ID\""; then + check_pass "Agent in agentToAgent.allow" +else + check_fail "Agent not in agentToAgent.allow" +fi + +# 4. Check if Docker is running and agent is deployed +echo "" +echo -e "${YELLOW}4. Runtime (if Docker running)${NC}" + +if command -v docker &> /dev/null && docker compose -f "$ROOT_DIR/docker-compose.yml" ps 2>/dev/null | grep -q "openclaw"; then + AGENT_WS="/data/workspace-$AGENT_ID" + [ "$AGENT_TYPE" = "lead" ] && AGENT_WS="/data/workspace" + + if docker compose -f "$ROOT_DIR/docker-compose.yml" exec -T openclaw test -d "$AGENT_WS" 2>/dev/null; then + check_pass "Workspace deployed in container" + else + check_warn "Workspace not in container (rebuild needed?)" + fi + + if docker compose -f "$ROOT_DIR/docker-compose.yml" exec -T openclaw cat /data/config/openclaw.json 2>/dev/null | grep -q "\"$AGENT_ID\""; then + check_pass "Agent in running config" + else + check_warn "Agent not in running config (rebuild needed?)" + fi +else + echo -e " ${YELLOW}-${NC} Docker not running, skipping runtime checks" +fi + +# Summary +echo "" +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +if [ $ERRORS -eq 0 ] && [ $WARNINGS -eq 0 ]; then + echo -e "${GREEN}✅ All checks passed!${NC}" +elif [ $ERRORS -eq 0 ]; then + echo -e "${YELLOW}⚠️ $WARNINGS warning(s), no errors${NC}" +else + echo -e "${RED}❌ $ERRORS error(s), $WARNINGS warning(s)${NC}" +fi + +exit $ERRORS