* feat: merge gbrain-jobs into minion-orchestrator — single unified minions skill * fix(skill/minion-orchestrator): correct MCP boundary, real handler names, PGLite path The initial merge commit a51c737 documented `submit_job name="shell"` as agent-callable, but src/core/operations.ts:1106 rejects protected names from MCP callers (shell is in src/core/minions/protected-names.ts:16) — shell-job submission is CLI-only. Subagent examples referenced non-existent handler names (`research`, `orchestrate`) instead of the real `subagent` / `subagent_aggregator` handlers. PGLite section wrongly told users to migrate to Supabase when `gbrain jobs submit ... --follow` inline mode works per docs/guides/minions-shell-jobs.md:15. Contract section canonized "every task through Minions" against the `pain_triggered` default in skills/conventions/subagent-routing.md:16,27. Rewrite addresses all four: - Shell Jobs section is explicit about CLI-only submission; agents observe via get_job / list_jobs / get_job_progress (non-protected). - Subagent examples route through `gbrain agent run` (user-facing CLI) with raw handler names documented as the power-user path. - PGLite gets --follow inline execution, not migration friction. - Contract softened to point at subagent-routing.md convention. Also adds a Preconditions block for Shell Jobs (env gate, RCE warning, execution-mode choice, verification command), narrows the frontmatter "gbrain jobs" trigger to "gbrain jobs submit" + "submit a gbrain job" (bare was too broad — CLI namespace covers 9 subcommands), inlines a "replaces older gbrain-jobs routing intent" note in the description, and removes non-existent `get_job_stats` from the tools list (CLI is `gbrain jobs stats`; no MCP equivalent). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(resolver): narrow "gbrain jobs" trigger to specific intents Replace bare "gbrain jobs" in the routing table with "gbrain jobs submit" + "submit a gbrain job". The bare phrase was too broad — the CLI namespace covers 9 subcommands (submit, list, get, retry, delete, prune, stats, smoke, work). Users asking about stats/prune/retry now fall through to `gbrain --help` instead of getting misrouted to minion-orchestrator, which only documents shell execution and subagent orchestration. Matches the frontmatter trigger narrow in minion-orchestrator/SKILL.md. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test(resolver): add round-trip + skill-example-name validator Two new assertion blocks in test/resolver.test.ts: 1. RESOLVER.md trigger round-trip: every quoted phrase in a routing-table row has a fuzzy match in the target skill's frontmatter `triggers:` list. Catches RESOLVER ↔ frontmatter drift that checkResolvable's reachability check doesn't. Fuzzy match is case-insensitive, trailing-punctuation- insensitive, and splits on "/" for compound phrases like "pause/resume agent" — accommodates RESOLVER.md's natural-language summary style without allowing real drift through. 2. Skill example-name validator: every `name="<word>"` reference in any SKILL.md body must resolve to either a declared operation in src/core/operations.ts or a known Minions handler in PROTECTED_JOB_NAMES. Would have caught the `name="research"` / `name="orchestrate"` drift that slipped through the first review — nothing in CI caught those handler names referencing non-existent handlers until a Codex cold-read found them. This test closes that class of regression gap. 51 / 51 tests pass locally. Full E2E suite (bun run test:e2e) still passes 197 / 197 across 19 files. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test(e2e): PGLite shell-job --follow inline path Closes the T4 coverage gap surfaced during PR #381 eng review. The sibling test/e2e/minions-shell.test.ts covers Postgres + persistent-daemon; this file covers the PGLite + --follow path the minion-orchestrator skill now documents. Two assertions: 1. submit → registerBuiltinHandlers → worker.start → shell runs → completes with exit_code 0 and stdout_tail "hello\n". Exercises the exact dispatch path src/commands/jobs.ts:207 takes when --follow is set, including the GBRAIN_ALLOW_SHELL_JOBS=1 gate. 2. With GBRAIN_ALLOW_SHELL_JOBS unset, registerBuiltinHandlers leaves the shell handler unregistered. Confirms the env gate from src/commands/jobs.ts:611 works. Runs in-memory against PGLiteEngine — no DATABASE_URL, no Docker, runs in CI unconditionally. Completes in ~1.2s. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix: pre-landing review fixes Pre-landing review caught 4 doc bugs + 2 test fragilities + 2 pre-existing drift cases. All auto-fix category (clear correct answer, single obvious fix). minion-orchestrator/SKILL.md: - Shell submit examples used nonexistent `--cmd`/`--argv`/`--cwd` flags. Real CLI takes `--params '{"cmd":"...","cwd":"..."}'` (src/commands/jobs.ts:55-85). Examples now match `gbrain jobs submit --help` output. - `--tools "search,web_search"` referenced `web_search` which isn't in BRAIN_TOOL_ALLOWLIST (src/core/minions/tools/brain-allowlist.ts:47-59). Swapped to `search,query`. Added a full allowlist enumeration so readers don't have to grep. - `gbrain agent run` flags section listed `--queue`, `--priority`, `--max-attempts`, `--delay` — none of these exist on that command (src/commands/agent.ts:105-129). Replaced with the real flag set (`--subagent-def`, `--model`, `--max-turns`, `--tools`, `--timeout-ms`, `--fanout-manifest`, `--follow`, `--no-follow`, `--detach`) and a note about using `gbrain jobs submit` for queue tuning. - MCP boundary claim "returns permission_denied" was imprecise. Reworded: throws an OperationError with code permission_denied. test/resolver.test.ts: - D5/C row regex required the backtick-quoted skill path to be followed immediately by `|`, silently skipping rows with trailing parentheticals (e.g., `` `skills/maintain/SKILL.md` (extraction sections) |``). Broadened to `[^|]*\|` so every row gets audited. test/e2e/minions-shell-pglite.test.ts: - Shared engine across both tests with no per-test reset. Future test additions would hit order-dependency. Added beforeEach TRUNCATE on minion_jobs / minion_inbox / minion_attachments, matching the Postgres sibling at test/e2e/minions-shell.test.ts:55-58. skills/query/SKILL.md: - Added 4 triggers RESOLVER.md routes to this skill but the frontmatter never declared: "who knows who", "relationship between", "connections", "graph query". Pre-existing drift — the broadened D5/C regex surfaced it. skills/maintain/SKILL.md: - Added 6 triggers with the same pre-existing drift: "extract links", "build link graph", "populate timeline", "populate links", "backfill graph", "extract timeline entries". 57/57 tests pass on the fixed tree. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix: second-pass review fixes — stale CLI flag + handler name Two more stale references caught by specialist re-dispatch on the fixed tree: skills/minion-orchestrator/SKILL.md:72 — Routing table row described shell jobs as taking `--cmd` or `--argv` as CLI flags. Same class of bug as M1 from the prior fix commit but in a different location. Now says `--params` with `cmd` or `argv`, matching the corrected submit examples (lines 112-120). skills/conventions/subagent-routing.md:82 — "Check `get_job_stats` queue_health.active" referenced an MCP operation that doesn't exist in src/core/operations.ts. The new minion-orchestrator skill cross-references this convention file, so agents following the routing pointer would hit a non-existent op. Replaced with the real ops: `list_jobs --status active` (MCP) or `gbrain jobs stats` (CLI). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix: adversarial pass cleanups — manifest.json + anti-pattern scope Claude adversarial subagent caught two last consistency gaps: skills/manifest.json:135 — Skill description still read "Manage background agents via Minions job queue" (subagent-only framing), out of sync with the reframed SKILL.md frontmatter. Manifest is what the skill registry indexes; leaving this stale meant shell-job-intent routers would miss it. Updated to match the unified wording. skills/minion-orchestrator/SKILL.md:288 — Anti-pattern line "Don't use sessions_spawn with runtime: subagent when Minions is available" was subagent-lane-specific inside the now-consolidated skill, reading like the one rule in the skill but only addressing one lane. Scoped to "For subagent work" and pointed at `gbrain agent run` so the rule doesn't confuse shell-job readers. Two investigate-class items deferred to follow-up: - D13 regex could false-positive on future skills with unrelated `name="..."` usage. Today clean; scope to backtick-fenced snippets if it bites. - PGLite E2E env-var race if bun:test ever goes file-parallel. Today isolated per file; add helper + comment when needed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: bump version and changelog (v0.19.2) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: update README + CLAUDE.md for v0.19.2 Minions consolidation - Skill count 28 -> 29 across README and CLAUDE.md (adds smoke-test from v0.19.1 to the Skills section, closes a prior drift). - README minion-orchestrator row rewritten to name both lanes (shell jobs via `gbrain jobs submit shell`, LLM subagents via `gbrain agent run`) so the surface matches the consolidated skill file. - README Operational table gains a smoke-test row. - CLAUDE.md key-files entry for minion-orchestrator now describes the v0.19.2 consolidation, trust boundary (MCP permission_denied on protected names), and the narrowed trigger set. - CLAUDE.md Skills section notes the consolidation and the new v0.19.1 smoke-test skill. - CLAUDE.md test inventory picks up `test/e2e/minions-shell-pglite.test.ts` and the v0.19.2 round-trip + name-validator additions in `test/resolver.test.ts`. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(ci): update PGLite test for new env-gate behavior + regenerate llms-full.txt CI caught two issues: 1. `test/e2e/minions-shell-pglite.test.ts` — the "GBRAIN_ALLOW_SHELL_JOBS unset → shell handler not registered" test was written against pre-v0.20.3 `registerBuiltinHandlers` behavior (env gate at registration time). Master's queue-resilience merge moved the gate from registration to execution: shell handler is now always registered so claimed jobs emit a clear rejection log, and `shellHandler` itself throws UnrecoverableError when GBRAIN_ALLOW_SHELL_JOBS != '1' (see src/core/minions/handlers/shell.ts:210). Updated the test to invoke shellHandler directly with a minimal ctx and assert the throw. Preserves the test's intent (prove the guard works) under the new control flow. 2. `llms-full.txt` drift — README.md + CLAUDE.md updates in v0.19.2 and v0.20.4 updated the skill count to 29 and rewrote the minion-orchestrator description, but the committed `llms-full.txt` bundle still reflected the pre-consolidation content. Regenerated via `bun run build:llms`. The third CI failure (`planInstall + applyInstall D-CX-11`) passes cleanly locally (26/26 in test/skillpack-install.test.ts). The 1ms runtime in CI suggests a filesystem-mtime flake, not a real regression from this branch. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(skillpack): treat future-mtime lock as stale (CI race fix) D-CX-11 ("--force-unlock overrides a stale lock") flaked in CI with a 1ms runtime. Root cause: on fast CI filesystems (ext4 with high-resolution mtimes on GitHub runners), `writeFileSync` can set a lock's mtime a few microseconds ahead of the subsequent `Date.now()`, making `age` negative. Old logic: const stale = age >= staleMs; With `staleMs: 0` and `age = -0.3ms`: `-0.3 >= 0` is false → NOT stale → the `!stale` branch throws `lock_held` before reaching the force-unlock path. Test failed at the first ms, never exercised the actual unlock logic. Fix (src/core/skillpack/installer.ts:189): const stale = age < 0 || age >= staleMs; Treats negative age (future mtime) as stale. Safe: if the lock's mtime is in the future, either the filesystem clock just jumped forward or the lock was written by a racing process; either way it's not a live, healthy lock and the stale path is the correct branch. Passes locally (26/26 in test/skillpack-install.test.ts). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: root <root@localhost> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
11 KiB
name, version, description, triggers, tools, mutating
| name | version | description | triggers | tools | mutating | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| minion-orchestrator | 1.0.0 | Unified Minions skill for both deterministic shell jobs and LLM subagent orchestration. Replaces the older `gbrain-jobs` routing intent. Use when: submitting gbrain jobs, shell/background tasks, spawning subagents, checking progress, steering running work, pausing/resuming, parallel fan-out. One durable, observable, steerable queue interface. |
|
|
true |
Minion Orchestrator
Contract
Minions is a Postgres-native job queue for durable, observable background work. This single skill handles two lanes:
- Deterministic shell jobs (
gbrain jobs submit shell ...) - LLM subagent jobs (
gbrain agent run ...)
When to route to Minions: durable, observable work that must survive restarts,
fan out across many parallel tasks, or persist across sessions. Routing policy
is defined in skills/conventions/subagent-routing.md — the project default is
pain_triggered (native subagents first, Minions after specific pain signals
fire); Mode A (all-through-Minions) is opt-in.
Guarantees:
- Jobs survive gateway restart (Postgres-backed)
- Every job has structured progress, token accounting, and session transcripts
- Running agents can be steered mid-flight via inbox messages
- Jobs can be paused, resumed, or cancelled at any time
- Parent-child DAGs with configurable failure policies
Route the Request: Shell Job vs Subagent
| Condition | Action |
|---|---|
| User asks for deterministic command/script run | Shell job (CLI: gbrain jobs submit shell ...) |
| User asks to "run in minions" + explicit command/argv | Shell job (CLI, --params with cmd or argv) |
| User asks for research/reasoning/iterative agent | Subagent job (CLI: gbrain agent run) |
| User asks to steer/pause/resume an agent | Subagent job lifecycle tools (MCP-callable) |
| Single simple operation under ~30s | Consider inline execution first |
| Needs restart durability/observability | Submit as Minion job |
| Parallel work (2+ streams) | gbrain agent run --fanout-manifest or parent + child subagents |
If intent is ambiguous, ask one clarification: "Do you want a deterministic shell command job, or an LLM agent job?"
Shell Jobs (Deterministic Scripts)
Use for reproducible command execution, ETL steps, cron work, and scriptable tasks where no LLM reasoning loop is needed.
Preconditions (read before submitting your first shell job)
GBRAIN_ALLOW_SHELL_JOBS=1must be set on the worker environment. Without it, the shell handler refuses to register and submissions sit inwaitingsilently. Gate lives insrc/core/minions/handlers/shell.ts.- Security: flipping
GBRAIN_ALLOW_SHELL_JOBS=1authorizes arbitrary command execution on the worker. On a shared queue, this is a remote code execution surface. Treat as privileged infrastructure authorization. - Execution mode — pick one:
- Postgres + daemon:
gbrain jobs workruns a persistent worker that claims and executes jobs from the queue. - PGLite + --follow:
gbrain jobs submit ... --followruns inline. The daemon mode is not available on PGLite (exclusive file lock). Seedocs/guides/minions-shell-jobs.md.
- Postgres + daemon:
- MCP boundary: shell-job submission is CLI-only.
submit_job name="shell"over MCP throws anOperationErrorwith codepermission_denied("'shell' jobs cannot be submitted over MCP") becauseshellis inPROTECTED_JOB_NAMES. Agents CAN observe shell jobs viaget_job/list_jobs/get_job_progress(not protected), but cannot submit them. Operator or autopilot submits; agent observes. - Verify setup: after configuration, run
gbrain jobs stats(CLI) to confirm the worker is registered and consuming the queue.
Submit (CLI, operator or autopilot)
Shell jobs take their command via --params as a JSON object with cmd (string)
or argv (array), plus cwd and optional env.
Command string form:
gbrain jobs submit shell --params '{"cmd":"echo hello","cwd":"/abs/path"}'
Argv form (no shell expansion):
gbrain jobs submit shell --params '{"argv":["bash","-lc","echo hello"],"cwd":"/abs/path"}'
Inline execution on PGLite or any one-shot deployment:
gbrain jobs submit shell --params '{"cmd":"echo hello","cwd":"/tmp"}' --follow
Queue/lifecycle flags exposed by gbrain jobs submit --help: --queue,
--priority, --delay, --max-attempts, --max-stalled, --backoff-type,
--backoff-delay, --backoff-jitter, --timeout-ms, --idempotency-key,
--dry-run.
Monitor (agents or operator)
These operations are MCP-callable and safe for agent use:
list_jobs --name shell --status active
get_job ID
get_job_progress ID
Check structured result fields (exit code, stdout/stderr tails, attempts,
timings) from get_job. Use gbrain jobs stats (CLI) for worker/queue
health dashboard.
Control (MCP-callable)
cancel_job id=ID
replay_job id=ID
replay_job is not protected — only shell submission is. Agents can
cancel or replay a shell job without CLI access.
Use idempotency keys for recurring shell workloads to avoid duplicate runs.
Subagent Jobs (LLM Orchestration)
Use for open-ended reasoning, tool-using research, and fan-out synthesis.
User-facing entrypoint: gbrain agent run <prompt> is the canonical way
to submit subagent work. It handles the elevated-trust plumbing — subagent
and subagent_aggregator are both in PROTECTED_JOB_NAMES, so direct MCP
submission requires {allowProtectedSubmit: true}, which gbrain agent run
supplies.
Phase 1: Submit
gbrain agent run "Research Acme Corp revenue" --tools "search,query"
--tools accepts a comma-separated subset of BRAIN_TOOL_ALLOWLIST (see
src/core/minions/tools/brain-allowlist.ts): query, search, get_page,
list_pages, file_list, file_url, get_backlinks, traverse_graph,
resolve_slugs, get_ingest_log, put_page. Anything outside the allow-list
is rejected at submit time with allowed_tools references unknown tool.
For parallel work with a fan-out manifest:
gbrain agent run --fanout-manifest companies.json
The manifest describes N children + 1 aggregator. Each child runs
name="subagent" under the hood; the aggregator runs name="subagent_aggregator"
and claims AFTER every child terminates. See
src/core/minions/handlers/subagent.ts and
src/core/minions/handlers/subagent-aggregator.ts.
Flags (from src/commands/agent.ts):
--subagent-def <name>— named subagent definition--model <id>— override model--max-turns <N>— cap the LLM loop--tools <csv>— allow-listed brain tools (see above)--timeout-ms <N>— hard timeout per job--fanout-manifest <file>— N children + 1 aggregator--follow/--no-follow— stream logs + wait (default on TTY)--detach— submit and return immediately
Queue/priority/retry tuning is not exposed by gbrain agent run; submit the
raw subagent handler via gbrain jobs submit (requires CLI trust) if you
need those knobs.
Phase 2: Monitor
list_jobs --status active # MCP — what's running?
get_job ID # MCP — full details + logs + tokens
get_job_progress ID # MCP — structured progress snapshot
gbrain jobs stats # CLI — queue health dashboard
gbrain agent logs ID --follow # CLI — streaming transcript + heartbeat
Progress includes: step count, total steps, message, token usage, last tool called.
Phase 3: Steer
Send a message to redirect a running agent:
send_job_message id=ID payload={"directive":"focus on revenue, skip headcount"}
The agent handler reads inbox messages on each iteration and injects them as context. Messages are acknowledged (read receipts tracked).
Only the parent job or admin can send messages (sender validation).
Phase 4: Lifecycle
pause_job id=ID # freeze without losing state
resume_job id=ID # pick up where it left off
cancel_job id=ID # hard stop
replay_job id=ID # re-run with same or modified params
replay_job id=ID data_overrides={"depth":"deep"} # replay with changes
All lifecycle ops are MCP-callable.
Phase 5: Review Results
get_job ID # result, token counts, transcript
Token accounting: every job tracks tokens_input, tokens_output, tokens_cache_read.
Child tokens roll up to parent automatically on completion.
Output Format
When reporting job status to the user:
Job #ID (name) — status
Progress: step/total — last action
Tokens: input_count in / output_count out (+ cache_read cached)
Runtime: Xs
Children: N pending, M completed
When reporting completion:
Job #ID completed in Xs
Tokens used: input / output / cache_read
Result: <summary>
When reporting batch status (parent with children):
Parent #ID — waiting-children
#A subagent(Acme) — active, 3/5 steps, 2.5k tokens
#B subagent(Beta) — completed, 1.8k tokens
#C subagent(Gamma) — paused
Total tokens so far: 4.3k
Anti-Patterns
- Don't spawn a Minion for a single search query (use search tool directly)
- Don't fire-and-forget without checking results
- Don't spawn > 5 concurrent agents without checking
gbrain jobs statsfirst - For subagent work, don't use
sessions_spawnwithruntime: "subagent"when Minions is available (usegbrain agent runinstead) - Don't poll
get_jobin a tight loop (useget_job_progressfor lightweight checks)
Tools Used
- Submit a background job —
submit_job(MCP, non-protected names only; shell jobs are CLI-only, subagent jobs viagbrain agent run) - Get job details —
get_job(MCP) - List jobs with filters —
list_jobs(MCP) - Cancel a job —
cancel_job(MCP) - Pause a job —
pause_job(MCP) - Resume a paused job —
resume_job(MCP) - Replay a completed/failed job —
replay_job(MCP) - Send sidechannel message —
send_job_message(MCP) - Get structured progress —
get_job_progress(MCP) - Queue stats —
gbrain jobs stats(CLI; no MCP equivalent)