* 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)