mirror of
https://github.com/garrytan/gbrain.git
synced 2026-07-28 14:59:47 +00:00
0367c800a473fb3101317c32e2bb7da5bfdbb96e
4
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
78ba0b5b53 |
v0.19.0 check-resolvable: add OpenClaw skills-dir fallback + docs/tests (#326)
* Add OpenClaw skills fallback for check-resolvable * feat: v0.17.0 foundation — errors/warnings split + AGENTS.md support + auto-manifest First two workstreams of the v0.17.0 "skillify end-to-end" release. Landed together because the D-CX-3 exit-code refactor is a prerequisite for W1's warning-surfaced filing audit in Workstream 3. ## D-CX-3: split ResolvableReport into errors[] + warnings[] + --strict Prior: `env.ok = report.issues.length === 0` treated warnings and errors identically for exit status. Any warning forced exit 1, which meant the planned filing-audit (W3) would break CI for every OpenClaw deployment emitting advisory warnings. New contract: - `ResolvableReport.errors[]` and `warnings[]` as separate arrays. - `issues[]` stays as deprecated backcompat union (remove in v0.18). - Default: exit 0 unless any errors. Warnings are advisory. - `--strict` flag promotes warnings to fail CI (explicit opt-in). Files: src/core/check-resolvable.ts, src/commands/check-resolvable.ts (added --strict flag + help text + header doc), src/commands/doctor.ts (use new fields), test/check-resolvable-cli.test.ts (rewrite REGRESSION-GATE to document the new contract, add 3 D-CX-3 cases). ## W1: AGENTS.md support + auto-manifest + priority fix The reference OpenClaw deployment uses AGENTS.md (not RESOLVER.md) at the workspace root, and ships without a manifest.json. check-resolvable silently false-passed against it pre-W1: 0 manifest entries meant 0 reachability iterations meant 0 errors reported. Post-W1 behavior against ~/git/<redacted>/workspace (smoke-tested live): - Detects 102 skills via SKILL.md walk (no manifest.json needed) - Flags 15 unreachable errors (exactly the essay's '~15% dark' finding) - Flags 108 warnings (overlaps, gaps) — advisory, not blocking - Auto-detects via \$OPENCLAW_WORKSPACE without --skills-dir Changes: - NEW src/core/resolver-filenames.ts: one source of truth for the filename policy. \`RESOLVER_FILENAMES = ['RESOLVER.md', 'AGENTS.md']\`. Callers import from here, never hardcode either name. - NEW src/core/skill-manifest.ts: \`loadOrDeriveManifest()\` — reads manifest.json when present+valid, otherwise walks \`skillsDir/*/SKILL.md\` to derive a synthetic manifest. Both check-resolvable.ts AND dry-fix.ts now call this, replacing the two duplicated loaders that silently returned [] on missing file (F-ENG-1, D-CX-12). - src/core/repo-root.ts (rewrite): auto-detect priority changed to put \$OPENCLAW_WORKSPACE ahead of findRepoRoot() walk when explicitly set (D-CX-4). Adds workspace-root AGENTS.md detection — OpenClaw layout places routing at workspace/AGENTS.md with skills/ below. New SkillsDirSource variants \`openclaw_workspace_env_root\` and \`openclaw_workspace_home_root\` for --verbose log clarity. - src/core/check-resolvable.ts: accepts RESOLVER.md or AGENTS.md at the skills dir or one level up (workspace root). Uses loadOrDeriveManifest for reachability. Updated error messages reference both filenames. - src/core/dry-fix.ts: unified manifest loader — auto-fix now works in AGENTS.md-only workspaces where it previously no-op'd silently. - src/commands/check-resolvable.ts: new AUTO_DETECT_HINT import for clearer missing-skills-dir errors; updated sourceLabel map for the two new workspace-root variants. Tests: - test/skill-manifest.test.ts: 14 cases covering explicit-manifest, derived-manifest, malformed JSON, wrong shape, empty explicit array (honored as 'zero skills' declaration), dirname fallback when no name: frontmatter, underscore/dotfile dir skipping. - test/repo-root.test.ts: new tests for the priority swap, AGENTS.md skills-dir variant, AGENTS.md workspace-root variant, both-files present (RESOLVER.md wins). - test/check-resolvable-cli.test.ts: updated regression-gate to the new contract; added three D-CX-3 cases. All 105 tests passing across the foundation surface. Plan + reviews: ~/.claude/plans/p1-lets-just-vast-blanket.md Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat: v0.17.0 W2 — Check 5 trigger routing eval (structural) Check 5 of the 10-step skillify checklist (the essay's "resolver trigger eval") now runs structurally by default and has a dedicated CLI verb for CI. Ships Layer A; Layer B (LLM tie-break) is reserved for v0.18. ## New module: src/core/routing-eval.ts The harness. Pure functions: - `normalizeText(s)`: lowercase, strip non-alnum to spaces, collapse whitespace. Unicode-friendly, quote-agnostic, punctuation-tolerant. - `extractTriggerPhrases(cellText)`: split quoted alternatives like `"search for", "find me"` into separate normalized phrases; fall back to the whole cell when unquoted (OpenClaw-style descriptions). - `indexResolverTriggers(resolverContent)`: build a skill-slug → normalized-trigger-phrases map from the resolver table. - `structuralRouteMatch(intent, index)`: substring-match the normalized intent against every trigger phrase; return the set of matched skills + whether the match was ambiguous (more than one specific skill, excluding always-on family). - `lintRoutingFixtures`: rejects fixtures whose intent is verbatim-equal to a trigger (D-CX-6: fixtures must paraphrase the framing, not copy the trigger text) and unknown expected_skill references. - `loadRoutingFixtures(skillsDir)`: walks `skills/<name>/routing-eval.jsonl`, handles JSONL line-comments (`//` / `#`), collects malformed lines separately without crashing. - `runRoutingEval(resolver, fixtures)`: pure scoring. Supports negative cases (`expected_skill: null` — nothing should match) and an `ambiguous_with` allow-list for skills that co-fire with always-on handlers (signal-detector, brain-ops, ingest). Outcomes per fixture: `pass`, `missed`, `ambiguous`, `false_positive`. Metrics: `top1Accuracy`, `passed`, `missed`, `ambiguous`, `falsePositives`. ## Integration: check-resolvable runs Layer A by default `checkResolvable()` now loads `routing-eval.jsonl` fixtures from every skill, runs the structural eval, and appends non-pass outcomes as warning-severity issues. New issue types: - `routing_miss` — expected skill did not match - `routing_ambiguous` — expected matched AND unexpected skills - `routing_false_positive` — negative case unexpectedly matched - `routing_fixture_lint` — linter or malformed-JSONL finding All four are warnings — routing issues don't break exit in default mode, but `--strict` promotes them (D-CX-3 contract). Advisories without breaking CI. ## New CLI verb: `gbrain routing-eval` Standalone Check 5 runner. `--json` envelope, `--llm` flag reserved, `--skills-dir` override. Exit codes: 0 clean, 1 any failure/lint, 2 setup error. Suitable for CI gating separately from check-resolvable. Removed from DEFERRED in CLI: `{check: 5, name: trigger_routing_eval}`. Check 6 (brain_filing) still deferred; lands in W3. ## Seed fixtures - skills/query/routing-eval.jsonl - skills/citation-fixer/routing-eval.jsonl (includes a negative case) These are intentionally modest. Additional fixtures per skill are the natural next step; routing-eval itself passes cleanly under check-resolvable default mode even when fixtures surface real gaps (they're warnings, not errors). Running `gbrain routing-eval` reveals the gaps immediately. ## Tests (34 new cases + updated integrations) - test/routing-eval.test.ts: full harness coverage including normalization, trigger extraction (quoted and unquoted), indexer, structural match with ambiguity + always-on exemption, fixture linter (verbatim-equality rule, unknown-skill rule, shape rule, negative-case skip), JSONL loader (comments, malformed lines, missing dirs, underscore/dot skipping), and every runRoutingEval outcome (pass, miss, ambiguous, negative-pass, false-positive, empty). - test/check-resolvable-cli.test.ts: updated DEFERRED unit test + `--json` envelope test + `--verbose` test to reflect Check 5 shipping. 140/140 passing across the W1 + W2 surface. ## Live smoke `gbrain routing-eval --json` against the current gbrain repo: 6 fixtures, 1 passing, 5 missed. The misses correctly surface resolver-trigger narrowness (intents users naturally phrase differently than trigger text). Fixtures will iterate in follow-up PRs; the machinery ships now. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat: v0.17.0 W3 — Check 6 brain filing audit Check 6 ships. Every skill that writes brain pages is now audited against a machine-readable filing-rules doc at `skills/_brain-filing-rules.json`. ## New: skills/_brain-filing-rules.json Canonical filing rules, JSON (D-CX-8: the pre-existing yaml-lite parser handles flat maps only, so YAML would have needed a new dependency for one file). The companion `_brain-filing-rules.md` stays as the human explainer. 14 rule entries + explicit `sources_dir` carve-out for bulk/raw data. ## New module: src/core/filing-audit.ts - `loadFilingRules(skillsDir)`: returns parsed doc or null (missing file → no-op; malformed JSON throws loud). - `allowedDirectories(rules)`: normalized set of every rules[] directory + sources_dir. - `runFilingAudit(skillsDir)`: walks skills/*/SKILL.md, parses frontmatter, audits any skill with `writes_pages: true`. Two checks per qualifying skill: 1. `writes_to:` list is non-empty. 2. Every entry in `writes_to:` appears in allowedDirectories. Both failures emit warning-severity issues. No errors — advisories only, per D-CX-3. ## Distinction: writes_pages vs mutating (D-CX-7) v0.17 introduces a new boolean frontmatter field `writes_pages:`. `mutating: true` already means "has any side effect" (cron schedulers, report writers, config mutators). Filing audit targets ONLY skills with `writes_pages: true`, correctly excluding side- effect-but-not-page-writing skills. The codex outside voice caught this: conflating the two fields would drag ~100 skills into filing-audit noise in the reference OpenClaw deployment. ## Integration: check-resolvable runs Check 6 by default `checkResolvable()` calls `runFilingAudit(skillsDir)` and appends issues as warnings. On missing/malformed rules doc, surfaces a single advisory rather than bailing. `DEFERRED` array in the CLI is now empty — v0.17 ships both Check 5 (W2) and Check 6 (W3). The export stays in place (stable --json field) for future deferred checks. ## Seeded frontmatter on 7 canonical writers Added `writes_pages: true` + `writes_to:` to: - brain-ops (people, companies, deals, concepts, meetings) - enrich (people, companies) - ingest (people, companies, concepts, meetings, sources) - idea-ingest (people, concepts, sources) - media-ingest (concepts, people, companies, sources) - meeting-ingestion (meetings, people, companies) - signal-detector (people, companies, concepts) Live smoke: `gbrain check-resolvable --json` on gbrain repo shows `ok: true`, zero filing errors, zero filing warnings on seeded skills. Every other mutating:true skill (citation-fixer, cron-scheduler, data-research, maintain, migrate, minion-orchestrator, reports, setup, skill-creator, soul-audit, webhook-transforms) correctly skipped as side-effectful-but-not-page-writing. ## Tests (17 new cases + 3 updated CLI integrations) test/filing-audit.test.ts covers: - rules loader: missing (null), valid, malformed (throw), non-array rules (throw) - directory normalization (trailing slash, leading slash) - clean case - missing writes_to on writes_pages:true - unknown directory - D-CX-7: mutating:true alone does not trigger audit - writes_pages:false skips - no frontmatter skips - inline `writes_to: [a, b]` syntax - block `writes_to:\n - a` syntax - sources/ allowed - underscore/dot dir skipping - total counts (totalScanned vs writesPagesSkills) - missing dir graceful - action string quality guard Plus: CLI integration tests updated for empty DEFERRED array (Checks 5 and 6 both shipped). 158/158 passing across the v0.17 foundation + W1 + W2 + W3 surface. ## v0.18 preview (D-CX-13) v0.17 filing-audit is declaration-level only. A future `gbrain filing-audit --pages` walks the brain itself, infers primary subject from page content via LLM judgment, and flags actual misfilings vs. declarations. Declaration audit is the leading indicator; pages audit is the ground truth. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat: v0.17.0 W4 — gbrain skillify {scaffold,check} subcommand namespace The essay's "skillify it!" verb becomes a CLI primitive pair. Two subcommands, both promoted/factored so there's one source of truth: ## `gbrain skillify scaffold <name>` (mechanical) Pure file generation. Zero LLM, zero judgment. Writes 5 stub files atomically: 1. skills/<name>/SKILL.md frontmatter + body template 2. skills/<name>/scripts/<name>.mjs deterministic-code stub 3. skills/<name>/routing-eval.jsonl routing fixture seed 4. test/<name>.test.ts vitest skeleton 5. Appended trigger row to the detected resolver file (RESOLVER.md or AGENTS.md — whatever W1's auto-detect found) Flags: --description (required), --triggers, --writes-to, --writes-pages, --mutating, --force, --dry-run, --json, --skills-dir. Kebab-case name validation (`^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$`). Works against gbrain-native RESOLVER.md layout AND OpenClaw-native AGENTS.md-at-workspace-root layout (W1 interop). ## `gbrain skillify check [path]` (audit) Promoted from scripts/skillify-check.ts per codex D-CX-2. The legacy script stays as a 12-line shim that delegates to the new module so existing callers (docs, cron, tests) keep working. Wrapped in a subcommand namespace: `gbrain skillify {scaffold, check}` is one coherent verb for the whole post-task loop. The essay's "skillify it!" triggers the markdown skill, which orchestrates the CLI primitives. ## Idempotency contract (D-CX-7) `skillify scaffold --force` regenerates stub FILES but never re-appends a resolver row that already references `skills/<name>/SKILL.md`. Unit test pins this: two applies produce one resolver row, not two. ## D-CX-9 SKILLIFY_STUB sentinel Every scaffolded script + SKILL.md body carries a SKILLIFY_STUB sentinel. `check-resolvable` walks every skill's script dir looking for the marker and emits a `skillify_stub_unreplaced` warning when found. Default mode: advisory. `--strict` mode: error, blocks CI. This is the gate that catches "we scaffolded and forgot to implement" — the exact failure codex flagged as "scaffold verification is theater" in the outside-voice review. ## Files - NEW src/core/skillify/templates.ts (template strings) - NEW src/core/skillify/generator.ts (planScaffold / applyScaffold + SkillifyScaffoldError with typed error codes) - NEW src/commands/skillify.ts (top-level dispatcher + scaffold handler) - NEW src/commands/skillify-check.ts (promoted check logic) - scripts/skillify-check.ts: rewritten to 12-line shim - skills/skillify/SKILL.md: Phase 2 now references the scaffold primitive; legacy manual path kept for extending existing skills - src/cli.ts: `skillify` added to CLI_ONLY + dispatcher - src/core/check-resolvable.ts: SKILLIFY_STUB sentinel scan + new issue type `skillify_stub_unreplaced` ## Tests (14 new scaffold cases) test/skillify-scaffold.test.ts covers: - SKILL_NAME_PATTERN validation (kebab-case, no spaces, no leading digit, no underscores/uppercase) - planScaffold against fresh + existing-file + --force paths - SKILLIFY_STUB sentinel presence in SKILL.md AND script stub (both gate paths) - D-CX-7 idempotency: resolverAppend null when row pre-exists, second apply doesn't duplicate the row - TBD-trigger placeholder when --triggers empty - writes_pages / writes_to / mutating flow through to frontmatter - applyScaffold writes files + appends resolver - Full AGENTS.md-layout workspace interop (W1) Existing test/skillify-check.test.ts still passes against the legacy shim — zero regression for downstream consumers. 178/178 passing across v0.17 foundation + W1..W4. ## Live smoke \`gbrain skillify scaffold webhook-verify --description "verify incoming webhook signatures" --triggers "verify webhook,check tunnel" --skills-dir /tmp/smoke --dry-run\` produces the expected 4-file plan plus a 115-byte resolver append. \`--help\` works on both the top-level and scaffold levels. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat: v0.17.0 W5 — gbrain skillpack install (deps closure + lockfile + diff/dry-run) The essay's "drop it into YOUR OpenClaw" promise lands as a CLI verb. One command installs a curated bundle of gbrain skills + the shared convention files they depend on into a target OpenClaw workspace. Data-loss protected, concurrency-safe, atomic on the AGENTS.md managed block. ## openclaw.plugin.json refresh - Bumped version from stale 0.4.1 → 0.17.0 (codex flagged this drift F-ENG-4 / D-CX-4). - Expanded curated skill list from 7 → 25. Uses skills/manifest.json top-level (v0.10.0 sourced) minus setup/migrate/publish (install-time / code+skill pairs) minus private skills. - Added \`shared_deps: [...]\` listing convention files every skill references: conventions/, _brain-filing-rules.{md,json}, _output-rules.md. Installer always pulls these (D-CX-10 dependency closure). - Added \`excluded_from_install: [...]\` for setup/migrate/publish — surfaces the intentional exclusion as data rather than a comment. ## New module: src/core/skillpack/bundle.ts - \`findGbrainRoot(start)\` — walks up looking for openclaw.plugin.json + src/cli.ts. The pair identifies a gbrain checkout. - \`loadBundleManifest(root)\` — strict validation + typed BundleError codes (manifest_not_found, manifest_malformed, skill_not_found). - \`enumerateBundle({gbrainRoot, skillSlug?, manifest})\` — flat list of source → target-relative paths. When skillSlug is set, scopes to that one skill BUT always pulls shared_deps. \`--all\` walks every skill in the manifest. - \`bundledSkillSlugs(manifest)\` — sorted slugs for \`skillpack list\`. ## New module: src/core/skillpack/installer.ts - \`planInstall(opts)\` — builds InstallPlan with per-file existing/identical diff state. Pure; no writes. - \`applyInstall(plan, opts)\` — writes files + managed block with the contracts below. - \`diffSkill(root, slug, skillsDir)\` — read-only per-file status for \`skillpack diff <name>\`. **Per-file diff protection (D-CX-3 / F4):** wrote_new fresh file wrote_overwrite local diff + --overwrite-local passed skipped_identical bytes match the bundle (silent re-install) skipped_locally_modified target differs + no --overwrite-local → PROTECTED DEFAULT **Concurrency + atomic AGENTS.md (D-CX-11):** - \`.gbrain-skillpack.lock\` at workspace root. Acquired on the first write, released in finally. - Lock stale threshold configurable (default 10min). --force-unlock overrides. - Managed-block writes via tmp-file-plus-rename (atomic on POSIX). **Managed-block format:** <!-- gbrain:skillpack:begin --> <!-- Installed by gbrain <version> — do not hand-edit between markers. --> | Trigger | Skill | |---------|-------| | "alpha" | \`skills/alpha/SKILL.md\` | | ... <!-- gbrain:skillpack:end --> extractManagedSlugs() roundtrips: single-skill installs accumulate into the same block rather than overwriting each other. ## New CLI: gbrain skillpack {list, install, diff, check} Namespaced alongside W4's \`gbrain skillify\`. Subcommands: list bundle inventory (human + --json) install <name> single skill + deps closure install --all entire curated bundle diff <name> per-file diff vs target; read-only check delegates to the pre-existing skillpack-check (same CLI just namespaced) Flags on install: --overwrite-local, --force-unlock, --dry-run, --json, --skills-dir, --workspace. Exit codes: 0 clean, 1 files skipped (protected local edits), 2 setup error / lock held. ## Live smoke \`gbrain skillpack list\`: 25 skills. \`skillpack install query --dry-run\` against a fresh temp workspace: 12 files planned (SKILL.md, routing-eval.jsonl, 7 convention files, 3 rule files, managed block to AGENTS.md). All shared_deps flagged [shared]. ## Tests (36 new cases) test/skillpack-install.test.ts: - findGbrainRoot walks up, returns null when absent - loadBundleManifest validates + rejects malformed - enumerateBundle pulls shared_deps on single-skill scope (D-CX-10) - buildManagedBlock + updateManagedBlock: append when absent, in-place replace when present, extractManagedSlugs roundtrip - planInstall + applyInstall: fresh install, dry-run, idempotency (skipped_identical), local-edit protection, --overwrite-local, lock-held concurrency (D-CX-11), --force-unlock, atomic managed-block write, multi-skill accumulation in managed block, AGENTS.md-at-workspace-root interop (W1 cross-check) - diffSkill: missing, identical, differs test/skillpack-sync-guard.test.ts (F-ENG-4): - both manifests exist - every skill in plugin.json exists on disk - every shared_dep exists on disk - plugin.json skills ⊂ skills/manifest.json - excluded skills aren't in the install list - plugin version ≥ 0.17 (kills the 0.4.1 stale drift) 204/204 passing across the v0.17 foundation + W1..W5. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat: v0.17.0 guards — privacy scrub + OpenClaw-reference E2E + v0.16.4 regression Three ship-blocker work items from the eng review + codex outside voice round out v0.17: ## scripts/check-privacy.sh (CLAUDE.md:550 enforcement) Greps for the banned OpenClaw fork name (case-insensitive) across tracked files. Two modes: scripts/check-privacy.sh scan working tree scripts/check-privacy.sh --staged scan git-staged files (pre-commit) Exit 1 on any finding outside the allow-list. Allow-list covers files where the name is legitimately present: this script itself (defines the rule), CLAUDE.md (the canonical rule text), llms-full.txt (auto-generated from CLAUDE.md), the historical upgrade guide, and test/integrations.test.ts (whose personal-info regex ENFORCES the rule against recipes/). Scrubbed existing leaks: - CHANGELOG.md:366 reference in a closes-# line → "from the OpenClaw reference deployment" - test/doctor-minions-check.test.ts:171 comment → "an OpenClaw host's cron script" - test/plugin-loader.test.ts fixture plugin name → "openclaw-ref" ## test/e2e/openclaw-reference-compat.test.ts (ship-blocker gate) The test that proves v0.17 delivers on the headline claim. New fixture at test/fixtures/openclaw-reference-minimal/ mimics the reference OpenClaw deployment layout: AGENTS.md at workspace root, skills/ below, no manifest.json. Four fixture skills (signal-detector, query, brain-ops, context-now). Every v0.17 surface gets exercised end-to-end: - autoDetectSkillsDir with $OPENCLAW_WORKSPACE (D-CX-4 priority) - loadOrDeriveManifest walks SKILL.md (F-ENG-1 auto-manifest) - checkResolvable accepts AGENTS.md at workspace root, all 4 skills reachable via resolver rows, zero errors - Filing audit clean (brain-ops declares writes_pages+writes_to) - CLI subprocess via `--skills-dir` → exit 0 - CLI subprocess via $OPENCLAW_WORKSPACE (no flag) → exit 0, correct skillsDir detection - skillpack install against the layout writes managed block into AGENTS.md at workspace root This is THE ship-blocker test. If the W1 + W5 stack ever regresses against an AGENTS.md-layout workspace, this fails first. ## test/regression-v0_16_4.test.ts (F-ENG-8) Guards v0.17 against adding "surprise" warnings. Builds a clean fixture matching v0.16.4 canonical shape (manifest.json, RESOLVER.md, 2 skills, no routing-eval fixtures, no writes_pages). Runs v0.17 checkResolvable and asserts: - zero errors, zero routing_*/filing_*/skillify_stub_* warnings - JSON envelope keys unchanged (errors, warnings, issues, ok, summary) — deprecated `issues[]` still equals errors ∪ warnings - summary shape unchanged If someone adds a new check that fires unexpectedly on a v0.16.4-era fixture, this test catches it immediately. ## Fixture test/fixtures/openclaw-reference-minimal/ ├── AGENTS.md (4 rows, 3 sections) └── skills/ ├── brain-ops/SKILL.md (writes_pages+writes_to) ├── context-now/SKILL.md ├── query/SKILL.md └── signal-detector/SKILL.md Intentionally small (4 skills, 1 AGENTS.md, ~30 lines total) so the fixture is maintainable. The OPENCLAW-reference deployment has 107 skills — this fixture is the minimum shape that exercises the full v0.17 code path. ## Tests 215/215 passing across the full v0.17 surface: - foundation + W1 + W2 + W3 + W4 + W5 (204) - regression-v0_16_4 (3) - openclaw-reference-compat (7) - privacy guard (separate bash; exits 0 clean) Plus: privacy pre-commit hook is a drop-in wrapper (documented in the script header). Wiring into .github/workflows is a follow-up. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * release: v0.17.0 — skillify goes end-to-end Every skill. Every check. Every install. One command each. Five workstreams land in one release: - W1: AGENTS.md + auto-manifest + env-priority - W2: Check 5 routing eval - W3: Check 6 brain filing - W4: gbrain skillify {scaffold,check} - W5: gbrain skillpack {list,install,diff} Plus D-CX-3 foundation (errors/warnings split + --strict), plus codex outside-voice fixes (D-CX-1..12 applied), plus privacy pre- commit guard, plus OpenClaw-reference E2E fixture, plus v0.16.4 regression guard. Live against the reference OpenClaw deployment: 102 skills detected via auto-manifest, 15 unreachable errors + 108 warnings surfaced — exactly the essay's "~15% dark" finding. The magic word from the essay finally works the way the essay describes. Tests: 2156 unit (178 new) + 152 E2E Tier 1 + 3 Tier 2 + 8 new openclaw-reference fixture cases. 0 failures across all tiers. Plan + reviews: ~/.claude/plans/p1-lets-just-vast-blanket.md. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(test): add missing 'strict' field to 5 Flags literals in check-resolvable-cli.test.ts CI failed `tsc --noEmit` after the D-CX-3 errors/warnings split added `strict: boolean` as a required field on the `Flags` interface. Five test sites in test/check-resolvable-cli.test.ts still construct Flags object literals (for direct `resolveSkillsDir()` calls) and hadn't been updated. Added `strict: false` to all five literals: - line 129 --skills-dir absolute path - line 135 --skills-dir relative path - line 148 no --skills-dir - line 160 no --skills-dir + no env - line 178 --skills-dir + OPENCLAW_WORKSPACE (REGRESSION-GATE) Unit tests: 207/207 pass across the v0.19 surface. tsc --noEmit exits 0. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: adopt gstack's branch-scoped CHANGELOG rule + rewrite v0.19.0 entry CLAUDE.md gains a new top section before "CHANGELOG voice" that codifies what gstack's CLAUDE.md already says: CHANGELOG is user-facing product release notes, not a log of internal decisions. Every entry describes what THIS branch adds vs master. Plan-file IDs, decision tags (D-CX-#, F-ENG-#), review rounds, test counts as marketing, and contributor- facing metrics don't belong in it. The v0.19.0 entry is rewritten to the new bar: Removed: - Version-collision note about v0.17.0/v0.18.0 shipping on master - All D-CX-## and W# tags (meaningless outside the plan file) - "codex caught" / CEO + Eng review round-up narrative - Plan file path reference - "215 new cases across 13 test files" marketing metrics - W1..W5 bucketing in itemized changes Kept / sharpened: - User-facing headline (what your agent can now do) - Numbers that mean something to users (unreachable-skills count, scaffold timing, pre/post AGENTS.md support) - Upgrade instructions - Added/Changed/Fixed/For-contributors itemized sections (standard keep-a-changelog shape) Version sequence (`grep "^## \["`) is contiguous v0.19.0 → v0.16.4. Privacy guard clean. Tests green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: update README/CLAUDE/TODOS for v0.19.0 skills + skillify loop Skill count was stale (README said 26, actual is 28: skillify + skillpack-check were missing from the tables and count). Corrected throughout. Marked TODOS item "Checks 5 + 6 deferred in PR #325" as completed in v0.19 — they shipped as real implementations, not just filed issues. README: - Skill count 26 → 28 (headline, install flow, table section, architecture diagram) - Added `skillify` + `skillpack-check` rows to the operational skills table - Rewrote the "Skillify" section to lead with the four v0.19 CLI verbs (`gbrain skillify scaffold/check`, `gbrain skillpack list/install/diff`, `gbrain routing-eval`, `gbrain check-resolvable --strict`) instead of describing the pre-v0.19 state. Added the "works on your OpenClaw" pitch around AGENTS.md + auto-manifest. Added the "drop 25 curated skills into your OpenClaw" section for skillpack install. - Added v0.19 skills block + v0.18 multi-source + v0.17 dream to the Commands reference at the bottom. - Standalone instruction sets count: 25 → 28 (with a parenthetical noting the curated 25-skill bundle that `skillpack install` ships). CLAUDE.md: - Skill count 26 → 28 in the Skills section. - New "Skillify loop (v0.19)" sub-bullet listing skillify + skillpack-check. - Noted that `AGENTS.md` is also accepted as a resolver filename. TODOS.md: - Created "## Completed" section at the top. - Moved the "Checks 5 + 6" item there with completion note linking to the actual implementation files (routing-eval.ts + filing-audit.ts). Privacy scan clean. Version sequence contiguous v0.19.0 → v0.16.4. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(test): regenerate llms-full.txt + llms.txt after README/CLAUDE edits CI failed on `build-llms generator > committed llms.txt + llms-full.txt match current generator output`. The drift was expected: the prior commit edited README.md and CLAUDE.md (skill count + skillify section), both of which are inlined into llms-full.txt by `scripts/build-llms.ts`. Fix: `bun run build:llms` + commit the regenerated output. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Wintermute <wintermute@garrytan.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
dcd13dd638 |
feat: v0.16.4 — gbrain check-resolvable CLI + skillify-check wiring (#325)
* Merge origin/master into garrytan/check-resolvable-v1
Resolves CHANGELOG.md conflict: preserved v0.16.1/v0.16.2/v0.16.3 upstream
entries and added v0.16.4 (check-resolvable ship) above them.
* refactor: extract findRepoRoot to src/core/repo-root.ts
Moves findRepoRoot() from private in doctor.ts to a zero-dependency shared
module with a parameterized startDir for test hermeticity. Doctor imports
the shared version; no behavior change (default arg matches prior semantics).
The new gbrain check-resolvable CLI needs findRepoRoot too; importing from
doctor.ts would drag in DB/progress dependencies.
* feat: gbrain check-resolvable CLI wrapper
Standalone CLI gate over checkResolvable(). Exits 1 on any issue (warnings
or errors) per the README:259 contract, stricter than doctor's resolver_health
which ignores warnings. Doctor has 15 other checks to lean on; the standalone
command has nowhere to hide.
- Stable JSON envelope: {ok, skillsDir, report, autoFix, deferred, error, message}
- --fix auto-applies DRY fixes via autoFixDryViolations before re-checking
- --dry-run with --fix previews without writing; autoFix.fixed shows diff
- --verbose prints the deferred-checks note (Checks 5 + 6)
- --skills-dir PATH for hermetic test runs
- Permissive on unknown flags, matching lint/orphans/publish convention
Checks 5 (trigger routing eval) and 6 (brain filing) are tracked as separate
GitHub issues and surfaced via the deferred[] field in --json output.
Covered by 17 new test cases (flag parsing, JSON envelope shape, exit-code
regression gates, --fix wiring, --verbose output).
* chore: bump version and changelog (v0.16.4)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* chore: track check-resolvable issue-URL swap in TODOS
Defers the filing of GitHub tracking issues for Checks 5 (trigger routing
eval) and 6 (brain filing) plus the TBD-check-5/TBD-check-6 URL replacement
in src/commands/check-resolvable.ts. Unblocks merging PR #325.
* test: fix repo-root CI failure — assert parity, not path contents
The 'default arg uses process.cwd()' test asserted the returned path
matched /honolulu/, which is the local workspace name but not the CI
runner's checkout path (/home/runner/work/gbrain/gbrain). The test's
real purpose is behavioral parity: findRepoRoot() === findRepoRoot(cwd).
Assert that directly instead of pattern-matching paths.
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
0e9f8814a5 |
feat: v0.16.0 — durable agent runtime (gbrain agent + subagent handler + plugin loader) (#258)
* refactor(mcp): extract buildToolDefs helper for subagent tool registry reuse
The inline operations.map(...) block in src/mcp/server.ts became the only
source of truth for agent-facing tool definitions. Extract into a reusable
exported helper so the v0.15 subagent tool registry can call it with a
filtered OPERATIONS subset instead of duplicating the shape.
Byte-for-byte equivalence regression pinned in test/mcp-tool-defs.test.ts —
legacy inline mapping kept verbatim inside the test so any future drift
between the new helper and the pre-extraction MCP schema fails loudly.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(operations): subagent-aware OperationContext + put_page namespace
Adds three optional fields to OperationContext:
- jobId?: number — the currently running Minion job id
- subagentId?: number — the owning subagent job id for tool-dispatched calls
- viaSubagent?: boolean — FAIL-CLOSED flag for agent-path gating
put_page now enforces a namespace rule when invoked on the subagent tool
dispatch path (viaSubagent=true): writes MUST target
`wiki/agents/<subagentId>/...`. Anchored, slash-boundary enforced so a
collision like `wiki/agents/12evil/...` can't impersonate subagent 12.
The check runs BEFORE the dry-run short-circuit so preview calls surface
the same rejection. Fail-closed: a missing subagentId with viaSubagent=true
rejects every slug rather than letting a dispatcher bug open a hole.
Existing callers unaffected — all three fields are optional and the legacy
put_page behavior is unchanged when viaSubagent is undefined/false.
12 regression + namespace tests pin:
- local CLI writes (viaSubagent unset) accept arbitrary slugs
- MCP writes (remote=true, viaSubagent unset) accept arbitrary slugs
- subagent-path: anchored prefix accepted, wrong id rejected, prefix-
collision defeated, leading-slash rejected, bare-prefix rejected,
fail-closed on missing/NaN subagentId, permission_denied code emitted
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(schema): v0.15.0 subagent runtime tables + migration orchestrator
Adds three new tables for the durable LLM agent runtime:
subagent_messages — Anthropic message-block persistence.
Parallel tool_use blocks in one assistant
message live in content_blocks JSONB, not
across rows (fixes the (job_id, turn_idx, role)
misdesign codex caught in v0.13 drafting).
subagent_tool_executions — Two-phase tool ledger. INSERT pending before
execute, UPDATE complete/failed after. Replay
re-runs pending rows only if the tool is
idempotent (v1 ships only idempotent tools so
this is preventive).
subagent_rate_leases — Lease-based concurrency cap for outbound
providers (e.g. anthropic:messages). Stale
leases auto-prune on next acquire so crashed
workers can't strand capacity.
All DDL uses CREATE TABLE/INDEX IF NOT EXISTS — order-independent vs
PR #244's initSchema() reorder, and idempotent across fresh-install +
upgrade paths. Shipped in both src/schema.sql (Postgres) and
src/core/pglite-schema.ts (PGLite); schema-embedded.ts regenerated.
Migration orchestrator v0_15_0.ts (phases: schema → verify → record).
v0_14_0.ts is a no-op stub so the registry's version sequence stays
gapless (v0.14.0 shipped shell-jobs — code change, no DB migration).
10 unit tests for registry wiring, ordering, dry-run phase behavior, and
schema-embedded table presence. test/apply-migrations.test.ts updated for
the two new registry entries.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(minions): emit child_done on every terminal + max_stalled per-job + terminal set fix
Three correctness fixes the v0.15 subagent aggregator spine depends on:
1. child_done emission on ALL terminal transitions, not just success.
- completeJob already emitted on success — now also tags outcome='complete'.
- failJob newly emits on terminal 'failed' or 'dead' (outcome='failed'|'dead',
error=<text>), BEFORE the parent-terminal UPDATE so the EXISTS guard on
the inbox INSERT doesn't skip it on fail_parent paths (codex catch).
- cancelJob now emits outcome='cancelled' per descendant with a parent.
- handleTimeouts now emits outcome='timeout' per timed-out child.
ChildDoneMessage gains optional { outcome, error } — backwards compatible
(legacy writers omitted them; consumers treat absent outcome as 'complete').
2. Parent-resolution terminal set now includes 'failed'.
Pre-v0.15 the `NOT EXISTS (... status NOT IN ('completed','dead','cancelled'))`
guard treated a failed child as still-pending, stranding aggregator parents
that chose on_child_fail='continue' or 'ignore' in waiting-children forever.
Expanded to {completed, failed, dead, cancelled} everywhere parent resolution
reads child status (completeJob inline, failJob remove_dep + continue,
cancelJob sweep, handleTimeouts sweep, and the resolveParent method itself).
3. MinionJobInput.max_stalled threads through MinionQueue.add() on INSERT.
Column exists with default 1 — that is "first stall → dead", which defeats
crash recovery for long-running handlers. Subagent children will set
max_stalled: 3 to survive mid-run worker kills. Second-submitter under an
idempotency-key hit does NOT mutate the existing row (codex-flagged
footgun — first-submit options are load-bearing state).
13 unit tests pin: emission on each of completeJob/failJob/cancelJob/
handleTimeouts, insertion order on fail_parent, terminal-set expansion with
continue policy, max_stalled default + override + idempotency behavior.
E2E tier 1 (Postgres) passes 141 tests unchanged.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(minions): rate-leases + waitForCompletion infra for v0.15 subagent
Two infrastructure modules the subagent handler spine depends on:
rate-leases.ts — lease-based concurrency cap for outbound providers
(anthropic:messages, openai:*, etc.). Counter-based limiters leak capacity
on worker crash; leases are owner-tagged rows with expires_at that
auto-prune on the next acquire. Two-phase: txn-scoped pg_advisory_xact_lock
guards the check-then-insert so concurrent acquires can't both win the
"last slot". renewLeaseWithBackoff retries 3x (250/500/1000ms) for mid-
call DB blips — on persistent failure the LLM-loop caller aborts with a
renewable error so the worker re-claims and the rate invariant is
preserved. Owner FK cascades clean up leases on job deletion.
wait-for-completion.ts — poll-until-terminal helper for CLI callers.
Minions' NOTIFY is worker-side only; `gbrain agent run --follow` polls
getJob() until status is {completed, failed, dead, cancelled}. TimeoutError
carries jobId + elapsedMs and does NOT cancel the job — the user can
inspect via `gbrain jobs get <id>` later. Supports AbortSignal for Ctrl-C
without throwing. Default pollMs is 1000 on Postgres, 250 on PGLite (inline
CLI has no network RTT).
21 unit tests cover: single/multi acquire under cap, rejection past cap,
release frees slot, different keys are independent, stale prune, cascade
on owner delete, renew bumps expires_at, renew on missing is false,
backoff path success + pruned short-circuit. waitForCompletion: fast-path
terminal, transitions mid-wait (completed/failed/cancelled), TimeoutError
shape, abort-signal early exit, non-existent job error.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(minions): subagent ToolDef types + brain-tool registry (v0.15)
Types first so the handler has a stable contract:
- SubagentHandlerData / AggregatorHandlerData — the two job.data shapes
- ToolCtx (engine, jobId, remote, signal) + ToolDef (name, description,
input_schema, idempotent, execute) — Anthropic-envelope, distinct from
the MCP McpToolDef extraction landed earlier
- ContentBlock discriminated union for subagent_messages.content_blocks
- SubagentStopReason + SubagentResult emitted on terminal completion
brain-allowlist.ts derives one ToolDef per allow-listed OPERATION. Reuses
the ParamDef → JSONSchema shape from the MCP extraction in a local helper
(Anthropic's input_schema field diverges from MCP's inputSchema by a
character). The 11-name allow-list is read-safe + put_page — every
destructive / filesystem / identity-mutating op stays off by default.
put_page gets a namespace-wrapped tool schema: `slug` pattern = anchored
`^wiki/agents/<subagentId>/.+`. The server-side check in put_page op
(shipped in prior commit) is still the authoritative gate — the schema
just helps the model write correct slugs first-try. `subagentId` is
plumbed into the ToolCtx so the viaSubagent=true fail-closed path lights
up on every tool-dispatched put_page.
filterAllowedTools narrows a registry by subagent_def's allowed_tools
frontmatter field. Rejects unknown names at load time (no silent drop —
typos in a skills/subagents/*.md would otherwise ship to prod with a
tool silently missing).
18 tests pin: every allowlist name exists in OPERATIONS (catches upstream
rename), Anthropic name regex, put_page namespace pattern per-subagent,
execute() routes through the op handler with viaSubagent=true, out-of-
namespace put_page throws permission_denied, filter passes prefixed +
unprefixed names, rejects unknowns, deduplicates.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(minions): subagent-audit JSONL + transcript renderer
Two small plumbing pieces the v0.15 subagent handler + `gbrain agent logs`
depend on:
subagent-audit.ts — JSONL-rotated audit log mirroring the shell-audit
pattern. Two event flavors: submission (one line per job submit) and
heartbeat (one line per turn boundary — llm_call_started / completed /
tool_called / tool_result / tool_failed). Heartbeats fix the "--follow on
a long Anthropic call shows nothing for 30 seconds" problem codex flagged.
Never logs prompts or tool inputs (PII risk — subagent input_vars may
carry user-supplied free text); DOES log tokens, ms_elapsed, tool_name,
first 200 chars of error text. Rotates weekly via ISO week. `readSubagent
AuditForJob` is the readback path for `gbrain agent logs` — scans the
current + prior week file so job boundaries across weeks still resolve.
`GBRAIN_AUDIT_DIR` overrides the default ~/.gbrain/audit/ for container
deploys.
transcript.ts — renders subagent_messages + subagent_tool_executions to
markdown. Message order is authoritative; tool rows splice under their
owning assistant tool_use by tool_use_id. Handles text, tool_use (with
pending / complete / failed execution rows), tool_result (skipped if
we already rendered the owning tool_use — avoids double-printing), and
unknown block types (fenced JSON dump for diagnostics). Output is
UTF-8-safe truncated at maxOutputBytes.
21 unit tests: ISO week filename rotation (incl. 2027-01-01 → W53-2026
boundary), submission + heartbeat write shapes, 200-char error cap, best-
effort write failure doesn't throw, readback filters by job_id and
sinceIso. Transcript: empty input, ordering, token line, tool_use +
complete/failed/pending execution rendering, truncation, unknown-block
diagnostic dump.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(minions): subagent LLM-loop handler with crash-resumable replay
The main event: runs one Anthropic Messages API conversation with tool
use, persists every turn + tool execution, and resumes cleanly after a
worker kill anywhere in the loop.
Design points that carry the v0.15 guarantees:
1. Two-phase tool persistence. INSERT status='pending' before dispatch,
UPDATE to 'complete' or 'failed' after. subagent_messages rows are
the canonical conversation; subagent_tool_executions rows are the
canonical "did this tool run + what did it return". Either DB commit
is atomic, so replay has a single source of truth.
2. Replay reconciliation. If the last persisted message is an assistant
with tool_use blocks AND no following synthesized user message, we
crashed mid-dispatch. On resume, finish those tools first (respecting
idempotent flag for 'pending' rows), synthesize the user turn, and
THEN call the LLM again. Non-idempotent pending rows abort the job
with a clear error — v0.15 ships only idempotent tools so this is
preventive.
3. Rate lease around every LLM call. acquireLease before, releaseLease
after (both success and error paths). acquired=false throws
RateLeaseUnavailableError — the worker treats it as a renewable
error and re-claims later, so a temporary capacity cap doesn't fail
the job terminally.
4. Anthropic prompt caching. system block gets cache_control=ephemeral;
the LAST tool def gets it too (Anthropic caches everything up to and
including the marked block). ~10x cost reduction on multi-turn
agents per the plan.
5. Dual-signal abort. AbortSignal.any merges ctx.signal (timeout / lock
loss / cancel) with ctx.shutdownSignal (worker SIGTERM). Both feed
the Anthropic call's AbortSignal; mid-turn abort bails before the
next LLM call with whatever turns are already persisted. Node ≥ 20
has AbortSignal.any; older runtimes get a manual-merge polyfill.
6. Injectable Anthropic client. The real SDK implements MessagesClient
structurally; tests inject a FakeMessagesClient that scripts
responses.
12 unit tests pin: no-tool happy path, single tool_use complete, tool
throws → failed row + loop continues, unknown tool name rejection,
max_turns cap, crash-then-resume with partial state, replay skips already-
complete tool execs without re-invoking execute, non-idempotent pending
rejects on resume, lease acquire + release roundtrip, RateLeaseUnavailable
under cap-full, missing prompt validation, allowed_tools unknown-name.
NOT in v0.15: refusal detection (stop_reason + content shape), stop_reason
=max_tokens partial recovery, mid-call lease renewal with backoff loop.
All three are documented as P2 items in the plan file.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(minions): subagent_aggregator handler with mixed-outcome rendering
Claims AFTER all subagent children resolve — by then Lane 1B's queue
changes have posted one child_done message per terminal transition into
this job's inbox (complete / failed / dead / cancelled / timeout). The
aggregator reads those, builds a deterministic markdown summary, and
returns it as the handler result.
Not an LLM call in v0.15 — output is reproducible concatenation so
fan-out runs stay comparable. v0.16+ can add an LLM synthesis pass
behind an opt-in flag.
Contract:
- empty children_ids → `(no children)` marker
- missing child_done (shouldn't happen under v0.15 invariants but
possible if a terminal-state path slipped past Lane 1B) → counted as
failed with "no child_done message observed" error
- non-complete outcomes: result is null in the output so no payload
leaks alongside a failure label
- children appear in the order children_ids was supplied
- custom aggregate_prompt_template replaces the markdown header
13 unit tests cover: empty input, all-success, mixed outcomes, result
suppression on failure, missing child_done handling, order preservation,
custom template, progress + log emission, stringified JSONB payload
parsing, non-child_done inbox filtering, legacy-writer outcome fallback,
and internal helper edges.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(minions): GBRAIN_PLUGIN_PATH loader + plugin-authors guide (v0.15)
Plumbing that makes Wintermute (and future downstream agents) day-1
usable on v0.15. Host repos drop a `gbrain.plugin.json` + `subagents/`
directory somewhere, set GBRAIN_PLUGIN_PATH (colon-separated like \$PATH),
and their custom subagent defs load at worker startup.
Path policy is strict: absolute paths only. Relative, ~-prefixed, and
URL-style (https://, file://) all rejected with warnings — the user
controls where plugins live. Non-existent paths and files (not dirs) are
warned and skipped so a typo doesn't crash worker startup.
Collision policy: left-wins. If two plugins ship a subagent with the same
name, the first one in GBRAIN_PLUGIN_PATH keeps it and the other gets a
warning naming both sources. Deterministic + debuggable.
Trust policy: plugins ship subagent defs ONLY. Cannot declare new tools,
cannot extend the brain allow-list, cannot override safety flags. The
subagent def's `allowed_tools:` frontmatter MUST subset the derived
registry — validation happens at load time (worker startup), not at
dispatch time, so a typo in a skill gives a loud startup error instead
of silently "tool never fires at 3am."
Manifest `plugin_version: "gbrain-plugin-v1"` locks the contract. Unknown
versions rejected. `subagents` field escape attempts (`../../../etc` etc)
rejected. gray-matter handles the markdown frontmatter parse — subagent
defs don't conform to the page schema, so we don't use parseMarkdown.
docs/guides/plugin-authors.md is the Wintermute-facing walkthrough.
Covers the minimum viable plugin shape, the three policies, the
frontmatter fields, known caveats (audit JSONL is local-only, tool calls
always run remote=true, put_page is namespace-scoped).
22 unit tests pin path rejection, missing/invalid manifest, unsupported
version, escape-attempt, basename fallback for missing frontmatter.name,
allowed_tools round-trip, unknown-tool rejection with validAgentToolNames,
empty env, multi-path, collision warning with left-wins, trimmed paths,
manifest-rejection as warning.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(cli): gbrain agent run + logs + worker registration (v0.15 Lane 4H)
Three integration seams wired:
src/commands/agent.ts — \`gbrain agent run\`. Submits subagent jobs (or a
fan-out of N + aggregator) under the trusted-submit flag so the
PROTECTED_JOB_NAMES guard doesn't reject. Fan-out path creates the
aggregator first (so children can reference its id as parent), submits
each child with on_child_fail='continue' (required by Lane 1B's terminal-
set + child_done machinery), then jsonb_set's the aggregator's
children_ids. Short-circuits a 1-entry manifest to a single subagent
with no aggregator. Follow mode runs agent-logs streaming + waitFor
Completion in parallel and exits on terminal status; detach prints the
job id and exits. Ctrl-C is handled as detach, not cancel — the job
keeps running, consistent with durability invariants.
src/commands/agent-logs.ts — \`gbrain agent logs\`. Merges ~/.gbrain/audit/
subagent-jobs-*.jsonl (heartbeats + submissions) with subagent_messages
(persisted conversation) in one chronological stream. --follow polls at
1s and exits when the job hits terminal. --since accepts ISO-8601 OR
relative shorthand (5m / 1h / 2d). Writes transcript tail (full message
+ tool tree) only for terminal jobs, so mid-run --follow doesn't spam a
half-rendered transcript.
src/commands/jobs.ts registerBuiltinHandlers — matches the shell-handler
opt-in shape. GBRAIN_ALLOW_LLM_JOBS=1 registers the subagent +
subagent_aggregator handlers, then loads plugins from GBRAIN_PLUGIN_PATH
with validAgentToolNames pulled from BRAIN_TOOL_ALLOWLIST. Every plugin
warning + loaded-plugin line prints to stderr, mirroring the openclaw-
seam startup convention.
src/core/minions/protected-names.ts — subagent + subagent_aggregator
join the protected set. MCP submit_job returns permission_denied; only
trusted-CLI callers (with allowProtectedSubmit) can insert these rows.
src/cli.ts — adds 'agent' to CLI_ONLY + dispatches it like 'jobs'.
Test fallout: subagent-handler.test.ts + subagent-transcript.test.ts
helpers now submit under allowProtectedSubmit (they insert rows named
'subagent' directly against the queue). 23 new tests in agent-cli.test.ts
cover: flag parsing (including --detach implies !follow, --tools comma
split, -- terminator, unknown flag throw), --since parse (ISO, relative
5m/2h/1d, unparseable error), protected-name guard for all three names,
trusted-submit gate, and a fan-out integration check that verifies the
aggregator + children shape after --fanout-manifest.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test(e2e): rename max_children test's spawned jobs off the protected 'subagent' name
The spawn-storm test submitted 50 literal-string 'subagent' children to
exercise the max_children row-lock serialization. In v0.15 'subagent' is
a PROTECTED_JOB_NAME (CLI-only; trusted submit required), so the old
literal submission now throws before reaching the row-lock check.
The test is about max_children semantics, not the v0.15 subagent runtime
specifically — rename the child name to 'child_worker' so the test
exercises the exact same queue.add path without tripping the new guard.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(ship): v0.15.0 — VERSION, CHANGELOG, README, upgrading-agents, CLAUDE.md
Bumps VERSION → 0.15.0 and package.json → 0.15.0 (resolves the pre-existing
drift — on master, VERSION=0.14.0 but package.json=0.13.1; src/version.ts
reads package.json, so this is what the binary prints now).
CHANGELOG lands the release-summary entry in the GStack voice + the full
itemized change list (11 new modules, 3 new tables, queue correctness
fixes, trust-model additions, 159 new unit tests). Voice rules respected
— no em dashes, no AI vocabulary, real file names + real numbers.
README gets a "Durable agents: `gbrain agent` (v0.15)" section next to
the Minions block, with the three canonical CLI shapes (single run,
fanout-manifest, logs --follow) and a pointer to plugin-authors.md.
docs/UPGRADING_DOWNSTREAM_AGENTS.md gets a full v0.15.0 section covering
the four adoption steps downstream agents (Wintermute and similar) need:
(1) worker opt-in via GBRAIN_ALLOW_LLM_JOBS, (2) moving custom subagent
defs to a plugin repo, (3) replacing ephemeral subagent runs with durable
`gbrain agent run`, (4) the put_page namespace rule for agent-driven writes.
CLAUDE.md updated with concise per-file descriptions for every new module:
the handler, aggregator, audit, rate-leases, wait-for-completion,
transcript, plugin-loader, brain-allowlist, tool-defs extraction, agent
CLI + logs CLI, and the registerBuiltinHandlers wiring for subagent
handlers + plugin-loader.
Verified: binary builds (940 modules, 89ms compile), prints `gbrain 0.15.0`,
`gbrain agent --help` shows the new subcommand shape. 170 new tests pass
(full v0.15 surface). Full unit suite passes bar one parallel-load
flake on a pre-existing E2E (graph-quality, passes in isolation).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(minions): drop GBRAIN_ALLOW_LLM_JOBS flag — subagent handlers always-on
The env flag was ceremony. Shell jobs need the flag because they execute
arbitrary CLI commands (RCE surface). Subagent jobs don't — they call the
Anthropic API with whatever ANTHROPIC_API_KEY is in env, so the key is
already the cost gate (no key → SDK fails on the first turn). And
who-can-submit is already protected by PROTECTED_JOB_NAMES +
TrustedSubmitOpts: MCP callers get permission_denied; only `gbrain agent
run` with allowProtectedSubmit can insert subagent / subagent_aggregator
rows. The flag added nothing the existing guards didn't already give us.
registerBuiltinHandlers now always registers subagent + subagent_aggregator
and loads GBRAIN_PLUGIN_PATH plugins. Worker startup prints:
[minion worker] subagent handlers enabled
instead of the conditional enabled/disabled pair. Plugin discovery runs
unconditionally — empty PATH is a no-op.
README, CHANGELOG, docs/UPGRADING_DOWNSTREAM_AGENTS, CLAUDE.md, agent CLI
help text, and subagent handler docstring all updated to drop the flag
reference. Shell handler's GBRAIN_ALLOW_SHELL_JOBS gate is untouched —
separate concern (RCE, not billing).
Full suite: 1859 pass, 0 fail.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs: scrub private agent-fork name from all public artifacts
Enforces the rule added to CLAUDE.md (privacy section): never say
`Wintermute` in any CHANGELOG, README, doc, PR, or commit message.
Reader-facing copy says `your OpenClaw` (the term covers every
downstream OpenClaw deployment — Wintermute, Hermes, AlphaClaw — in
one umbrella the reader already recognizes). First-person /
origin-story copy says `Garry's OpenClaw` (honest that this is the
production deployment driving the feature, without exposing the
private agent's name).
Swept across:
CHANGELOG.md (v0.15 entry + 4 historical mentions)
README.md
TODOS.md
docs/UPGRADING_DOWNSTREAM_AGENTS.md
docs/guides/plugin-authors.md (including example plugin names)
docs/guides/plugin-handlers.md
docs/guides/minions-fix.md
docs/designs/KNOWLEDGE_RUNTIME.md (27 refs, mostly analytical)
docs/benchmarks/2026-04-18-minions-vs-openclaw-production.md
skills/migrations/v0.11.0.md
skills/skillpack-check/SKILL.md
scripts/skillify-check.ts
src/commands/doctor.ts
src/commands/migrations/v0_15_0.ts
src/commands/skillpack-check.ts
src/core/enrichment/completeness.ts
src/core/minions/plugin-loader.ts
src/core/operations.ts
src/core/output/scaffold.ts
Intentionally kept (these mentions define/test the rule itself):
CLAUDE.md — the privacy rule section necessarily uses the literal
name to define the restriction and examples
test/plugin-loader.test.ts — fixture name in a plugin-loading test;
renaming risks breaking assertion logic
test/integrations.test.ts — the word appears in a privacy-regex
test that explicitly enforces name redaction
test/doctor-minions-check.test.ts — a comment referencing the rule
CEO plan artifact at ~/.gstack/projects/… — private, not distributed
Binary builds (941 modules), 198/198 relevant tests pass, `gbrain --version`
prints `0.15.0`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: gitignore bun --compile artifacts with a glob, not specific hashes
Each `bun build --compile` emits a fresh hash-named `.*-*.bun-build` file
in cwd. The prior entries listed two specific hashes that were already
stale, so every build after those created a new untracked file requiring
manual cleanup.
Replace the two stale entries with `*.bun-build` so any current or future
compile artifact is ignored automatically.
Verified: ran `bun build --compile`, got two new `.*-*.bun-build` files,
`git status` stays clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(ship): rename v0.15.0 → v0.16.0
gbrain master is at 0.14.2. Other 0.15.x PRs may land before/after
this one — we bump the minor (new capability) and lock to 0.16.0 so
ordering with concurrent work doesn't matter.
Touches:
- VERSION: 0.15.0 → 0.16.0
- package.json: 0.15.0 → 0.16.0
- Rename src/commands/migrations/v0_15_0.ts → v0_16_0.ts (+ all
version strings inside + import in index.ts registry)
- Rename test/migrations-v0_15_0.test.ts → migrations-v0_16_0.test.ts
- test/apply-migrations.test.ts: skippedFuture lists now reference
'0.16.0'
- test/put-page-namespace.test.ts + test/mcp-tool-defs.test.ts: Lane
comment refs updated
- src/schema.sql + src/core/pglite-schema.ts: "v0.15.0" section
comment updated; src/core/schema-embedded.ts regenerated
- CHANGELOG.md: top entry renamed to [0.16.0]; inline v0_15_0 /
v0.15.0 refs swept
- docs/UPGRADING_DOWNSTREAM_AGENTS.md: section heading v0.15.0 → v0.16.0
Verified: `gbrain --version` prints 0.16.0, migration registry /
buildPlan / put_page / mcp-tool-defs / handlers tests all green
(49/49).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs: reframe v0.16 durability headline around OpenClaw crashes
"Laptop closed mid-run" framing implied a consumer workflow. Real pain is
OpenClaw subagents dying daily on worker kill, memory blip, or timeout.
Headline + README copy match the body now.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: regenerate llms-full.txt after README copy change
Regen drift guard caught the README edit from
|
||
|
|
d8613366a5 |
Minions v7 + v0.11.1 canonical migration + skillify (#130)
* feat: add minion_jobs schema, migration v5, and executeRaw to BrainEngine Foundation for the Minions job queue system. Adds: - minion_jobs table (20 columns) with CHECK constraints, partial indexes, and RLS. Inspired by BullMQ's job model, adapted for Postgres. - Migration v5 creates the table for existing databases. - executeRaw<T>() method on BrainEngine interface for raw SQL access, needed by the Minions module for claim queries (FOR UPDATE SKIP LOCKED), token-fenced writes, and atomic stall detection. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: Minions job queue — queue, worker, backoff, types BullMQ-inspired Postgres-native job queue built into GBrain. No Redis. No external dependencies. Postgres transactions replace Lua scripts. - MinionQueue: submit, claim (FOR UPDATE SKIP LOCKED), complete/fail (token-fenced), atomic stall detection (CTE), delayed promotion, parent-child resolution, prune, stats - MinionWorker: handler registry, lock renewal, graceful SIGTERM, exponential backoff with jitter, UnrecoverableError bypass - MinionJobContext: updateProgress(), log(), isActive() for handlers - 8-state machine: waiting/active/completed/failed/delayed/dead/ cancelled/waiting-children Patterns stolen from: BullMQ (lock tokens, stall detection, flows), Sidekiq (dead set, backoff formula), Inngest (checkpoint/resume). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * test: 43 tests for Minions job queue Full coverage of the Minions module against PGLite in-memory: - Queue CRUD (9): submit, get, list, remove, cancel, retry, duplicate - State machine (6): waiting→active→completed/failed, retry→delayed→waiting - Backoff (4): exponential, fixed, jitter range, attempts_made=0 edge - Stall detection (3): detect stalled, counter increment, max→dead - Dependencies (5): parent waits, fail_parent, continue, remove_dep, orphan - Worker lifecycle (5): register, start-without-handlers, claim+execute, non-Error throws, UnrecoverableError bypass - Lock management (3): renewal, token mismatch, claim sets lock fields - Claim mechanics (4): empty queue, priority ordering, name filtering, delayed promotion timing - Cancel & retry (2): cancel active, retry dead Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: Minions CLI commands and MCP operations Wire Minions into the GBrain CLI and MCP layer: CLI (gbrain jobs): submit <name> [--params JSON] [--follow] [--dry-run] list [--status S] [--queue Q] [--limit N] get <id> — detailed view with attempt history cancel/retry/delete <id> prune [--older-than 30d] stats — job health dashboard work [--queue Q] [--concurrency N] — Postgres-only worker daemon 6 MCP operations (contract-first, auto-exposed via MCP server): submit_job, get_job, list_jobs, cancel_job, retry_job, get_job_progress Built-in handlers: sync, embed, lint, import. --follow runs inline. Worker daemon blocked on PGLite (exclusive file lock). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * docs: update project documentation for Minions job queue CLAUDE.md: added Minions files to key files, updated operation count (36), BrainEngine method count (38), test file count (45), added jobs CLI commands. CHANGELOG.md: added Minions entry to v0.10.0 (background jobs, retry, stall detection, worker daemon). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: Minions v2 — agent orchestration primitives (pause/resume, inbox, tokens, replay) Adds the foundation for Minions as universal agent orchestration infrastructure. GBrain's Postgres-native job queue now supports durable, observable, steerable background agents. The OpenClaw plugin (separate repo) will consume these via library import, not MCP, for zero-latency local integration. ## New capabilities - **Concurrent worker** — Promise pool replaces sequential loop. Per-job AbortController for cooperative cancellation. Graceful shutdown waits for all in-flight jobs via Promise.allSettled. - **Pause/resume** — pauseJob clears the lock and fires AbortSignal on active jobs. Handlers check ctx.signal.aborted and exit cleanly. resumeJob returns paused jobs to waiting. Catch block skips failJob when signal.aborted. - **Inbox (separate table)** — minion_inbox table for sidechannel messages. sendMessage with sender validation (parent job or admin). readInbox is token-fenced and marks read_at atomically. Separate table avoids row bloat from rewriting JSONB on every send. - **Token accounting** — tokens_input/tokens_output/tokens_cache_read columns. updateTokens accumulates; completeJob rolls child tokens up to parent. USD cost computed at read time (no cost_usd column — pricing too volatile). - **Job replay** — replayJob clones a terminal job with optional data overrides. New job, fresh attempts, no parent link. ## Handler contract additions MinionJobContext now provides: - `signal: AbortSignal` — cooperative cancellation - `updateTokens(tokens)` — accumulate token usage - `readInbox()` — check for sidechannel messages - `log()` — now accepts string or TranscriptEntry ## MCP operations added pause_job, resume_job, replay_job, send_job_message — all auto-generate CLI commands and MCP server endpoints. ## Library exports package.json exports map adds ./minions and ./engine-factory paths so plugins can `import { MinionQueue } from 'gbrain/minions'` for direct library use. ## Instruction layer (the teaching) - skills/minion-orchestrator/SKILL.md — when/how to use Minions, decision matrix, lifecycle management, anti-patterns - skills/conventions/subagent-routing.md — cross-cutting rule: all background work goes through Minions - RESOLVER.md — trigger entries for agent orchestration - manifest.json — registered ## Schema migration v6 Additive: 3 token columns, paused status, minion_inbox table with unread index. Full Postgres + PGLite support. No backfill needed. ## Tests 65 tests (was 43): pause/resume (5), inbox (6), tokens (4), replay (4), concurrent worker context (3), plus all existing coverage. ## What's NOT in this commit Deferred to follow-up PRs: - LISTEN/NOTIFY subscribe (needs real Postgres E2E) - Resource governor (depends on concurrent worker stress testing) - Routing eval harness (needs API keys + benchmark data) - OpenClaw plugin (separate @gbrain/openclaw-minions-plugin repo) See docs/designs/MINIONS_AGENT_ORCHESTRATION.md for full CEO-approved design. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(minions): migration v7 — agent_parity_layer schema Adds columns on minion_jobs (depth, max_children, timeout_ms, timeout_at, remove_on_complete, remove_on_fail, idempotency_key) plus the new minion_attachments table. Three partial indexes for bounded scans: idx_minion_jobs_timeout, idx_minion_jobs_parent_status, and uniq_minion_jobs_idempotency. Check constraints enforce non-negative depth and positive child cap / timeout. Additive migration — existing installs pick it up via ensureSchema on next use. No user action required. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * feat(minions): extend types for v7 parity layer Extends MinionJob with depth/max_children/timeout_ms/timeout_at/ remove_on_complete/remove_on_fail/idempotency_key. Extends MinionJobInput with the same options plus max_spawn_depth override. Adds MinionQueueOpts (maxSpawnDepth default 5, maxAttachmentBytes default 5 MiB). Adds AttachmentInput/Attachment shapes and ChildDoneMessage in the InboxMessage union. rowToMinionJob updated to pick up the new columns. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * feat(minions): attachments validator New module validateAttachment() gates every attachment write. Rejects empty filenames, path traversal (.., /, \), null bytes, oversized content (5 MiB default, per-queue override), invalid base64, and implausible content_type headers. Returns normalized { filename, content_type, content (Buffer), sha256, size } on success. The DB also enforces UNIQUE (job_id, filename) as defense-in-depth for concurrent addAttachment races — JS-only checks are not sufficient. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * feat(minions): queue v7 — depth, child cap, timeouts, cascade, idempotency, child_done Wraps completeJob and failJob in engine.transaction() so parent hook invocations (resolveParent, failParent, removeChildDependency) fold into the same transaction as the child update. A process crash between child and parent can't strand the parent in waiting-children anymore. Adds v7 behaviors: - Depth tracking. add() computes depth = parent.depth + 1 and rejects past maxSpawnDepth (default 5). - Per-parent child cap. add() takes SELECT ... FOR UPDATE on the parent, counts non-terminal children, rejects when count >= max_children. NULL max_children = no cap. - Per-job wall-clock timeout. claim() populates timeout_at when timeout_ms is set. New handleTimeouts() dead-letters expired rows with error_text='timeout exceeded'. Terminal — no retry. - Cascade cancel. cancelJob() walks descendants via recursive CTE with depth-100 runaway cap. Returns the root row. Re-parented descendants (parent_job_id NULL) are naturally excluded. - Idempotency. add() uses INSERT ... ON CONFLICT (idempotency_key) DO NOTHING RETURNING; falls back to SELECT when RETURNING is empty. Same key always yields the same job id. - child_done inbox. completeJob inserts {type:'child_done', child_id, job_name, result} into the parent's inbox in the same transaction as the token rollup, guarded by EXISTS so terminal/deleted parents skip without FK violation. New readChildCompletions(parent_id, lock_token, since?) helper; token-fenced like readInbox. - removeOnComplete / removeOnFail. Deletes the row after the parent hook fires, so parent policy sees consistent state. - Attachment methods. addAttachment validates via validateAttachment then INSERTs; UNIQUE (job_id, filename) backs the JS dup check. listAttachments, getAttachment, deleteAttachment round out the API. Fixes pre-existing inverted status bug: add() now puts children in waiting/delayed (not waiting-children) and atomically flips the parent to waiting-children in the same transaction. Tests no longer need manual UPDATE workarounds. Two correctness fixes: - Sibling completion race. Under READ COMMITTED, two grandchildren completing concurrently each saw the other as still-active in the pre-commit snapshot and neither flipped the parent. Fixed by taking SELECT ... FOR UPDATE on the parent row at the start of completeJob and failJob transactions, serializing siblings on the parent lock. - JSONB double-encode. postgres.js conn.unsafe(sql, params) auto- JSON-encodes parameters. Calling JSON.stringify(obj) first stored a JSON string literal (jsonb_typeof=string) and broke payload->>'key' queries silently. Removed JSON.stringify from three call sites (child_done inbox post, updateProgress, sendMessage). PGLite tolerated both forms so unit tests missed it — real-PG E2E caught it. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * feat(minions): worker — timeout safety net + handleTimeouts tick Worker tick now calls handleStalled() first, then handleTimeouts() — stall requeue wins over timeout dead-letter when both could fire in the same cycle. handleTimeouts() guards on lock_until > now() so stalled jobs take the retryable path. launchJob schedules a per-job setTimeout(timeout_ms) that fires ctx.signal as a best-effort handler interrupt. The timer is always cleared in .finally so process exit isn't delayed by a dangling timer. Handlers that respect AbortSignal stop cleanly; handlers that ignore it still get dead-lettered by the DB-side handleTimeouts. Removed post-completeJob and post-failJob parent-hook calls from the worker — those are now inside the queue method transactions. Worker becomes simpler and crash-safer. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * test(minions): 33 new unit tests for v7 parity layer Covers depth cap, per-parent child cap, timeout dead-letter, cascade cancel (including the re-parent edge case), removeOnComplete / removeOnFail, idempotency (single + concurrent), child_done inbox (posted in txn + survives child removeOnComplete + since cursor), attachment validation (oversize, path traversal, null byte, duplicates, base64), AbortSignal firing on pause mid-handler, catch-block skipping failJob when aborted, worker in-flight bookkeeping, token-rollup guard when parent already terminal, and setTimeout safety-net cleanup. Existing tests updated to remove the inverted-status manual UPDATE workarounds that the add() fix made obsolete. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * test(e2e): Minions v7 concurrency + OpenClaw resilience coverage minions-concurrency.test.ts spins two MinionWorker instances against the test Postgres, submits 20 jobs, and asserts zero double-claims (every job runs exactly once). This is the only test that actually proves FOR UPDATE SKIP LOCKED under real concurrency — PGLite runs on a single connection and can't exercise the race. minions-resilience.test.ts covers the six OpenClaw daily pains: 1. Spawn storm caps enforce under concurrent submit. 2. Agent stall → handleStalled() requeues; handleTimeouts() skips (lock_until guard). 3. Forgotten dispatches recoverable via child_done inbox. 4. Cascade cancel stops grandchildren mid-flight. 5. Deep tree fan-in (parent → 3 children → 2 grandchildren each) completes with the full inbox chain. 6. Parent crash/recovery resumes from persisted state. helpers.ts extends ALL_TABLES with minion_attachments, minion_inbox, and minion_jobs (FK dependents first) so E2E teardown doesn't leak rows between runs. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * chore: release v0.11.0 — Minions v7 agent orchestration primitives Bumps VERSION / package.json to 0.11.0. Adds CHANGELOG entry covering depth tracking, max_children, per-job timeouts, cascade cancel, idempotency keys, child_done inbox, removeOnComplete/Fail, attachments, migration v7, plus the two correctness fixes (sibling completion race and JSONB double-encode). TODOS.md captures the four v7 follow-ups: per-queue rate limiting, repeat/cron scheduler, worker event emitter, and waitForChildren convenience helpers. 1066 unit + 105 E2E = 1171 tests passing. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(minions): unify JSONB inserts, tighten nullish coalescing Three non-blocker cleanups from post-ship review of v0.11.0: - queue.ts add() and completeJob(): pre-stringifying with JSON.stringify while other sites pass raw objects with $n::jsonb casts. postgres.js double-encodes if you stringify first — works on PGLite (text→JSONB auto-cast), fails silently on real PG. Unify on raw object + explicit $n::jsonb cast. - queue.ts readChildCompletions: since clause used sent_at > $2 relying on PG's implicit text→TIMESTAMPTZ coercion. Explicit $2::timestamptz is safer and clearer. - types.ts rowToMinionJob: parent_job_id used || which coerces 0 to null. Harmless today (SERIAL IDs start at 1) but ?? is semantically correct. All 110 unit tests pass. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(minions): updateProgress missed $1::jsonb cast in unification Residual from |