diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..2bd77fdb --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,32 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '22' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Run typecheck + run: npm run typecheck + + - name: Run tests + run: npm test + + - name: Build + run: npm run build diff --git a/package-lock.json b/package-lock.json index 3fa58666..a1ad386e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "antfarm", - "version": "0.4.1", + "version": "0.5.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "antfarm", - "version": "0.4.1", + "version": "0.5.1", "dependencies": { "json5": "^2.2.3", "yaml": "^2.4.5" diff --git a/package.json b/package.json index c3af3fbf..fd067fae 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,9 @@ }, "scripts": { "build": "tsc -p tsconfig.json && cp src/server/index.html dist/server/index.html && chmod +x dist/cli/cli.js && node scripts/inject-version.js", - "start": "node dist/cli/cli.js" + "start": "node dist/cli/cli.js", + "test": "node --test tests/*.test.ts", + "typecheck": "tsc --noEmit" }, "dependencies": { "json5": "^2.2.3", diff --git a/src/cli/cli.ts b/src/cli/cli.ts index b69ee8fa..066a6c95 100755 --- a/src/cli/cli.ts +++ b/src/cli/cli.ts @@ -18,7 +18,7 @@ try { import { installWorkflow } from "../installer/install.js"; import { uninstallAllWorkflows, uninstallWorkflow, checkActiveRuns } from "../installer/uninstall.js"; import { getWorkflowStatus, listRuns, stopWorkflow } from "../installer/status.js"; -import { runWorkflow } from "../installer/run.js"; +import { runWorkflow, dryRunWorkflow } from "../installer/run.js"; import { listBundledWorkflows } from "../installer/workflow-fetch.js"; import { readRecentLogs } from "../lib/logger.js"; import { getRecentEvents, getRunEvents, type AntfarmEvent } from "../installer/events.js"; @@ -93,7 +93,7 @@ function printUsage() { "antfarm workflow install Install a workflow", "antfarm workflow uninstall Uninstall a workflow (blocked if runs active)", "antfarm workflow uninstall --all Uninstall all workflows (--force to override)", - "antfarm workflow run Start a workflow run", + "antfarm workflow run Start a workflow run (--dry-run to validate only)", "antfarm workflow status Check run status (task substring, run ID prefix)", "antfarm workflow runs List all workflow runs", "antfarm workflow resume Resume a failed run from where it left off", @@ -671,13 +671,23 @@ async function main() { if (action === "run") { let notifyUrl: string | undefined; + let dryRun = false; const runArgs = args.slice(3); const nuIdx = runArgs.indexOf("--notify-url"); if (nuIdx !== -1) { notifyUrl = runArgs[nuIdx + 1]; runArgs.splice(nuIdx, 2); } + const drIdx = runArgs.indexOf("--dry-run"); + if (drIdx !== -1) { + dryRun = true; + runArgs.splice(drIdx, 1); + } const taskTitle = runArgs.join(" ").trim(); + if (dryRun) { + await dryRunWorkflow({ workflowId: target, taskTitle }); + return; + } if (!taskTitle) { process.stderr.write("Missing task title.\n"); printUsage(); process.exit(1); } const run = await runWorkflow({ workflowId: target, taskTitle, notifyUrl }); process.stdout.write( diff --git a/src/db.ts b/src/db.ts index 2e9c5552..5e769028 100644 --- a/src/db.ts +++ b/src/db.ts @@ -85,6 +85,9 @@ function migrate(db: DatabaseSync): void { if (!colNames.has("abandoned_count")) { db.exec("ALTER TABLE steps ADD COLUMN abandoned_count INTEGER DEFAULT 0"); } + if (!colNames.has("session_key")) { + db.exec("ALTER TABLE steps ADD COLUMN session_key TEXT"); + } // Add columns to runs table for backwards compat const runCols = db.prepare("PRAGMA table_info(runs)").all() as Array<{ name: string }>; diff --git a/src/installer/agent-cron.ts b/src/installer/agent-cron.ts index c5ae7973..3236d206 100644 --- a/src/installer/agent-cron.ts +++ b/src/installer/agent-cron.ts @@ -89,7 +89,7 @@ The workflow cannot advance until you report. Your session ending without report } const DEFAULT_POLLING_TIMEOUT_SECONDS = 120; -const DEFAULT_POLLING_MODEL = "default"; +const DEFAULT_POLLING_MODEL = "minimax/MiniMax-M2.5"; function extractModel(value: unknown): string | undefined { if (!value) return undefined; @@ -125,10 +125,11 @@ async function resolveAgentCronModel(agentId: string, requestedModel?: string): return requestedModel; } -export function buildPollingPrompt(workflowId: string, agentId: string, workModel?: string): string { +export function buildPollingPrompt(workflowId: string, agentId: string, workModel?: string, workTimeoutSeconds?: number): string { const fullAgentId = `${workflowId}_${agentId}`; const cli = resolveAntfarmCli(); - const model = workModel ?? "default"; + const model = workModel ?? "minimax/MiniMax-M2.5"; + const timeoutSec = workTimeoutSeconds ?? DEFAULT_AGENT_TIMEOUT_SECONDS; const workPrompt = buildWorkPrompt(workflowId, agentId); return `Step 1 — Quick check for pending work (lightweight, no side effects): @@ -147,6 +148,7 @@ If JSON is returned, parse it to extract stepId, runId, and input fields. Then call sessions_spawn with these parameters: - agentId: "${fullAgentId}" - model: "${model}" +- runTimeoutSeconds: ${timeoutSec} - task: The full work prompt below, followed by "\\n\\nCLAIMED STEP JSON:\\n" and the exact JSON output from step claim. Full work prompt to include in the spawned task: @@ -173,11 +175,11 @@ export async function setupAgentCrons(workflow: WorkflowSpec): Promise { const agentId = `${workflow.id}_${agent.id}`; // Two-phase: Phase 1 uses cheap polling model + minimal prompt - const requestedPollingModel = agent.pollingModel ?? workflowPollingModel; - const pollingModel = await resolveAgentCronModel(agentId, requestedPollingModel); - const requestedWorkModel = agent.model ?? workflowPollingModel; - const workModel = await resolveAgentCronModel(agentId, requestedWorkModel); - const prompt = buildPollingPrompt(workflow.id, agent.id, workModel); + const pollingModel = agent.pollingModel ?? workflowPollingModel; + const workModel = agent.model ?? workflowPollingModel; + // Work agent timeout: per-agent > workflow default > library default (30 min) + const workTimeoutSeconds = agent.timeoutSeconds ?? DEFAULT_AGENT_TIMEOUT_SECONDS; + const prompt = buildPollingPrompt(workflow.id, agent.id, workModel, workTimeoutSeconds); const timeoutSeconds = workflowPollingTimeout; const result = await createAgentCronJob({ diff --git a/src/installer/gateway-api.ts b/src/installer/gateway-api.ts index d5ee4be4..99caf75b 100644 --- a/src/installer/gateway-api.ts +++ b/src/installer/gateway-api.ts @@ -256,7 +256,7 @@ export async function checkCronToolAvailable(): Promise<{ ok: boolean; error?: s } } -export async function listCronJobs(): Promise<{ ok: boolean; jobs?: Array<{ id: string; name: string }>; error?: string }> { +export async function listCronJobs(): Promise<{ ok: boolean; jobs?: Array<{ id: string; name: string; lastStatus?: string; consecutiveErrors?: number; enabled?: boolean }>; error?: string }> { // --- Try HTTP first --- const httpResult = await listCronJobsHTTP(); if (httpResult !== null) return httpResult; @@ -273,7 +273,7 @@ export async function listCronJobs(): Promise<{ ok: boolean; jobs?: Array<{ id: } /** HTTP-only list. Returns null on 404/network error. */ -async function listCronJobsHTTP(): Promise<{ ok: boolean; jobs?: Array<{ id: string; name: string }>; error?: string } | null> { +async function listCronJobsHTTP(): Promise<{ ok: boolean; jobs?: Array<{ id: string; name: string; lastStatus?: string; consecutiveErrors?: number; enabled?: boolean }>; error?: string } | null> { const gateway = await getGatewayConfig(); try { const headers: Record = { "Content-Type": "application/json" }; @@ -296,12 +296,18 @@ async function listCronJobsHTTP(): Promise<{ ok: boolean; jobs?: Array<{ id: str return { ok: false, error: result.error?.message ?? "Unknown error" }; } - let jobs: Array<{ id: string; name: string }> = []; + let jobs: Array<{ id: string; name: string; lastStatus?: string; consecutiveErrors?: number; enabled?: boolean }> = []; const content = result.result?.content; if (Array.isArray(content) && content[0]?.text) { try { const parsed = JSON.parse(content[0].text); - jobs = parsed.jobs ?? []; + jobs = (parsed.jobs ?? []).map((j: any) => ({ + id: j.id, + name: j.name, + lastStatus: j.state?.lastStatus, + consecutiveErrors: j.state?.consecutiveErrors, + enabled: j.enabled, + })); } catch { /* fallback */ } } if (jobs.length === 0) { @@ -364,6 +370,49 @@ export async function deleteAgentCronJobs(namePrefix: string): Promise { } } +/** + * Disable a cron job by ID (circuit breaker action). + */ +export async function disableCronJob(jobId: string): Promise<{ ok: boolean; error?: string }> { + // --- Try HTTP first --- + const httpResult = await disableCronJobHTTP(jobId); + if (httpResult !== null) return httpResult; + + // --- CLI fallback --- + try { + await runCli(["cron", "disable", jobId, "--json"]); + return { ok: true }; + } catch (err) { + return { ok: false, error: `CLI fallback failed: ${err}. ${UPDATE_HINT}` }; + } +} + +/** HTTP-only disable. Returns null on 404/network error. */ +async function disableCronJobHTTP(jobId: string): Promise<{ ok: boolean; error?: string } | null> { + const gateway = await getGatewayConfig(); + try { + const headers: Record = { "Content-Type": "application/json" }; + if (gateway.secret) headers["Authorization"] = `Bearer ${gateway.secret}`; + + const response = await fetch(`${gateway.url}/tools/invoke`, { + method: "POST", + headers, + body: JSON.stringify({ tool: "cron", args: { action: "disable", id: jobId }, sessionKey: "agent:main:main" }), + }); + + if (isTransientGatewayFailure(response.status)) return null; + + if (!response.ok) { + return { ok: false, error: `Gateway returned ${response.status}` }; + } + + const result = await response.json(); + return result.ok ? { ok: true } : { ok: false, error: result.error?.message ?? "Unknown error" }; + } catch { + return null; + } +} + export async function sendSessionMessage(params: { sessionKey: string; message: string }): Promise<{ ok: boolean; error?: string }> { const payload = { tool: "sessions_send", @@ -420,3 +469,81 @@ export async function sendSessionMessage(params: { sessionKey: string; message: return { ok: false, error: `CLI fallback failed: ${err}. ${UPDATE_HINT}` }; } } + +/** + * Kill a gateway session by session key. + * Sends a termination message to the session to gracefully shut it down. + */ +export async function killSession(sessionKey: string): Promise<{ ok: boolean; error?: string }> { + const gateway = await getGatewayConfig(); + + // Try HTTP first - use the gateway call to send a kill message + try { + const headers: Record = { "Content-Type": "application/json" }; + if (gateway.secret) headers["Authorization"] = `Bearer ${gateway.secret}`; + + // Try calling the sessions API to kill the session + const response = await fetch(`${gateway.url}/tools/invoke`, { + method: "POST", + headers, + body: JSON.stringify({ + tool: "sessions_send", + args: { + action: "kill", + sessionKey: sessionKey, + }, + sessionKey: "agent:main:main", + }), + }); + + if (response.ok) { + const result = await response.json(); + if (result.ok) return { ok: true }; + // If the tool doesn't exist or failed, try alternative approach + } + + // If the above didn't work, try a different approach - send a termination signal + const terminateResponse = await fetch(`${gateway.url}/tools/invoke`, { + method: "POST", + headers, + body: JSON.stringify({ + tool: "exec", + args: { + command: `openclaw sessions kill ${sessionKey}`, + }, + sessionKey: "agent:main:main", + }), + }); + + if (terminateResponse.ok) { + return { ok: true }; + } + } catch { + // Fall through to CLI fallback + } + + // --- Fallback to CLI --- + try { + // Try to kill the session via CLI + await runCli(["sessions", "kill", sessionKey, "--json"]); + return { ok: true }; + } catch { + // sessions kill might not be a valid command, try using message to signal exit + try { + await runCli([ + "tool", + "run", + "--tool", + "sessions_send", + "--session", + sessionKey, + "--json", + "--message", + "SESSION_KILL_REQUESTED: This session has been terminated by antfarm. Please stop immediately.", + ]); + return { ok: true }; + } catch (err) { + return { ok: false, error: `Failed to kill session: ${err}` }; + } + } +} diff --git a/src/installer/run.ts b/src/installer/run.ts index 711d5744..c6afaab5 100644 --- a/src/installer/run.ts +++ b/src/installer/run.ts @@ -5,6 +5,121 @@ import { getDb, nextRunNumber } from "../db.js"; import { logger } from "../lib/logger.js"; import { ensureWorkflowCrons } from "./agent-cron.js"; import { emitEvent } from "./events.js"; +import { resolveTemplate } from "./step-ops.js"; + +export interface DryRunResult { + workflowId: string; + workflowName: string; + task: string; + steps: DryRunStep[]; + context: Record; +} + +export interface DryRunStep { + stepIndex: number; + stepId: string; + agentId: string; + type: "single" | "loop"; + inputTemplate: string; + resolvedInput: string; + expects: string; + status: string; +} + +export async function dryRunWorkflow(params: { + workflowId: string; + taskTitle: string; +}): Promise { + // 1. Validate workflow YAML + const workflowDir = resolveWorkflowDir(params.workflowId); + const workflow = await loadWorkflowSpec(workflowDir); + + // 2. Build execution context with placeholder values + const placeholderContext: Record = { + task: params.taskTitle, + run_id: "dry-run-00000000-0000-0000-0000-000000000000", + run_number: "0", + ...workflow.context, + }; + + // Add placeholder values for any workflow context variables not provided + if (workflow.context) { + for (const [key, value] of Object.entries(workflow.context)) { + placeholderContext[key] = value; + } + } + + // 3. Resolve all step input templates + const steps: DryRunStep[] = []; + for (let i = 0; i < workflow.steps.length; i++) { + const step = workflow.steps[i]; + const agentId = workflow.id + "_" + step.agent; + const stepType = step.type ?? "single"; + + // Resolve the input template against our context + const resolvedInput = resolveTemplate(step.input, placeholderContext); + + steps.push({ + stepIndex: i, + stepId: step.id, + agentId, + type: stepType, + inputTemplate: step.input, + resolvedInput, + expects: step.expects, + status: i === 0 ? "pending" : "waiting", + }); + } + + // 4. Print execution plan + console.log(""); + console.log("═══════════════════════════════════════════════════════════════"); + console.log(" DRY-RUN EXECUTION PLAN"); + console.log("═══════════════════════════════════════════════════════════════"); + console.log(""); + console.log("Workflow: " + (workflow.name ?? workflow.id) + " (" + workflow.id + ")"); + console.log("Task: " + params.taskTitle); + console.log("Steps: " + steps.length); + console.log(""); + + console.log("─────────────────────────────────────────────────────────────────"); + console.log("CONTEXT (placeholder values):"); + console.log("─────────────────────────────────────────────────────────────────"); + for (const [key, value] of Object.entries(placeholderContext)) { + console.log(" {{" + key + "}}: " + value); + } + console.log(""); + + console.log("─────────────────────────────────────────────────────────────────"); + console.log("EXECUTION ORDER:"); + console.log("─────────────────────────────────────────────────────────────────"); + for (const step of steps) { + const statusIcon = step.status === "pending" ? "→" : "…"; + const typeLabel = step.type === "loop" ? " [LOOP]" : ""; + console.log(statusIcon + " Step " + (step.stepIndex + 1) + ": " + step.stepId + typeLabel); + console.log(" Agent: " + step.agentId); + const inputPreview = step.resolvedInput.slice(0, 100); + const inputSuffix = step.resolvedInput.length > 100 ? "..." : ""; + console.log(" Input: " + inputPreview + inputSuffix); + console.log(" Expects: " + step.expects); + console.log(""); + } + + console.log("═══════════════════════════════════════════════════════════════"); + console.log(" VALIDATION PASSED"); + console.log("═══════════════════════════════════════════════════════════════"); + console.log("Workflow YAML is valid. All templates resolved."); + console.log("No database entries created. No agents spawned."); + console.log(""); + + return { + workflowId: workflow.id, + workflowName: workflow.name ?? workflow.id, + task: params.taskTitle, + steps, + context: placeholderContext, + }; +} export async function runWorkflow(params: { workflowId: string; @@ -38,7 +153,7 @@ export async function runWorkflow(params: { for (let i = 0; i < workflow.steps.length; i++) { const step = workflow.steps[i]; const stepUuid = crypto.randomUUID(); - const agentId = `${workflow.id}_${step.agent}`; + const agentId = workflow.id + "_" + step.agent; const status = i === 0 ? "pending" : "waiting"; const maxRetries = step.max_retries ?? step.on_fail?.max_retries ?? 2; const stepType = step.type ?? "single"; @@ -60,12 +175,12 @@ export async function runWorkflow(params: { const db2 = getDb(); db2.prepare("UPDATE runs SET status = 'failed', updated_at = ? WHERE id = ?").run(new Date().toISOString(), runId); const message = err instanceof Error ? err.message : String(err); - throw new Error(`Cannot start workflow run: cron setup failed. ${message}`); + throw new Error("Cannot start workflow run: cron setup failed. " + message); } emitEvent({ ts: new Date().toISOString(), event: "run.started", runId, workflowId: workflow.id }); - logger.info(`Run started: "${params.taskTitle}"`, { + logger.info("Run started: \"" + params.taskTitle + "\"", { workflowId: workflow.id, runId, stepId: workflow.steps[0]?.id, diff --git a/src/installer/step-ops.ts b/src/installer/step-ops.ts index bf47b057..c267b223 100644 --- a/src/installer/step-ops.ts +++ b/src/installer/step-ops.ts @@ -53,6 +53,32 @@ export function parseOutputKeyValues(output: string): Record { return result; } +/** + * Validate that step output contains all required keys specified in expects. + * Throws an error if any required keys are missing. + * The expects format is "KEY1: value1, KEY2: value2" - we only check key presence. + */ +function validateStepOutput(expects: string, output: string): void { + if (!expects?.trim()) return; + + // Parse expected keys from expects string (format: "KEY1: value1, KEY2: value2") + const expectedKeys = expects.split(",").map(s => s.trim().split(":")[0].toLowerCase()).filter(k => k); + + // Parse actual output keys + const actualKeys = parseOutputKeyValues(output); + + // Check each expected key is present + const missingKeys = expectedKeys.filter(key => !actualKeys.hasOwnProperty(key)); + + if (missingKeys.length > 0) { + throw new Error( + `Step output missing required keys: ${missingKeys.join(", ")}. ` + + `Expected keys from 'expects': ${expects}. ` + + `Add these keys to your output before completing the step.` + ); + } +} + /** * Fire-and-forget cron teardown when a run ends. * Looks up the workflow_id for the run and tears down crons if no other active runs. @@ -481,8 +507,10 @@ const CLEANUP_THROTTLE_MS = 5 * 60 * 1000; // 5 minutes /** * Find and claim a pending step for an agent, returning the resolved input. + * @param agentId - The agent ID claiming the step + * @param sessionKey - Optional session key to track the gateway session for this step */ -export function claimStep(agentId: string): ClaimResult { +export function claimStep(agentId: string, sessionKey?: string): ClaimResult { // Throttle cleanup: run at most once every 5 minutes across all agents const now = Date.now(); if (now - lastCleanupTime >= CLEANUP_THROTTLE_MS) { @@ -492,7 +520,7 @@ export function claimStep(agentId: string): ClaimResult { const db = getDb(); const step = db.prepare( - `SELECT s.id, s.step_id, s.run_id, s.input_template, s.type, s.loop_config, s.step_index + `SELECT s.id, s.step_id, s.run_id, s.input_template, s.type, s.loop_config, s.step_index, s.output, s.expects FROM steps s JOIN runs r ON r.id = s.run_id WHERE s.agent_id = ? AND s.status = 'pending' @@ -509,6 +537,8 @@ export function claimStep(agentId: string): ClaimResult { id: string; step_id: string; run_id: string; input_template: string; type: string; loop_config: string | null; step_index: number; + current_story_id: string | null; status: string; + output: string | null; expects: string; } | undefined; if (!step) return { found: false }; @@ -533,6 +563,15 @@ export function claimStep(agentId: string): ClaimResult { // T6: Loop step claim logic if (step.type === "loop") { + // Safety: if step is "running" but has no current_story_id, re-claim a pending story + if (!step.current_story_id && step.status === "running") { + logger.warn(`Safety reset: step ${step.step_id} is running but has no current_story_id, resetting to pending`); + db.prepare( + "UPDATE steps SET status = 'pending', updated_at = datetime('now') WHERE id = ?" + ).run(step.id); + step.status = "pending"; // Update local state + } + const loopConfig: LoopConfig | null = step.loop_config ? JSON.parse(step.loop_config) : null; if (loopConfig?.over === "stories") { if (!runHasStories(step.run_id)) { @@ -575,7 +614,27 @@ export function claimStep(agentId: string): ClaimResult { return { found: false }; } - // No pending or failed stories — mark step done and advance + // No pending or failed stories — validate output before marking done + const stepOutput = step.output ?? ""; + try { + validateStepOutput(step.expects, stepOutput); + } catch (validationError: any) { + // Validation failed: mark step as failed instead of done + const message = validationError.message; + db.prepare( + "UPDATE steps SET status = 'failed', output = ?, updated_at = datetime('now') WHERE id = ?" + ).run(message, step.id); + db.prepare( + "UPDATE runs SET status = 'failed', updated_at = datetime('now') WHERE id = ?" + ).run(step.run_id); + const wfId = getWorkflowId(step.run_id); + emitEvent({ ts: new Date().toISOString(), event: "step.failed", runId: step.run_id, workflowId: wfId, stepId: step.step_id, agentId: agentId, detail: message }); + emitEvent({ ts: new Date().toISOString(), event: "run.failed", runId: step.run_id, workflowId: wfId, detail: message }); + scheduleRunCronTeardown(step.run_id); + return { found: false }; + } + + // Validation passed — mark step done and advance db.prepare( "UPDATE steps SET status = 'done', updated_at = datetime('now') WHERE id = ?" ).run(step.id); @@ -589,8 +648,8 @@ export function claimStep(agentId: string): ClaimResult { "UPDATE stories SET status = 'running', updated_at = datetime('now') WHERE id = ?" ).run(nextStory.id); db.prepare( - "UPDATE steps SET status = 'running', current_story_id = ?, updated_at = datetime('now') WHERE id = ?" - ).run(nextStory.id, step.id); + "UPDATE steps SET status = 'running', current_story_id = ?, session_key = ?, updated_at = datetime('now') WHERE id = ?" + ).run(nextStory.id, sessionKey || null, step.id); const wfId = getWorkflowId(step.run_id); emitEvent({ ts: new Date().toISOString(), event: "step.running", runId: step.run_id, workflowId: wfId, stepId: step.step_id, agentId: agentId }); @@ -618,6 +677,10 @@ export function claimStep(agentId: string): ClaimResult { context["current_story"] = formatStoryForTemplate(story); context["current_story_id"] = story.storyId; context["current_story_title"] = story.title; + context["current_story.id"] = story.storyId; + context["current_story.title"] = story.title; + context["current_story.files"] = nextStory.files || ""; + context["current_story.description"] = story.description; context["completed_stories"] = formatCompletedStories(allStories); context["stories_remaining"] = String(pendingCount); context["progress"] = readProgressFile(step.run_id); @@ -642,8 +705,8 @@ export function claimStep(agentId: string): ClaimResult { // Single step: existing logic db.prepare( - "UPDATE steps SET status = 'running', updated_at = datetime('now') WHERE id = ? AND status = 'pending'" - ).run(step.id); + "UPDATE steps SET status = 'running', session_key = ?, updated_at = datetime('now') WHERE id = ? AND status = 'pending'" + ).run(sessionKey || null, step.id); emitEvent({ ts: new Date().toISOString(), event: "step.running", runId: step.run_id, workflowId: getWorkflowId(step.run_id), stepId: step.step_id, agentId: agentId }); logger.info(`Step claimed by ${agentId}`, { runId: step.run_id, stepId: step.step_id }); @@ -680,11 +743,14 @@ export function completeStep(stepId: string, output: string): { advanced: boolea const db = getDb(); const step = db.prepare( - "SELECT id, run_id, step_id, step_index, type, loop_config, current_story_id FROM steps WHERE id = ?" - ).get(stepId) as { id: string; run_id: string; step_id: string; step_index: number; type: string; loop_config: string | null; current_story_id: string | null } | undefined; + "SELECT id, run_id, step_id, step_index, type, loop_config, current_story_id, expects, session_key FROM steps WHERE id = ?" + ).get(stepId) as { id: string; run_id: string; step_id: string; step_index: number; type: string; loop_config: string | null; current_story_id: string | null; expects: string; session_key: string | null } | undefined; if (!step) throw new Error(`Step not found: ${stepId}`); + // Validate expected output keys before processing + validateStepOutput(step.expects, output); + // Guard: don't process completions for failed runs const runCheck = db.prepare("SELECT status FROM runs WHERE id = ?").get(step.run_id) as { status: string } | undefined; if (runCheck?.status === "failed") { @@ -701,6 +767,10 @@ export function completeStep(stepId: string, output: string): { advanced: boolea context[key] = value; } + // Set defaults for reviewer template keys if not provided + if (!context["commit"]) context["commit"] = "none"; + if (!context["test_result"]) context["test_result"] = "none"; + db.prepare( "UPDATE runs SET context = ?, updated_at = datetime('now') WHERE id = ?" ).run(JSON.stringify(context), step.run_id); @@ -737,10 +807,10 @@ export function completeStep(stepId: string, output: string): { advanced: boolea db.prepare( "UPDATE steps SET status = 'pending', updated_at = datetime('now') WHERE id = ?" ).run(verifyStep.id); - // Loop step stays 'running' + // Loop step stays 'running' - preserve existing session_key db.prepare( - "UPDATE steps SET status = 'running', updated_at = datetime('now') WHERE id = ?" - ).run(step.id); + "UPDATE steps SET status = 'running', session_key = ?, updated_at = datetime('now') WHERE id = ?" + ).run(step.session_key, step.id); return { advanced: false, runCompleted: false }; } } @@ -857,6 +927,8 @@ function checkLoopContinuation(runId: string, loopStepId: string): { advanced: b "SELECT status FROM steps WHERE id = ?" ).get(loopStepId) as { status: string } | undefined; + logger.info(`checkLoopContinuation: runId=${runId}, loopStepId=${loopStepId}, pendingStory=${!!pendingStory}, loopStatus=${loopStatus?.status}`); + if (pendingStory) { if (loopStatus?.status === "failed") { return { advanced: false, runCompleted: false }; diff --git a/src/medic/checks.ts b/src/medic/checks.ts index 4ddffab5..a248f547 100644 --- a/src/medic/checks.ts +++ b/src/medic/checks.ts @@ -9,6 +9,7 @@ export type MedicActionType = | "reset_step" | "fail_run" | "teardown_crons" + | "disable_cron" | "none"; export interface MedicFinding { @@ -156,6 +157,11 @@ export function checkDeadRuns(): MedicFinding[] { // ── Check: Orphaned Crons ─────────────────────────────────────────── +/** + * Configuration for circuit breaker: number of consecutive errors before auto-disabling a cron. + */ +const CRON_CIRCUIT_BREAKER_THRESHOLD = 5; + /** * Check if agent crons exist for workflows with zero active runs. * Returns workflow IDs that should have their crons torn down. @@ -196,6 +202,42 @@ export function checkOrphanedCrons( return findings; } +// ── Check: Failing Cron Jobs (Circuit Breaker) ────────────────────── + +/** + * Find cron jobs with too many consecutive errors and auto-disable them. + * This prevents wasted tokens on persistently failing cron jobs. + * + * NOTE: This check requires the list of current cron jobs with status info, + * since reading crons is async (gateway API). The medic runner handles this. + */ +export function checkFailingCrons( + cronJobs: Array<{ id: string; name: string; consecutiveErrors?: number; enabled?: boolean }>, +): MedicFinding[] { + const findings: MedicFinding[] = []; + + for (const job of cronJobs) { + // Skip already disabled crons + if (job.enabled === false) continue; + + // Only check antfarm cron jobs + if (!job.name.startsWith("antfarm/")) continue; + + const errors = job.consecutiveErrors ?? 0; + if (errors >= CRON_CIRCUIT_BREAKER_THRESHOLD) { + findings.push({ + check: "failing_crons", + severity: "warning", + message: `Cron job "${job.name}" has ${errors} consecutive errors — circuit breaker auto-disabling`, + action: "disable_cron", + remediated: false, + }); + } + } + + return findings; +} + // ── Run All Checks ────────────────────────────────────────────────── /** diff --git a/src/medic/medic-cron.ts b/src/medic/medic-cron.ts index 082281e6..e556a03d 100644 --- a/src/medic/medic-cron.ts +++ b/src/medic/medic-cron.ts @@ -7,27 +7,24 @@ import { readOpenClawConfig, writeOpenClawConfig } from "../installer/openclaw-c const MEDIC_CRON_NAME = "antfarm/medic"; const MEDIC_EVERY_MS = 5 * 60 * 1000; // 5 minutes -const MEDIC_MODEL = "default"; +const MEDIC_MODEL = "minimax/MiniMax-M2.5"; const MEDIC_TIMEOUT_SECONDS = 120; function buildMedicPrompt(): string { const cli = resolveAntfarmCli(); - return `You are the Antfarm Medic — a health watchdog for workflow runs. + return `You are the Antfarm Medic — a lightweight health watchdog. -Run the medic check: +Run the medic check and respond: \`\`\` node ${cli} medic run --json \`\`\` -If the check output contains "issuesFound": 0, reply HEARTBEAT_OK and stop. -If issues were found, summarize what was detected and what actions were taken. +Respond with ONLY: +- "HEARTBEAT_OK" (exact text, no other output) if issuesFound is 0 +- A summary if issues were found -If there are critical unremediated issues, use sessions_send to alert the main session: -\`\`\` -sessions_send(sessionKey: "agent:main:main", message: "🚑 Antfarm Medic Alert: ") -\`\`\` - -Do NOT attempt to fix issues yourself beyond what the medic check already handles.`; +Do NOT attempt to fix issues yourself. The medic check handles remediation. +If critical issues, alert via sessions_send to agent:main:main.`; } async function ensureMedicAgent(): Promise { diff --git a/src/medic/medic.ts b/src/medic/medic.ts index aa89b541..ce2a1d2e 100644 --- a/src/medic/medic.ts +++ b/src/medic/medic.ts @@ -7,10 +7,11 @@ import { getDb } from "../db.js"; import { emitEvent, type EventType } from "../installer/events.js"; import { teardownWorkflowCronsIfIdle } from "../installer/agent-cron.js"; -import { listCronJobs } from "../installer/gateway-api.js"; +import { listCronJobs, disableCronJob } from "../installer/gateway-api.js"; import { runSyncChecks, checkOrphanedCrons, + checkFailingCrons, type MedicFinding, } from "./checks.js"; import crypto from "node:crypto"; @@ -119,6 +120,41 @@ async function remediate(finding: MedicFinding): Promise { } } + case "disable_cron": { + // Extract cron job name from the message (format: 'Cron job "xyz" has N consecutive errors') + const cronMatch = finding.message.match(/Cron job "([^"]+)"/); + if (!cronMatch) return false; + + // Find the cron job ID from the name + const listResult = await listCronJobs(); + if (!listResult.ok || !listResult.jobs) return false; + + const cronJob = listResult.jobs.find(j => j.name === cronMatch[1]); + if (!cronJob) return false; + + try { + const result = await disableCronJob(cronJob.id); + if (result.ok) { + // Log the disabling + const db = getDb(); + db.prepare( + "INSERT INTO medic_checks (id, checked_at, issues_found, actions_taken, summary, details) VALUES (?, ?, ?, ?, ?, ?)" + ).run( + crypto.randomUUID(), + new Date().toISOString(), + 0, + 1, + `Circuit breaker disabled cron: ${cronMatch[1]}`, + JSON.stringify([{ action: "disable_cron", jobId: cronJob.id, jobName: cronMatch[1] }]) + ); + return true; + } + return false; + } catch { + return false; + } + } + case "none": default: return false; @@ -151,6 +187,7 @@ export async function runMedicCheck(): Promise { if (cronResult.ok && cronResult.jobs) { const antfarmCrons = cronResult.jobs.filter(j => j.name.startsWith("antfarm/")); findings.push(...checkOrphanedCrons(antfarmCrons)); + findings.push(...checkFailingCrons(cronResult.jobs)); } } catch { // Can't check crons — skip this check diff --git a/tests/bug-fix-polling.test.ts b/tests/bug-fix-polling.test.ts index 99f26d2d..13d8f858 100644 --- a/tests/bug-fix-polling.test.ts +++ b/tests/bug-fix-polling.test.ts @@ -21,7 +21,7 @@ describe("bug-fix workflow polling config", () => { it("has a polling section with model and timeoutSeconds", async () => { const spec = await loadWorkflowSpec(WORKFLOW_DIR); assert.ok(spec.polling, "polling config should exist"); - assert.equal(spec.polling.model, "default"); + assert.equal(spec.polling.model, "minimax/MiniMax-M2.5"); assert.equal(spec.polling.timeoutSeconds, 120); }); diff --git a/tests/feature-dev-polling.test.ts b/tests/feature-dev-polling.test.ts index cfe9ee53..674bbfa9 100644 --- a/tests/feature-dev-polling.test.ts +++ b/tests/feature-dev-polling.test.ts @@ -21,7 +21,7 @@ describe("feature-dev workflow polling config", () => { it("has a polling section with model and timeoutSeconds", async () => { const spec = await loadWorkflowSpec(WORKFLOW_DIR); assert.ok(spec.polling, "polling config should exist"); - assert.equal(spec.polling.model, "default"); + assert.equal(spec.polling.model, "minimax/MiniMax-M2.5"); assert.equal(spec.polling.timeoutSeconds, 120); }); diff --git a/tests/polling-prompt.test.ts b/tests/polling-prompt.test.ts index ec40de06..35ac2f83 100644 --- a/tests/polling-prompt.test.ts +++ b/tests/polling-prompt.test.ts @@ -54,7 +54,7 @@ describe("buildPollingPrompt", () => { it("uses default model when workModel not provided", () => { const prompt = buildPollingPrompt("feature-dev", "developer"); - assert.ok(prompt.includes('"default"'), "should use default model"); + assert.ok(prompt.includes('"minimax/MiniMax-M2.5"'), "should use default minimax model"); }); it("instructs to include claimed JSON in spawned task", () => { diff --git a/tests/polling-timeout-sync.test.ts b/tests/polling-timeout-sync.test.ts index 88f6eb71..31a5f360 100644 --- a/tests/polling-timeout-sync.test.ts +++ b/tests/polling-timeout-sync.test.ts @@ -39,15 +39,15 @@ describe("polling timeout sync across all workflows", () => { ); }); - it(`${name} workflow polling.model is set to 'default' (OpenClaw resolves model)`, async () => { + it(`${name} workflow polling.model is set to 'minimax/MiniMax-M2.5'`, async () => { const dir = path.join(WORKFLOWS_DIR, name); const spec = await loadWorkflowSpec(dir); assert.ok(spec.polling, `${name} should have a polling config`); assert.equal( spec.polling.model, - "default", - `${name} polling model should be "default" to let OpenClaw resolve the model, got: ${spec.polling.model}` + "minimax/MiniMax-M2.5", + `${name} polling model should be "minimax/MiniMax-M2.5", got: ${spec.polling.model}` ); }); } diff --git a/tests/security-audit-polling.test.ts b/tests/security-audit-polling.test.ts index cb20ceeb..c954c76b 100644 --- a/tests/security-audit-polling.test.ts +++ b/tests/security-audit-polling.test.ts @@ -21,7 +21,7 @@ describe("security-audit workflow polling config", () => { it("has a polling section with model and timeoutSeconds", async () => { const spec = await loadWorkflowSpec(WORKFLOW_DIR); assert.ok(spec.polling, "polling config should exist"); - assert.equal(spec.polling.model, "default"); + assert.equal(spec.polling.model, "minimax/MiniMax-M2.5"); assert.equal(spec.polling.timeoutSeconds, 120); }); diff --git a/tests/two-phase-cron.test.ts b/tests/two-phase-cron.test.ts index 6b214182..5d5929ca 100644 --- a/tests/two-phase-cron.test.ts +++ b/tests/two-phase-cron.test.ts @@ -16,7 +16,7 @@ describe("two-phase-cron-setup", () => { it("includes the default work model when none specified", () => { const prompt = buildPollingPrompt("feature-dev", "developer"); - assert.ok(prompt.includes('"default"'), "should include default work model"); + assert.ok(prompt.includes('"minimax/MiniMax-M2.5"'), "should include default minimax work model"); }); it("includes custom work model when specified", () => { @@ -44,14 +44,14 @@ describe("two-phase-cron-setup", () => { // These tests verify the exported constants and prompt builder behavior // that setupAgentCrons depends on - it("default work model is 'default'", async () => { + it("default work model is minimax", async () => { // We verify this through the module — the constant is used in setupAgentCrons // The polling prompt doesn't contain the polling model (that's in the cron payload) // but we can verify the work model default const prompt = buildPollingPrompt("test", "agent"); // The polling prompt contains the WORK model, not the polling model // The polling model is set in the cron job payload by setupAgentCrons - assert.ok(prompt.includes('"default"'), "default work model in prompt"); + assert.ok(prompt.includes('"minimax/MiniMax-M2.5"'), "default work model in prompt"); }); it("polling prompt uses correct agent id format", () => { diff --git a/tests/two-phase-integration.test.ts b/tests/two-phase-integration.test.ts index 0e902759..42fac39a 100644 --- a/tests/two-phase-integration.test.ts +++ b/tests/two-phase-integration.test.ts @@ -27,13 +27,13 @@ describe("two-phase-integration", () => { }); }); - // AC2: Without polling config, defaults to "default" model + // AC2: Without polling config, defaults to minimax model // (The default polling MODEL is set in setupAgentCrons payload, not in the prompt itself. // The prompt contains the WORK model. We verify default work model here.) describe("defaults without polling config", () => { - it("uses 'default' work model when no workModel specified", () => { + it("uses minimax work model when no workModel specified", () => { const prompt = buildPollingPrompt("feature-dev", "developer"); - assert.ok(prompt.includes('"default"'), "default work model"); + assert.ok(prompt.includes('"minimax/MiniMax-M2.5"'), "default work model"); }); it("agent id uses namespaced format (workflowId_agentId)", () => { diff --git a/workflows/bug-fix/agents/fixer/AGENTS.md b/workflows/bug-fix/agents/fixer/AGENTS.md index dba9504e..171ddd50 100644 --- a/workflows/bug-fix/agents/fixer/AGENTS.md +++ b/workflows/bug-fix/agents/fixer/AGENTS.md @@ -2,6 +2,21 @@ You implement the bug fix and write a regression test. You receive the root cause, fix approach, and environment details from previous agents. +## Memory Access + +You have access to the workspace memory system. Use it to find context. + +```bash +# Search for relevant files, past decisions, patterns, conventions +~/.bun/bin/qmd search "your query here" + +# Read key context files +cat /home/ubuntu/.openclaw/workspace/memory/core/boot.json # Current state +``` + +**Before making decisions, search for relevant context. Never guess when you can search.** + + ## Your Process 1. **cd into the repo** and checkout the bugfix branch @@ -48,6 +63,26 @@ CHANGES: what files were changed and what was done (e.g., "Updated filterUsers i REGRESSION_TEST: what test was added (e.g., "Added 'handles null displayName in search' test in src/lib/search.test.ts") ``` +## ⚠️ CRITICAL: Complete Your Step + +**You MUST call `step complete` after outputting your status, or the workflow will be stuck forever.** + +After outputting the format above, you MUST run: + +```bash +# Write output to file first (shell escaping breaks direct args) +cat <<'ANTFARM_EOF' > /tmp/antfarm-step-output.txt +STATUS: done +CHANGES: what files were changed and what was done +REGRESSION_TEST: what test was added +ANTFARM_EOF + +# Then pipe to step complete - replace with your actual step ID +cat /tmp/antfarm-step-output.txt | node /home/ubuntu/.openclaw/workspace/antfarm/dist/cli/cli.js step complete "" +``` + +**This is non-negotiable. Your session will end after this call.** + ## Critical: All Changes Must Be In The Repo Your changes MUST be to files tracked in the git repo at `{{repo}}`. If the bug requires changing files outside the repo (e.g., workspace config, external tool settings), those changes still need to originate from the repo's source code (installer templates, config generators, etc.). Never edit external files directly — find and fix the repo code that produces them. diff --git a/workflows/bug-fix/agents/investigator/AGENTS.md b/workflows/bug-fix/agents/investigator/AGENTS.md index 168225e7..54177a87 100644 --- a/workflows/bug-fix/agents/investigator/AGENTS.md +++ b/workflows/bug-fix/agents/investigator/AGENTS.md @@ -2,6 +2,21 @@ You trace bugs to their root cause. You receive triage data (affected area, reproduction steps, problem statement) and dig deeper to understand exactly what's wrong and why. +## Memory Access + +You have access to the workspace memory system. Use it to find context. + +```bash +# Search for relevant files, past decisions, patterns, conventions +~/.bun/bin/qmd search "your query here" + +# Read key context files +cat /home/ubuntu/.openclaw/workspace/memory/core/boot.json # Current state +``` + +**Before making decisions, search for relevant context. Never guess when you can search.** + + ## Your Process 1. **Read the affected code** — Open the files identified by the triager diff --git a/workflows/bug-fix/agents/triager/AGENTS.md b/workflows/bug-fix/agents/triager/AGENTS.md index 119d9cf6..6f21cbcf 100644 --- a/workflows/bug-fix/agents/triager/AGENTS.md +++ b/workflows/bug-fix/agents/triager/AGENTS.md @@ -2,6 +2,21 @@ You analyze bug reports, explore the codebase to find affected areas, attempt to reproduce the issue, and classify severity. +## Memory Access + +You have access to the workspace memory system. Use it to find context. + +```bash +# Search for relevant files, past decisions, patterns, conventions +~/.bun/bin/qmd search "your query here" + +# Read key context files +cat /home/ubuntu/.openclaw/workspace/memory/core/boot.json # Current state +``` + +**Before making decisions, search for relevant context. Never guess when you can search.** + + ## Your Process 1. **Read the bug report** — Extract symptoms, error messages, steps to reproduce, affected features diff --git a/workflows/bug-fix/workflow.yml b/workflows/bug-fix/workflow.yml index 1ec1126a..54918202 100644 --- a/workflows/bug-fix/workflow.yml +++ b/workflows/bug-fix/workflow.yml @@ -9,7 +9,7 @@ description: | PR agent creates the pull request. polling: - model: default + model: minimax/MiniMax-M2.5 timeoutSeconds: 120 agents: diff --git a/workflows/coding-sprint/agents/coder/AGENTS.md b/workflows/coding-sprint/agents/coder/AGENTS.md new file mode 100644 index 00000000..3aea0fc9 --- /dev/null +++ b/workflows/coding-sprint/agents/coder/AGENTS.md @@ -0,0 +1,115 @@ +# Coder Agent + +You implement a single coding task on a feature branch, test it, and commit it. You work autonomously. Do not ask questions — make reasonable decisions and document them. + +## Memory Access + +You have access to the workspace memory system. Use it when you need context. + +```bash +# Search for patterns, conventions, past decisions about this codebase +~/.bun/bin/qmd search "your query here" + +# Check current workspace state +cat /home/ubuntu/.openclaw/workspace/memory/core/boot.json +``` + +**If you're unsure about a convention or pattern, search before guessing.** + +## Before You Start + +- Read the CURRENT TASK carefully — understand exactly what needs to change +- Read the PROGRESS LOG — understand what previous tasks did +- Read the relevant files BEFORE writing any code +- If the task touches unfamiliar code, run `qmd search` for context + +## Implementation Standards + +- Make ONLY the changes described in your task — no scope creep +- Follow existing code style (indentation, naming, patterns in the file) +- Handle edge cases and errors +- Don't leave TODOs or incomplete work — finish what you start +- If something is unclear, make a reasonable assumption and note it in the commit message + +## Branch Management + +Always work on the feature branch provided. Never touch `main` or `master`. + +```bash +cd {{repo}} +git checkout {{branch}} 2>/dev/null || git checkout -b {{branch}} +``` + +If the branch already has commits from previous tasks, pull them first: +```bash +git pull origin {{branch}} 2>/dev/null || true +``` + +## Testing + +After implementing, try to validate your changes: +- Python: run pytest or at minimum `python -m py_compile` on changed files +- Node/TypeScript: run `npx tsc --noEmit` or `npm test` +- If no test runner exists, at least confirm the file is valid syntax +- Document the test result in your reply + +## Committing + +```bash +git add -A +git commit -m "sprint: [task title]" +``` + +Get the commit hash for your reply: +```bash +git rev-parse HEAD +``` + +## Progress Log + +Always append to the progress log after completing your task: +```bash +echo "## TASK: [id] - [title] +- Files: [list] +- Summary: [what you did] +- Test: [result] +" >> {{repo}}/progress-{{run_id}}.txt +``` + +## Output Format + +Reply with EXACTLY: +``` +STATUS: done +CHANGES: [bullet list of changes] +TEST_RESULT: PASSED | FAILED | NO_TESTS | SYNTAX_OK +COMMIT: [git commit hash] +``` + +## ⚠️ CRITICAL: Complete Your Step + +**You MUST call `step complete` after outputting your status, or the workflow will be stuck forever.** + +After outputting the format above, you MUST run: + +```bash +# Write output to file first (shell escaping breaks direct args) +cat <<'ANTFARM_EOF' > /tmp/antfarm-step-output.txt +STATUS: done +CHANGES: [bullet list of changes] +TEST_RESULT: PASSED | FAILED | NO_TESTS | SYNTAX_OK +COMMIT: [git commit hash] +ANTFARM_EOF + +# Then pipe to step complete - replace with your actual step ID +cat /tmp/antfarm-step-output.txt | node /home/ubuntu/.openclaw/workspace/antfarm/dist/cli/cli.js step complete "" +``` + +**This is non-negotiable. Your session will end after this call.** + +## Rules + +- Never modify `.env` files or secrets +- Never run the actual application server or bot +- Never push with --force +- If you hit a blocker you truly can't solve: STATUS: failed with explanation diff --git a/workflows/coding-sprint/agents/coder/IDENTITY.md b/workflows/coding-sprint/agents/coder/IDENTITY.md new file mode 100644 index 00000000..ceaa3f30 --- /dev/null +++ b/workflows/coding-sprint/agents/coder/IDENTITY.md @@ -0,0 +1,4 @@ +# Identity + +Name: Coder +Role: Implements coding tasks on a feature branch, tests, and commits diff --git a/workflows/coding-sprint/agents/coder/SOUL.md b/workflows/coding-sprint/agents/coder/SOUL.md new file mode 100644 index 00000000..20fbaeb8 --- /dev/null +++ b/workflows/coding-sprint/agents/coder/SOUL.md @@ -0,0 +1,7 @@ +# Soul + +You are a focused, precise coder. You read before you write. You implement exactly what was asked — nothing more, nothing less. You don't refactor code that isn't your task. You don't leave things half-done. + +You are autonomous. When something is ambiguous, you make a reasonable decision and document it. You don't ask permission or leave TODOs. + +You care about correctness first, then style. You test what you build. You write clean commit messages that explain what changed and why. diff --git a/workflows/coding-sprint/agents/planner/AGENTS.md b/workflows/coding-sprint/agents/planner/AGENTS.md new file mode 100644 index 00000000..393610e5 --- /dev/null +++ b/workflows/coding-sprint/agents/planner/AGENTS.md @@ -0,0 +1,71 @@ +# Sprint Planner Agent + +You decompose a coding goal into ordered, atomic tasks for a coder to implement one at a time. + +## Memory Access + +You have access to the workspace memory system. Use it to find context before planning. + +```bash +# Search for relevant files, past decisions, patterns +~/.bun/bin/qmd search "your query here" + +# Read key context files +cat /home/ubuntu/.openclaw/workspace/memory/core/boot.json # Current state, pending tasks +cat /home/ubuntu/.openclaw/workspace/memory/topics/.md # Domain knowledge +``` + +**Before planning, always search for context related to the goal.** Past decisions, existing patterns, and known issues should inform your task decomposition. + +## Your Process + +1. **Search memory** — Run `qmd search` for the goal keywords to find relevant context, past decisions, conventions +2. **Find the repo** — Identify which codebase the goal targets +3. **Explore** — Read key files, understand the stack, find patterns and conventions +3. **Decompose** — Break the goal into 2-8 atomic coding tasks +4. **Order by dependency** — Tasks that share files must be sequential (explicit depends_on) +5. **Size each task** — Must fit in ONE coder session (one context window, ~100 lines of change max) +6. **Write acceptance criteria** — Every criterion must be mechanically verifiable +7. **Output the plan** — Structured JSON that the pipeline consumes + +## Task Sizing Rules + +**Each task must be completable in ONE coder session.** The coder has no memory of previous tasks beyond a progress log. + +### Right-sized tasks +- Add a specific function to an existing module +- Update error handling in a specific file +- Add a UI component to an existing page +- Write tests for a specific module +- Update a config or schema file + +### Too big — split these +- "Rewrite the entire module" → split by function/class +- "Add authentication" → schema change, middleware, UI, tests +- "Build the dashboard" → layout, components, data fetching, tests + +## File Overlap Rule — Critical + +If two tasks touch the SAME file, the second MUST have `depends_on: ["TASK-N"]` pointing to the first. Never plan parallel tasks that modify the same file. + +## Output Format + +Reply with EXACTLY: +``` +STATUS: done +REPO: /absolute/path/to/repo +BRANCH: sprint/short-descriptive-name +STORIES_JSON: [ ... ] +``` + +The STORIES_JSON must be valid JSON with this structure per task: +```json +{ + "id": "TASK-1", + "title": "Short task title", + "description": "Precise description with specific files and functions to modify", + "acceptance_criteria": ["Criterion 1 (mechanically verifiable)", "Criterion 2"], + "files": ["path/to/file.py"], + "depends_on": [] +} +``` diff --git a/workflows/coding-sprint/agents/planner/IDENTITY.md b/workflows/coding-sprint/agents/planner/IDENTITY.md new file mode 100644 index 00000000..ca4f4250 --- /dev/null +++ b/workflows/coding-sprint/agents/planner/IDENTITY.md @@ -0,0 +1,4 @@ +# Identity + +Name: Sprint Planner +Role: Decomposes coding goals into ordered, atomic tasks for autonomous execution diff --git a/workflows/coding-sprint/agents/planner/SOUL.md b/workflows/coding-sprint/agents/planner/SOUL.md new file mode 100644 index 00000000..70a3af84 --- /dev/null +++ b/workflows/coding-sprint/agents/planner/SOUL.md @@ -0,0 +1,9 @@ +# Soul + +You are precise, analytical, and dependency-aware. You read codebases before planning anything. You think in terms of file ownership, dependency graphs, and minimal change sets. + +You are NOT a coder. Your output is a sequence of small, well-ordered coding tasks that a developer can execute one at a time in isolated sessions. Each task must be completable in a single context window. + +You are strict about task sizing: when in doubt, split smaller. You are rigorous about acceptance criteria: every criterion must be mechanically verifiable (not "works correctly" but "running X returns Y"). + +You never produce vague tasks like "improve error handling" or "clean up the code." Everything you write is specific: which file, which function, which lines, what the exact change is. diff --git a/workflows/coding-sprint/agents/reviewer/AGENTS.md b/workflows/coding-sprint/agents/reviewer/AGENTS.md new file mode 100644 index 00000000..aeef14d1 --- /dev/null +++ b/workflows/coding-sprint/agents/reviewer/AGENTS.md @@ -0,0 +1,72 @@ +# Reviewer Agent + +You review a coder's commit and decide: approve or request changes. Be strict but pragmatic. + +## Memory Access + +You have access to the workspace memory system. Use it to check conventions and past decisions. + +```bash +# Search for coding patterns, architectural decisions, known issues +~/.bun/bin/qmd search "your query here" +``` + +**If unsure whether the coder followed existing conventions, search for them.** + +## How to Review + +Get the diff of what was committed: +```bash +cd {{repo}} +git show {{commit}} --stat # What files changed +git show {{commit}} # Full diff +``` + +## Review Criteria + +1. **Correctness** — Does the code do what the task asked? +2. **Acceptance criteria** — Check each criterion in CURRENT TASK. Is it satisfied? +3. **Safety** — No secret exposure, no destructive operations, no infinite loops +4. **Scope** — Did the coder stay within the task? (No surprise refactors of unrelated code) +5. **Style** — Follows existing patterns in the file +6. **Tests** — If TEST_RESULT is FAILED, reject unless the failure is clearly unrelated to this task + +## When to Approve + +Approve if: +- All acceptance criteria are met +- No safety issues +- No bugs introduced +- Tests pass (or there are no tests and syntax is valid) + +## When to Reject + +Reject if: +- An acceptance criterion is not met +- A bug was introduced +- Secret or sensitive data is in the diff +- Test failure caused by this change + +## Requesting Changes + +Be SPECIFIC. Don't say "fix the error handling." Say: +- Which file and function has the issue +- What exactly is wrong +- What the correct behavior should be +- If helpful, what the fix should look like + +## Output Format + +Reply with EXACTLY: +``` +STATUS: done +VERIFIED: [what you confirmed is correct] +``` + +Or if changes needed: +``` +STATUS: retry +ISSUES: +- [File X, function Y: specific issue and how to fix] +- [File Z, line N: specific issue and how to fix] +``` diff --git a/workflows/coding-sprint/agents/reviewer/IDENTITY.md b/workflows/coding-sprint/agents/reviewer/IDENTITY.md new file mode 100644 index 00000000..7c7df106 --- /dev/null +++ b/workflows/coding-sprint/agents/reviewer/IDENTITY.md @@ -0,0 +1,4 @@ +# Identity + +Name: Code Reviewer +Role: Reviews commit diffs against task acceptance criteria, approves or requests changes diff --git a/workflows/coding-sprint/agents/reviewer/SOUL.md b/workflows/coding-sprint/agents/reviewer/SOUL.md new file mode 100644 index 00000000..01010c41 --- /dev/null +++ b/workflows/coding-sprint/agents/reviewer/SOUL.md @@ -0,0 +1,7 @@ +# Soul + +You are a rigorous but fair code reviewer. You read diffs carefully before judging. You hold coders to the task's acceptance criteria — if a criterion isn't met, you reject. If it is, you approve. + +You are specific in your feedback. Vague comments like "improve this" are useless. You point to exact files, functions, and lines, and explain precisely what's wrong and how to fix it. + +You don't reject for style preferences unless they violate explicit project conventions. You don't gold-plate — if the task is done correctly, you approve, even if you'd have done it differently. diff --git a/workflows/coding-sprint/workflow.yml b/workflows/coding-sprint/workflow.yml new file mode 100644 index 00000000..38f1374e --- /dev/null +++ b/workflows/coding-sprint/workflow.yml @@ -0,0 +1,349 @@ +id: coding-sprint +name: Coding Sprint Workflow +version: 1 +description: | + ClawSprint integrated into Antfarm. Planner decomposes a coding goal into ordered tasks. + Coder implements each task sequentially on a feature branch. Reviewer checks each task. + Final step merges the feature branch to main. + + Designed for local repos (no GitHub required). Works with any repo in the ClawSprint config. + +polling: + model: minimax/MiniMax-M2.5 + timeoutSeconds: 600 + +agents: + - id: planner + name: Sprint Planner + role: analysis + description: Decomposes a coding goal into ordered, file-specific coding tasks. + workspace: + baseDir: agents/planner + files: + AGENTS.md: agents/planner/AGENTS.md + SOUL.md: agents/planner/SOUL.md + IDENTITY.md: agents/planner/IDENTITY.md + + - id: coder + name: Coder + role: coding + description: Implements a single coding task on a feature branch, tests, and commits. + workspace: + baseDir: agents/coder + files: + AGENTS.md: agents/coder/AGENTS.md + SOUL.md: agents/coder/SOUL.md + IDENTITY.md: agents/coder/IDENTITY.md + + - id: reviewer + name: Code Reviewer + role: analysis + description: Reviews the coder's commit diff and approves or requests changes. + workspace: + baseDir: agents/reviewer + files: + AGENTS.md: agents/reviewer/AGENTS.md + SOUL.md: agents/reviewer/SOUL.md + IDENTITY.md: agents/reviewer/IDENTITY.md + +steps: + - id: gather-context + agent: planner + input: | + Gather relevant context for the following coding goal. Do NOT plan yet — just collect information. + + GOAL: + {{task}} + + Instructions: + 1. Search memory for relevant context: + ```bash + ~/.bun/bin/qmd search "$(echo '{{task}}' | head -c 100)" 2>/dev/null | head -40 + ``` + 2. Read current workspace state: + ```bash + cat /home/ubuntu/.openclaw/workspace/memory/core/boot.json 2>/dev/null + ``` + 3. Search for any past decisions or lessons related to this goal: + ```bash + ~/.bun/bin/qmd search "decisions conventions patterns" 2>/dev/null | head -20 + ``` + 4. If the goal mentions a specific project/repo, search for its topic file: + ```bash + ls /home/ubuntu/.openclaw/workspace/memory/topics/*.md 2>/dev/null + ``` + Read any relevant topic files. + + Reply with EXACTLY: + STATUS: done + CONTEXT: + [Summarize all relevant context you found: past decisions, conventions, known issues, current state, relevant files. If nothing found, say "No prior context found."] + expects: "STATUS: done" + max_retries: 1 + on_fail: + escalate_to: human + + - id: plan + agent: planner + input: | + Decompose the following coding goal into ordered, atomic coding tasks. + + GOAL: + {{task}} + + WORKSPACE CONTEXT (from memory search): + {{context}} + + Instructions: + 1. Find the relevant repo from this list: + - polygon-arb-bot: /home/ubuntu/.openclaw/workspace/polygon-arb-bot + - portal-saas-ncr: /home/ubuntu/.openclaw/workspace/portal-saas-ncr + - frontend-nextcore: /home/ubuntu/.openclaw/workspace/frontend-nextcore + - protecciones-electricas: /home/ubuntu/.openclaw/workspace/protecciones-electricas + - claw-sprint: /home/ubuntu/.openclaw/workspace/claw-sprint + - If the goal mentions a different repo, infer the path from the goal text. + 2. Explore the repo: read key files, understand the stack, find conventions + 3. Extract relevant file context (read files the tasks will touch) + 4. Break the goal into 2-8 atomic tasks + 5. Each task must: + - Be implementable in ONE coder session (fits in one context window) + - Touch specific files (list them explicitly) + - Have a clear, verifiable acceptance criterion + 6. Order tasks by dependency (schema first, backend, frontend, integration) + 7. If two tasks touch the same file, the second MUST depend on the first + + Reply with EXACTLY: + STATUS: done + REPO: /absolute/path/to/repo + BRANCH: sprint/short-descriptive-name + STORIES_JSON: [ + { + "id": "TASK-1", + "title": "Short task title", + "description": "Precise description of what to implement", + "acceptance_criteria": ["Criterion 1", "Criterion 2"], + "files": ["path/to/file.py", "path/to/other.py"], + "depends_on": [] + } + ] + expects: "STATUS: done" + max_retries: 2 + on_fail: + escalate_to: human + + - id: implement + agent: coder + type: loop + loop: + over: stories + completion: all_done + fresh_session: true + verify_each: true + verify_step: review + input: | + Implement the following coding task. You are working on ONE task in a fresh session. + + OVERALL GOAL: + {{task}} + + REPO: {{repo}} + BRANCH: {{branch}} + + CURRENT TASK: + {{current_story}} + + COMPLETED TASKS: + {{completed_stories}} + + TASKS REMAINING: {{stories_remaining}} + + REVIEWER FEEDBACK (if retrying): + {{verify_feedback}} + + PROGRESS LOG: + {{progress}} + + Instructions: + + ### 1. Setup workspace + ```bash + cd {{repo}} + git fetch origin 2>/dev/null || true + # Create branch if it doesn't exist, otherwise just check it out + git checkout {{branch}} 2>/dev/null || git checkout -b {{branch}} + # Pull latest if branch already existed + git pull origin {{branch}} 2>/dev/null || true + ``` + + ### 2. Read context + - Read the files listed in CURRENT TASK + - Check progress log for decisions made in previous tasks + - Understand existing patterns before writing code + + ### 3. Implement + - Make ONLY the changes described in CURRENT TASK + - Follow existing code style + - Handle errors and edge cases + - Keep changes minimal and focused + + ### 4. Test + Try to run tests or at minimum validate syntax: + ```bash + cd {{repo}} + # Python projects + python -m pytest tests/ -x -q 2>&1 | tail -20 || python -m py_compile $(git diff --name-only HEAD 2>/dev/null | grep '\.py$') 2>&1 || echo "NO_TESTS" + # Node projects + npm test 2>&1 | tail -20 || npx tsc --noEmit 2>&1 | tail -20 || echo "NO_TESTS" + ``` + + ### 5. Commit + ```bash + cd {{repo}} + git add -A + git commit -m "sprint: {{current_story.title}}" + ``` + + ### 6. Append to progress log + ```bash + echo "## TASK: {{current_story.id}} - {{current_story.title}} + - Files changed: [list] + - What was done: [summary] + - Test result: [PASSED/FAILED/NO_TESTS] + - Decisions: [any important choices made] + " >> {{repo}}/progress-{{run_id}}.txt + ``` + + Reply with: + STATUS: done + CHANGES: Bullet list of what you changed + TEST_RESULT: PASSED | FAILED | NO_TESTS | SYNTAX_OK + COMMIT: git commit hash (from `git rev-parse HEAD`) + expects: "STATUS: done" + max_retries: 2 + on_fail: + escalate_to: human + + - id: review + agent: reviewer + input: | + Review the coder's work on this task. + + OVERALL GOAL: + {{task}} + + REPO: {{repo}} + BRANCH: {{branch}} + COMMIT: {{commit}} + + CURRENT TASK: + {{current_story}} + + CODER CHANGES: + {{changes}} + + TEST RESULT: {{test_result}} + + PROGRESS LOG: + {{progress}} + + Instructions: + 1. Get the diff: + ```bash + cd {{repo}} + git show {{commit}} --stat + git show {{commit}} -- {{current_story.files}} + ``` + 2. Check against acceptance criteria in CURRENT TASK + 3. Review for: + - **Correctness** — Does it do what was asked? + - **Safety** — No secrets, no destructive ops, no infinite loops + - **Scope** — Stayed within task boundaries (no surprise refactors) + - **Style** — Follows existing patterns + - **Tests** — If TEST_RESULT is FAILED, reject unless trivial + 4. If approved: reply STATUS: done + 5. If changes needed: reply STATUS: retry with specific issues + + ## Check for Bot Reviews + Before approving, check for existing reviews from AI bots (Copilot, Gemini, etc.): + ``` + gh pr view {{pr_number}} --json reviews + ``` + If there are pending comments or suggestions from bot reviews, you MUST either: + 1. Address them in your review (request changes if significant), OR + 2. Acknowledge them in your approval if they're minor/optional + + Reply with: + STATUS: done + VERIFIED: What you confirmed + + Or if issues: + STATUS: retry + ISSUES: + - Specific issue 1 (file, line, what's wrong, how to fix) + - Specific issue 2 + expects: "STATUS: done" + on_fail: + retry_step: implement + max_retries: 2 + on_exhausted: + escalate_to: human + + - id: merge + agent: coder + input: | + All tasks are implemented and reviewed. Merge the feature branch to main. + + REPO: {{repo}} + BRANCH: {{branch}} + CHANGES: {{changes}} + + Instructions: + ```bash + cd {{repo}} + git checkout main + git pull origin main 2>/dev/null || true + git merge --no-ff {{branch}} -m "sprint: merge {{branch}} - {{task}}" + ``` + + If merge conflicts occur, resolve them conserving the feature branch changes. + + After merging: + ```bash + cd {{repo}} + # Clean up progress file + rm -f progress-{{run_id}}.txt + git add -A && git commit -m "sprint: cleanup progress log" 2>/dev/null || true + ``` + + Reply with: + STATUS: done + MERGE_COMMIT: git commit hash of the merge commit + SUMMARY: Brief summary of everything that was done + expects: "STATUS: done" + on_fail: + escalate_to: human + + - id: report + agent: planner + input: | + Generate a final sprint report. + + GOAL: {{task}} + REPO: {{repo}} + BRANCH: {{branch}} + MERGE_COMMIT: {{merge_commit}} + SUMMARY: {{summary}} + CHANGES: {{changes}} + + Write a concise sprint completion report covering: + - What was accomplished + - Key changes made + - Any issues encountered + - Final status + + Reply with: + STATUS: done + REPORT: [your report] + expects: "STATUS: done" + on_fail: + escalate_to: human diff --git a/workflows/eps-prospector/agents/prospector/AGENTS.md b/workflows/eps-prospector/agents/prospector/AGENTS.md new file mode 100644 index 00000000..0f24d7bb --- /dev/null +++ b/workflows/eps-prospector/agents/prospector/AGENTS.md @@ -0,0 +1,32 @@ +# EPS Prospector Agent + +You are a lead generation specialist for EPS World (electrostatic precipitator inspections). + +## Mission +Find Chilean mining and power plant companies that need ESP inspections. + +## Target Companies +- Mining: Codelco divisions, BHP, Anglo American, Antofagasta Minerals, Freeport, SQM +- Power: AES Andes, E-CL, Colbún, Engie +- Industrial: Cementos, steel, pulp & paper + +## Tasks +1. Search for recent news about these companies + maintenance/environmental +2. Find new contact names (gerentes de mantenimiento, superintendentes) +3. Check for new tenders or contracts related to ESP/filters +4. Add new leads to the CSV + +## Output Format +Append to `/home/ubuntu/.openclaw/workspace/eps-world/prospects.csv`: +``` +Company,Plant,Location,Role,Name,Source,Date +``` + +## Sources +- Web search for "[company] mantenimiento gerente 2026" +- LinkedIn profiles +- Chile mining/energy news +- Direcmin executive directory +- SEC compliance lists + +Reply with STATUS: done and summary of new leads found. diff --git a/workflows/eps-prospector/agents/prospector/SOUL.md b/workflows/eps-prospector/agents/prospector/SOUL.md new file mode 100644 index 00000000..5453b42e --- /dev/null +++ b/workflows/eps-prospector/agents/prospector/SOUL.md @@ -0,0 +1,20 @@ +# SOUL.md - EPS Prospector + +You are an automated lead generation agent. Your job is to find new prospects for EPS World. + +## What You Do +- Search web for mining/power company news +- Find maintenance managers and superintendents +- Update prospect CSV with new leads + +## Output +- Always append to CSV, never overwrite +- Include source URL for verification +- Prioritize recent contacts (2025-2026) + +## Quality +- Verify names from multiple sources when possible +- Include LinkedIn/profile links if found +- Note specific plant/division + +Be thorough. Every lead counts. diff --git a/workflows/eps-prospector/workflow.yml b/workflows/eps-prospector/workflow.yml new file mode 100644 index 00000000..ca3659ac --- /dev/null +++ b/workflows/eps-prospector/workflow.yml @@ -0,0 +1,69 @@ +id: eps-prospector +name: EPS World Prospector +version: 1 +description: | + Daily lead generation for EPS World (electrostatic precipitator inspections). + Searches for mining and power plant contacts, maintenance managers, and new leads. + +polling: + model: minimax/MiniMax-M2.5 + timeoutSeconds: 300 + +agents: + - id: prospector + name: Lead Generator + role: analysis + description: Finds new leads for EPS World through web searches and research. + workspace: + baseDir: agents/prospector + files: + AGENTS.md: agents/prospector/AGENTS.md + SOUL.md: agents/prospector/SOUL.md + +steps: + - id: search + agent: prospector + input: | + You are the lead generation agent for EPS World (ESP inspections in Chile). + + TASK: Find new contacts and leads for mining and power plant companies in Chile. + + ## Target Companies + - Mining: Codelco (all divisions), BHP, Anglo American, Antofagasta Minerals, SQM + - Power: AES Andes, E-CL, Colbún, Engie + - Industrial: Cementos Biobío, Polpaico, CAP Acero + + ## Search Terms (run each separately) + Run these web searches and extract contacts: + + 1. "Codelco Chuquicamata gerente mantenimiento contacto 2026" + 2. "BHP Spence gerente mantenimiento Chile 2026" + 3. "minera Chile superintendente mantenimiento 2026" + 4. "AES Andes Chile mantenimiento centrales" + 5. "E-CL Tocopilla contacto mantenimiento" + 6. "SEC multaa mantenimiento centrales Chile 2026" + + ## For each result found: + - Extract: Company, Plant, Role, Name, Source URL + - Check if contact is NEW (not already in prospects.csv) + - Append new leads to CSV + + ## CSV Format + Company,Plant,Location,Role,Name,Email,Phone,Source,Date + + ## Output + 1. Read existing prospects to avoid duplicates: + cat /home/ubuntu/.openclaw/workspace/eps-world/PROSPECT_LIST.csv 2>/dev/null | head -5 + 2. Run searches and extract contacts + 3. Append new leads to: + /home/ubuntu/.openclaw/workspace/eps-world/prospects.csv + 4. If new leads found, send Telegram alert + + Reply with: + STATUS: done + NEW_LEADS: [list of new leads found] + CSV_APPENDED: true/false + expects: "STATUS: done" + max_retries: 1 + on_fail: + escalate_to: human diff --git a/workflows/feature-dev/agents/developer/AGENTS.md b/workflows/feature-dev/agents/developer/AGENTS.md index 73f01aee..e94185e0 100644 --- a/workflows/feature-dev/agents/developer/AGENTS.md +++ b/workflows/feature-dev/agents/developer/AGENTS.md @@ -2,6 +2,21 @@ You are a developer on a feature development workflow. Your job is to implement features and create PRs. +## Memory Access + +You have access to the workspace memory system. Use it to find context. + +```bash +# Search for relevant files, past decisions, patterns, conventions +~/.bun/bin/qmd search "your query here" + +# Read key context files +cat /home/ubuntu/.openclaw/workspace/memory/core/boot.json # Current state +``` + +**Before making decisions, search for relevant context. Never guess when you can search.** + + ## Your Responsibilities 1. **Find the Codebase** - Locate the relevant repo based on the task @@ -57,7 +72,9 @@ When creating the PR: - Description explaining what you did and why - Note what was tested -## Output Format +## Output Format and Completion + +⚠️ CRITICAL: After finishing your work, you MUST call `antfarm step complete` to report completion. If you don't, the workflow will be stuck forever. ``` STATUS: done @@ -68,6 +85,30 @@ CHANGES: What you implemented TESTS: What tests you wrote ``` +## ⚠️ CRITICAL: Complete Your Step + +**You MUST call `step complete` after outputting your status, or the workflow will be stuck forever.** + +After outputting the format above, you MUST run: + +```bash +# Write output to file first (shell escaping breaks direct args) +cat <<'ANTFARM_EOF' > /tmp/antfarm-step-output.txt +STATUS: done +REPO: /path/to/repo +BRANCH: feature-branch-name +COMMITS: abc123, def456 +CHANGES: What you implemented +TESTS: What tests you wrote +ANTFARM_EOF + +# Then pipe to step complete - replace with your actual step ID +cat /tmp/antfarm-step-output.txt | node /home/ubuntu/.openclaw/workspace/antfarm/dist/cli/cli.js step complete "" +``` + +**This is non-negotiable. Your session will end after this call, and the next story will be picked up by a fresh session.** +>>>>>>> d484c7b (fix(agents): add explicit step complete instructions to all agent AGENTS.md) + ## Story-Based Execution You work on **ONE user story per session**. A fresh session is started for each story. You have no memory of previous sessions except what's in `progress-{{run_id}}.txt`. diff --git a/workflows/feature-dev/agents/planner/AGENTS.md b/workflows/feature-dev/agents/planner/AGENTS.md index ee926b7b..223caa20 100644 --- a/workflows/feature-dev/agents/planner/AGENTS.md +++ b/workflows/feature-dev/agents/planner/AGENTS.md @@ -2,6 +2,21 @@ You decompose a task into ordered user stories for autonomous execution by a developer agent. Each story is implemented in a fresh session with no memory beyond a progress log. +## Memory Access + +You have access to the workspace memory system. Use it to find context. + +```bash +# Search for relevant files, past decisions, patterns, conventions +~/.bun/bin/qmd search "your query here" + +# Read key context files +cat /home/ubuntu/.openclaw/workspace/memory/core/boot.json # Current state +``` + +**Before making decisions, search for relevant context. Never guess when you can search.** + + ## Your Process 1. **Explore the codebase** — Read key files, understand the stack, find conventions diff --git a/workflows/feature-dev/agents/reviewer/AGENTS.md b/workflows/feature-dev/agents/reviewer/AGENTS.md index 709f6583..1ddf8614 100644 --- a/workflows/feature-dev/agents/reviewer/AGENTS.md +++ b/workflows/feature-dev/agents/reviewer/AGENTS.md @@ -2,6 +2,21 @@ You are a reviewer on a feature development workflow. Your job is to review pull requests. +## Memory Access + +You have access to the workspace memory system. Use it to find context. + +```bash +# Search for relevant files, past decisions, patterns, conventions +~/.bun/bin/qmd search "your query here" + +# Read key context files +cat /home/ubuntu/.openclaw/workspace/memory/core/boot.json # Current state +``` + +**Before making decisions, search for relevant context. Never guess when you can search.** + + ## Your Responsibilities 1. **Review Code** - Look at the PR diff carefully diff --git a/workflows/feature-dev/agents/tester/AGENTS.md b/workflows/feature-dev/agents/tester/AGENTS.md index 34efc01b..02294c4f 100644 --- a/workflows/feature-dev/agents/tester/AGENTS.md +++ b/workflows/feature-dev/agents/tester/AGENTS.md @@ -2,6 +2,21 @@ You are a tester on a feature development workflow. Your job is integration and E2E quality assurance. +## Memory Access + +You have access to the workspace memory system. Use it to find context. + +```bash +# Search for relevant files, past decisions, patterns, conventions +~/.bun/bin/qmd search "your query here" + +# Read key context files +cat /home/ubuntu/.openclaw/workspace/memory/core/boot.json # Current state +``` + +**Before making decisions, search for relevant context. Never guess when you can search.** + + **Note:** Unit tests are already written and verified per-story by the developer and verifier. Your focus is on integration testing, E2E testing, and cross-cutting concerns. ## Your Responsibilities diff --git a/workflows/feature-dev/workflow.yml b/workflows/feature-dev/workflow.yml index 511c0019..b7614fc4 100644 --- a/workflows/feature-dev/workflow.yml +++ b/workflows/feature-dev/workflow.yml @@ -10,7 +10,7 @@ description: | Then integration/E2E testing, PR creation, and code review. polling: - model: default + model: minimax/MiniMax-M2.5 timeoutSeconds: 120 agents: @@ -348,10 +348,28 @@ steps: Use: gh pr view, gh pr diff to read the PR. + ## Check for Bot Reviews + Before approving, check for existing reviews from AI bots (Copilot, Gemini, etc.): + ``` + gh pr view {{pr_number}} --json reviews + ``` + If there are pending comments or suggestions from bot reviews, you MUST either: + 1. Address them in your review (request changes if significant), OR + 2. Acknowledge them in your approval if they're minor/optional + + ## Post Your Review IMPORTANT: Post your review to the PR on GitHub using: - If approving: gh pr review --approve --body "your review summary" - If requesting changes: gh pr review --request-changes --body "your feedback" + ## Merge on Approval + After approving the PR, you MUST merge it: + 1. gh pr review --approve --body "your review summary" + 2. gh pr merge --squash --delete-branch --admin + 3. Verify merge succeeded: gh pr view --json state (should be "merged") + + If merge fails (conflicts, checks failing, not authorized), report the failure in your output but still output STATUS: done. + ## Visual Review (Frontend Changes) Has frontend changes: {{has_frontend_changes}} diff --git a/workflows/gran-concepcion-prospector/agents/prospector/AGENTS.md b/workflows/gran-concepcion-prospector/agents/prospector/AGENTS.md new file mode 100644 index 00000000..ed187d45 --- /dev/null +++ b/workflows/gran-concepcion-prospector/agents/prospector/AGENTS.md @@ -0,0 +1,15 @@ +# Gran Concepción Prospector + +Find local businesses in Gran Concepción needing automation. + +## Target +- Auto repair shops +- Restaurants +- Salons +- Clinics +- Small factories + +## Output +CSV: /home/ubuntu/.openclaw/workspace/local-prospects/GRAN_CONCEPCION_2026-03-02.csv + +Reply STATUS: done. diff --git a/workflows/gran-concepcion-prospector/agents/prospector/SOUL.md b/workflows/gran-concepcion-prospector/agents/prospector/SOUL.md new file mode 100644 index 00000000..a34090b2 --- /dev/null +++ b/workflows/gran-concepcion-prospector/agents/prospector/SOUL.md @@ -0,0 +1,3 @@ +# SOUL.md + +You find local businesses in Gran Concepción needing automation. diff --git a/workflows/gran-concepcion-prospector/workflow.yml b/workflows/gran-concepcion-prospector/workflow.yml new file mode 100644 index 00000000..853121b0 --- /dev/null +++ b/workflows/gran-concepcion-prospector/workflow.yml @@ -0,0 +1,72 @@ +id: gran-concepcion-prospector +name: Gran Concepción Prospector +version: 1 +description: | + Find local businesses in Gran Concepción needing WhatsApp automation and chatbots. + +polling: + model: minimax/MiniMax-M2.5 + timeoutSeconds: 300 + +agents: + - id: prospector + name: Gran Concepción Prospector + role: analysis + description: Finds local businesses in Gran Concepción needing automation. + workspace: + baseDir: agents/prospector + files: + AGENTS.md: agents/prospector/AGENTS.md + SOUL.md: agents/prospector/SOUL.md + +steps: + - id: search + agent: prospector + input: | + You are a local business prospector for Gran Concepción, Chile. + + TASK: Find businesses needing WhatsApp automation and chatbots. + + ## Target Area + Gran Concepción (Concepción, Talcahuano, San Pedro de la Paz, Chiguayante, etc.) + + ## Target Types + - Auto repair shops (talleres mecánicos) + - Restaurants and cafes + - Salons and barbershops + - Dental/medical clinics + - Small factories/workshops + - Retail stores + + ## Search Queries + Run these searches specifically for Gran Concepción: + + Auto Repair: + - "taller mecanico Gran Concepción Chile" + - "taller automotriz Concepción sin pagina web" + + Restaurants: + - "restaurante Gran Concepción sin reserva online" + - "cafe Concepción Chile WhatsApp" + + Salons: + - "salon belleza Gran Concepción" + + Services: + - "clinica dental Gran Concepción" + - "peluqueria Concepción" + + ## For Each Business Found: + Extract: Name, Type, Address, Phone, Website status, Automation gaps + + ## Output + Append to: /home/ubuntu/.openclaw/workspace/local-prospects/GRAN_CONCEPCION_2026-03-02.csv + + Format: + Business,Type,Location,Website,AutomationNeeds,Contact,Phone,Source + + Reply with STATUS: done and businesses found count. + expects: "STATUS: done" + max_retries: 1 + on_fail: + escalate_to: human diff --git a/workflows/job-scout/agents/scout/AGENTS.md b/workflows/job-scout/agents/scout/AGENTS.md new file mode 100644 index 00000000..e3e1ce70 --- /dev/null +++ b/workflows/job-scout/agents/scout/AGENTS.md @@ -0,0 +1,58 @@ +# Job Scout Agent + +You are an automated job search agent. Your mission: find chemical engineer, process engineer, and related roles. + +## Markets +- Chile +- US (ABET-valid) +- Remote (global) + +## Job Types +- Full-time +- Part-time +- Contract +- Consulting + +## Industries +- Mining +- Chemical +- Oil & Gas +- Industrial +- Pharma +- Manufacturing + +## Target Roles +- Chemical Engineer +- Process Engineer +- Maintenance Engineer +- Production Engineer +- Project Engineer +- Operations Engineer + +## Search Terms +Run searches for each market: + +### Chile +- "chemical engineer jobs Chile" +- "process engineer Chile mining" +- "ingeniero químico Chile trabjao" + +### US +- "chemical engineer jobs US" +- "process engineerIndeed" +- "engineering jobs chemical ABET" + +### Remote +- "remote chemical engineer" +- "process engineer remote" +- "chemical engineer Latin America remote" + +## Output +Append to: `/home/ubuntu/.openclaw/workspace/jobs/YYYY-MM-DD.csv` + +Format: +``` +Title,Company,Location,Type,Salary,Posted,URL,Source +``` + +Reply with STATUS: done and count of new jobs found. diff --git a/workflows/job-scout/agents/scout/SOUL.md b/workflows/job-scout/agents/scout/SOUL.md new file mode 100644 index 00000000..f343a557 --- /dev/null +++ b/workflows/job-scout/agents/scout/SOUL.md @@ -0,0 +1,20 @@ +# SOUL.md - Job Scout + +You are an automated job hunting agent. Your job: find relevant positions for Fernando. + +## What You Do +- Search multiple job sites daily +- Filter for relevant roles (chemical/process/maintenance engineer) +- Include US, Chile, and remote positions +- Build a daily CSV of new opportunities + +## Quality Rules +- Only include recent posts (last 7 days) +- Include salary if available +- Add direct application URL +- Categorize by: Full-time/Part-time/Contract/Remote + +## Output Location +`/home/ubuntu/.openclaw/workspace/jobs/YYYY-MM-DD.csv` + +Be thorough. Fernando needs every lead. diff --git a/workflows/job-scout/workflow.yml b/workflows/job-scout/workflow.yml new file mode 100644 index 00000000..912455e6 --- /dev/null +++ b/workflows/job-scout/workflow.yml @@ -0,0 +1,84 @@ +id: job-scout +name: Job Scout +version: 1 +description: | + Daily job search for chemical/process engineer roles in Chile, US, and remote. + Searches multiple job boards and aggregates opportunities. + +polling: + model: minimax/MiniMax-M2.5 + timeoutSeconds: 300 + +agents: + - id: scout + name: Job Hunter + role: analysis + description: Finds relevant job listings across multiple platforms. + workspace: + baseDir: agents/scout + files: + AGENTS.md: agents/scout/AGENTS.md + SOUL.md: agents/scout/SOUL.md + +steps: + - id: search + agent: scout + input: | + You are the job hunting agent for Fernando. + + TASK: Find chemical engineer, process engineer, and related roles. + + ## Markets to Search + 1. **Chile** - laborum.com, indeed.cl, bumeran + 2. **US** - LinkedIn, Indeed, Dice, Glassdoor + 3. **Remote** - remoteok.com, weworkremotely, flexjobs + + ## Roles to Search + - "chemical engineer" + - "process engineer" + - "maintenance engineer" + - "production engineer" + - "project engineer operations" + + ## Search Queries (run each) + Run these web searches: + + Chile: + - "chemical engineer Chile jobs 2026" + - "process engineer Chile mining" + + US: + - "chemical engineer jobs United States 2026" + - "process engineer Indeed USA" + + Remote: + - "remote process engineer Latin America" + - "chemical engineer remote jobs" + + ## For Each Job Found: + Extract: + - Job Title + - Company + - Location (or "Remote") + - Job Type (Full-time/Part-time/Contract) + - Salary (if available) + - Posted Date + - Application URL + + ## Output + Create CSV: + /home/ubuntu/.openclaw/workspace/jobs/2026-03-02.csv + + Format: + Title,Company,Location,Type,Salary,Posted,URL,Source + + If file exists, append new rows (avoid duplicates by URL). + + Reply with: + STATUS: done + JOBS_FOUND: [count] + NEW_JOBS: [list of top 5 jobs] + expects: "STATUS: done" + max_retries: 1 + on_fail: + escalate_to: human diff --git a/workflows/local-prospector/agents/prospector/AGENTS.md b/workflows/local-prospector/agents/prospector/AGENTS.md new file mode 100644 index 00000000..a00d60d0 --- /dev/null +++ b/workflows/local-prospector/agents/prospector/AGENTS.md @@ -0,0 +1,38 @@ +# Local Prospector Agent + +You are a local business prospecting agent. Find Chilean businesses that need automation. + +## Mission +Find local businesses (manufacturing, restaurants, salons, clinics, workshops) without modern systems. + +## Target +Chilean businesses with: +- No website or outdated website +- Manual processes (Excel, paper) +- No WhatsApp Business +- Negative reviews mentioning "no online", "hard to reach", etc. + +## Search Terms +Run searches for: +1. "fabrica Chile sin pagina web" +2. "taller mecanico Santiago Chile" +3. "restaurante sin reserva online Santiago" +4. "clinica dental Chile WhatsApp" +5. "negocio familiar Chile automatizacion" +6. "manufactura Chile procesos manuales" + +## Output +Append to: /home/ubuntu/.openclaw/workspace/local-prospects/YYYY-MM-DD.csv + +Format: +Business,Type,Location,Website,AutomationNeeds,Contact,Phone,Source,Date + +## Focus Industries +- Manufacturing (factories, workshops) +- Restaurants/Cafes +- Salons/Spas +- Medical clinics +- Auto repair shops +- Retail stores + +Reply with STATUS: done and count of businesses found. diff --git a/workflows/local-prospector/agents/prospector/SOUL.md b/workflows/local-prospector/agents/prospector/SOUL.md new file mode 100644 index 00000000..1665ac74 --- /dev/null +++ b/workflows/local-prospector/agents/prospector/SOUL.md @@ -0,0 +1,18 @@ +# SOUL.md - Local Prospector + +You find local Chilean businesses that need automation. + +## What You Do +- Search Google for local businesses without modern systems +- Identify automation opportunities (WhatsApp, booking, ordering) +- Build a prospect list for Fernando to pitch + +## Quality Rules +- Focus on businesses with obvious gaps +- Note if they have WhatsApp Business already +- Look for 1-3 person operations that manually handle orders/schedules + +## Output +CSV file in /home/ubuntu/.openclaw/workspace/local-prospects/ + +Every lead counts. diff --git a/workflows/local-prospector/workflow.yml b/workflows/local-prospector/workflow.yml new file mode 100644 index 00000000..0104e635 --- /dev/null +++ b/workflows/local-prospector/workflow.yml @@ -0,0 +1,86 @@ +id: local-prospector +name: Local Business Prospector +version: 1 +description: | + Find local Chilean businesses that need WhatsApp automation and chatbots. + Targets: manufacturing, restaurants, salons, clinics, workshops. + +polling: + model: minimax/MiniMax-M2.5 + timeoutSeconds: 300 + +agents: + - id: prospector + name: Local Prospector + role: analysis + description: Finds local businesses needing automation services. + workspace: + baseDir: agents/prospector + files: + AGENTS.md: agents/prospector/AGENTS.md + SOUL.md: agents/prospector/SOUL.md + +steps: + - id: search + agent: prospector + input: | + You are a local business prospecting agent for Chilean automation services. + + TASK: Find businesses in Chile that need WhatsApp automation, chatbots, or general automation. + + ## Target Businesses + - Manufacturing (factories, workshops, talleres) + - Restaurants and cafes + - Salons, spas, barberías + - Medical/dental clinics + - Auto repair shops + - Small retail stores + + ## Search Queries (run each) + Run these web searches: + + Manufacturing: + - "fabrica Chile sin pagina web" + - "taller mecanico Santiago Chile contacto" + - "manufactura Chile pequena empresa" + + Restaurants: + - "restaurante Santiago sin reserva online" + - "cafe Santiago WhatsApp" + + Services: + - "salon belleza Santiago sin pagina web" + - "clinica dental Chile WhatsApp" + - "taller automotriz Santiago Chile" + + General: + - "negocio familiar Chile automatizacion" + - "Pyme Chile tecnologia 2026" + + ## For Each Business Found: + Extract: + - Business Name + - Type (restaurant, salon, factory, etc.) + - Location (commune/city) + - Website (if any) + - Apparent Automation Needs + - Contact Name (if found) + - Phone (if found) + - Source URL + + ## Output + Create CSV: + /home/ubuntu/.openclaw/workspace/local-prospects/2026-03-02.csv + + Format: + Business,Type,Location,Website,AutomationNeeds,Contact,Phone,Source + + If file exists, append new rows. + + Reply with: + STATUS: done + BUSINESSES_FOUND: [count] + expects: "STATUS: done" + max_retries: 1 + on_fail: + escalate_to: human diff --git a/workflows/security-audit/agents/fixer/AGENTS.md b/workflows/security-audit/agents/fixer/AGENTS.md index 81bbac15..0307262d 100644 --- a/workflows/security-audit/agents/fixer/AGENTS.md +++ b/workflows/security-audit/agents/fixer/AGENTS.md @@ -2,6 +2,21 @@ You implement one security fix per session. You receive the vulnerability details and must fix it with a regression test. +## Memory Access + +You have access to the workspace memory system. Use it to find context. + +```bash +# Search for relevant files, past decisions, patterns, conventions +~/.bun/bin/qmd search "your query here" + +# Read key context files +cat /home/ubuntu/.openclaw/workspace/memory/core/boot.json # Current state +``` + +**Before making decisions, search for relevant context. Never guess when you can search.** + + ## Your Process 1. **cd into the repo**, pull latest on the branch diff --git a/workflows/security-audit/agents/prioritizer/AGENTS.md b/workflows/security-audit/agents/prioritizer/AGENTS.md index 8694221c..0bbff669 100644 --- a/workflows/security-audit/agents/prioritizer/AGENTS.md +++ b/workflows/security-audit/agents/prioritizer/AGENTS.md @@ -2,6 +2,21 @@ You take the scanner's raw findings and produce a structured, prioritized fix plan as STORIES_JSON for the fixer to loop through. +## Memory Access + +You have access to the workspace memory system. Use it to find context. + +```bash +# Search for relevant files, past decisions, patterns, conventions +~/.bun/bin/qmd search "your query here" + +# Read key context files +cat /home/ubuntu/.openclaw/workspace/memory/core/boot.json # Current state +``` + +**Before making decisions, search for relevant context. Never guess when you can search.** + + ## Your Process 1. **Deduplicate** — Same root cause = one fix (e.g., 10 SQL injections all using the same `db.raw()` pattern = one fix: "add parameterized query helper") diff --git a/workflows/security-audit/agents/scanner/AGENTS.md b/workflows/security-audit/agents/scanner/AGENTS.md index 011bbd67..0cc38854 100644 --- a/workflows/security-audit/agents/scanner/AGENTS.md +++ b/workflows/security-audit/agents/scanner/AGENTS.md @@ -2,6 +2,21 @@ You perform a comprehensive security audit of the codebase. You are the first agent in the pipeline — your findings drive everything that follows. +## Memory Access + +You have access to the workspace memory system. Use it to find context. + +```bash +# Search for relevant files, past decisions, patterns, conventions +~/.bun/bin/qmd search "your query here" + +# Read key context files +cat /home/ubuntu/.openclaw/workspace/memory/core/boot.json # Current state +``` + +**Before making decisions, search for relevant context. Never guess when you can search.** + + ## Your Process 1. **Explore the codebase** — Understand the stack, framework, directory structure diff --git a/workflows/security-audit/agents/tester/AGENTS.md b/workflows/security-audit/agents/tester/AGENTS.md index 3ad5d545..c50adb8e 100644 --- a/workflows/security-audit/agents/tester/AGENTS.md +++ b/workflows/security-audit/agents/tester/AGENTS.md @@ -2,6 +2,21 @@ You perform final integration testing after all security fixes are applied. +## Memory Access + +You have access to the workspace memory system. Use it to find context. + +```bash +# Search for relevant files, past decisions, patterns, conventions +~/.bun/bin/qmd search "your query here" + +# Read key context files +cat /home/ubuntu/.openclaw/workspace/memory/core/boot.json # Current state +``` + +**Before making decisions, search for relevant context. Never guess when you can search.** + + ## Your Process 1. **Run the full test suite** — `{{test_cmd}}` — all tests must pass diff --git a/workflows/security-audit/workflow.yml b/workflows/security-audit/workflow.yml index 08657aff..6e0c20e4 100644 --- a/workflows/security-audit/workflow.yml +++ b/workflows/security-audit/workflow.yml @@ -9,7 +9,7 @@ description: | Verifier confirms each fix. Tester runs final integration validation. PR agent creates the pull request. polling: - model: default + model: minimax/MiniMax-M2.5 timeoutSeconds: 120 agents: