diff --git a/docs/GBRAIN_SKILLPACK.md b/docs/GBRAIN_SKILLPACK.md index 1941e4e87..a056a1a85 100644 --- a/docs/GBRAIN_SKILLPACK.md +++ b/docs/GBRAIN_SKILLPACK.md @@ -49,11 +49,24 @@ Running a production brain. | Guide | What It Covers | |-------|---------------| | [Reference Cron Schedule](guides/cron-schedule.md) | 20+ recurring jobs, quiet hours, dream cycle | +| [Cron via Minions](../skills/conventions/cron-via-minions.md) | Why scheduled work runs as Minion jobs, not `agentTurn`. Auto-applied by v0.11.0 migration for built-in handlers; host-specific handlers use the plugin contract below. | +| [Plugin Handlers](guides/plugin-handlers.md) | Registering host-specific Minion handlers via code (no data-file exec surface). | +| [Minions fix](guides/minions-fix.md) | Repairing a half-migrated v0.11.0 install. | | [Quiet Hours & Timezone](guides/quiet-hours.md) | Hold notifications during sleep, timezone-aware delivery | | [Executive Assistant Pattern](guides/executive-assistant.md) | Email triage, meeting prep, scheduling | | [Operational Disciplines](guides/operational-disciplines.md) | Signal detection, brain-first, sync-after-write, heartbeat, dream cycle | | [Skill Development Cycle](guides/skill-development.md) | 5-step cycle: concept, prototype, evaluate, codify, cron | +**Subagent routing (v0.11.0+):** agents that dispatch background work should route through +`skills/conventions/subagent-routing.md` — it reads `~/.gbrain/preferences.json#minion_mode` +and branches between native subagents and Minion jobs. The v0.11.0 migration auto-injects +a marker into AGENTS.md pointing at this convention. + +**Cron routing (v0.11.0+):** scheduled work goes through Minions, not OpenClaw's `agentTurn`. +See `skills/conventions/cron-via-minions.md` for the rewrite pattern. The v0.11.0 migration +auto-rewrites entries whose handler is a gbrain builtin; host-specific handlers (e.g. +`ea-inbox-sweep`) need a code-level registration per `docs/guides/plugin-handlers.md`. + ## Architecture How to structure your system. diff --git a/docs/guides/minions-fix.md b/docs/guides/minions-fix.md new file mode 100644 index 000000000..1bfb05d95 --- /dev/null +++ b/docs/guides/minions-fix.md @@ -0,0 +1,125 @@ +# Minions fix — repairing a half-migrated v0.11.0 install + +**tl;dr:** if your gbrain upgrade to v0.11.0 left Minions half-wired +(no preferences, autopilot still inline, or cron jobs still on +`agentTurn`), run: + +```bash +gbrain upgrade && gbrain apply-migrations --yes +``` + +If you're stuck on a v0.11.0 binary that predates `apply-migrations`, +paste the stopgap: + +```bash +curl -fsSL https://raw.githubusercontent.com/garrytan/gbrain/v0.11.1/scripts/fix-v0.11.0.sh | bash +``` + +Then upgrade + run `apply-migrations` once v0.11.1 is installed. + +## What went wrong + +The v0.11.0 release shipped the Minions schema, worker, queue, and +migration skill at `skills/migrations/v0.11.0.md`. But `gbrain upgrade`'s +`runPostUpgrade()` only printed the feature pitch — it never executed +the migration steps. Users ended up with: + +- Schema migrated to v7 (thanks to `gbrain init` on upgrade). +- Minions table created. +- Worker handlers registered (but no worker daemon running). +- No `~/.gbrain/preferences.json` (so `minion_mode` was unset). +- Autopilot still running sync/extract/embed inline, not dispatching + through Minions. +- AGENTS.md + cron manifests still referencing the old `sessions_spawn` + + `agentTurn` routes. + +## The fix (v0.11.1 binary or later) + +`gbrain apply-migrations` is the canonical repair. It reads +`~/.gbrain/migrations/completed.jsonl`, sees v0.11.0 is pending (or +stopgap-partial), and runs the orchestrator's seven phases: + +``` +A. Schema gbrain init --migrate-only +B. Smoke gbrain jobs smoke +C. Mode prompt (or --yes default pain_triggered) +D. Prefs write ~/.gbrain/preferences.json +E. Host AGENTS.md marker injection + cron rewrites for gbrain + builtins; JSONL TODOs for host-specific handlers +F. Install gbrain autopilot --install (env-aware) +G. Record append completed.jsonl status:"complete" +``` + +If Phase E emits TODOs for host-specific handlers (Wintermute's +~29 non-gbrain crons, for example), the migration finishes with +`status: "partial"`. Your host agent walks the TODOs using +`skills/migrations/v0.11.0.md` + `docs/guides/plugin-handlers.md`, ships +handler registrations in the host repo, then you re-run +`gbrain apply-migrations --yes` — the newly-registerable cron entries +get rewritten and the JSONL rows mark `status: "complete"`. + +## The stopgap (v0.11.0 binary, no apply-migrations yet) + +`scripts/fix-v0.11.0.sh` is a shell script that does what apply-migrations +does from a bash environment without depending on any new CLI. It: + +1. Runs `gbrain init --migrate-only` to ensure schema v7. +2. Runs `gbrain jobs smoke`. +3. Prompts for `minion_mode` (pain_triggered default on non-TTY). +4. Writes `~/.gbrain/preferences.json` atomically. +5. Appends `~/.gbrain/migrations/completed.jsonl` with + `status: "partial"` + `apply_migrations_pending: true` so a later + v0.11.1 `apply-migrations` run picks up where it left off. +6. Detects host agent repos and **prints** rewrite instructions (never + auto-edits from a curl-piped script — too high blast radius). +7. Tells the user to run `gbrain autopilot --install` as the one-stop + finisher (autopilot forks the Minions worker as a child; no separate + daemon to manage). + +Once v0.11.1 is installed, the stopgap retires: the canonical fix +becomes `gbrain upgrade && gbrain apply-migrations`. + +## Verify the fix landed + +```bash +# 1. Preferences exist and are readable +cat ~/.gbrain/preferences.json + +# 2. Migration recorded +cat ~/.gbrain/migrations/completed.jsonl + +# 3. Autopilot is supervising a Minions worker child +gbrain autopilot --status +ps aux | grep 'jobs work' + +# 4. Jobs show up in the queue +gbrain jobs list + +# 5. Any host-specific TODOs still pending +cat ~/.gbrain/migrations/pending-host-work.jsonl 2>/dev/null || echo "(none — all host work is done)" +``` + +## If the fix fails + +Each phase is idempotent. Re-running is safe. Common failure modes: + +- **Phase B smoke fails:** the schema didn't apply. Check + `~/.gbrain/config.json` has a valid `database_url` (or `database_path` + for PGLite). Run `gbrain init --migrate-only` directly and look at + the error. +- **Phase F install fails:** your host environment doesn't match any + detected target. Pass `--target ` + explicitly. +- **Pending host work never clears:** your host agent hasn't shipped + handler registrations yet. Read + `~/.gbrain/migrations/pending-host-work.jsonl`, open + `skills/migrations/v0.11.0.md`, and follow the host-agent instruction + manual. + +## Related + +- `skills/migrations/v0.11.0.md` — full migration skill for host agents. +- `docs/guides/plugin-handlers.md` — plugin contract for host-specific + handlers. +- `skills/conventions/cron-via-minions.md` — the canonical cron rewrite + pattern. diff --git a/docs/guides/plugin-handlers.md b/docs/guides/plugin-handlers.md new file mode 100644 index 000000000..73b98ceec --- /dev/null +++ b/docs/guides/plugin-handlers.md @@ -0,0 +1,137 @@ +# Plugin handlers — registering host-specific Minion handlers + +GBrain's Minion worker ships with seven built-in handlers: `sync`, +`embed`, `lint`, `import`, `extract`, `backlinks`, `autopilot-cycle`. +These cover every background operation the gbrain CLI itself performs. + +Host platforms (Wintermute, other OpenClaw deployments, future hosts) +register their own handlers via a plugin bootstrap that imports +`gbrain/minions`. No `handlers.json`-style data file — handlers are +code, loaded by the worker, with the same trust model as any other +code in the host's repo. + +## Why code, not data + +An earlier design draft shipped `~/.claude/gbrain-handlers.json` where +each entry was a shell command the worker would exec on job claim. +Codex flagged this as a durable RCE surface: an agent-writable data +file that spawns arbitrary shell. We dropped the data-file approach; +handlers are code that the host imports explicitly and ships through +code review. + +## The plugin contract + +A host worker bootstrap looks like this (TypeScript): + +```ts +import { MinionQueue, MinionWorker } from 'gbrain/minions'; +import type { BrainEngine } from 'gbrain/engine'; + +async function main() { + const engine: BrainEngine = /* your engine setup */; + await engine.connect({}); + + const worker = new MinionWorker(engine, { queue: 'default' }); + + // Register every host-specific handler the host's cron manifest references. + // Each handler returns a plain object (serialized as the job result). + // Throw on failure — the worker catches and retries per max_attempts. + + worker.register('ea-inbox-sweep', async (ctx) => { + const slot = ctx.data.slot ?? new Date().toISOString(); + // Host-specific agent turn: call your LLM, scan the inbox, write + // brain pages, return a summary. ctx.signal.aborted indicates the + // worker wants you to cooperate with shutdown — honor it. + return { swept: true, slot }; + }); + + worker.register('morning-briefing', async (ctx) => { + /* host logic */ + return { briefed: true }; + }); + + // Call start() AFTER every handler is registered. The worker's + // stall-detector ignores jobs whose name is not in the registered set. + await worker.start(); +} + +main().catch(err => { console.error(err); process.exit(1); }); +``` + +Ship this as a separate binary in the host repo (e.g. `wintermute-worker`) +or as a side-effect module that the stock `gbrain jobs work` command +auto-loads on startup (configurable via a host-provided entry point). + +## Handler contract + +Every handler receives a `MinionJobContext`: + +```ts +interface MinionJobContext { + data: Record; // job params (whatever the cron submit passed) + job: MinionJob; // full job row (id, queue, attempts, etc.) + signal: AbortSignal; // set to aborted when the worker is shutting down + inbox: MinionInbox; // read messages sent to this job while it runs +} +``` + +Return a serializable object on success. Throw on failure (the worker +will log + retry per `max_attempts`). + +**Abort cooperation.** When `ctx.signal.aborted` becomes true, finish +gracefully. The worker will wait 30s for you to return before SIGKILL. +Long-running LLM calls should pass the signal through to whatever +network library they use. + +**Idempotency.** The queue enforces unique `idempotency_key` at the DB +layer, so you don't need to worry about double-submits from a cron that +fires while the previous invocation is still running. + +## Gbrain's migration flow + +The v0.11.0 migration orchestrator (run by `gbrain apply-migrations`) +detects cron entries whose handler name is NOT in GBrain's builtin set +and emits a structured TODO to `~/.gbrain/migrations/pending-host-work.jsonl`. +Each TODO has shape: + +```json +{ + "type": "cron-handler-needs-host-registration", + "handler": "ea-inbox-sweep", + "cron_schedule": "0 */30 * * *", + "manifest_path": "/path/to/cron/jobs.json", + "current_cmd": "agentTurn ea-inbox-sweep", + "recommendation": "Add a handler registration for `ea-inbox-sweep` in your host worker bootstrap per docs/guides/plugin-handlers.md. Once registered, re-run `gbrain apply-migrations` to auto-rewrite this entry.", + "status": "pending" +} +``` + +The host agent walks these entries using `skills/migrations/v0.11.0.md`: + +1. Read `~/.gbrain/migrations/pending-host-work.jsonl`. +2. For each `cron-handler-needs-host-registration` row, ship a handler + registration in the host's worker bootstrap following the pattern + above. +3. Deploy the updated worker. +4. Re-run `gbrain apply-migrations --yes`. The orchestrator now + recognizes the newly-registerable handler (worker writes the + registered names to a discovery file on startup) and rewrites the + cron entry to use `gbrain jobs submit`. The JSONL row is marked + `status: "complete"`. + +## Trust boundary + +Handler code runs inside the worker process with the same privileges +as the rest of the host binary. There is no elevation. But there is +also no runtime sandbox — handlers can read + write anywhere the +worker user can. Review handler PRs the same way you review any other +code that touches production data. + +## Related + +- `skills/conventions/cron-via-minions.md` — the rewrite convention + for cron manifests. +- `skills/migrations/v0.11.0.md` — how the migration orchestrator + drives the host agent through this work. +- `skills/minion-orchestrator/SKILL.md` — patterns for submitting, + monitoring, steering, and replaying jobs once the handler is live. diff --git a/skills/conventions/cron-via-minions.md b/skills/conventions/cron-via-minions.md new file mode 100644 index 000000000..486e62f4f --- /dev/null +++ b/skills/conventions/cron-via-minions.md @@ -0,0 +1,93 @@ +# Cron via Minions Convention + +How cron-scheduled agent work is dispatched in a GBrain-backed install. + +## Rule: scheduled work runs as Minion jobs, not `agentTurn` + +When a cron fires, it should submit a Minion job. Not call OpenClaw's +native `agentTurn` (300s timeout, no durability, no transcript). Not +start an isolated session that races the gateway for resources. + +``` +# Bad: agentTurn with a fixed timeout, no durability. +{ "schedule": "*/30 * * * *", "kind": "agentTurn", "skill": "ea-inbox-sweep" } + +# Good (Postgres): fire-and-forget submit with an idempotency key per +# cycle slot. The queue dedupes long-running overlaps at the DB layer. +{ + "schedule": "*/30 * * * *", + "kind": "shell", + "cmd": "gbrain jobs submit ea-inbox-sweep --params '{\"slot\":\"$(date -u +%Y-%m-%dT%H:%M)\"}' --idempotency-key ea-inbox-sweep:$(date -u +%Y-%m-%dT%H:%M)" +} + +# Good (PGLite): inline execution with --follow. PGLite's exclusive file +# lock blocks a separate worker daemon, so the cron runs the job directly. +{ + "schedule": "*/30 * * * *", + "kind": "shell", + "cmd": "gbrain jobs submit ea-inbox-sweep --params '{}' --follow" +} +``` + +## Why + +- **Durability.** Gateway restart mid-task? Worker picks the job up on + boot. No lost state. +- **Observability.** `gbrain jobs list` + `gbrain jobs get ` show + every run, its duration, its transcript, its token accounting. +- **Steering.** Running jobs accept inbox messages. "Skip the + newsletter thread, focus on the urgent DMs" lands as context on the + next iteration. +- **Concurrency safety.** Idempotency-key on the cycle slot means a cron + that fires during a still-running previous invocation produces a noop + at the queue layer. Without this, a 5-min cron running 8-min jobs + stacks 4 overlapping copies at steady state. + +## Who registers the handler? + +**GBrain only rewrites cron entries whose handler name matches a +gbrain builtin** (`sync`, `embed`, `lint`, `import`, `extract`, +`backlinks`, `autopilot-cycle`). For host-specific handlers +(`ea-inbox-sweep`, `morning-briefing`, whatever your deployment runs +on cron), the host platform ships the handler as code. + +See `docs/guides/plugin-handlers.md` for the plugin contract. In short: + +```ts +import { MinionQueue, MinionWorker } from 'gbrain/minions'; + +const worker = new MinionWorker(engine, { queue: 'default' }); +worker.register('ea-inbox-sweep', async (ctx) => { + // Host-specific agent turn. Call whatever LLM + tools the host has. + // ctx.data contains the cron slot payload; return a result object. +}); +await worker.start(); +``` + +Ship the bootstrap in the host repo. Autopilot spawns the worker as a +child; the host's custom worker binary (or a side-effect module the +stock worker auto-loads on startup) registers handlers before `start()`. + +## Off mode + +Users who set `minion_mode: off` in `~/.gbrain/preferences.json` keep +using `agentTurn`. Respect that. No auto-rewrite. + +## Forward note (v0.12.0) + +GBrain v0.12.0 ships `gbrain cron`: a scheduler loop inside +`gbrain jobs work` that owns cron expressions natively — no more +handing off to host schedulers. Until v0.12.0 lands, the host +scheduler keeps firing on schedule; v0.11.1 only replaces the execution +layer (what the cron trigger *does*), not the scheduling layer. + +## Related + +- `skills/conventions/subagent-routing.md` — native subagents vs + Minions for ad-hoc (not cron-scheduled) work. +- `skills/minion-orchestrator/SKILL.md` — patterns for managing jobs + once they're in the queue. +- `skills/cron-scheduler/SKILL.md` — scheduling guidance (quiet hours, + staggering, idempotency). Now references this convention. +- `skills/migrations/v0.11.0.md` — how GBrain migrates an existing host + cron manifest to this convention. diff --git a/skills/cron-scheduler/SKILL.md b/skills/cron-scheduler/SKILL.md index 10b19cd14..b7ddd541d 100644 --- a/skills/cron-scheduler/SKILL.md +++ b/skills/cron-scheduler/SKILL.md @@ -39,7 +39,7 @@ This skill guarantees: - Override: user-awake flag (if user is active, quiet hours suspended) - During quiet hours: save output to held queue - Morning contact releases the backlog -4. **Register with host scheduler.** OpenClaw cron, Railway cron, crontab, or process manager. +4. **Register with host scheduler.** OpenClaw cron, Railway cron, crontab, or process manager. **Each registered entry should execute via Minions, not `agentTurn`.** See `skills/conventions/cron-via-minions.md` for the rewrite pattern (PGLite uses `--follow`, Postgres uses fire-and-forget + `--idempotency-key` on the cycle slot). GBrain's v0.11.0 migration auto-rewrites entries for built-in handlers; host-specific handlers need a code-level registration per `docs/guides/plugin-handlers.md`. 5. **Write thin prompt.** Job prompt is one line: "Read skills/{name}/SKILL.md and run it." ## Idempotency Requirement diff --git a/skills/maintain/SKILL.md b/skills/maintain/SKILL.md index 3ec4fd25c..80b74207c 100644 --- a/skills/maintain/SKILL.md +++ b/skills/maintain/SKILL.md @@ -85,6 +85,29 @@ If not running, install it: gbrain autopilot --install --repo ~/brain ``` Autopilot runs sync, extract, and embed in a continuous loop with adaptive scheduling. +In v0.11.1+, autopilot dispatches each cycle as a single `autopilot-cycle` +Minion job and supervises the worker child — one install step gives you +sync + extract + embed + backlinks + durable job processing. + +### Fix a half-migrated install +A v0.11.0 install where the migration skill never fired leaves Minions +partially set up: schema is applied, but `~/.gbrain/preferences.json` +doesn't exist, autopilot runs inline, host manifests still reference +`agentTurn`. Repair: + +```bash +# Check migration status +gbrain apply-migrations --list + +# Apply pending migrations (idempotent; safe on healthy installs) +gbrain apply-migrations --yes + +# If host-specific handlers are flagged in ~/.gbrain/migrations/pending-host-work.jsonl: +# walk them per skills/migrations/v0.11.0.md + docs/guides/plugin-handlers.md, +# ship handler registrations in the host repo, then re-run apply-migrations. +``` + +Full troubleshooting guide: `docs/guides/minions-fix.md`. ### Back-link enforcement Check that the back-linking iron law is being followed: diff --git a/skills/migrations/v0.11.0.md b/skills/migrations/v0.11.0.md index 45880ea10..ab3b20431 100644 --- a/skills/migrations/v0.11.0.md +++ b/skills/migrations/v0.11.0.md @@ -7,156 +7,192 @@ feature_pitch: tiers: null --- -# v0.11.0 Migration: Minions +# v0.11.0 Migration: Minions — host-agent instruction manual -Minions is a job queue for agent work. Submit a task, get an id back. Check -progress whenever. The job keeps running even if the gateway restarts. Parent -jobs fan out to children, children post results back to the parent's inbox, -fan-in just works. +**Audience: host agents (Wintermute, other OpenClaw deployments, future +hosts) reading this AFTER `gbrain apply-migrations` has run its +mechanical phases.** The orchestrator in +`src/commands/migrations/v0_11_0.ts` is the runtime source of truth for +the mechanical work (schema, smoke, prefs, built-in cron rewrites, +autopilot install). This file tells the host agent how to finish the +host-specific work that can't be done host-agnostically. -This migration adds the Minions schema (additive, safe to re-run), runs a -smoke test, and asks the user how they want their agent to use it. - -## What's New - -Minions primitives landed in this release: - -- **Durability.** Jobs live in Postgres. Gateway crash = agent picks up where - it left off on restart. No lost state. -- **Observability.** Every job has structured progress, token accounting, and - a transcript. `gbrain jobs stats` is the health dashboard. -- **Steering.** Running jobs can receive inbox messages. "Focus on revenue, - skip headcount" lands as context on the next iteration. -- **Parallel fan-out.** Parent job spawns N children with depth + child-cap - guardrails (depth ≤ 5, max_children per parent). Children post `child_done` - into parent's inbox on completion. Parent polls and aggregates. -- **Per-job timeout.** `timeout_ms` + cooperative AbortSignal + DB-enforced - dead-letter. Agents that hang don't hold resources forever. -- **Cascade cancel.** Kill a parent → all descendants cancel via recursive CTE. -- **Idempotency.** Same key = same job. Safe to submit the same task twice. - -Postgres only for the worker daemon (`gbrain jobs work`). PGLite supports -inline execution via `gbrain jobs submit --follow` but not background. - -## Agent Instructions - -### Step 1: Apply the schema - -Minions needs schema version 7. If the user's brain is below that, bump it. +## Check what gbrain left you ```bash -gbrain init +cat ~/.gbrain/migrations/pending-host-work.jsonl ``` -On existing Postgres brains this is additive (new columns via `ALTER TABLE -... ADD COLUMN IF NOT EXISTS`). On PGLite brains, the DDL bundle is re-applied -and the new columns appear. +Empty? You're done. The migration is complete; nothing for you to do. -If `gbrain init` errors, read the output and fix before continuing. Common -causes: stale lock file, wrong DATABASE_URL, PGLite directory permissions. +Non-empty? Each line is a TODO. Each `type` routes to a section below. -### Step 2: Smoke test +## `type: cron-handler-needs-host-registration` -```bash -gbrain jobs smoke -``` +Gbrain rewrites cron entries whose handler name matches a gbrain +builtin (`sync`, `embed`, `lint`, `import`, `extract`, `backlinks`, +`autopilot-cycle`). For host-specific handlers (e.g. `ea-inbox-sweep`, +`frameio-scan`, `x-dm-triage`, `calendar-sync` on Wintermute), gbrain +leaves the manifest alone and emits a TODO with shape: -Expected output on success: - -``` -SMOKE PASS — Minions healthy in 0.5s (engine: postgres) -``` - -On PGLite: - -``` -SMOKE PASS — Minions healthy in 0.3s (engine: pglite) -Note: the `gbrain jobs work` daemon requires Postgres. PGLite -supports inline execution only (`submit --follow`). -``` - -If SMOKE FAIL: report the exact error to the user, do not proceed. The most -common failure is "table not found, schema version 1 need 7" which means -Step 1 didn't actually run. Re-run `gbrain init` and try again. - -### Step 3: Ask the user how they want to use Minions - -Use AskUserQuestion. Re-ground first (project + branch + what Minions is), -then ask with these three options: - -> GBrain Minions is a durable, observable job queue for background agent -> work. It survives restarts, tracks progress, and lets agents fan out -> parallel work with parent-child DAGs. How should your agent use it? - -Options: -- **A) Switch to Minions for everything (recommended).** All background - work goes through Minions. Most durable, most observable. Adds some - overhead (Postgres queries per job) but pays off the first time the - gateway restarts mid-task. `Completeness: 10/10` -- **B) Use only when pain signals fire.** Default to native subagents - (lower overhead, zero extra setup). Auto-switch to Minions when the - gateway drops subagent state, the user asks "what happened to the agent," - or > 3 parallel streams are needed. Agent watches for these signals and - proposes the switch in-session. `Completeness: 8/10` -- **C) Don't use Minions.** Keep subagents only. Turn this on later with - `gbrain config set minion_mode always` or `pain_triggered`. - `Completeness: 5/10` - -RECOMMENDATION: Choose A for production brains and research-heavy workflows -(most durability, best transcripts). Choose B if the user is exploring -or on PGLite (lower friction, opt-in on demand). Choose C only if the -user explicitly says they don't want it. - -### Step 4: Persist the choice - -Write the user's answer to `~/.gbrain/preferences.json`: - -```bash -mkdir -p ~/.gbrain -MODE="always" # or "pain_triggered" or "off" — substitute the user's pick -cat > ~/.gbrain/preferences.json < { + // Host-specific agent turn. +}); +// ...register every TODO handler... +await worker.start(); +``` + +### (b) Ship the bootstrap in your host repo + +Autopilot already spawns `gbrain jobs work` as a child. Configure it to +spawn your custom worker binary (e.g. `wintermute-worker`) instead, or +register handlers as a side-effect module that the stock worker loads on +startup. Either path is documented in `plugin-handlers.md`. + +### (c) Unit tests for the handler body + +Mock the LLM. Assert the happy path, the empty-input path, the +network-timeout path, the LLM-failure path. Every branch of the handler's +deterministic logic has a unit test. + +### (d) Integration tests + +Submit a real Minion job against your test environment. Assert: +- The job completes (not dead-lettered). +- The side effects happen (brain pages written, external API called). +- The result shape matches what your cron consumers expect. + +### (e) LLM evals + +Whatever prompt the handler runs, add a case in your eval suite. At +minimum: one happy case, one edge case, one adversarial case. If the +handler's job is "sweep the EA inbox," the eval asserts urgent items +surface and routine items don't. + +### (f) Resolver trigger update + +The host's AGENTS.md dispatcher used to reference the skill name +(`ea-inbox-sweep`) as an `agentTurn` route. Rewrite that route to +`submit_job` with the same trigger phrases — the user-facing language +doesn't change; the execution path does. + +### (g) Resolver trigger eval + +Add a test: feed each trigger phrase to the resolver, assert it routes +to the new `submit_job` path, not the old `agentTurn`. Without this +test, your router can silently keep old behavior and nobody notices +until the cron quietly breaks. + +### (h) E2E smoke + +One test that exercises the full pipeline: scheduler → `gbrain jobs +submit` → worker claim → handler body → side effect. Gated by your +host's E2E DB fixture. + +### (i) Brain filing + +If the handler writes brain pages, `brain/RESOLVER.md` needs an entry +for the directory. Orphaned brain pages are worse than no brain pages — +nobody can find them on read. + +### (j) check-resolvable + +Run `gbrain check-resolvable`. It validates reachability (is the skill +mentioned from RESOLVER.md?), MECE overlap, DRY, gap detection. If it +fails, fix the skill or extend an existing one instead of creating a +duplicate. + +### Finalize + +When all ten gates are green for a handler: ```bash -gbrain jobs work --concurrency 1 & -WORKER_PID=$! -sleep 2 -gbrain jobs submit sync --params '{"dry":true}' --follow -kill $WORKER_PID 2>/dev/null +gbrain apply-migrations --yes ``` -This proves the built-in handlers (`sync`, `embed`, `lint`, `import`) are -wired up. Skip on PGLite (use `--follow` instead of daemon). +The orchestrator detects the newly-registerable handler, rewrites the +cron entry from `agentTurn ea-inbox-sweep` to `gbrain jobs submit ...` +with the right engine-aware form (PGLite uses `--follow`; Postgres uses +fire-and-forget + `--idempotency-key`), and marks the JSONL row +`status: "complete"`. -### Step 6: Done +**Iron rule:** `scripts/skillify-check.ts ` must +return 10/10 before you mark a handler migrated. No partial-credit +"we'll add tests later." See `skills/skillify/SKILL.md` for the meta. + +## `type: agents-md-dispatcher-needs-host-review` + +Gbrain injects a marker into each AGENTS.md pointing to +`skills/conventions/subagent-routing.md`. But if your dispatcher has +inline `sessions_spawn` routing logic (custom branching on tool names +or intent patterns), that's not a mechanical rewrite — it involves host +judgment about which routes should flip to `submit_job` and which should +stay on `sessions_spawn` for real-time tasks. + +Read `skills/conventions/subagent-routing.md`. Walk your dispatcher's +`sessions_spawn` blocks. For each one, decide: + +- **Brain-write, multi-step, user will ask later** → route through + `submit_job` (Minions). +- **Real-time, user waiting, < 30s** → keep on `sessions_spawn`. +- **Somewhere in between** → `minion_mode: pain_triggered` is the + right default; let the convention's pain signals decide. + +Mark the JSONL row `status: "complete"` by appending a new line with +the same shape + updated status. Or: edit the dispatcher, rerun +`gbrain apply-migrations`, and let gbrain re-detect + update +automatically (it dedupes on file path, so no duplicate rows). + +## When every TODO clears ```bash -mkdir -p ~/.gbrain/migrations -echo '{"version":"0.11.0","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","status":"complete","minion_mode":"'$MODE'"}' >> ~/.gbrain/migrations/completed.jsonl +gbrain apply-migrations --list ``` -## For the user +Should show v0.11.0 as `applied`. Your host is fully migrated to +Minions. Autopilot is dispatching through the queue, cron jobs are +durable, and every handler you registered appears in +`worker.registeredNames` on next worker startup. -If mode = `always`: "Your agent now runs every multi-step task through -Minions. `gbrain jobs stats` shows what's running. `gbrain jobs list` shows -everything. Nothing to configure." +## Related -If mode = `pain_triggered`: "Your agent keeps using subagents by default. -If the gateway drops state or you ask 'what happened to the agent,' it'll -offer to switch that task to Minions. You can flip modes any time with -`gbrain config set minion_mode always`." - -If mode = `off`: "Minions is installed but dormant. Turn it on later with -`gbrain config set minion_mode always` or `pain_triggered`." +- `skills/migrations/index.ts` (code, not file) — the TS registry the + runtime consults; `apply-migrations --list` reads from here, not + from this markdown file. +- `skills/conventions/cron-via-minions.md` — the rewrite pattern + gbrain applies for builtins + the one your host ships for custom + handlers. +- `skills/conventions/subagent-routing.md` — runtime dispatcher rules + (native subagent vs Minion) that AGENTS.md should reference. +- `docs/guides/plugin-handlers.md` — the plugin contract for host + handler registration. +- `skills/skillify/SKILL.md` — the 10-item checklist every handler + must pass before its JSONL row clears. +- `scripts/skillify-check.ts` — machine-readable version of the + checklist; use with `--json` in CI. +- `docs/guides/minions-fix.md` — user-facing troubleshooting for + broken-v0.11.0 installs that never ran the migration at all. diff --git a/skills/setup/SKILL.md b/skills/setup/SKILL.md index fa45f7381..001ad58e4 100644 --- a/skills/setup/SKILL.md +++ b/skills/setup/SKILL.md @@ -168,6 +168,40 @@ echo "=== Discovery Complete ===" If no markdown repos are found, create a starter brain with a few template pages (a person page, a company page, a concept page) from docs/GBRAIN_RECOMMENDED_SCHEMA.md. +## Phase C.5: One-step autopilot + Minions install (v0.11.1+) + +Run the migration runner once, then install autopilot. Two commands, done: + +```bash +gbrain apply-migrations --yes # applies any pending migrations; idempotent on healthy installs +gbrain autopilot --install # supervises itself + forks the Minions worker; env-aware +``` + +What `gbrain autopilot --install` does: + +- On **macOS**: writes a launchd plist at `~/Library/LaunchAgents/com.gbrain.autopilot.plist`. +- On **Linux with systemd**: writes `~/.config/systemd/user/gbrain-autopilot.service` + with `Restart=on-failure`. +- On **ephemeral containers** (Render / Railway / Fly / Docker): writes + `~/.gbrain/start-autopilot.sh` and prints the one-line your agent's + bootstrap should source to launch autopilot on every container start. + Auto-injects into OpenClaw's `hooks/bootstrap/ensure-services.sh` if + detected (use `--no-inject` to opt out). +- On **Linux without systemd**: installs a crontab entry (every 5 min). + +Autopilot then supervises the Minions worker as a child process. Users get +sync + extract + embed + backlinks + durable Postgres-backed job processing +from ONE install step. No separate `gbrain jobs work` daemon to manage. + +On PGLite, autopilot runs inline (PGLite's exclusive file lock blocks a +separate worker process). Everything else still works. + +If `apply-migrations` prints "N host-specific items need your agent's +attention," read `~/.gbrain/migrations/pending-host-work.jsonl` + walk +`skills/migrations/v0.11.0.md` + `docs/guides/plugin-handlers.md` to +register host-specific handlers. Re-run `apply-migrations` after each +batch. + ## Phase D: Brain-First Lookup Protocol Inject the brain-first lookup protocol into the project's AGENTS.md (or equivalent).