mirror of
https://github.com/garrytan/gbrain.git
synced 2026-07-27 22:15:33 +00:00
docs(v0.11.1): cron-via-minions convention, plugin-handlers guide, minions-fix, skill updates
New reference docs:
- skills/conventions/cron-via-minions.md — the rewrite convention for
cron manifests. Shows the Postgres (fire-and-forget + idempotency-
key) vs PGLite (--follow inline) branch; explains why builtin-only
auto-rewrite is safe + how host-specific handlers get the plugin
contract.
- docs/guides/plugin-handlers.md — the plugin contract for host-
specific Minion handlers. Code-level registration via import +
worker.register(), not a data file (Codex D: handlers.json was an
RCE surface). Concrete TypeScript skeleton + handler contract
(ctx.data, ctx.signal, ctx.inbox) + full migration flow from TODO
JSONL to a rewritten cron entry.
- docs/guides/minions-fix.md — user-facing troubleshooting for
half-migrated v0.11.0 installs. Paste-one-liner for the stopgap,
gbrain apply-migrations path for v0.11.1+, verification commands,
failure-mode recipes.
Rewrites + updates:
- skills/migrations/v0.11.0.md — body restored as the host-agent
instruction manual. Audience is the host agent reading
~/.gbrain/migrations/pending-host-work.jsonl after the CLI
orchestrator has done the mechanical phases. Walks each TODO type
through the 10-item skillify checklist (plugin contract, ship
bootstrap, unit tests, integration tests, LLM evals, resolver
trigger, trigger eval, E2E smoke, brain filing, check-resolvable).
Reverses the earlier "delete the body" decision (1B) because the
body serves a different audience now — host-agent, not CLI
documentation.
- skills/cron-scheduler/SKILL.md — Phase 4 ("Register with host
scheduler") now references cron-via-minions + plugin-handlers.
- skills/maintain/SKILL.md — new "Fix a half-migrated install"
section with the apply-migrations recipe.
- skills/setup/SKILL.md — new Phase C.5 "One-step autopilot +
Minions install (v0.11.1+)" explaining the four install targets
+ the OpenClaw auto-injection default.
- docs/GBRAIN_SKILLPACK.md — Operations section adds the three new
guides + the subagent-routing and cron-routing SKILLPACK notes
(v0.11.0+).
All 167 related tests (conformance + resolver + skillify-check + v0_11_0
orchestrator) stay green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
f9b71de10d
commit
5c55ab86b5
@@ -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.
|
||||
|
||||
@@ -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 <macos|linux-systemd|ephemeral-container|linux-cron>`
|
||||
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.
|
||||
@@ -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<string, unknown>; // 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.
|
||||
@@ -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 <id>` 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.
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
+163
-127
@@ -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 <<EOF
|
||||
```json
|
||||
{
|
||||
"minion_mode": "$MODE",
|
||||
"set_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
|
||||
"set_in_version": "0.11.0"
|
||||
"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 ...",
|
||||
"status": "pending"
|
||||
}
|
||||
EOF
|
||||
```
|
||||
|
||||
The subagent-routing convention (`skills/conventions/subagent-routing.md`)
|
||||
reads this file at runtime. If the file is missing, the convention defaults
|
||||
to `pain_triggered`.
|
||||
**Each TODO is a full skillify, not a drive-by handler add.** A properly
|
||||
migrated cron meets every one of these gates before its JSONL row gets
|
||||
marked `complete`:
|
||||
|
||||
### Step 5: Sanity-check the registered handlers
|
||||
### (a) Plugin contract
|
||||
|
||||
Read `docs/guides/plugin-handlers.md`. Add a `worker.register()` call
|
||||
in your host's worker bootstrap:
|
||||
|
||||
```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.
|
||||
});
|
||||
// ...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 <handler-code-path>` 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.
|
||||
|
||||
@@ -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).
|
||||
|
||||
Reference in New Issue
Block a user