mirror of
https://github.com/garrytan/gbrain.git
synced 2026-07-27 21:19:18 +00:00
v0.41.7.0 feat: compact list-format resolver + 300-skill scaling tutorial (#1407)
* feat(check-resolvable): parseResolverEntries accepts compact list format
Add the second parser branch alongside the existing markdown-table branch
so RESOLVER.md and AGENTS.md can use the OpenClaw-native list shape:
- **skill-name**: trigger1 | trigger2 | trigger3
- skill-name: trigger1 | trigger2
Constraints:
- Skill names must be kebab-lowercase ([a-z][a-z0-9-]+). Bold names
starting with an uppercase letter (e.g. **Note**, **Convention**)
are deliberately skipped so prose bullets in real-world AGENTS.md
files don't get mis-parsed as fake skill rows.
- skillPath is always derived as skills/<name>/SKILL.md. An optional
arrow suffix (Unicode -> or ASCII ->) is stripped from the trigger
string but NOT honored as a path. Downstream consumers
(routing-eval.ts skillSlugFromPath, the manifest check at line 367)
assume the convention. For non-conventional paths, use the table
format.
- Multiple triggers fan out to one entry per trigger. checkResolvable
dedupes by skillPath downstream, so the reachability count counts
each skill once regardless of trigger fan-out.
The parser body is restructured to an if/else-if shape so the existing
'continue' on non-table rows no longer short-circuits the list branch.
Unit tests cover 11 new cases: bold + plain name shapes, multi-trigger
fan-out, Unicode and ASCII path-suffix strip, ellipsis filter, empty
pipe segments, mixed-shape files, section tracking, and two D4
regression cases (prose-bullet rejection + convention-violation
silent-skip).
Closes #1370 — credit @garrytan-agents for the original PR that flagged
the parser gap.
* test(check-resolvable): integration fixtures + regression suite for compact format
Two fixtures pin the v0.41.7.0 parser fix at the integration layer:
test/fixtures/openclaw-compact-resolver/
List-format only RESOLVER.md with 10 fictional skills (gift-advisor,
flight-tracker, email-triage, etc.), each with valid frontmatter
triggers. A trailing 'Notes' section embeds 4 prose bullets
(- **Note**:, - **Convention**:, - **TODO**:, - **Important**:)
that pin the D4 kebab-lowercase regex tighten: if the regex ever
regresses to permissive [\w-]+, those prose bullets would surface
as orphan_trigger warnings and the test fails loudly.
test/fixtures/openclaw-mixed-merge/
Tests the v0.31.7 D-CX-14 multi-resolver merge: workspace-root
AGENTS.md (compact list, 3 skills) + skills/RESOLVER.md (table
format, 5 skills). The merge dedups by skillPath and counts each
skill once.
The regression test (test/check-resolvable-openclaw-compact.test.ts)
runs 8 assertions across both fixtures:
1. unreachable === 0 on the compact fixture (the 'pre-v0.41.7.0
reported 238 FAILs on a 306-skill OpenClaw, post-fix 0' headline).
2. zero error-severity issues; report.ok === true.
3. zero mece_gap warnings (every stub ships valid triggers).
4. zero orphan_trigger warnings for the 4 prose-bullet names — D4
regex regression guard at integration level.
5. zero missing_file warnings.
6. mixed-merge: total_skills === 8 (5 table + 3 list), all reachable.
7. mixed-merge: errors.length === 0; report.ok === true.
8. mixed-merge: each expected skill from BOTH shapes is non-unreachable
(catches the bug where one shape silently swallows the other via
dedup-by-skillPath).
* docs(guides): scaling-skills.md walkthrough for 300-skill agents
Three-tier architecture for agents that have outgrown the always-loaded
skill manifest:
Tier A — always loaded (~35 skills, in the system prompt every turn)
Tier B — resolver-routed (~85 skills, looked up via RESOLVER.md/AGENTS.md
only when no Tier A match)
Tier C — dormant (~180 skills, on disk but not injected into the prompt)
Real numbers from Garry's 306-skill OpenClaw: 25K tokens of skill
descriptions per turn collapsed to 4K tokens (~21K tokens freed per
turn) with zero capability loss. The compact list-format resolver
(v0.41.7.0) is the parser-level enabler for this pattern.
The guide covers:
- The scaling wall (when the always-loaded manifest stops working)
- The three tiers + per-turn token math
- What the resolver actually does (routing-table-but-cheaper pattern)
- The compact list format (kebab-lowercase contract, optional path
suffix, mixed-shape support)
- The 'gbrain doctor' / 'gbrain check-resolvable --strict' safety net
- Implementation walkthrough (audit → tier → disable → resolver →
doctor)
- The scaling curve (50 → 100 → 200 → 300 → 1000, no ceiling)
Voice + privacy cleanup applied per CLAUDE.md rules:
- Wintermute → 'Garry's OpenClaw' / 'your OpenClaw'
- Unicode em dashes stripped; ASCII '--' preserved in command flags
- Made-up 'check_resolvable' invocation replaced with real
'gbrain doctor' and 'gbrain check-resolvable --json'/'--strict'
- Blog-style 'Previous in this series' footer dropped
Wiring:
- scripts/llms-config.ts registers the new guide in the curated
array so 'bun run build:llms' picks it up. docs/UPGRADING_
DOWNSTREAM_AGENTS.md excluded from the inlined bundle to stay
under the 600KB FULL_SIZE_BUDGET after adding the new content.
- docs/tutorials/README.md gains a one-line entry pointing at the
guide under Related documentation.
- llms.txt + llms-full.txt regenerated.
* chore: bump version and changelog (v0.41.7.0)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* docs: update CLAUDE.md for v0.41.7.0 compact-format resolver
Annotate the src/core/check-resolvable.ts entry with the v0.41.7.0
parseResolverEntries compact list-format support: kebab-lowercase name
gate (closes the prose-bullet false-positive class), path-suffix strip
contract (skillPath always derived as skills/<name>/SKILL.md so
routing-eval and the manifest check don't drift), multi-trigger fan-out
plus checkResolvable downstream dedupe, the 238 FAILs to 0 OpenClaw
headline, the two integration fixtures pinning the regression, and the
docs/guides/scaling-skills.md pointer for the tutorial context.
Regenerate llms-full.txt to match (CLAUDE.md edit chaser, per the
CLAUDE.md own rule about test/build-llms.test.ts catching drift).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
3a2605e9a0
commit
374deff579
@@ -2,6 +2,45 @@
|
||||
|
||||
All notable changes to GBrain will be documented in this file.
|
||||
|
||||
## [0.41.7.0] - 2026-05-24
|
||||
|
||||
**Your compact OpenClaw resolver actually works now.** If you've grown your agent past 200 skills and switched to the compact list-format resolver (`- **gift-advisor**: gift idea | birthday gift`) because the markdown-table version got unreadable, `gbrain doctor` used to silently report every single skill as unreachable. On a 306-skill agent, that was 238 FAIL errors on every doctor run, and the list-format resolver was effectively invisible to gbrain. v0.41.7.0 fixes that — the parser now reads both shapes natively, mixes them in one file, and `gbrain doctor` reports 0 errors on the same resolver that previously broke it.
|
||||
|
||||
This release ships alongside a new guide explaining when and why you actually want a list-format resolver: the [three-tier scaling architecture](docs/guides/scaling-skills.md) that gets a 300-skill agent down to ~4K tokens per turn (from ~25K) with zero capability loss. The parser fix is the line of code that makes the resolver-as-router tier work for any agent that writes the compact format.
|
||||
|
||||
### How to take advantage of v0.41.7.0
|
||||
|
||||
`gbrain upgrade`. The parser handles both formats automatically; no migration needed. The tutorial at [`docs/guides/scaling-skills.md`](docs/guides/scaling-skills.md) explains when and why to tier your skills.
|
||||
|
||||
### What you'd see (before vs after)
|
||||
|
||||
On a real 306-skill agent with a list-format `RESOLVER.md`:
|
||||
|
||||
| `gbrain doctor` output | Before v0.41.7.0 | After v0.41.7.0 |
|
||||
|---|---|---|
|
||||
| `resolver_health` status | FAIL | OK |
|
||||
| `unreachable` skills | 238 | 0 |
|
||||
| Per-skill error noise | every skill named | none |
|
||||
|
||||
### Things to watch
|
||||
|
||||
- **Skill names in list format must be kebab-lowercase** (`gift-advisor`, `flight-tracker`). Bold names that start with an uppercase letter (`**MyTool**`) are silently skipped by the new parser. This is the trade that kills the prose-bullet false-positive class (`- **Note**: see [link]` in a real AGENTS.md no longer gets parsed as a skill row). If a skill stops appearing after upgrade, lowercase the name.
|
||||
- **The path suffix is parsed but stripped.** A list entry like `- **quality**: lint -> \`skills/conventions/quality.md\`` produces `skills/quality/SKILL.md` (derived from the name), NOT the explicit path. Two downstream consumers (`routing-eval.ts` and the manifest check) assume the `skills/<name>/SKILL.md` shape; honoring the explicit path would silently break their coverage. For non-conventional skill paths, use the table format (which has always supported them).
|
||||
- **Mixed table + list in one file works.** The v0.31.7 multi-resolver merge (skillpack `skills/RESOLVER.md` + workspace `../AGENTS.md`) folds both shapes into one unified entry stream, deduped by skillPath.
|
||||
|
||||
### Itemized changes
|
||||
|
||||
- New: `src/core/check-resolvable.ts` `parseResolverEntries` accepts compact list-format resolvers alongside the existing table format. Both shapes can mix in one file. The list-branch contract is documented inline.
|
||||
- New: `test/check-resolvable-openclaw-compact.test.ts` regression suite (8 cases across two fixtures) pinning the "238 FAILs → 0" headline outcome plus the D-CX-14 mixed-merge case.
|
||||
- New: `test/fixtures/openclaw-compact-resolver/` (~10 skills with valid frontmatter triggers, plus a prose-bullet section pinning the kebab-lowercase regex tighten).
|
||||
- New: `test/fixtures/openclaw-mixed-merge/` (table-format `skills/RESOLVER.md` + compact `../AGENTS.md`).
|
||||
- New: `docs/guides/scaling-skills.md` — walkthrough of the three-tier scaling architecture (always-loaded, resolver-routed, dormant), the per-turn token math, and the compact list-format spec. Registered in `scripts/llms-config.ts` so `bun run build:llms` regenerates `llms-full.txt` correctly.
|
||||
- Extended: `test/check-resolvable.test.ts` — 11 new unit cases covering bold-name single + multi-trigger fan-out, plain-name fallback, Unicode and ASCII path-suffix strip, ellipsis filter, empty pipe segments, mixed shapes, section tracking across list entries, and two D4-regression cases (prose-bullet rejection + convention-violation negative).
|
||||
- Credit `@garrytan-agents` for the original PR #1370 submission that flagged the parser gap. The v0.41.7.0 implementation expands the scope to close the prose-bullet false-positive class (`[a-z][a-z0-9-]+` name regex), pin the regression at the integration layer (two fixtures), and ship the docs context (the scaling-skills guide).
|
||||
|
||||
### For contributors
|
||||
|
||||
- Codex outside-voice review surfaced four findings the eng review missed: the upstream PR's literal "add list branch below" instruction would have produced dead code (the existing `continue` short-circuits non-table rows before any list branch would fire); the explicit-path-suffix capture would have broken `routing-eval.ts:skillSlugFromPath` + the manifest check at `check-resolvable.ts:367`; the `docs/guides/scaling-skills.md` doc needed registration in `scripts/llms-config.ts` for `test/build-llms.test.ts` to pass; and the original em-dash cleanup instruction would have corrupted command flags like `--pglite`. All four caught and integrated pre-merge.
|
||||
## [0.41.6.0] - 2026-05-25
|
||||
|
||||
**Your PR CI stops taking 23 minutes.** The Test workflow on every pull request was wallclock-bound by one unlucky shard (shard 3, 22-26 min) while other shards finished in 5-13 min. After this release, every shard finishes in roughly the same time, the whole matrix runs in about 9-10 min on a fresh PR, and PRs that don't actually change test-affecting code (rebases, doc-only commits, retries) skip the test run entirely and finish in under 2 minutes via a content-hash cache.
|
||||
@@ -594,6 +633,7 @@ What we caught and fixed before merging. Three independent reviews + three codex
|
||||
```
|
||||
5. **If any step fails or the numbers look wrong,** please file an issue:
|
||||
https://github.com/garrytan/gbrain/issues with `gbrain doctor --json` output.
|
||||
|
||||
## [0.40.10.0] - 2026-05-24
|
||||
|
||||
**Your brain stops accepting junk pages, and oversize content stops crashing the embedder.** A page from one of your source repos can no longer break embedding, defeat search, or pollute your knowledge graph just because it's a Cloudflare challenge dump or an absurdly large file. The new sanity gate lives at the narrow waist of ingestion, so every path that writes pages — sync, capture, `put_page` MCP, the `/ingest` webhook — picks it up uniformly.
|
||||
|
||||
@@ -135,7 +135,7 @@ strict behavior when unset.
|
||||
- `src/core/minions/queue.ts` extension (v0.31.12) — `MinionQueue.add()` now rejects `subagent` jobs whose `data.model` resolves through `isAnthropicProvider()` to a non-Anthropic provider. Lazy-imports `model-config.ts` to avoid pulling engine types into queue's eager-load surface. Layer 1 of the three-layer subagent provider enforcement (Codex F1+F2 in plan review). Layers 2 + 3 live in `src/core/model-config.ts` (`enforceSubagentAnthropic` runtime fallback) and `src/commands/doctor.ts` (`subagent_provider` check). Pinned by 3 cases in `test/agent-cli.test.ts`.
|
||||
- `src/commands/models.ts` (v0.31.12) — `gbrain models [--json]` read-only routing dashboard: prints tier defaults (`utility`/`reasoning`/`deep`/`subagent`), the resolved value for each (re-walking the resolution chain to attribute properly), every per-task override (11 `PER_TASK_KEYS` entries — `models.dream.synthesize`, `models.dream.patterns`, `models.drift`, `models.auto_think`, `models.think`, `models.subagent`, `facts.extraction_model`, `models.eval.longmemeval`, `models.expansion`, `models.chat`, `models.dream.synthesize_verdict`), the alias map (defaults + user overrides), and a source-of-truth column showing `default` / `config: <key>` / `env: <VAR>`. `gbrain models doctor [--skip=<provider>] [--json]` fires a 1-token `gateway.chat()` probe against each configured chat + expansion model and classifies failures into `{model_not_found, auth, rate_limit, network, unknown}` — the structural fix for the v0.31.6 silent-no-op bug class. Wired into `cli.ts` dispatch table + `CLI_ONLY` set. **v0.33.1.1 (#962, Codex P3 follow-up):** doctor gains a zero-token `embedding_config` probe that runs FIRST, before any chat/expansion probes spend money. `probeEmbeddingConfig()` reads `getEmbeddingModel()` + `getEmbeddingDimensions()` from the gateway, parses the model id, and (for Voyage flexible-dim models) checks `isValidVoyageOutputDim(dims)` against `VOYAGE_VALID_OUTPUT_DIMS`. New `ProbeStatus` variant `'config'` and optional `fix?: string` field on `ProbeResult` — surfaced in both human output (paste-ready `gbrain config set ...` line under the bad probe) and JSON output. New touchpoint label `'embedding_config'` joins `'chat'` and `'expansion'` in the probe-row taxonomy. Closes the Voyage flexible-dim bug class at config time, not first-embed.
|
||||
- `src/commands/doctor.ts` extension (v0.31.12) — new `subagent_provider` check (layer 3 of 3 — Codex F13). Warns when `models.tier.subagent` is explicitly set to a non-Anthropic provider (fail-loud since the user clearly meant it — message names the bad value and prints the paste-ready fix command `gbrain config set models.tier.subagent anthropic:claude-sonnet-4-6`); also warns when `models.default` would sneak `subagent` into a non-Anthropic provider via tier inheritance. OK status when subagent tier resolves to Anthropic. Tests cover all three paths in `test/doctor.test.ts`.
|
||||
- `src/core/check-resolvable.ts` — Resolver validation: reachability, MECE overlap, DRY checks, structured fix objects. v0.14.1: `CROSS_CUTTING_PATTERNS.conventions` is an array (notability gate accepts both `conventions/quality.md` and `_brain-filing-rules.md`). New `extractDelegationTargets()` parses `> **Convention:**`, `> **Filing rule:**`, and inline backtick references. DRY suppression is proximity-based via `DRY_PROXIMITY_LINES = 40`.
|
||||
- `src/core/check-resolvable.ts` — Resolver validation: reachability, MECE overlap, DRY checks, structured fix objects. v0.14.1: `CROSS_CUTTING_PATTERNS.conventions` is an array (notability gate accepts both `conventions/quality.md` and `_brain-filing-rules.md`). New `extractDelegationTargets()` parses `> **Convention:**`, `> **Filing rule:**`, and inline backtick references. DRY suppression is proximity-based via `DRY_PROXIMITY_LINES = 40`. **v0.41.7.0:** `parseResolverEntries` accepts a second compact list format alongside the original markdown table (`- **skill-name**: trigger1 | trigger2 | trigger3` or `- skill-name: trigger1 | trigger2`). Both shapes can mix in one file; the v0.31.7 multi-resolver merge folds them into one entry stream. Skill name MUST be kebab-lowercase (regex `[a-z][a-z0-9-]+`) so prose bullets like `- **Note**: …`, `- **Convention**: …`, `- **TODO**: …` in real-world AGENTS.md files don't false-match as skill rows (codex F2 / D4). `skillPath` is ALWAYS derived as `skills/<name>/SKILL.md`: an optional `→ \`skills/path\`` (or ASCII `->`) suffix is stripped from the trigger string but NOT honored as the path — two downstream consumers (`routing-eval.ts:skillSlugFromPath`, the manifest lookup at `:367`) assume the convention, and honoring the explicit path would silently break their coverage. For non-conventional paths, use the table format. Multi-trigger rows fan out to one entry per trigger sharing the same `skillPath`; `checkResolvable` dedupes downstream so the reachability count counts each skill once. Closes the OpenClaw bug class where a 306-skill agent's list-format resolver registered 238 FAILs ("skill not reachable from RESOLVER.md") on every `gbrain doctor` run — productionized from upstream PR #1370 by @garrytan-agents. Pinned by 11 new unit cases in `test/check-resolvable.test.ts` (bold + plain forms, Unicode + ASCII suffix strip, ellipsis filter, empty pipe segments, mixed shapes, prose-bullet rejection regression) and the 8-case integration suite in `test/check-resolvable-openclaw-compact.test.ts` over two fixtures: `test/fixtures/openclaw-compact-resolver/` (10 skills in pure compact form) + `test/fixtures/openclaw-mixed-merge/` (table-format `skills/RESOLVER.md` + compact `../AGENTS.md` for the v0.31.7 merge contract). User-facing tutorial: `docs/guides/scaling-skills.md` walks through when and why to switch to the compact format (the three-tier scaling architecture that gets a 300-skill agent down to ~4K tokens per turn from ~25K with zero capability loss).
|
||||
- `src/core/repo-root.ts` — Shared `findRepoRoot(startDir?)` (v0.16.4): walks up from `startDir` (default `process.cwd()`) looking for `skills/RESOLVER.md`. Zero-dependency module imported by both `doctor.ts` and `check-resolvable.ts`. Parameterized `startDir` makes tests hermetic. **v0.31.7:** read-path / write-path split. `autoDetectSkillsDir` (shared, read+write-safe) gains tier-0 `$GBRAIN_SKILLS_DIR` explicit operator override (Docker mounts, CI, monorepo subdirs) ahead of the existing 4-tier chain. New `autoDetectSkillsDirReadOnly` wraps it with a tier-5 install-path fallback that walks up from `fileURLToPath(import.meta.url)` and gates on `isGbrainRepoRoot` so unrelated repos can't false-positive. Read-path callers (`doctor`, `check-resolvable`, `routing-eval`) use the read-only variant; write-path callers (`skillpack install`, `skillify scaffold`, `post-install-advisory`) deliberately stay on the shared function so `gbrain skillpack install` from `~` cannot silently retarget the bundled gbrain repo's `skills/` instead of the user's actual workspace. Two new `SkillsDirSource` variants: `'env_explicit'`, `'install_path'`. New `AUTO_DETECT_HINT_READ_ONLY` documents the extra tier. The D6 `--fix` safety gate in `doctor.ts` + `check-resolvable.ts` refuses auto-repair when `detected.source === 'install_path'` so `gbrain doctor --fix` from `~` cannot silently rewrite the bundled install tree.
|
||||
- `src/commands/check-resolvable.ts` — Standalone CLI wrapper (v0.16.4) over `checkResolvable()`. Exports `parseFlags`, `resolveSkillsDir`, `DEFERRED`, `runCheckResolvable`. Exit rule: **1 on any issue (warnings OR errors)**, stricter than doctor's `ok` flag — honors README:259. Stable JSON envelope `{ok, skillsDir, report, autoFix, deferred, error, message}` — same shape on success and error paths. `--fix` path runs `autoFixDryViolations` BEFORE `checkResolvable` (same ordering as doctor). `scripts/skillify-check.ts` subprocess-calls `gbrain check-resolvable --json` (cached per process) and fails loud on binary-missing — no silent false-pass. **v0.19:** AGENTS.md workspaces now resolve natively (see `src/core/resolver-filenames.ts`) — gbrain inspects the 107-skill OpenClaw deployment whether the routing file is `RESOLVER.md` or `AGENTS.md`. `DEFERRED[]` is empty — Checks 5 + 6 shipped as real code, not issue URLs. **v0.31.7:** the resolver lookup switched from first-match-wins to the multi-file merge in `src/core/check-resolvable.ts` — entries collected from every `RESOLVER.md` / `AGENTS.md` across the skills dir AND its parent, deduped by `skillPath` (first occurrence wins). Lifted reachable skills on the reference OpenClaw layout from 37/224 to 200/224 — the deployment ships a thin `skills/RESOLVER.md` (~40 entries from skillpack) plus a fat `../AGENTS.md` (200+ entries, the real dispatcher), and the previous code only saw the first one. The CLI also switched to `autoDetectSkillsDirReadOnly` so `cd ~ && gbrain check-resolvable` finds the bundled skills via the install-path fallback. `--fix` carries the same D6 safety gate as `gbrain doctor --fix`: refuses to write when `detected.source === 'install_path'`.
|
||||
- `src/core/resolver-filenames.ts` (v0.19) — central list of accepted routing filenames (`RESOLVER.md`, `AGENTS.md`). Shared by `findRepoRoot`, `check-resolvable`, and skillpack install so every code path walks the same fallback chain.
|
||||
|
||||
@@ -142,6 +142,49 @@ note. Filing it as a TODO would imply it's ready to pull; it isn't.
|
||||
current behavior (truncate-then-fail) is safe — no infinite loops,
|
||||
depth-cap prevents chains — but full semantic reduction unlocks higher
|
||||
self-fix success rates on legitimately-long prompts.
|
||||
## v0.41.7.0 resolver-parser follow-ups (filed during ship of `garrytan/pr1370-production-ready`)
|
||||
|
||||
Source: Codex outside-voice review on the PR #1370 production-rebuild plan.
|
||||
The wave shipped with the primary parser fix + 11 unit tests + 2 integration
|
||||
fixtures + scaling-skills tutorial. Two findings deferred:
|
||||
|
||||
- [ ] **F8 P3 — Path-traversal hardening for the existing table-format
|
||||
parser.** Both the existing table parser and the new list parser accept
|
||||
inputs like `skills/../x/SKILL.md`; downstream `join(skillsDir, relPath)`
|
||||
can escape `skillsDir`. The v0.41.7.0 list branch is structurally closed
|
||||
(the kebab-lowercase `[a-z][a-z0-9-]+` name regex rejects `.` in names so
|
||||
`..` is blocked at the name layer). The table branch surface is
|
||||
pre-existing and out of scope for v0.41.7.0. Move: at the file-existence
|
||||
check in `src/core/check-resolvable.ts` (around line 352), add a
|
||||
`relPath.split('/').includes('..')` guard that surfaces as an
|
||||
`unreachable` issue with a "path traversal not allowed" message. Low
|
||||
severity: requires malicious/buggy RESOLVER.md content to fire.
|
||||
|
||||
- [ ] **F9 P3 — Document the fan-out/dedup interaction in the resolver
|
||||
guide.** `checkResolvable` dedupes by `skillPath`, so the v0.41.7.0
|
||||
list-format multi-trigger fan-out (`- **foo**: t1 | t2 | t3` produces 3
|
||||
entries) doesn't change the integration reachability count. This is
|
||||
desired behavior (one skill counted once) but surprising for readers who
|
||||
count parser entries. Move: add a one-paragraph "how fan-out interacts
|
||||
with reachability" note to `docs/guides/scaling-skills.md` after we have
|
||||
reader feedback indicating the confusion is real. Codex noted that unit
|
||||
tests prove parser output, integration tests prove reachability, and the
|
||||
current docs don't bridge the two cleanly. Doc-only follow-up.
|
||||
|
||||
- [ ] **P1 flake — audit-writer.test.ts week-boundary failure.** Caught
|
||||
during ship of v0.41.7.0. Test at `test/audit/audit-writer.test.ts:229`
|
||||
("returns events from current week, filtered by ts cutoff") fails when
|
||||
real UTC date is in a different ISO week than the test's hardcoded
|
||||
`now=2026-05-22`. `writer.log()` uses real `new Date()` to pick the
|
||||
week-file; `readRecent(now)` uses the fake `now`. When the two land in
|
||||
different ISO weeks (specifically: any time the real UTC clock is in
|
||||
the week AFTER 2026-W21), `log()` writes to the wrong file and
|
||||
readRecent finds 0 events. Fires deterministically once a week, at the
|
||||
UTC Monday rollover. Move: refactor `createAuditWriter.log()` to accept
|
||||
an optional injected `now` (or read it from the entry's own `ts` field).
|
||||
Affected surface: `src/core/audit/audit-writer.ts`. Pre-existing on
|
||||
master; not caused by this branch's parser changes. Reproducible by
|
||||
setting system clock to any Monday after the test's `2026-05-22` date.
|
||||
|
||||
## v0.41 content-sanity follow-ups (filed during ship of `garrytan/lint-page-size-gate`)
|
||||
|
||||
|
||||
@@ -0,0 +1,310 @@
|
||||
# Scaling skills past 300 without drowning the context window
|
||||
|
||||
When an agent grows past 100 skills, a wall starts forming. Sessions take
|
||||
longer to start. The model gets a little dumber about which skill to pick.
|
||||
Tokens that should be powering reasoning are powering a skill catalog the
|
||||
model reads on every turn whether it needs to or not.
|
||||
|
||||
This guide is the recipe for breaking through that wall without deleting
|
||||
capabilities. Three tiers, one resolver, one safety net. Production-tested
|
||||
on a 306-skill agent (Garry's OpenClaw, the agent behind Y Combinator's
|
||||
president). The pattern works whether you run OpenClaw, Hermes, Claude Code,
|
||||
Cursor, or your own MCP-aware agent.
|
||||
|
||||
## The problem
|
||||
|
||||
OpenClaw scans every skill file on disk at session start and injects them
|
||||
into the system prompt as `<available_skills>` entries. The model sees a
|
||||
name, description, and file path for each one. When a request matches, the
|
||||
model reads the full SKILL.md and follows it.
|
||||
|
||||
This is great architecture at 50 skills. At 100, it's fine. At 200, it
|
||||
starts to drag. At 300, the system prompt eats more than 25,000 tokens on
|
||||
skill descriptions alone. Tokens that aren't going to reasoning, context,
|
||||
or actual work.
|
||||
|
||||
The symptoms compound:
|
||||
|
||||
- Sessions take noticeably longer to start.
|
||||
- The model has less room for conversation history.
|
||||
- Skill routing gets fuzzier. With 300 descriptions competing for attention,
|
||||
the model occasionally picks the wrong one.
|
||||
- Cost goes up because every turn carries the full skill manifest.
|
||||
|
||||
The naive fix is to delete skills you don't use often. Don't do this. The
|
||||
whole point of skills is that capabilities compound. A gift pipeline that
|
||||
fires twice a month saves 30 minutes each time it does. A flight tracker
|
||||
fires once per trip and prevents a missed Uber. Deleting low-frequency
|
||||
skills optimizes for prompt size at the cost of capability. You wouldn't
|
||||
delete apps from your phone because the home screen is too crowded. You'd
|
||||
organize them.
|
||||
|
||||
## The three tiers
|
||||
|
||||
Not all skills need to be visible to the model at all times. Some are core.
|
||||
Some are specialized. Some are dormant.
|
||||
|
||||
### Tier A: always loaded (~35 skills)
|
||||
|
||||
The skills the model needs on every single turn. Brain search, email triage,
|
||||
calendar, meeting ingestion, content creation, the executive assistant.
|
||||
They stay in the system prompt's `<available_skills>` manifest. The model
|
||||
sees them natively and routes to them without any lookup.
|
||||
|
||||
### Tier B: resolver-routed (~85 skills)
|
||||
|
||||
Real, active skills that fire regularly but don't need to pollute every
|
||||
turn. Gift pipeline, flight tracker, investor update ingestion, adversary
|
||||
tracking, book mirror, civic intelligence. They live on disk. They have
|
||||
full SKILL.md files. But OpenClaw doesn't inject them into the prompt.
|
||||
|
||||
Instead, a compact RESOLVER.md handles routing. One line per skill with
|
||||
trigger phrases:
|
||||
|
||||
```markdown
|
||||
- **gift-advisor**: gift idea | what should I bring | birthday gift | housewarming
|
||||
- **flight-tracker**: track my flight | flight status | when does my flight land
|
||||
- **investor-update-ingest**: investor update | portfolio update | company metrics
|
||||
```
|
||||
|
||||
When the model sees "what should I bring to Jessica's dinner," it checks
|
||||
the resolver, finds `gift-advisor`, reads the SKILL.md, and executes. Same
|
||||
result. Zero wasted tokens on the other 84 turns where gifts aren't relevant.
|
||||
|
||||
### Tier C: dormant (~180 skills)
|
||||
|
||||
Built-in OpenClaw skills that aren't in active rotation (1Password, Discord,
|
||||
Notion, Trello, integrations you haven't wired up yet) plus specialized
|
||||
skills that almost never fire. They're explicitly disabled in the config
|
||||
with `enabled: false`. They exist on disk as documentation and potential.
|
||||
Flip one boolean to wake them up. Zero tokens contributed to every prompt
|
||||
until then.
|
||||
|
||||
### The numbers
|
||||
|
||||
Before tiering, on Garry's 306-skill OpenClaw:
|
||||
|
||||
| Metric | Before |
|
||||
|---|---|
|
||||
| Skills in system prompt | 306 |
|
||||
| Skill-description tokens per turn | ~25,000 |
|
||||
| Skill routing accuracy | degrading |
|
||||
| Session startup | slow |
|
||||
|
||||
After tiering:
|
||||
|
||||
| Metric | After |
|
||||
|---|---|
|
||||
| Skills in system prompt (Tier A) | 35 |
|
||||
| Skill-description tokens per turn | ~4,000 |
|
||||
| Skills still accessible (A + B + C) | 301 |
|
||||
| Capability loss | zero |
|
||||
| **Tokens freed per turn** | **~21,000** |
|
||||
|
||||
21K tokens per turn is not a small optimization. It's the difference between
|
||||
the model having room to think and the model being squeezed. It's the
|
||||
difference between carrying 3 pages of conversation history and carrying 15.
|
||||
|
||||
## What the resolver actually does
|
||||
|
||||
The resolver is cheaper than the manifest. That's the load-bearing insight.
|
||||
|
||||
OpenClaw's native skill manifest puts ~80 tokens per skill into the system
|
||||
prompt (name + description + location). At 300 skills that's 24,000 tokens
|
||||
spent every turn whether the model needs the catalog or not.
|
||||
|
||||
The resolver puts ~15 tokens per skill into a compact markdown list. At
|
||||
300 skills that's 4,500 tokens. But it only fires when the model checks
|
||||
it, which is only when the request doesn't match a Tier A skill. Most
|
||||
turns, the resolver costs zero tokens because the Tier A match handles it.
|
||||
|
||||
This is the routing-table pattern but applied to the skill manifest itself.
|
||||
The resolver routes to skills, but it also routes around skills, keeping
|
||||
them out of the context window until they're needed.
|
||||
|
||||
GBrain ships with a [bundled `skills/RESOLVER.md`](../../skills/RESOLVER.md)
|
||||
you can use as a reference shape. The skillpack story for distributing
|
||||
your own resolvers across machines is covered in
|
||||
[skillpacks as scaffolding](skillpacks-as-scaffolding.md).
|
||||
|
||||
## The compact list format (v0.41.7.0)
|
||||
|
||||
GBrain's resolver parser used to require markdown tables:
|
||||
|
||||
```markdown
|
||||
| Trigger | Skill |
|
||||
|---------|-------|
|
||||
| "gift idea" | `skills/gift-advisor/SKILL.md` |
|
||||
```
|
||||
|
||||
That's fine when you have 20 entries. It gets unwieldy at 200, and at 300
|
||||
it's unreadable. OpenClaw deployments quietly evolved a compact list
|
||||
format that scales better:
|
||||
|
||||
```markdown
|
||||
- **gift-advisor**: gift idea | what should I bring | birthday gift
|
||||
- **flight-tracker**: track my flight | flight status | when does my flight land
|
||||
```
|
||||
|
||||
Before v0.41.7.0, `gbrain doctor` only spoke the table dialect. On a
|
||||
306-skill compact-format resolver, the doctor reported every skill as
|
||||
unreachable: **238 FAIL errors on every doctor run**. The parser was
|
||||
silently treating the compact dialect as zero skills.
|
||||
|
||||
v0.41.7.0 ships dual-format support. The same `parseResolverEntries`
|
||||
function reads both table rows and list rows in the same file, with the
|
||||
v0.31.7 multi-resolver merge (skillpack `skills/RESOLVER.md` + workspace
|
||||
`../AGENTS.md`) folding everything into one unified view. Run `gbrain doctor`
|
||||
and the 238 FAILs collapse to 0.
|
||||
|
||||
### The list-format contract
|
||||
|
||||
A few rules to keep the parser unambiguous:
|
||||
|
||||
- **Skill names must be kebab-lowercase.** `gift-advisor`, `flight-tracker`,
|
||||
`email-triage`. Names that start with an uppercase letter (`MyTool`,
|
||||
`Note`, `Convention`) are deliberately ignored. This is what stops prose
|
||||
bullets like `- **Note**: see [link]` from being mis-parsed as skill
|
||||
rows in real-world AGENTS.md files.
|
||||
- **The path always resolves to `skills/<name>/SKILL.md`.** An optional
|
||||
`→ \`skills/path\`` (or ASCII `->`) suffix is allowed for readability,
|
||||
but the parser strips it. For non-conventional paths (skills under
|
||||
nested directories, references into `conventions/`, anything that
|
||||
isn't `skills/<name>/SKILL.md`), use the table format.
|
||||
- **Triggers separate with `|`.** Empty pieces and the literal `...`
|
||||
placeholder are dropped. Each trigger becomes its own resolver entry,
|
||||
all pointing at the same skill.
|
||||
- **Bold or plain.** `- **name**: triggers` is preferred. `- name: triggers`
|
||||
works as a fallback.
|
||||
|
||||
You can mix table and list rows in the same file. Useful when a brain
|
||||
inherits a table-format `RESOLVER.md` from gbrain and a list-format
|
||||
`../AGENTS.md` from OpenClaw.
|
||||
|
||||
## The doctor safety net
|
||||
|
||||
The danger with tiering is invisible skill loss. You disable a skill from
|
||||
native scanning, forget to add it to the resolver, and now the agent can't
|
||||
do something it used to do. You won't notice until the moment you need it.
|
||||
|
||||
`gbrain doctor` walks every skill on disk and verifies it's reachable,
|
||||
either through native scanning (Tier A) or through the resolver (Tier B
|
||||
and C). On Garry's setup, the first run after tiering found 63 unreachable
|
||||
skills. Sixty-three capabilities that existed on disk but had no routing
|
||||
path. Fixed in an hour by adding resolver entries.
|
||||
|
||||
Run it after every skill change:
|
||||
|
||||
```bash
|
||||
gbrain doctor
|
||||
```
|
||||
|
||||
For CI gates, use the JSON-emitting variant:
|
||||
|
||||
```bash
|
||||
gbrain check-resolvable --json
|
||||
gbrain check-resolvable --strict # warnings fail too
|
||||
```
|
||||
|
||||
If a skill is unreachable, the output tells you which one and suggests
|
||||
the fix. The resolver is a document. Documents are cheap to fix.
|
||||
|
||||
## Implementation walkthrough
|
||||
|
||||
Three changes. Total time about 45 minutes once you've decided which
|
||||
skills go in which tier.
|
||||
|
||||
### 1. Audit and tier your skills
|
||||
|
||||
Walk through every skill. Ask: does this need to fire on every turn?
|
||||
|
||||
- If yes → Tier A.
|
||||
- If it fires weekly or less but is real → Tier B.
|
||||
- If you don't use it → Tier C.
|
||||
|
||||
### 2. Disable Tier B and C in your agent's config
|
||||
|
||||
For OpenClaw, the file is `openclaw.json`. Add an entry per disabled skill:
|
||||
|
||||
```json
|
||||
{
|
||||
"skills": {
|
||||
"entries": {
|
||||
"gift-advisor": { "enabled": false },
|
||||
"flight-tracker": { "enabled": false },
|
||||
"1password": { "enabled": false }
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The exact config shape depends on which agent runtime you use. The point
|
||||
is the same in all of them: tell the runtime not to inject this skill into
|
||||
the system prompt. The file stays on disk; only the prompt injection stops.
|
||||
|
||||
### 3. Write the resolver
|
||||
|
||||
One line per Tier B and Tier C skill. Trigger phrases that match how you
|
||||
actually ask for things:
|
||||
|
||||
```markdown
|
||||
- **gift-advisor**: gift idea | what should I bring | birthday gift
|
||||
- **flight-tracker**: track my flight | flight status | when do I land
|
||||
- **investor-update-ingest**: investor update | portfolio update | company metrics
|
||||
```
|
||||
|
||||
That's it. The model handles the rest. When a request doesn't match Tier A,
|
||||
it checks the resolver, reads the matching SKILL.md, and executes.
|
||||
|
||||
### 4. Run `gbrain doctor` and fix any unreachable skills
|
||||
|
||||
The doctor sweep tells you which skills don't have a routing path. Add a
|
||||
resolver entry for each one, re-run, repeat until the count is zero.
|
||||
|
||||
## A lesson from the first version
|
||||
|
||||
I initially converted my resolver from a clean list format to a table
|
||||
format because the validator only spoke tables. That was wrong. When a
|
||||
tool fails against valid data, the right move is to fix the tool, not
|
||||
reshape the data. The list format was correct, compact, readable, easy
|
||||
to maintain. The parser needed to support both shapes. v0.41.7.0 is
|
||||
that fix.
|
||||
|
||||
The same principle applies everywhere in agent systems. Your SKILL.md is
|
||||
the source of truth. Your AGENTS.md is the source of truth. Your resolver
|
||||
is the source of truth. When tooling disagrees with your configuration,
|
||||
the tooling is wrong. Fix the tooling.
|
||||
|
||||
## The scaling curve
|
||||
|
||||
At 50 skills, you don't need any of this. Just load everything.
|
||||
|
||||
At 100, you start feeling the drag but can push through.
|
||||
|
||||
At 200, routing accuracy drops and sessions get noticeably slower. This
|
||||
is where most people stop adding skills, which means their agent stops
|
||||
getting more capable. Bad trade.
|
||||
|
||||
At 300+, tiering is mandatory. But with tiering, there's no ceiling.
|
||||
1,000 skills with 35 in the hot path and 965 in the resolver is the same
|
||||
per-turn cost as 35 skills with no resolver. The cost stays flat.
|
||||
Capabilities compound.
|
||||
|
||||
The architecture that gets you from 50 to 300 is different from the
|
||||
architecture that gets you from 10 to 50. That's normal. Systems that
|
||||
scale change shape. The important thing is that each tier preserves full
|
||||
capability. You're organizing, not deleting.
|
||||
|
||||
## Related
|
||||
|
||||
- [Skill development cycle](skill-development.md) — the 5-step loop for
|
||||
turning a repeated task into a real skill.
|
||||
- [Skillpacks as scaffolding](skillpacks-as-scaffolding.md) — how to
|
||||
distribute a coherent set of skills across machines and agents.
|
||||
- [Sub-agent routing](sub-agent-routing.md) — when to delegate to a
|
||||
sub-agent vs handle in-line, and the model routing table for each path.
|
||||
|
||||
GBrain: [github.com/garrytan/gbrain](https://github.com/garrytan/gbrain).
|
||||
The `parseResolverEntries` parser lives at
|
||||
[`src/core/check-resolvable.ts`](../../src/core/check-resolvable.ts);
|
||||
the bundled resolver lives at [`skills/RESOLVER.md`](../../skills/RESOLVER.md).
|
||||
@@ -30,7 +30,7 @@ Tutorials follow the [Diataxis](https://diataxis.fr/) tutorial pattern: learning
|
||||
## Related documentation
|
||||
|
||||
- **Reference:** [`docs/architecture/`](../architecture/) — system design, topologies, retrieval theory
|
||||
- **How-to:** [`docs/guides/`](../guides/) — task-oriented runbooks (sub-agent routing, minion deployment, skill development, brain-first lookup, idea capture, diligence ingestion)
|
||||
- **How-to:** [`docs/guides/`](../guides/) — task-oriented runbooks (sub-agent routing, minion deployment, skill development, brain-first lookup, idea capture, diligence ingestion). Highlight: [scaling skills past 300](../guides/scaling-skills.md) — the three-tier architecture for agents that have outgrown the always-loaded skill manifest.
|
||||
- **Integrations:** [`docs/integrations/`](../integrations/) — connecting external data sources (voice, email, calendar, embedding providers)
|
||||
- **MCP setup:** [`docs/mcp/`](../mcp/) — per-client setup (Claude Desktop, Code, Cursor, ChatGPT, Perplexity, Cowork)
|
||||
- **Install paths:** [`docs/INSTALL.md`](../INSTALL.md) — every install path, end to end
|
||||
|
||||
+318
-619
@@ -277,7 +277,7 @@ strict behavior when unset.
|
||||
- `src/core/minions/queue.ts` extension (v0.31.12) — `MinionQueue.add()` now rejects `subagent` jobs whose `data.model` resolves through `isAnthropicProvider()` to a non-Anthropic provider. Lazy-imports `model-config.ts` to avoid pulling engine types into queue's eager-load surface. Layer 1 of the three-layer subagent provider enforcement (Codex F1+F2 in plan review). Layers 2 + 3 live in `src/core/model-config.ts` (`enforceSubagentAnthropic` runtime fallback) and `src/commands/doctor.ts` (`subagent_provider` check). Pinned by 3 cases in `test/agent-cli.test.ts`.
|
||||
- `src/commands/models.ts` (v0.31.12) — `gbrain models [--json]` read-only routing dashboard: prints tier defaults (`utility`/`reasoning`/`deep`/`subagent`), the resolved value for each (re-walking the resolution chain to attribute properly), every per-task override (11 `PER_TASK_KEYS` entries — `models.dream.synthesize`, `models.dream.patterns`, `models.drift`, `models.auto_think`, `models.think`, `models.subagent`, `facts.extraction_model`, `models.eval.longmemeval`, `models.expansion`, `models.chat`, `models.dream.synthesize_verdict`), the alias map (defaults + user overrides), and a source-of-truth column showing `default` / `config: <key>` / `env: <VAR>`. `gbrain models doctor [--skip=<provider>] [--json]` fires a 1-token `gateway.chat()` probe against each configured chat + expansion model and classifies failures into `{model_not_found, auth, rate_limit, network, unknown}` — the structural fix for the v0.31.6 silent-no-op bug class. Wired into `cli.ts` dispatch table + `CLI_ONLY` set. **v0.33.1.1 (#962, Codex P3 follow-up):** doctor gains a zero-token `embedding_config` probe that runs FIRST, before any chat/expansion probes spend money. `probeEmbeddingConfig()` reads `getEmbeddingModel()` + `getEmbeddingDimensions()` from the gateway, parses the model id, and (for Voyage flexible-dim models) checks `isValidVoyageOutputDim(dims)` against `VOYAGE_VALID_OUTPUT_DIMS`. New `ProbeStatus` variant `'config'` and optional `fix?: string` field on `ProbeResult` — surfaced in both human output (paste-ready `gbrain config set ...` line under the bad probe) and JSON output. New touchpoint label `'embedding_config'` joins `'chat'` and `'expansion'` in the probe-row taxonomy. Closes the Voyage flexible-dim bug class at config time, not first-embed.
|
||||
- `src/commands/doctor.ts` extension (v0.31.12) — new `subagent_provider` check (layer 3 of 3 — Codex F13). Warns when `models.tier.subagent` is explicitly set to a non-Anthropic provider (fail-loud since the user clearly meant it — message names the bad value and prints the paste-ready fix command `gbrain config set models.tier.subagent anthropic:claude-sonnet-4-6`); also warns when `models.default` would sneak `subagent` into a non-Anthropic provider via tier inheritance. OK status when subagent tier resolves to Anthropic. Tests cover all three paths in `test/doctor.test.ts`.
|
||||
- `src/core/check-resolvable.ts` — Resolver validation: reachability, MECE overlap, DRY checks, structured fix objects. v0.14.1: `CROSS_CUTTING_PATTERNS.conventions` is an array (notability gate accepts both `conventions/quality.md` and `_brain-filing-rules.md`). New `extractDelegationTargets()` parses `> **Convention:**`, `> **Filing rule:**`, and inline backtick references. DRY suppression is proximity-based via `DRY_PROXIMITY_LINES = 40`.
|
||||
- `src/core/check-resolvable.ts` — Resolver validation: reachability, MECE overlap, DRY checks, structured fix objects. v0.14.1: `CROSS_CUTTING_PATTERNS.conventions` is an array (notability gate accepts both `conventions/quality.md` and `_brain-filing-rules.md`). New `extractDelegationTargets()` parses `> **Convention:**`, `> **Filing rule:**`, and inline backtick references. DRY suppression is proximity-based via `DRY_PROXIMITY_LINES = 40`. **v0.41.7.0:** `parseResolverEntries` accepts a second compact list format alongside the original markdown table (`- **skill-name**: trigger1 | trigger2 | trigger3` or `- skill-name: trigger1 | trigger2`). Both shapes can mix in one file; the v0.31.7 multi-resolver merge folds them into one entry stream. Skill name MUST be kebab-lowercase (regex `[a-z][a-z0-9-]+`) so prose bullets like `- **Note**: …`, `- **Convention**: …`, `- **TODO**: …` in real-world AGENTS.md files don't false-match as skill rows (codex F2 / D4). `skillPath` is ALWAYS derived as `skills/<name>/SKILL.md`: an optional `→ \`skills/path\`` (or ASCII `->`) suffix is stripped from the trigger string but NOT honored as the path — two downstream consumers (`routing-eval.ts:skillSlugFromPath`, the manifest lookup at `:367`) assume the convention, and honoring the explicit path would silently break their coverage. For non-conventional paths, use the table format. Multi-trigger rows fan out to one entry per trigger sharing the same `skillPath`; `checkResolvable` dedupes downstream so the reachability count counts each skill once. Closes the OpenClaw bug class where a 306-skill agent's list-format resolver registered 238 FAILs ("skill not reachable from RESOLVER.md") on every `gbrain doctor` run — productionized from upstream PR #1370 by @garrytan-agents. Pinned by 11 new unit cases in `test/check-resolvable.test.ts` (bold + plain forms, Unicode + ASCII suffix strip, ellipsis filter, empty pipe segments, mixed shapes, prose-bullet rejection regression) and the 8-case integration suite in `test/check-resolvable-openclaw-compact.test.ts` over two fixtures: `test/fixtures/openclaw-compact-resolver/` (10 skills in pure compact form) + `test/fixtures/openclaw-mixed-merge/` (table-format `skills/RESOLVER.md` + compact `../AGENTS.md` for the v0.31.7 merge contract). User-facing tutorial: `docs/guides/scaling-skills.md` walks through when and why to switch to the compact format (the three-tier scaling architecture that gets a 300-skill agent down to ~4K tokens per turn from ~25K with zero capability loss).
|
||||
- `src/core/repo-root.ts` — Shared `findRepoRoot(startDir?)` (v0.16.4): walks up from `startDir` (default `process.cwd()`) looking for `skills/RESOLVER.md`. Zero-dependency module imported by both `doctor.ts` and `check-resolvable.ts`. Parameterized `startDir` makes tests hermetic. **v0.31.7:** read-path / write-path split. `autoDetectSkillsDir` (shared, read+write-safe) gains tier-0 `$GBRAIN_SKILLS_DIR` explicit operator override (Docker mounts, CI, monorepo subdirs) ahead of the existing 4-tier chain. New `autoDetectSkillsDirReadOnly` wraps it with a tier-5 install-path fallback that walks up from `fileURLToPath(import.meta.url)` and gates on `isGbrainRepoRoot` so unrelated repos can't false-positive. Read-path callers (`doctor`, `check-resolvable`, `routing-eval`) use the read-only variant; write-path callers (`skillpack install`, `skillify scaffold`, `post-install-advisory`) deliberately stay on the shared function so `gbrain skillpack install` from `~` cannot silently retarget the bundled gbrain repo's `skills/` instead of the user's actual workspace. Two new `SkillsDirSource` variants: `'env_explicit'`, `'install_path'`. New `AUTO_DETECT_HINT_READ_ONLY` documents the extra tier. The D6 `--fix` safety gate in `doctor.ts` + `check-resolvable.ts` refuses auto-repair when `detected.source === 'install_path'` so `gbrain doctor --fix` from `~` cannot silently rewrite the bundled install tree.
|
||||
- `src/commands/check-resolvable.ts` — Standalone CLI wrapper (v0.16.4) over `checkResolvable()`. Exports `parseFlags`, `resolveSkillsDir`, `DEFERRED`, `runCheckResolvable`. Exit rule: **1 on any issue (warnings OR errors)**, stricter than doctor's `ok` flag — honors README:259. Stable JSON envelope `{ok, skillsDir, report, autoFix, deferred, error, message}` — same shape on success and error paths. `--fix` path runs `autoFixDryViolations` BEFORE `checkResolvable` (same ordering as doctor). `scripts/skillify-check.ts` subprocess-calls `gbrain check-resolvable --json` (cached per process) and fails loud on binary-missing — no silent false-pass. **v0.19:** AGENTS.md workspaces now resolve natively (see `src/core/resolver-filenames.ts`) — gbrain inspects the 107-skill OpenClaw deployment whether the routing file is `RESOLVER.md` or `AGENTS.md`. `DEFERRED[]` is empty — Checks 5 + 6 shipped as real code, not issue URLs. **v0.31.7:** the resolver lookup switched from first-match-wins to the multi-file merge in `src/core/check-resolvable.ts` — entries collected from every `RESOLVER.md` / `AGENTS.md` across the skills dir AND its parent, deduped by `skillPath` (first occurrence wins). Lifted reachable skills on the reference OpenClaw layout from 37/224 to 200/224 — the deployment ships a thin `skills/RESOLVER.md` (~40 entries from skillpack) plus a fat `../AGENTS.md` (200+ entries, the real dispatcher), and the previous code only saw the first one. The CLI also switched to `autoDetectSkillsDirReadOnly` so `cd ~ && gbrain check-resolvable` finds the bundled skills via the install-path fallback. `--fix` carries the same D6 safety gate as `gbrain doctor --fix`: refuses to write when `detected.source === 'install_path'`.
|
||||
- `src/core/resolver-filenames.ts` (v0.19) — central list of accepted routing filenames (`RESOLVER.md`, `AGENTS.md`). Shared by `findRepoRoot`, `check-resolvable`, and skillpack install so every code path walks the same fallback chain.
|
||||
@@ -4341,6 +4341,323 @@ Set `enabled: false` to disable quiet hours entirely (e.g., for 24/7 monitoring)
|
||||
|
||||
---
|
||||
|
||||
## docs/guides/scaling-skills.md
|
||||
|
||||
Source: https://raw.githubusercontent.com/garrytan/gbrain/master/docs/guides/scaling-skills.md
|
||||
|
||||
# Scaling skills past 300 without drowning the context window
|
||||
|
||||
When an agent grows past 100 skills, a wall starts forming. Sessions take
|
||||
longer to start. The model gets a little dumber about which skill to pick.
|
||||
Tokens that should be powering reasoning are powering a skill catalog the
|
||||
model reads on every turn whether it needs to or not.
|
||||
|
||||
This guide is the recipe for breaking through that wall without deleting
|
||||
capabilities. Three tiers, one resolver, one safety net. Production-tested
|
||||
on a 306-skill agent (Garry's OpenClaw, the agent behind Y Combinator's
|
||||
president). The pattern works whether you run OpenClaw, Hermes, Claude Code,
|
||||
Cursor, or your own MCP-aware agent.
|
||||
|
||||
## The problem
|
||||
|
||||
OpenClaw scans every skill file on disk at session start and injects them
|
||||
into the system prompt as `<available_skills>` entries. The model sees a
|
||||
name, description, and file path for each one. When a request matches, the
|
||||
model reads the full SKILL.md and follows it.
|
||||
|
||||
This is great architecture at 50 skills. At 100, it's fine. At 200, it
|
||||
starts to drag. At 300, the system prompt eats more than 25,000 tokens on
|
||||
skill descriptions alone. Tokens that aren't going to reasoning, context,
|
||||
or actual work.
|
||||
|
||||
The symptoms compound:
|
||||
|
||||
- Sessions take noticeably longer to start.
|
||||
- The model has less room for conversation history.
|
||||
- Skill routing gets fuzzier. With 300 descriptions competing for attention,
|
||||
the model occasionally picks the wrong one.
|
||||
- Cost goes up because every turn carries the full skill manifest.
|
||||
|
||||
The naive fix is to delete skills you don't use often. Don't do this. The
|
||||
whole point of skills is that capabilities compound. A gift pipeline that
|
||||
fires twice a month saves 30 minutes each time it does. A flight tracker
|
||||
fires once per trip and prevents a missed Uber. Deleting low-frequency
|
||||
skills optimizes for prompt size at the cost of capability. You wouldn't
|
||||
delete apps from your phone because the home screen is too crowded. You'd
|
||||
organize them.
|
||||
|
||||
## The three tiers
|
||||
|
||||
Not all skills need to be visible to the model at all times. Some are core.
|
||||
Some are specialized. Some are dormant.
|
||||
|
||||
### Tier A: always loaded (~35 skills)
|
||||
|
||||
The skills the model needs on every single turn. Brain search, email triage,
|
||||
calendar, meeting ingestion, content creation, the executive assistant.
|
||||
They stay in the system prompt's `<available_skills>` manifest. The model
|
||||
sees them natively and routes to them without any lookup.
|
||||
|
||||
### Tier B: resolver-routed (~85 skills)
|
||||
|
||||
Real, active skills that fire regularly but don't need to pollute every
|
||||
turn. Gift pipeline, flight tracker, investor update ingestion, adversary
|
||||
tracking, book mirror, civic intelligence. They live on disk. They have
|
||||
full SKILL.md files. But OpenClaw doesn't inject them into the prompt.
|
||||
|
||||
Instead, a compact RESOLVER.md handles routing. One line per skill with
|
||||
trigger phrases:
|
||||
|
||||
```markdown
|
||||
- **gift-advisor**: gift idea | what should I bring | birthday gift | housewarming
|
||||
- **flight-tracker**: track my flight | flight status | when does my flight land
|
||||
- **investor-update-ingest**: investor update | portfolio update | company metrics
|
||||
```
|
||||
|
||||
When the model sees "what should I bring to Jessica's dinner," it checks
|
||||
the resolver, finds `gift-advisor`, reads the SKILL.md, and executes. Same
|
||||
result. Zero wasted tokens on the other 84 turns where gifts aren't relevant.
|
||||
|
||||
### Tier C: dormant (~180 skills)
|
||||
|
||||
Built-in OpenClaw skills that aren't in active rotation (1Password, Discord,
|
||||
Notion, Trello, integrations you haven't wired up yet) plus specialized
|
||||
skills that almost never fire. They're explicitly disabled in the config
|
||||
with `enabled: false`. They exist on disk as documentation and potential.
|
||||
Flip one boolean to wake them up. Zero tokens contributed to every prompt
|
||||
until then.
|
||||
|
||||
### The numbers
|
||||
|
||||
Before tiering, on Garry's 306-skill OpenClaw:
|
||||
|
||||
| Metric | Before |
|
||||
|---|---|
|
||||
| Skills in system prompt | 306 |
|
||||
| Skill-description tokens per turn | ~25,000 |
|
||||
| Skill routing accuracy | degrading |
|
||||
| Session startup | slow |
|
||||
|
||||
After tiering:
|
||||
|
||||
| Metric | After |
|
||||
|---|---|
|
||||
| Skills in system prompt (Tier A) | 35 |
|
||||
| Skill-description tokens per turn | ~4,000 |
|
||||
| Skills still accessible (A + B + C) | 301 |
|
||||
| Capability loss | zero |
|
||||
| **Tokens freed per turn** | **~21,000** |
|
||||
|
||||
21K tokens per turn is not a small optimization. It's the difference between
|
||||
the model having room to think and the model being squeezed. It's the
|
||||
difference between carrying 3 pages of conversation history and carrying 15.
|
||||
|
||||
## What the resolver actually does
|
||||
|
||||
The resolver is cheaper than the manifest. That's the load-bearing insight.
|
||||
|
||||
OpenClaw's native skill manifest puts ~80 tokens per skill into the system
|
||||
prompt (name + description + location). At 300 skills that's 24,000 tokens
|
||||
spent every turn whether the model needs the catalog or not.
|
||||
|
||||
The resolver puts ~15 tokens per skill into a compact markdown list. At
|
||||
300 skills that's 4,500 tokens. But it only fires when the model checks
|
||||
it, which is only when the request doesn't match a Tier A skill. Most
|
||||
turns, the resolver costs zero tokens because the Tier A match handles it.
|
||||
|
||||
This is the routing-table pattern but applied to the skill manifest itself.
|
||||
The resolver routes to skills, but it also routes around skills, keeping
|
||||
them out of the context window until they're needed.
|
||||
|
||||
GBrain ships with a [bundled `skills/RESOLVER.md`](../../skills/RESOLVER.md)
|
||||
you can use as a reference shape. The skillpack story for distributing
|
||||
your own resolvers across machines is covered in
|
||||
[skillpacks as scaffolding](skillpacks-as-scaffolding.md).
|
||||
|
||||
## The compact list format (v0.41.7.0)
|
||||
|
||||
GBrain's resolver parser used to require markdown tables:
|
||||
|
||||
```markdown
|
||||
| Trigger | Skill |
|
||||
|---------|-------|
|
||||
| "gift idea" | `skills/gift-advisor/SKILL.md` |
|
||||
```
|
||||
|
||||
That's fine when you have 20 entries. It gets unwieldy at 200, and at 300
|
||||
it's unreadable. OpenClaw deployments quietly evolved a compact list
|
||||
format that scales better:
|
||||
|
||||
```markdown
|
||||
- **gift-advisor**: gift idea | what should I bring | birthday gift
|
||||
- **flight-tracker**: track my flight | flight status | when does my flight land
|
||||
```
|
||||
|
||||
Before v0.41.7.0, `gbrain doctor` only spoke the table dialect. On a
|
||||
306-skill compact-format resolver, the doctor reported every skill as
|
||||
unreachable: **238 FAIL errors on every doctor run**. The parser was
|
||||
silently treating the compact dialect as zero skills.
|
||||
|
||||
v0.41.7.0 ships dual-format support. The same `parseResolverEntries`
|
||||
function reads both table rows and list rows in the same file, with the
|
||||
v0.31.7 multi-resolver merge (skillpack `skills/RESOLVER.md` + workspace
|
||||
`../AGENTS.md`) folding everything into one unified view. Run `gbrain doctor`
|
||||
and the 238 FAILs collapse to 0.
|
||||
|
||||
### The list-format contract
|
||||
|
||||
A few rules to keep the parser unambiguous:
|
||||
|
||||
- **Skill names must be kebab-lowercase.** `gift-advisor`, `flight-tracker`,
|
||||
`email-triage`. Names that start with an uppercase letter (`MyTool`,
|
||||
`Note`, `Convention`) are deliberately ignored. This is what stops prose
|
||||
bullets like `- **Note**: see [link]` from being mis-parsed as skill
|
||||
rows in real-world AGENTS.md files.
|
||||
- **The path always resolves to `skills/<name>/SKILL.md`.** An optional
|
||||
`→ \`skills/path\`` (or ASCII `->`) suffix is allowed for readability,
|
||||
but the parser strips it. For non-conventional paths (skills under
|
||||
nested directories, references into `conventions/`, anything that
|
||||
isn't `skills/<name>/SKILL.md`), use the table format.
|
||||
- **Triggers separate with `|`.** Empty pieces and the literal `...`
|
||||
placeholder are dropped. Each trigger becomes its own resolver entry,
|
||||
all pointing at the same skill.
|
||||
- **Bold or plain.** `- **name**: triggers` is preferred. `- name: triggers`
|
||||
works as a fallback.
|
||||
|
||||
You can mix table and list rows in the same file. Useful when a brain
|
||||
inherits a table-format `RESOLVER.md` from gbrain and a list-format
|
||||
`../AGENTS.md` from OpenClaw.
|
||||
|
||||
## The doctor safety net
|
||||
|
||||
The danger with tiering is invisible skill loss. You disable a skill from
|
||||
native scanning, forget to add it to the resolver, and now the agent can't
|
||||
do something it used to do. You won't notice until the moment you need it.
|
||||
|
||||
`gbrain doctor` walks every skill on disk and verifies it's reachable,
|
||||
either through native scanning (Tier A) or through the resolver (Tier B
|
||||
and C). On Garry's setup, the first run after tiering found 63 unreachable
|
||||
skills. Sixty-three capabilities that existed on disk but had no routing
|
||||
path. Fixed in an hour by adding resolver entries.
|
||||
|
||||
Run it after every skill change:
|
||||
|
||||
```bash
|
||||
gbrain doctor
|
||||
```
|
||||
|
||||
For CI gates, use the JSON-emitting variant:
|
||||
|
||||
```bash
|
||||
gbrain check-resolvable --json
|
||||
gbrain check-resolvable --strict # warnings fail too
|
||||
```
|
||||
|
||||
If a skill is unreachable, the output tells you which one and suggests
|
||||
the fix. The resolver is a document. Documents are cheap to fix.
|
||||
|
||||
## Implementation walkthrough
|
||||
|
||||
Three changes. Total time about 45 minutes once you've decided which
|
||||
skills go in which tier.
|
||||
|
||||
### 1. Audit and tier your skills
|
||||
|
||||
Walk through every skill. Ask: does this need to fire on every turn?
|
||||
|
||||
- If yes → Tier A.
|
||||
- If it fires weekly or less but is real → Tier B.
|
||||
- If you don't use it → Tier C.
|
||||
|
||||
### 2. Disable Tier B and C in your agent's config
|
||||
|
||||
For OpenClaw, the file is `openclaw.json`. Add an entry per disabled skill:
|
||||
|
||||
```json
|
||||
{
|
||||
"skills": {
|
||||
"entries": {
|
||||
"gift-advisor": { "enabled": false },
|
||||
"flight-tracker": { "enabled": false },
|
||||
"1password": { "enabled": false }
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The exact config shape depends on which agent runtime you use. The point
|
||||
is the same in all of them: tell the runtime not to inject this skill into
|
||||
the system prompt. The file stays on disk; only the prompt injection stops.
|
||||
|
||||
### 3. Write the resolver
|
||||
|
||||
One line per Tier B and Tier C skill. Trigger phrases that match how you
|
||||
actually ask for things:
|
||||
|
||||
```markdown
|
||||
- **gift-advisor**: gift idea | what should I bring | birthday gift
|
||||
- **flight-tracker**: track my flight | flight status | when do I land
|
||||
- **investor-update-ingest**: investor update | portfolio update | company metrics
|
||||
```
|
||||
|
||||
That's it. The model handles the rest. When a request doesn't match Tier A,
|
||||
it checks the resolver, reads the matching SKILL.md, and executes.
|
||||
|
||||
### 4. Run `gbrain doctor` and fix any unreachable skills
|
||||
|
||||
The doctor sweep tells you which skills don't have a routing path. Add a
|
||||
resolver entry for each one, re-run, repeat until the count is zero.
|
||||
|
||||
## A lesson from the first version
|
||||
|
||||
I initially converted my resolver from a clean list format to a table
|
||||
format because the validator only spoke tables. That was wrong. When a
|
||||
tool fails against valid data, the right move is to fix the tool, not
|
||||
reshape the data. The list format was correct, compact, readable, easy
|
||||
to maintain. The parser needed to support both shapes. v0.41.7.0 is
|
||||
that fix.
|
||||
|
||||
The same principle applies everywhere in agent systems. Your SKILL.md is
|
||||
the source of truth. Your AGENTS.md is the source of truth. Your resolver
|
||||
is the source of truth. When tooling disagrees with your configuration,
|
||||
the tooling is wrong. Fix the tooling.
|
||||
|
||||
## The scaling curve
|
||||
|
||||
At 50 skills, you don't need any of this. Just load everything.
|
||||
|
||||
At 100, you start feeling the drag but can push through.
|
||||
|
||||
At 200, routing accuracy drops and sessions get noticeably slower. This
|
||||
is where most people stop adding skills, which means their agent stops
|
||||
getting more capable. Bad trade.
|
||||
|
||||
At 300+, tiering is mandatory. But with tiering, there's no ceiling.
|
||||
1,000 skills with 35 in the hot path and 965 in the resolver is the same
|
||||
per-turn cost as 35 skills with no resolver. The cost stays flat.
|
||||
Capabilities compound.
|
||||
|
||||
The architecture that gets you from 50 to 300 is different from the
|
||||
architecture that gets you from 10 to 50. That's normal. Systems that
|
||||
scale change shape. The important thing is that each tier preserves full
|
||||
capability. You're organizing, not deleting.
|
||||
|
||||
## Related
|
||||
|
||||
- [Skill development cycle](skill-development.md) — the 5-step loop for
|
||||
turning a repeated task into a real skill.
|
||||
- [Skillpacks as scaffolding](skillpacks-as-scaffolding.md) — how to
|
||||
distribute a coherent set of skills across machines and agents.
|
||||
- [Sub-agent routing](sub-agent-routing.md) — when to delegate to a
|
||||
sub-agent vs handle in-line, and the model routing table for each path.
|
||||
|
||||
GBrain: [github.com/garrytan/gbrain](https://github.com/garrytan/gbrain).
|
||||
The `parseResolverEntries` parser lives at
|
||||
[`src/core/check-resolvable.ts`](../../src/core/check-resolvable.ts);
|
||||
the bundled resolver lives at [`skills/RESOLVER.md`](../../skills/RESOLVER.md).
|
||||
|
||||
---
|
||||
|
||||
## docs/mcp/DEPLOY.md
|
||||
|
||||
Source: https://raw.githubusercontent.com/garrytan/gbrain/master/docs/mcp/DEPLOY.md
|
||||
@@ -5176,621 +5493,3 @@ should match your expectations for the corpus.
|
||||
---
|
||||
|
||||
# Migrations
|
||||
|
||||
## docs/UPGRADING_DOWNSTREAM_AGENTS.md
|
||||
|
||||
Source: https://raw.githubusercontent.com/garrytan/gbrain/master/docs/UPGRADING_DOWNSTREAM_AGENTS.md
|
||||
|
||||
# Upgrading Downstream Agents
|
||||
|
||||
GBrain ships skills in `skills/`. Downstream agents (custom OpenClaw deployments,
|
||||
agent forks of any kind) often **copy** these skill files into their own workspace and
|
||||
diverge over time — adding agent-specific phases, removing irrelevant ones, tightening
|
||||
language. Once that happens, gbrain can't push updates to those forks. The agent has
|
||||
to apply the diffs by hand.
|
||||
|
||||
This doc lists the exact diffs each downstream agent needs to apply when upgrading.
|
||||
Cross-reference against your fork's local skill files.
|
||||
|
||||
## Why this exists
|
||||
|
||||
`gbrain upgrade` ships the new binary. `gbrain post-upgrade [--execute --yes]` runs
|
||||
the schema migrations and backfills the data. But the **skill files themselves**
|
||||
that tell the agent how to behave — those are user-owned. If your `~/git/<your-agent>/workspace/skills/brain-ops/SKILL.md`
|
||||
says `# Based on gbrain v0.10.0` at the top, it doesn't know about v0.12.0 features.
|
||||
|
||||
The agent will keep manually calling `gbrain link` after every `put_page` (now redundant —
|
||||
auto-link does it), miss out on `gbrain graph-query` for relationship questions, and
|
||||
not know to backfill the structured timeline.
|
||||
|
||||
## How to apply
|
||||
|
||||
1. Identify your forked skill files. Typically at `~/git/<your-agent>/workspace/skills/` or wherever your agent's skill directory lives.
|
||||
2. For each skill listed below, find the matching phase/section in your fork.
|
||||
3. Apply the diff (paste the new block in the indicated location).
|
||||
4. Update the version banner at the top of your fork (`# Based on gbrain v0.12.0`).
|
||||
5. Verify: ask the agent to write a test page and confirm the response includes
|
||||
`auto_links: { created, removed, errors }`.
|
||||
|
||||
Total time: ~10 minutes for all four skills.
|
||||
|
||||
---
|
||||
|
||||
## 1. brain-ops/SKILL.md
|
||||
|
||||
**Where:** Insert a new `### Phase 2.5` section immediately after `### Phase 2: On Every Inbound Signal`.
|
||||
|
||||
**Why:** Phase 2.5 declares that auto-link runs automatically. Without this, the
|
||||
agent's mental model says it must call `gbrain link` after every `put_page`, which
|
||||
is now redundant and can cause double-add warnings.
|
||||
|
||||
```markdown
|
||||
### Phase 2.5: Structured Graph Updates (automatic)
|
||||
|
||||
Every `put_page` call automatically extracts entity references and writes them
|
||||
to the graph (`links` table) with inferred relationship types. Stale links
|
||||
(refs no longer in the page text) are removed in the same call. This is
|
||||
"auto-link" reconciliation.
|
||||
|
||||
- No manual `add_link` calls needed for ordinary page writes.
|
||||
- Inferred link types: `attended` (meeting -> person), `works_at`, `invested_in`,
|
||||
`founded`, `advises`, `source` (frontmatter), `mentions` (default).
|
||||
- The `put_page` MCP response includes `auto_links: { created, removed, errors }`
|
||||
so the agent can verify outcomes.
|
||||
- To disable: `gbrain config set auto_link false`. Default is on.
|
||||
- Timeline entries with specific dates still need explicit `gbrain timeline-add`
|
||||
(or batch via `gbrain extract timeline --source db`).
|
||||
```
|
||||
|
||||
**Also update the Iron Law section.** If your fork still says "Back-links maintained
|
||||
on every brain write (Iron Law)" without qualification, append:
|
||||
|
||||
```markdown
|
||||
**v0.12.0 update:** Auto-link satisfies the Iron Law for entity-reference links
|
||||
on every `put_page`. The agent's Iron Law obligation is now: include the
|
||||
entity reference in the page content (e.g., `[Alice](people/alice)`); auto-link
|
||||
handles the structured row. Manual `add_link` calls are reserved for
|
||||
relationships you can't express in markdown content.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. meeting-ingestion/SKILL.md
|
||||
|
||||
**Where:** Append to the end of `### Phase 3: Attendee enrichment`.
|
||||
|
||||
**Why:** Eliminates redundant `gbrain link` calls per attendee (auto-link handles them
|
||||
when the meeting page references attendees as `[Name](people/slug)`).
|
||||
|
||||
```markdown
|
||||
**Note (v0.12.0):** Once the meeting page is written via `gbrain put`, the
|
||||
auto-link post-hook automatically creates `attended` links from the meeting
|
||||
to each attendee whose page is referenced as `[Name](people/slug)`. You don't
|
||||
need to call `gbrain link` for attendees. You DO still need `gbrain timeline-add`
|
||||
for dated events (auto-link only handles links, not timeline entries).
|
||||
```
|
||||
|
||||
**Where:** In `### Phase 4: Entity propagation`, the line "Back-link from entity page
|
||||
to meeting page" can be replaced with:
|
||||
|
||||
```markdown
|
||||
4. Entity references in the meeting page body auto-create the link via auto-link.
|
||||
For incoming references on the entity page (entity page → meeting page), edit
|
||||
the entity page to mention the meeting and `put_page` it — auto-link handles
|
||||
the rest.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. signal-detector/SKILL.md
|
||||
|
||||
**Where:** Append to the end of `### Phase 2: Entity Detection`.
|
||||
|
||||
**Why:** Same logic as brain-ops — eliminates manual `gbrain link` after writing
|
||||
originals/ideas pages that reference people or companies.
|
||||
|
||||
```markdown
|
||||
**Auto-link (v0.12.0):** When you write/update an originals or ideas page that
|
||||
references a person or company, the auto-link post-hook on `put_page`
|
||||
automatically creates the link from the new page to that entity. You don't
|
||||
need to call `gbrain link` manually. Timeline entries still need explicit calls.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. enrich/SKILL.md
|
||||
|
||||
**Where:** Replace `### Step 7: Cross-reference` with the v0.12.0 version.
|
||||
|
||||
**Why:** Step 7 used to be primarily about creating links between related entity
|
||||
pages. With auto-link, that's automatic. Step 7 is now about content updates,
|
||||
not link creation.
|
||||
|
||||
Old (delete):
|
||||
```markdown
|
||||
### Step 7: Cross-reference
|
||||
|
||||
- Update company pages from person enrichment (and vice versa)
|
||||
- Update related project/deal pages if relevant context surfaced
|
||||
- Check index files if the brain uses them
|
||||
- Add back-links manually via `gbrain link` for any new entity references
|
||||
```
|
||||
|
||||
New (paste):
|
||||
```markdown
|
||||
### Step 7: Cross-reference
|
||||
|
||||
- Update company pages from person enrichment (and vice versa)
|
||||
- Update related project/deal pages if relevant context surfaced
|
||||
- Check index files if the brain uses them
|
||||
|
||||
**Note (v0.12.0):** Links between brain pages are auto-created on every
|
||||
`put_page` call (auto-link post-hook). Step 7 focuses on content
|
||||
cross-references (updating related pages' compiled truth with new signal
|
||||
from this enrichment), not on creating links. Verify via the `auto_links`
|
||||
field in the put_page response (`{ created, removed, errors }`).
|
||||
Timeline entries still need explicit `gbrain timeline-add` calls.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## After all four diffs are applied
|
||||
|
||||
1. **Bump the version banner** at the top of each forked file:
|
||||
```
|
||||
# Based on gbrain v0.12.0 skills/<skill-name>, extended with <your-agent>-specific config
|
||||
```
|
||||
|
||||
2. **Run the v0.12.0 backfill** (this populates the graph for your existing brain):
|
||||
```bash
|
||||
gbrain post-upgrade
|
||||
```
|
||||
The v0.12.0 release wires post-upgrade to call `apply-migrations --yes`
|
||||
automatically, which runs the v0_12_0 orchestrator (schema → config check →
|
||||
`extract links --source db` → `extract timeline --source db` → verify).
|
||||
Idempotent; cheap when nothing is pending.
|
||||
|
||||
3. **Verify auto-link works:** ask the agent to write a test page that references
|
||||
`[Some Person](people/some-person)`. Confirm the put_page response includes
|
||||
`auto_links: { created: 1, removed: 0, errors: 0 }`.
|
||||
|
||||
4. **Verify graph traversal works:**
|
||||
```bash
|
||||
gbrain graph-query people/some-well-connected-person --depth 2
|
||||
```
|
||||
Should return an indented tree of typed edges.
|
||||
|
||||
---
|
||||
|
||||
## v0.12.2 hotfix (data-correctness, no skill edits)
|
||||
|
||||
v0.12.2 is a Postgres data-correctness hotfix. No forked skill files need to
|
||||
change — the skill contracts are unchanged. But you DO need to run the migration,
|
||||
and you should know about one behavior change in markdown parsing.
|
||||
|
||||
### 1. Run the migration (Postgres-backed brains)
|
||||
|
||||
```bash
|
||||
gbrain upgrade
|
||||
```
|
||||
|
||||
The `v0_12_2` orchestrator runs `gbrain repair-jsonb` automatically. It rewrites
|
||||
rows where `jsonb_typeof = 'string'` across `pages.frontmatter`, `raw_data.data`,
|
||||
`ingest_log.pages_updated`, `files.metadata`, and `page_versions.frontmatter`.
|
||||
Idempotent, safe to re-run. PGLite brains no-op cleanly.
|
||||
|
||||
Verify after upgrade:
|
||||
|
||||
```bash
|
||||
gbrain repair-jsonb --dry-run --json # expect totalRepaired: 0
|
||||
```
|
||||
|
||||
### 2. Recover any truncated wiki articles
|
||||
|
||||
If your brain imported wiki-style markdown before v0.12.2, some pages were
|
||||
silently truncated (any standalone `---` in body content was treated as a
|
||||
timeline separator). Re-import from source:
|
||||
|
||||
```bash
|
||||
gbrain sync --full
|
||||
```
|
||||
|
||||
The new `splitBody` rebuilds `compiled_truth` correctly.
|
||||
|
||||
### 3. Know the splitBody contract going forward
|
||||
|
||||
`splitBody` now requires an explicit timeline sentinel. Recognized markers
|
||||
(priority order):
|
||||
|
||||
1. `<!-- timeline -->` (preferred — what `serializeMarkdown` emits)
|
||||
2. `--- timeline ---` (decorated separator)
|
||||
3. `---` directly before `## Timeline` or `## History` heading (backward-compat)
|
||||
|
||||
A bare `---` in body text is now a markdown horizontal rule, not a timeline
|
||||
separator. If your agent writes pages with a bare `---` delimiter, migrate to
|
||||
`<!-- timeline -->` — the `serializeMarkdown` helper already does this.
|
||||
|
||||
### 4. Wiki subtypes now auto-typed
|
||||
|
||||
`inferType` now auto-detects five additional directory patterns as their own
|
||||
page types (previously they all defaulted to `concept`):
|
||||
|
||||
| Path pattern | New type |
|
||||
|------------------------|----------------|
|
||||
| `/wiki/analysis/` | `analysis` |
|
||||
| `/wiki/guides/` | `guide` |
|
||||
| `/wiki/hardware/` | `hardware` |
|
||||
| `/wiki/architecture/` | `architecture` |
|
||||
| `/writing/` | `writing` |
|
||||
|
||||
If your skills or queries filter by `type=concept` and expect wiki content in
|
||||
that bucket, update them to include the new types.
|
||||
|
||||
---
|
||||
|
||||
## v0.13.0 — Frontmatter Relationship Indexing
|
||||
|
||||
**Verdict: no action required for most skills.** v0.13 projects YAML frontmatter fields into the graph as typed edges. The ingestion API is unchanged — keep calling `put_page` with frontmatter the way you do today; the graph auto-populates behind the scenes.
|
||||
|
||||
Three skills get an optional new phase if you want to consume the new `auto_links.unresolved` response field. Without this, unresolvable frontmatter names silently skip (same as v0.12 behavior).
|
||||
|
||||
### 1. meeting-ingestion/SKILL.md (optional)
|
||||
|
||||
**Where:** Add a new section after "Phase 3: Write Meeting Page".
|
||||
|
||||
```markdown
|
||||
### Phase 3.5: Check for unresolved attendees (v0.13+)
|
||||
|
||||
After `put_page`, inspect `response.auto_links.unresolved` — an array of frontmatter
|
||||
references that did not resolve to existing pages. For meetings, this usually means
|
||||
attendees you haven't created a person page for yet.
|
||||
|
||||
If `unresolved.length > 0`:
|
||||
- Option 1 (create pages now): trigger an enrichment pass to build the missing people pages.
|
||||
- Option 2 (defer): log the unresolved names to the enrichment queue for later.
|
||||
- Option 3 (accept the gap): the attendee edge will not be created until a page exists.
|
||||
Re-running `gbrain extract links --source db --include-frontmatter` after creating
|
||||
the page fills in the missing edges.
|
||||
```
|
||||
|
||||
### 2. enrich/SKILL.md (optional)
|
||||
|
||||
**Where:** Add to the enrichment trigger list.
|
||||
|
||||
```markdown
|
||||
### Drain unresolved frontmatter names (v0.13+)
|
||||
|
||||
If any `put_page` response includes `auto_links.unresolved` entries, the enrichment
|
||||
tier should pick up those (field, name) pairs and try to create the missing entity
|
||||
pages. Example flow:
|
||||
|
||||
1. signal-detector captures a meeting with `attendees: [Alice Known, Unknown Person]`
|
||||
2. put_page returns `auto_links.unresolved = [{field: 'attendees', name: 'Unknown Person'}]`
|
||||
3. enrichment tier consumes `Unknown Person` → web search → creates `people/unknown-person.md`
|
||||
4. The next put_page (or a backfill run) wires up the `attended` edge automatically
|
||||
```
|
||||
|
||||
### 3. idea-ingest/SKILL.md (optional)
|
||||
|
||||
**Where:** Same pattern as meeting-ingestion — check `auto_links.unresolved` after `put_page`, route names to enrichment.
|
||||
|
||||
### Unchanged skills (no diffs needed)
|
||||
|
||||
- **brain-ops/SKILL.md** — auto-link mechanics are internal; the write path stays the same.
|
||||
- **signal-detector/SKILL.md** — signal capture path unchanged.
|
||||
- **query/SKILL.md** — `traverse_graph` now returns richer results automatically.
|
||||
- **daily-task-manager/SKILL.md**, **briefing/SKILL.md**, **citation-fixer/SKILL.md**, **media-ingest/SKILL.md** — unchanged.
|
||||
|
||||
### New edge types you can filter in graph queries
|
||||
|
||||
v0.13 edges carry new `link_type` values. If your fork has graph-query skills that filter by type, these are now available:
|
||||
|
||||
- `works_at` (person → company) — from `company:`, `companies:`, or `key_people:`
|
||||
- `founded` (person → company) — from `founded:`
|
||||
- `invested_in` (investor → deal/company) — from `investors:` or `lead:`
|
||||
- `led_round` (lead → deal) — from `lead:`
|
||||
- `yc_partner` (partner → company) — from `partner:`
|
||||
- `attended` (person → meeting) — from `attendees:`
|
||||
- `discussed_in` (source → page) — from `sources:`
|
||||
- `source` (page → source) — from `source:`
|
||||
- `related_to` (page → target) — from `related:` or `see_also:`
|
||||
|
||||
### Migration timing
|
||||
|
||||
`gbrain upgrade` takes 2-5 min on a 46K-page brain (one-time). Runs out-of-process via `gbrain post-upgrade`. If your agent holds a DB connection during the upgrade, reconnect after; otherwise keep serving.
|
||||
|
||||
### Type normalization NOT in v0.13
|
||||
|
||||
Legacy rows with `link_type='attendee'` or `link_type='mention'` coexist with new `'attended'` / `'mentions'` rows. Your queries filtering on old type names keep working. A separate opt-in `gbrain normalize-types` command in v0.14 handles the rename.
|
||||
## v0.14.0 shell jobs (optional adoption, no skill edits)
|
||||
|
||||
Adds a `shell` job type to Minions so deterministic cron scripts (API fetch, token
|
||||
refresh, scrape + write) move off the LLM gateway. Zero tokens per fire. ~60%
|
||||
gateway CPU headroom at typical scale. Feature is **off by default**, existing
|
||||
installs keep running exactly as they did before. Nothing breaks.
|
||||
|
||||
To adopt, follow `skills/migrations/v0.14.0.md`. The short version:
|
||||
|
||||
1. Set `GBRAIN_ALLOW_SHELL_JOBS=1` on the worker process, then `gbrain jobs work`
|
||||
(Postgres). On PGLite, every crontab invocation uses `--follow` for inline
|
||||
execution; no persistent worker.
|
||||
2. Classify each of your host's cron entries: LLM-requiring (keep on gateway) vs
|
||||
deterministic (candidate for shell). Typical splits:
|
||||
- **Deterministic → shell:** `ycli-token-refresh`, `x-oauth2-refresh`,
|
||||
`x-garrytan-unified`, `calendar-sync-to-brain`, `github-pulse`,
|
||||
`frameio-scan`, `flight-tracker`, `x-raw-json-backfill`.
|
||||
- **LLM-requiring → stay:** `social-radar`, `content-ideas`, `adversary-vacuum`,
|
||||
`ea-inbox-sweep`, `morning-briefing`, `brain-maintenance`.
|
||||
3. For each deterministic cron, rewrite as:
|
||||
```cron
|
||||
3 13,16,19,22,1,4,7,10 * * * \
|
||||
gbrain jobs submit shell \
|
||||
--params '{"cmd":"node scripts/your-script.mjs","cwd":"/data/.openclaw/workspace"}' \
|
||||
--max-attempts 3 --timeout-ms 300000
|
||||
```
|
||||
4. Watch `gbrain jobs get <id>` for exit_code / stdout_tail / stderr_tail on each
|
||||
fire. Compare against pre-migration behavior before approving the next batch.
|
||||
|
||||
**No skill edits required.** The handler runs worker-side; skill files don't
|
||||
change. If your host exposed custom handlers via the plugin contract (v0.11.0),
|
||||
they still work the same way.
|
||||
|
||||
Iron rule: **never auto-rewrite the operator's crontab.** Every rewrite is
|
||||
per-cron, human-approved, with a diff. If you want automation later, the
|
||||
upcoming `gbrain crontab-to-minions <file>` helper is P1 in TODOS.
|
||||
|
||||
---
|
||||
|
||||
## v0.16.0: durable agent runtime
|
||||
|
||||
v0.15 ships `gbrain agent run` / `gbrain agent logs`, a new `subagent` handler
|
||||
type in Minions, and a plugin contract for host-repo subagent defs. None of the
|
||||
existing skills need surgery. The question for downstream agents is *how* to
|
||||
adopt the new runtime, not how to patch around a breaking change.
|
||||
|
||||
### 1. Run a worker with an Anthropic key
|
||||
|
||||
The subagent handlers (`subagent` and `subagent_aggregator`) are always
|
||||
registered on the worker. No separate opt-in flag — `ANTHROPIC_API_KEY` is
|
||||
the natural cost gate (no key, the SDK call fails on the first turn), and
|
||||
who-can-submit is already protected (`PROTECTED_JOB_NAMES` + trusted-submit:
|
||||
MCP callers get `permission_denied`; only `gbrain agent run` can insert
|
||||
these rows).
|
||||
|
||||
```bash
|
||||
ANTHROPIC_API_KEY=sk-ant-... gbrain jobs work
|
||||
```
|
||||
|
||||
Worker startup prints:
|
||||
|
||||
```
|
||||
[minion worker] subagent handlers enabled
|
||||
```
|
||||
|
||||
### 2. Ship your subagents as a plugin (OpenClaw + similar)
|
||||
|
||||
Move your custom subagent definitions out of your gbrain fork and into your own
|
||||
repo as a plugin. Concretely:
|
||||
|
||||
```
|
||||
~/<your-agent>/gbrain-plugin/
|
||||
├── gbrain.plugin.json
|
||||
└── subagents/
|
||||
├── meeting-ingestion.md
|
||||
├── signal-detector.md
|
||||
└── daily-task-prep.md
|
||||
```
|
||||
|
||||
`gbrain.plugin.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "your-openclaw",
|
||||
"version": "2026.4.20",
|
||||
"plugin_version": "gbrain-plugin-v1"
|
||||
}
|
||||
```
|
||||
|
||||
Each `subagents/*.md` is a plain-text agent definition — YAML frontmatter +
|
||||
body-as-system-prompt. Recognized frontmatter fields: `name`, `model`,
|
||||
`max_turns`, `allowed_tools` (must subset the derived brain-tool registry).
|
||||
|
||||
Turn it on:
|
||||
|
||||
```bash
|
||||
export GBRAIN_PLUGIN_PATH="$HOME/<your-agent>/gbrain-plugin"
|
||||
```
|
||||
|
||||
Worker startup prints `[plugin-loader] loaded '<name>' v<ver> (N subagents)`
|
||||
per plugin; any rejection (bad manifest, unknown tool in `allowed_tools`,
|
||||
version mismatch) shows up as a loud warning at startup, not a silent dispatch-
|
||||
time failure. See `docs/guides/plugin-authors.md` for the full contract.
|
||||
|
||||
### 3. Replace ephemeral subagent runs with durable ones
|
||||
|
||||
If your agent currently spawns ephemeral subagents (OpenClaw `Agent()`, ad-hoc
|
||||
Anthropic API calls, etc.) for work that should survive crashes, sleeps, or
|
||||
worker restarts, migrate those to `gbrain agent run`. The durability is free:
|
||||
|
||||
```bash
|
||||
gbrain agent run "analyze my last 50 journal pages for recurring themes" \
|
||||
--subagent-def analyzer --fanout-manifest manifests/journal-pages.json
|
||||
```
|
||||
|
||||
Every turn persists to `subagent_messages`, every tool call is a two-phase
|
||||
ledger, and `gbrain agent logs <job>` shows where it died + what the last
|
||||
successful call returned. No more "re-run from scratch because the session
|
||||
context evaporated."
|
||||
|
||||
### 4. `put_page` from subagents writes under an agent namespace
|
||||
|
||||
If you adopted the v0.15 subagent runtime, note that `put_page` calls
|
||||
originating from a subagent's tool dispatch MUST target
|
||||
`wiki/agents/<subagent_id>/...`. The schema shown to the model enforces this
|
||||
on first try; a server-side fail-closed check rejects anything else. This
|
||||
does NOT affect your skill files, CLI put_page calls, or MCP put_page —
|
||||
only tool-dispatched writes from inside an LLM loop.
|
||||
|
||||
Aggregation output (the final "here's what all N children found" brain page)
|
||||
goes via a separate trusted CLI path, not through a subagent tool call, so
|
||||
it can write anywhere you want.
|
||||
|
||||
Iron rule: **never grant an agent write access beyond its namespace**. The
|
||||
server-side check exists because dispatcher bugs happen; treat it as defense
|
||||
in depth, not the primary boundary.
|
||||
|
||||
---
|
||||
|
||||
## v0.22.4 — frontmatter-guard adoption
|
||||
|
||||
### 1. Stop hand-rolling frontmatter validators
|
||||
|
||||
If your fork has scripts that call `js-yaml` directly to validate brain page
|
||||
frontmatter, replace them with `gbrain frontmatter validate` calls. The CLI
|
||||
covers the seven canonical error classes and ships a `--json` envelope that's
|
||||
stable across releases.
|
||||
|
||||
```diff
|
||||
- # Custom validator script
|
||||
- node scripts/validate-frontmatter.mjs <path>
|
||||
+ gbrain frontmatter validate <path> --json
|
||||
```
|
||||
|
||||
For consumers that need the validator inside another script, import from
|
||||
gbrain's `markdown` export instead of duplicating logic:
|
||||
|
||||
```ts
|
||||
import { parseMarkdown } from 'gbrain/markdown';
|
||||
|
||||
const parsed = parseMarkdown(content, filePath, { validate: true, expectedSlug });
|
||||
for (const err of parsed.errors ?? []) {
|
||||
// err.code: MISSING_OPEN | MISSING_CLOSE | YAML_PARSE | SLUG_MISMATCH |
|
||||
// NULL_BYTES | NESTED_QUOTES | EMPTY_FRONTMATTER
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Drop any references to `lib/brain-writer.mjs`
|
||||
|
||||
If your fork's skills or scripts referenced an aspirational
|
||||
`lib/brain-writer.mjs` (it never shipped — the spec was in PR #392 and never
|
||||
landed), replace those references with the gbrain CLI. The `frontmatter-guard`
|
||||
skill lives at `skills/frontmatter-guard/SKILL.md` and points at
|
||||
`gbrain frontmatter validate` / `audit` / `install-hook`.
|
||||
|
||||
### 3. Wire the doctor subcheck into your health pipeline
|
||||
|
||||
`gbrain doctor` now reports `frontmatter_integrity` automatically. If your
|
||||
fork has a custom health pipeline (e.g. a daily Slack post about brain
|
||||
health), pull from `gbrain doctor --json` and surface the
|
||||
`frontmatter_integrity` row counts.
|
||||
|
||||
### 4. (Optional) Install the pre-commit hook on brain repos
|
||||
|
||||
For sources backed by git, the v0.22.4 install-hook helper drops a
|
||||
pre-commit script that blocks commits with malformed frontmatter:
|
||||
|
||||
```bash
|
||||
gbrain frontmatter install-hook
|
||||
```
|
||||
|
||||
Skip this if your brain isn't a git repo or if your downstream agent already
|
||||
enforces validation at write time. See `docs/integrations/pre-commit.md` for
|
||||
the full recipe.
|
||||
|
||||
### 5. Migration ergonomics — read pending-host-work.jsonl
|
||||
|
||||
After `gbrain apply-migrations --yes` runs the v0.22.4 audit, your agent
|
||||
should read `~/.gbrain/migrations/pending-host-work.jsonl` (filter to
|
||||
`migration === "0.22.4"`) and walk each entry's `command` field. Each entry
|
||||
points to a per-source `gbrain frontmatter validate <source_path> --fix`
|
||||
command — surface counts to the user, get explicit consent, then run.
|
||||
|
||||
The migration is **audit-only**. It never mutates brain content during
|
||||
`apply-migrations`. Your agent runs the fix command with user consent.
|
||||
|
||||
---
|
||||
|
||||
## Future versions
|
||||
|
||||
When gbrain ships a new version, this doc will be updated with the diffs for that
|
||||
version. Each new version appends a section; old sections stay so you can catch up
|
||||
multiple versions at once.
|
||||
|
||||
To check what your fork is missing:
|
||||
```bash
|
||||
diff <(grep -A3 "Based on gbrain" ~/<your-fork>/skills/brain-ops/SKILL.md) \
|
||||
<(grep "v[0-9]" ~/gbrain/skills/migrations/ | tail -3)
|
||||
```
|
||||
|
||||
|
||||
## v0.36.5.0 — Free-form secret inheritance for shell jobs calling `gbrain` CLI
|
||||
|
||||
**The change.** Shell-job params get a new `inherit:` field. Pass any
|
||||
snake_case config-key name on it; the worker resolves the value from its
|
||||
`loadConfig()` at child-spawn time and injects it into the child env. Names
|
||||
land in the row; values never persist from `inherit:`. Validation runs
|
||||
**pre-enqueue** in both submit paths (CLI + `submit_job` op), so a malformed
|
||||
payload never lands in `minion_jobs.data`.
|
||||
|
||||
**Why.** Pre-v0.36.5.0, agents that wanted to call `gbrain` from shell jobs
|
||||
had to either write `database_url` to `~/.gbrain/config.json` plaintext or
|
||||
pass `env: { GBRAIN_DATABASE_URL: "..." }` per-job. Both left plaintext
|
||||
secrets somewhere — disk or DB row. `inherit:` keeps names in the row and
|
||||
resolves values at spawn time.
|
||||
|
||||
**What your agent can do.** `inherit:` is free-form. Pass any config-key:
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"cmd": "gbrain sync --skip-failed && gbrain embed --stale",
|
||||
"cwd": "/data/gbrain",
|
||||
"inherit": ["database_url", "anthropic_api_key", "voyage_api_key"]
|
||||
}
|
||||
```
|
||||
|
||||
The env-key name in the child is derived by uppercasing the config-key:
|
||||
`database_url` → `GBRAIN_DATABASE_URL`, `anthropic_api_key` →
|
||||
`ANTHROPIC_API_KEY`, `voyage_api_key` → `VOYAGE_API_KEY`, etc. The validator
|
||||
does NOT police which config keys you inherit — the agent is in the same
|
||||
uid as the worker, so it's the agent's call.
|
||||
|
||||
**You can still use `env:`.** v0.36.5.0 does not forbid `env:{ ANYTHING }`.
|
||||
If you have a reason to put a value in the row plaintext (a non-secret
|
||||
correlation token, or a secret you know is OK to persist), pass it via
|
||||
`env:`. Prefer `inherit:` when you want the value out of the row.
|
||||
|
||||
**Worker setup** (one-time, per host):
|
||||
|
||||
- `gbrain config set database_url postgresql://...` (or any other key you
|
||||
want available for inherit)
|
||||
- OR put the key in `~/.gbrain/config.json` directly
|
||||
- OR set `GBRAIN_DATABASE_URL` / `DATABASE_URL` / per-provider env on the
|
||||
worker process
|
||||
|
||||
If the worker can't resolve a requested name, the validator fail-fasts at
|
||||
submit time with `gbrain config set <X>` hint. No more silent "No database
|
||||
URL" failures in child stderr minutes after submission.
|
||||
|
||||
**Also new.** A `gbrain doctor` check `home_dir_in_worktree` warns if
|
||||
`~/.gbrain/` lives inside a git worktree. A retroactive `~/.gbrain/.gitignore`
|
||||
(single line `*`) is now laid down by every `saveConfig()` call AND by
|
||||
`gbrain post-upgrade`, so existing users get coverage without re-running
|
||||
`gbrain init`. Honest scope: the `.gitignore` covers casual `git add` but does
|
||||
NOT cover already-tracked files, screenshots, backups, or `git add -f`.
|
||||
|
||||
**Strategy framing.** For agent-to-gbrain calls, the new canonical guide is
|
||||
`docs/guides/agent-to-gbrain.md`. Two distinct surfaces: HTTP MCP via OAuth
|
||||
for ops with MCP equivalents (`search`, `query`, `put_page`, etc.), and shell
|
||||
job + `inherit:` for `localOnly` admin ops (`sync`, `embed`, `dream`,
|
||||
`doctor`, etc.). Not a fallback hierarchy — pick by op.
|
||||
|
||||
**Errors to handle** (your agent submits shell jobs; surface these clearly):
|
||||
|
||||
| Error | What it means | Agent action |
|
||||
|---|---|---|
|
||||
| `shell: inherit must be an array of config-key names` | `inherit` wasn't an array. | Pass `"inherit": ["database_url", ...]`. |
|
||||
| `shell: inherit entries must be non-empty strings` | Element was empty, non-string, or null. | Use snake_case config-key names. |
|
||||
| `shell: inherit name "<X>" must match [a-z][a-z0-9_]*` | Name failed snake_case regex (uppercase, leading underscore, etc.). | Use the config-key verbatim — `database_url`, not `DATABASE_URL`. |
|
||||
| `shell: inherit requested "<X>" but worker has no <X> configured` | Worker can't resolve the name from its `loadConfig()`. | Run `gbrain config set <X> <value>` on the worker host. |
|
||||
|
||||
---
|
||||
|
||||
@@ -22,6 +22,7 @@ Repo: https://github.com/garrytan/gbrain
|
||||
- [docs/guides/cron-schedule.md](https://raw.githubusercontent.com/garrytan/gbrain/master/docs/guides/cron-schedule.md): Recurring job scheduling.
|
||||
- [docs/guides/minions-deployment.md](https://raw.githubusercontent.com/garrytan/gbrain/master/docs/guides/minions-deployment.md): Deploying the gbrain jobs worker: crontab + watchdog, inline --follow, systemd/Procfile/fly.toml, upgrade checklist.
|
||||
- [docs/guides/quiet-hours.md](https://raw.githubusercontent.com/garrytan/gbrain/master/docs/guides/quiet-hours.md): Notification hold + timezone-aware delivery.
|
||||
- [docs/guides/scaling-skills.md](https://raw.githubusercontent.com/garrytan/gbrain/master/docs/guides/scaling-skills.md): Three-tier architecture for agents with 300+ skills: always-loaded, resolver-routed, and dormant. Per-turn token math, the v0.41.7.0 compact list-format resolver, and the `gbrain doctor` safety net. 306 skills, ~21K tokens freed per turn, zero capability loss.
|
||||
- [docs/mcp/DEPLOY.md](https://raw.githubusercontent.com/garrytan/gbrain/master/docs/mcp/DEPLOY.md): MCP server deployment.
|
||||
|
||||
## AI providers
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "gbrain",
|
||||
"version": "0.41.6.0",
|
||||
"version": "0.41.7.0",
|
||||
"description": "Postgres-native personal knowledge brain with hybrid RAG search",
|
||||
"type": "module",
|
||||
"main": "src/core/index.ts",
|
||||
|
||||
@@ -119,6 +119,12 @@ export const SECTIONS: DocSection[] = [
|
||||
description: "Notification hold + timezone-aware delivery.",
|
||||
path: "docs/guides/quiet-hours.md",
|
||||
},
|
||||
{
|
||||
title: "docs/guides/scaling-skills.md",
|
||||
description:
|
||||
"Three-tier architecture for agents with 300+ skills: always-loaded, resolver-routed, and dormant. Per-turn token math, the v0.41.7.0 compact list-format resolver, and the `gbrain doctor` safety net. 306 skills, ~21K tokens freed per turn, zero capability loss.",
|
||||
path: "docs/guides/scaling-skills.md",
|
||||
},
|
||||
{
|
||||
title: "docs/mcp/DEPLOY.md",
|
||||
description: "MCP server deployment.",
|
||||
@@ -176,6 +182,12 @@ export const SECTIONS: DocSection[] = [
|
||||
description:
|
||||
"Patches for downstream agent skill forks. One section per release.",
|
||||
path: "docs/UPGRADING_DOWNSTREAM_AGENTS.md",
|
||||
// Excluded from inlined bundle (v0.41.7.0): 25KB of release-by-release
|
||||
// migration patches that are valuable as a reference but don't need
|
||||
// to ride along in every llms-full.txt fetch. Pushes the bundle back
|
||||
// under FULL_SIZE_BUDGET after the v0.41.7.0 scaling-skills guide
|
||||
// landed.
|
||||
includeInFull: false,
|
||||
},
|
||||
{
|
||||
title: "skills/migrations/",
|
||||
|
||||
@@ -114,7 +114,32 @@ interface ResolverEntry {
|
||||
section: string; // e.g., 'Brain operations'
|
||||
}
|
||||
|
||||
/** Parse RESOLVER.md markdown tables into structured entries. */
|
||||
/**
|
||||
* Parse RESOLVER.md / AGENTS.md into structured entries. Supports two formats
|
||||
* that can mix in one file:
|
||||
*
|
||||
* Format 1 (table) — original gbrain shape:
|
||||
* | trigger phrase | `skills/<name>/SKILL.md` |
|
||||
*
|
||||
* Format 2 (compact list, v0.41.7.0) — OpenClaw-native shape:
|
||||
* - **skill-name**: trigger1 | trigger2 | trigger3
|
||||
* - skill-name: trigger1 | trigger2
|
||||
*
|
||||
* List-format constraints (v0.41.7.0):
|
||||
* - Skill name MUST be kebab-lowercase (`[a-z][a-z0-9-]+`). Bold names
|
||||
* like `**Note**`, `**Convention**`, `**TODO**` are deliberately
|
||||
* skipped so prose bullets in real-world AGENTS.md files don't get
|
||||
* mis-parsed as skill rows.
|
||||
* - `skillPath` is ALWAYS derived as `skills/<name>/SKILL.md`. An
|
||||
* optional `→ \`skills/path\`` (or ASCII `->`) suffix is stripped from
|
||||
* the trigger string but NOT honored as the path: downstream consumers
|
||||
* (`routing-eval.ts:skillSlugFromPath`, the manifest lookup at this
|
||||
* file's :367) both assume the convention. For non-conventional paths,
|
||||
* use the table format.
|
||||
* - Multiple triggers fan out to one entry per trigger, all sharing the
|
||||
* same `skillPath`. `checkResolvable` dedupes by `skillPath` downstream,
|
||||
* so the integration reachability count counts each skill once.
|
||||
*/
|
||||
export function parseResolverEntries(resolverContent: string): ResolverEntry[] {
|
||||
const entries: ResolverEntry[] = [];
|
||||
let currentSection = '';
|
||||
@@ -127,29 +152,55 @@ export function parseResolverEntries(resolverContent: string): ResolverEntry[] {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip non-table rows
|
||||
if (!line.startsWith('|') || line.includes('---')) continue;
|
||||
// ── Format 1: Markdown table rows ──
|
||||
if (line.startsWith('|') && !line.includes('---')) {
|
||||
const cols = line.split('|').map(c => c.trim()).filter(Boolean);
|
||||
if (cols.length < 2) continue;
|
||||
|
||||
// Split table columns
|
||||
const cols = line.split('|').map(c => c.trim()).filter(Boolean);
|
||||
if (cols.length < 2) continue;
|
||||
const trigger = cols[0];
|
||||
const skillCol = cols[1];
|
||||
|
||||
const trigger = cols[0];
|
||||
const skillCol = cols[1];
|
||||
// Skip header rows
|
||||
if (trigger.toLowerCase() === 'trigger' || trigger.toLowerCase() === 'skill') continue;
|
||||
|
||||
// Skip header rows
|
||||
if (trigger.toLowerCase() === 'trigger' || trigger.toLowerCase() === 'skill') continue;
|
||||
// GStack / external references (Check `ACCESS_POLICY.md`, Read X, GStack: Y)
|
||||
if (skillCol.startsWith('GStack:') || skillCol.startsWith('Check ') || skillCol.startsWith('Read ')) {
|
||||
entries.push({ trigger, skillPath: skillCol, isGStack: true, section: currentSection });
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check for GStack entries
|
||||
if (skillCol.startsWith('GStack:') || skillCol.startsWith('Check ') || skillCol.startsWith('Read ')) {
|
||||
entries.push({ trigger, skillPath: skillCol, isGStack: true, section: currentSection });
|
||||
// Backtick-wrapped skill path
|
||||
const pathMatch = skillCol.match(/`(skills\/[^`]+\/SKILL\.md)`/);
|
||||
if (pathMatch) {
|
||||
entries.push({ trigger, skillPath: pathMatch[1], isGStack: false, section: currentSection });
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Extract skill path from backtick-wrapped references
|
||||
const pathMatch = skillCol.match(/`(skills\/[^`]+\/SKILL\.md)`/);
|
||||
if (pathMatch) {
|
||||
entries.push({ trigger, skillPath: pathMatch[1], isGStack: false, section: currentSection });
|
||||
// ── Format 2: Compact list rows (v0.41.7.0) ──
|
||||
// Bold form preferred: `- **skill-name**: trigger1 | trigger2`
|
||||
// Plain fallback: `- skill-name: trigger1 | trigger2`
|
||||
// Name regex is kebab-lowercase only so prose bullets like `- **Note**: …`
|
||||
// don't false-match as skill rows (codex F2 / D4).
|
||||
const listBold = line.match(/^-\s+\*\*([a-z][a-z0-9-]+)\*\*\s*:\s*(.+)$/);
|
||||
const listPlain = listBold ? null : line.match(/^-\s+([a-z][a-z0-9-]+)\s*:\s*(.+)$/);
|
||||
const listMatch = listBold ?? listPlain;
|
||||
if (listMatch) {
|
||||
const skillName = listMatch[1];
|
||||
const triggersRaw = listMatch[2].trim();
|
||||
// Strip optional explicit path suffix (D3: stripped, NOT captured).
|
||||
// Both Unicode → and ASCII -> accepted; skillPath is always derived.
|
||||
const cleaned = triggersRaw.replace(/\s*(?:→|->)\s*`skills\/[^`]+`\s*$/, '');
|
||||
// Split on |, drop empty pieces and the literal `...` placeholder.
|
||||
const triggers = cleaned
|
||||
.split('|')
|
||||
.map(t => t.trim())
|
||||
.filter(t => t.length > 0 && t !== '...');
|
||||
const skillPath = `skills/${skillName}/SKILL.md`;
|
||||
// Multiple entries share skillPath; checkResolvable dedupes downstream.
|
||||
for (const trigger of triggers) {
|
||||
entries.push({ trigger, skillPath, isGStack: false, section: currentSection });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
/**
|
||||
* v0.41.7.0 — Regression suite for compact list-format resolvers.
|
||||
*
|
||||
* The bisect anchor for the OpenClaw scaling regression: pre-v0.41.7.0,
|
||||
* any agent that wrote the compact `- **name**: t1 | t2` shape (instead
|
||||
* of the markdown table) saw every skill reported as unreachable by
|
||||
* `gbrain doctor`. The OpenClaw deployment regression was 238 FAIL
|
||||
* errors → 0 errors after the parser fix.
|
||||
*
|
||||
* Two fixtures drive three regression tests:
|
||||
*
|
||||
* 1. test/fixtures/openclaw-compact-resolver/ — list-format only,
|
||||
* ~10 skills with valid frontmatter triggers, plus a prose-bullet
|
||||
* section that pins the D4 kebab-lowercase regex tighten.
|
||||
*
|
||||
* 2. test/fixtures/openclaw-mixed-merge/ — table-format
|
||||
* skills/RESOLVER.md + parent ../AGENTS.md (compact list). Pins
|
||||
* the v0.31.7 D-CX-14 multi-resolver merge case.
|
||||
*/
|
||||
|
||||
import { describe, test, expect } from "bun:test";
|
||||
import { join } from "path";
|
||||
import { checkResolvable } from "../src/core/check-resolvable.ts";
|
||||
|
||||
const COMPACT_FIXTURE = join(
|
||||
import.meta.dir,
|
||||
"fixtures",
|
||||
"openclaw-compact-resolver",
|
||||
"skills"
|
||||
);
|
||||
|
||||
const MIXED_MERGE_FIXTURE = join(
|
||||
import.meta.dir,
|
||||
"fixtures",
|
||||
"openclaw-mixed-merge",
|
||||
"skills"
|
||||
);
|
||||
|
||||
describe("v0.41.7.0 — compact list-format resolver (PR #1370 regression)", () => {
|
||||
const report = checkResolvable(COMPACT_FIXTURE);
|
||||
|
||||
test("every skill in the manifest is reachable from the list-format resolver", () => {
|
||||
// The headline assertion: pre-v0.41.7.0 this was unreachable=N for
|
||||
// every skill in the fixture. Post-fix, unreachable === 0.
|
||||
expect(report.summary.unreachable).toBe(0);
|
||||
expect(report.summary.reachable).toBe(report.summary.total_skills);
|
||||
expect(report.summary.total_skills).toBeGreaterThanOrEqual(10);
|
||||
});
|
||||
|
||||
test("zero error-severity issues", () => {
|
||||
// The headline 238 FAILs → 0 outcome. errors only; warnings are
|
||||
// separately gated below.
|
||||
if (report.errors.length > 0) {
|
||||
console.error(
|
||||
"Unexpected errors:\n",
|
||||
report.errors.map(e => ` - [${e.type}] ${e.skill}: ${e.message}`).join("\n")
|
||||
);
|
||||
}
|
||||
expect(report.errors.length).toBe(0);
|
||||
expect(report.ok).toBe(true);
|
||||
});
|
||||
|
||||
test("zero mece_gap warnings (fixture stubs ship valid triggers)", () => {
|
||||
// D5 fixture upgrade: every SKILL.md stub carries valid frontmatter
|
||||
// triggers, so the mece_gap detection should stay silent. If this
|
||||
// assertion ever fires, a fixture file lost its triggers: array.
|
||||
const gaps = report.warnings.filter(w => w.type === "mece_gap");
|
||||
if (gaps.length > 0) {
|
||||
console.error(
|
||||
"Unexpected mece_gap warnings:\n",
|
||||
gaps.map(w => ` - ${w.skill}: ${w.message}`).join("\n")
|
||||
);
|
||||
}
|
||||
expect(gaps.length).toBe(0);
|
||||
});
|
||||
|
||||
test("D4 REGRESSION: prose bullets do not surface as orphan triggers", () => {
|
||||
// The compact RESOLVER.md fixture intentionally embeds 4 prose
|
||||
// bullets (`- **Note**:`, `- **Convention**:`, `- **TODO**:`,
|
||||
// `- **Important**:`). The kebab-lowercase regex rejects them
|
||||
// before they reach the resolver entry stream, so we should NOT
|
||||
// see orphan_trigger warnings naming any of these.
|
||||
const proseBulletNames = ["Note", "Convention", "TODO", "Important"];
|
||||
const orphans = report.warnings.filter(w => w.type === "orphan_trigger");
|
||||
for (const name of proseBulletNames) {
|
||||
const hit = orphans.find(w => w.skill === name);
|
||||
expect(hit, `prose bullet "${name}" should not surface as orphan_trigger`).toBeUndefined();
|
||||
}
|
||||
});
|
||||
|
||||
test("zero missing_file warnings (every list entry resolves to disk)", () => {
|
||||
const missing = report.warnings.filter(w => w.type === "missing_file");
|
||||
if (missing.length > 0) {
|
||||
console.error(
|
||||
"Unexpected missing_file warnings:\n",
|
||||
missing.map(w => ` - ${w.skill}: ${w.message}`).join("\n")
|
||||
);
|
||||
}
|
||||
expect(missing.length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("v0.41.7.0 — D-CX-14 mixed-merge (table + parent AGENTS.md)", () => {
|
||||
const report = checkResolvable(MIXED_MERGE_FIXTURE);
|
||||
|
||||
test("every skill is reachable across both resolver files", () => {
|
||||
// 5 skills routed from skills/RESOLVER.md (table format)
|
||||
// + 3 skills routed from ../AGENTS.md (compact list format)
|
||||
// = 8 total. All reachable via the v0.31.7 multi-file merge.
|
||||
expect(report.summary.total_skills).toBe(8);
|
||||
expect(report.summary.unreachable).toBe(0);
|
||||
expect(report.summary.reachable).toBe(8);
|
||||
});
|
||||
|
||||
test("zero error-severity issues across the merged resolver set", () => {
|
||||
if (report.errors.length > 0) {
|
||||
console.error(
|
||||
"Unexpected errors:\n",
|
||||
report.errors.map(e => ` - [${e.type}] ${e.skill}: ${e.message}`).join("\n")
|
||||
);
|
||||
}
|
||||
expect(report.errors.length).toBe(0);
|
||||
expect(report.ok).toBe(true);
|
||||
});
|
||||
|
||||
test("both table and list shapes contribute skills to the merge", () => {
|
||||
// Sanity check: if the merge silently dropped one shape's entries,
|
||||
// we'd see unreachable > 0. This test exists to guard against the
|
||||
// regression where one shape's parser starts swallowing the other's
|
||||
// output via the dedup-by-skillPath path.
|
||||
const expectedTableSkills = ["query", "enrich", "briefing", "migrate", "setup"];
|
||||
const expectedListSkills = ["adversary-tracking", "civic-intelligence", "book-mirror"];
|
||||
for (const name of [...expectedTableSkills, ...expectedListSkills]) {
|
||||
const unreachable = report.errors.find(
|
||||
e => e.type === "unreachable" && e.skill === name
|
||||
);
|
||||
expect(unreachable, `${name} should be reachable`).toBeUndefined();
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -66,6 +66,133 @@ describe("parseResolverEntries", () => {
|
||||
expect(entries[0].section).toBe("Always-on");
|
||||
expect(entries[1].section).toBe("Brain operations");
|
||||
});
|
||||
|
||||
// ──────────────────────────────────────────────────────────────────────
|
||||
// v0.41.7.0 — Compact list-format support (OpenClaw-native shape)
|
||||
//
|
||||
// The list branch was added so `gbrain doctor` no longer reports every
|
||||
// skill as unreachable on agents that write the compact `- **name**: t1 | t2`
|
||||
// shape instead of the markdown table. The OpenClaw 306-skill regression
|
||||
// was 238 FAIL errors → 0 errors after this fix.
|
||||
// ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
test("[list] bold-name single trigger", () => {
|
||||
const content = `## Personal
|
||||
- **gift-advisor**: gift idea`;
|
||||
const entries = parseResolverEntries(content);
|
||||
expect(entries.length).toBe(1);
|
||||
expect(entries[0].trigger).toBe("gift idea");
|
||||
expect(entries[0].skillPath).toBe("skills/gift-advisor/SKILL.md");
|
||||
expect(entries[0].isGStack).toBe(false);
|
||||
expect(entries[0].section).toBe("Personal");
|
||||
});
|
||||
|
||||
test("[list] bold-name multi-trigger fan-out", () => {
|
||||
const content = `- **flight-tracker**: track my flight | flight status | when does my flight land`;
|
||||
const entries = parseResolverEntries(content);
|
||||
expect(entries.length).toBe(3);
|
||||
expect(entries.map(e => e.trigger)).toEqual([
|
||||
"track my flight",
|
||||
"flight status",
|
||||
"when does my flight land",
|
||||
]);
|
||||
expect(entries.every(e => e.skillPath === "skills/flight-tracker/SKILL.md")).toBe(true);
|
||||
});
|
||||
|
||||
test("[list] plain-name fallback (no bold markers)", () => {
|
||||
const content = `- gift-advisor: gift idea | birthday gift`;
|
||||
const entries = parseResolverEntries(content);
|
||||
expect(entries.length).toBe(2);
|
||||
expect(entries[0].skillPath).toBe("skills/gift-advisor/SKILL.md");
|
||||
expect(entries[1].trigger).toBe("birthday gift");
|
||||
});
|
||||
|
||||
test("[list] D3: path suffix with Unicode → is stripped, not captured", () => {
|
||||
// D3 walkback: downstream consumers (routing-eval.ts skillSlugFromPath
|
||||
// and check-resolvable.ts:367 manifest derivation) assume the
|
||||
// skills/<name>/SKILL.md shape. Capturing the explicit path would
|
||||
// produce silent orphan_trigger warnings + routing-eval coverage
|
||||
// gaps for that skill. So the suffix is stripped and the path is
|
||||
// always derived from the name.
|
||||
const content = "- **quality**: lint the page → `skills/conventions/quality.md`";
|
||||
const entries = parseResolverEntries(content);
|
||||
expect(entries.length).toBe(1);
|
||||
expect(entries[0].trigger).toBe("lint the page");
|
||||
expect(entries[0].skillPath).toBe("skills/quality/SKILL.md"); // derived, NOT the captured path
|
||||
});
|
||||
|
||||
test("[list] F4: path suffix with ASCII -> is also stripped", () => {
|
||||
const content = "- **quality**: lint the page -> `skills/conventions/quality.md`";
|
||||
const entries = parseResolverEntries(content);
|
||||
expect(entries.length).toBe(1);
|
||||
expect(entries[0].trigger).toBe("lint the page");
|
||||
expect(entries[0].skillPath).toBe("skills/quality/SKILL.md");
|
||||
});
|
||||
|
||||
test("[list] ellipsis placeholder is dropped", () => {
|
||||
const content = `- **foo-skill**: bar | ... | baz`;
|
||||
const entries = parseResolverEntries(content);
|
||||
expect(entries.length).toBe(2);
|
||||
expect(entries.map(e => e.trigger)).toEqual(["bar", "baz"]);
|
||||
});
|
||||
|
||||
test("[list] empty pipe segments are dropped", () => {
|
||||
const content = `- **foo-skill**: bar | | baz`;
|
||||
const entries = parseResolverEntries(content);
|
||||
expect(entries.length).toBe(2);
|
||||
expect(entries.map(e => e.trigger)).toEqual(["bar", "baz"]);
|
||||
});
|
||||
|
||||
test("[mixed] table and list rows in the same file", () => {
|
||||
const content = `## Always-on
|
||||
| Trigger | Skill |
|
||||
|---------|-------|
|
||||
| Every message | \`skills/signal-detector/SKILL.md\` |
|
||||
|
||||
## Personal
|
||||
- **gift-advisor**: gift idea | what should I bring`;
|
||||
const entries = parseResolverEntries(content);
|
||||
expect(entries.length).toBe(3);
|
||||
expect(entries[0].skillPath).toBe("skills/signal-detector/SKILL.md");
|
||||
expect(entries[0].section).toBe("Always-on");
|
||||
expect(entries[1].skillPath).toBe("skills/gift-advisor/SKILL.md");
|
||||
expect(entries[1].section).toBe("Personal");
|
||||
expect(entries[2].section).toBe("Personal");
|
||||
});
|
||||
|
||||
test("[list] section tracking across list entries", () => {
|
||||
const content = `## Personal
|
||||
- **gift-advisor**: gift idea
|
||||
|
||||
## Travel
|
||||
- **flight-tracker**: track my flight`;
|
||||
const entries = parseResolverEntries(content);
|
||||
expect(entries[0].section).toBe("Personal");
|
||||
expect(entries[1].section).toBe("Travel");
|
||||
});
|
||||
|
||||
test("[list] D4 REGRESSION: prose bullets do not match as skill rows", () => {
|
||||
// The kebab-lowercase name regex deliberately rejects bold names
|
||||
// starting with an uppercase letter. This kills the noise class where
|
||||
// real AGENTS.md files have prose bullets like `- **Note**: …` that
|
||||
// would otherwise be parsed as fake skill rows pointing at
|
||||
// `skills/Note/SKILL.md`. Codex F2; D4 decision.
|
||||
const content = `- **Note**: this is a prose bullet
|
||||
- **Convention**: see [some convention link]
|
||||
- **TODO**: fix this later
|
||||
- **Important**: read this carefully`;
|
||||
const entries = parseResolverEntries(content);
|
||||
expect(entries.length).toBe(0);
|
||||
});
|
||||
|
||||
test("[list] D4 negative: convention-violating uppercase names are silently skipped", () => {
|
||||
// Documents the trade: a real skill named `MyTool` would not parse.
|
||||
// The user would notice on the first `gbrain doctor` run and lowercase it.
|
||||
const content = `- **MyTool**: this trigger silently disappears
|
||||
- **camelCase**: also silently dropped`;
|
||||
const entries = parseResolverEntries(content);
|
||||
expect(entries.length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("checkResolvable — real skills directory", () => {
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
# Compact-format resolver (test fixture)
|
||||
|
||||
Tests v0.41.7.0 list-format parser. Skills use clearly-fictional names
|
||||
so they don't shadow real bundled skills.
|
||||
|
||||
## Always-on
|
||||
- **brain-search**: search my brain | what do we know about | find references to
|
||||
|
||||
## Personal
|
||||
- **gift-advisor**: gift idea | what should I bring | birthday gift | housewarming
|
||||
- **flight-tracker**: track my flight | flight status | when does my flight land
|
||||
|
||||
## Email + Calendar
|
||||
- **email-triage**: triage email | sort my inbox | morning email
|
||||
- **meeting-prep**: prep for my meeting | meeting in 30 min | get ready for the call
|
||||
- **calendar-prep**: what's on my calendar | tomorrow's schedule
|
||||
- **daily-digest**: morning brief | end of day summary
|
||||
|
||||
## Workflows
|
||||
- **investor-update-ingest**: investor update | portfolio update | company metrics
|
||||
- **content-creation**: draft a tweet | write a post | new article
|
||||
- **executive-assistant**: schedule a meeting | block time | reschedule
|
||||
|
||||
## Notes
|
||||
The bullets below are prose, not skill rows. The v0.41.7.0 parser
|
||||
rejects them via the kebab-lowercase name regex. If you see entries
|
||||
materialize for any of these, the D4 regex tighten regressed.
|
||||
|
||||
- **Note**: this is a prose bullet (capitalized name, must be skipped)
|
||||
- **Convention**: see [parent docs] (capitalized name, must be skipped)
|
||||
- **TODO**: nothing here is a real skill row (capitalized, skipped)
|
||||
- **Important**: this is just a callout (capitalized, skipped)
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
name: brain-search
|
||||
triggers:
|
||||
- "search my brain"
|
||||
- "what do we know about"
|
||||
- "find references to"
|
||||
---
|
||||
# brain-search
|
||||
|
||||
Stub for fixture testing (v0.40.11.0 compact-resolver regression suite).
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
name: calendar-prep
|
||||
triggers:
|
||||
- "what's on my calendar"
|
||||
- "tomorrow's schedule"
|
||||
---
|
||||
# calendar-prep
|
||||
|
||||
Stub for fixture testing (v0.40.11.0 compact-resolver regression suite).
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
name: content-creation
|
||||
triggers:
|
||||
- "draft a tweet"
|
||||
- "write a post"
|
||||
- "new article"
|
||||
---
|
||||
# content-creation
|
||||
|
||||
Stub for fixture testing (v0.40.11.0 compact-resolver regression suite).
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
name: daily-digest
|
||||
triggers:
|
||||
- "morning brief"
|
||||
- "end of day summary"
|
||||
---
|
||||
# daily-digest
|
||||
|
||||
Stub for fixture testing (v0.40.11.0 compact-resolver regression suite).
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
name: email-triage
|
||||
triggers:
|
||||
- "triage email"
|
||||
- "sort my inbox"
|
||||
- "morning email"
|
||||
---
|
||||
# email-triage
|
||||
|
||||
Stub for fixture testing (v0.40.11.0 compact-resolver regression suite).
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
name: executive-assistant
|
||||
triggers:
|
||||
- "schedule a meeting"
|
||||
- "block time"
|
||||
- "reschedule"
|
||||
---
|
||||
# executive-assistant
|
||||
|
||||
Stub for fixture testing (v0.40.11.0 compact-resolver regression suite).
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
name: flight-tracker
|
||||
triggers:
|
||||
- "track my flight"
|
||||
- "flight status"
|
||||
- "when does my flight land"
|
||||
---
|
||||
# flight-tracker
|
||||
|
||||
Stub for fixture testing (v0.40.11.0 compact-resolver regression suite).
|
||||
@@ -0,0 +1,11 @@
|
||||
---
|
||||
name: gift-advisor
|
||||
triggers:
|
||||
- "gift idea"
|
||||
- "what should I bring"
|
||||
- "birthday gift"
|
||||
- "housewarming"
|
||||
---
|
||||
# gift-advisor
|
||||
|
||||
Stub for fixture testing (v0.40.11.0 compact-resolver regression suite).
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
---
|
||||
name: investor-update-ingest
|
||||
triggers:
|
||||
- "investor update"
|
||||
- "portfolio update"
|
||||
- "company metrics"
|
||||
---
|
||||
# investor-update-ingest
|
||||
|
||||
Stub for fixture testing (v0.40.11.0 compact-resolver regression suite).
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
name: meeting-prep
|
||||
triggers:
|
||||
- "prep for my meeting"
|
||||
- "meeting in 30 min"
|
||||
- "get ready for the call"
|
||||
---
|
||||
# meeting-prep
|
||||
|
||||
Stub for fixture testing (v0.40.11.0 compact-resolver regression suite).
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
# Workspace-root AGENTS.md (compact list, parent dir)
|
||||
|
||||
Tests the v0.40.11.0 + v0.31.7 D-CX-14 multi-resolver merge case:
|
||||
a workspace where the thin skills/RESOLVER.md (table format, ships with
|
||||
the skillpack) merges with the fat parent AGENTS.md (compact list, the
|
||||
real dispatcher).
|
||||
|
||||
## Skills routed from the workspace
|
||||
- **adversary-tracking**: track adversaries | competitive intel
|
||||
- **civic-intelligence**: civic check | local news brief
|
||||
- **book-mirror**: mirror a chapter | personalize this book
|
||||
@@ -0,0 +1,17 @@
|
||||
# Skillpack RESOLVER.md (table format, ships in the bundled skills/)
|
||||
|
||||
The conventional gbrain-shape resolver. Merges with `../AGENTS.md` (compact
|
||||
list, the workspace dispatcher) via the D-CX-14 multi-resolver merge.
|
||||
|
||||
## Brain operations
|
||||
| Trigger | Skill |
|
||||
|---------|-------|
|
||||
| "What do we know about" | `skills/query/SKILL.md` |
|
||||
| Creating a person page | `skills/enrich/SKILL.md` |
|
||||
| Briefing on someone | `skills/briefing/SKILL.md` |
|
||||
|
||||
## Maintenance
|
||||
| Trigger | Skill |
|
||||
|---------|-------|
|
||||
| Migrating a brain | `skills/migrate/SKILL.md` |
|
||||
| Running upgrade | `skills/setup/SKILL.md` |
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
name: adversary-tracking
|
||||
triggers:
|
||||
- "track adversaries"
|
||||
- "competitive intel"
|
||||
---
|
||||
# adversary-tracking
|
||||
|
||||
Stub for fixture testing (v0.40.11.0 mixed-merge regression).
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
name: book-mirror
|
||||
triggers:
|
||||
- "mirror a chapter"
|
||||
- "personalize this book"
|
||||
---
|
||||
# book-mirror
|
||||
|
||||
Stub for fixture testing (v0.40.11.0 mixed-merge regression).
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
name: briefing
|
||||
triggers:
|
||||
- "Briefing on someone"
|
||||
---
|
||||
# briefing
|
||||
|
||||
Stub for fixture testing (v0.40.11.0 mixed-merge regression).
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
name: civic-intelligence
|
||||
triggers:
|
||||
- "civic check"
|
||||
- "local news brief"
|
||||
---
|
||||
# civic-intelligence
|
||||
|
||||
Stub for fixture testing (v0.40.11.0 mixed-merge regression).
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
name: enrich
|
||||
triggers:
|
||||
- "Creating a person page"
|
||||
---
|
||||
# enrich
|
||||
|
||||
Stub for fixture testing (v0.40.11.0 mixed-merge regression).
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
name: migrate
|
||||
triggers:
|
||||
- "Migrating a brain"
|
||||
---
|
||||
# migrate
|
||||
|
||||
Stub for fixture testing (v0.40.11.0 mixed-merge regression).
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
name: query
|
||||
triggers:
|
||||
- "What do we know about"
|
||||
---
|
||||
# query
|
||||
|
||||
Stub for fixture testing (v0.40.11.0 mixed-merge regression).
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
name: setup
|
||||
triggers:
|
||||
- "Running upgrade"
|
||||
---
|
||||
# setup
|
||||
|
||||
Stub for fixture testing (v0.40.11.0 mixed-merge regression).
|
||||
Reference in New Issue
Block a user