* feat(skills): check-skill-refs gate + per-commit skills gate runner Three integrity checks over skills/ markdown: dangling refs (backtick paths, composes: frontmatter, RESOLVER dispatcher slugs), donor-remnant paths behind a ratchet allowlist, and warn-only CLI-reference drift against the live tool surface. Wired as check:skill-refs in verify and bundled into scripts/skills-commit-gate.sh (conformance+resolver tests, check-resolvable --strict, lock regen+freshness, refs, optional per-file privacy lint) for any commit touching the skills pack. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(skillpack): machine-readable plugin curation record + membership assertions skills/plugin-exclusions.json records why each manifest skill is not bundled into openclaw.plugin.json (Tier B host-only decisions, seeded from the pre-existing split). test/openclaw-plugin-manifest.test.ts now asserts manifest = bundled + exclusions with no overlap, no orphans, no stale exclusions, and a sorted skills array (two pre-existing sort inversions fixed). Bundling decisions are now review-visible: moving a skill between tiers requires touching both files in one commit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(skills): skillify reconciled with the evolved upstream line (v2.0.0) Adopts eval contracts, the NO-REGRESSION LAW, idempotency guidance, and the 14-item checklist from the upstream skillify line while preserving this repo's divergent content. bulk-ingestion (later in this wave) delegates its codify phase to this checklist. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(skills): book-mirror 0.5.0 backport Table-misalignment root-cause fix, anti-repetition hard constraints, and the resonant-stance rewrite from the upstream line, generalized onto native gbrain surfaces (no external storage libs). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(conventions): test-before-bulk gains the progressive ramp + output-existence checks 10 -> 100 -> 500 -> full ramp with per-round stop gates, the count-before/count-after output-existence check (catches silent zero-row writes), the <2% error-rate gate, the copy-paste verification checklist, cost estimation, and pointers to native pace mode + progress flags so nobody rebuilds throttling in bash. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(conventions): regex-discipline — judgment vs heuristic epistemics The one-question test (deterministic-and-rote vs judgment), the earned- regex sequence, red flags/green lights, never-regex-what-an-attacker-can- imitate, and two genericized cautionary tales. Pairs with test-before-bulk: look before you compress, compress only the rote. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(conventions): path-discipline — display strings are not path strings Never pass a display-formatted link/URL to a file tool; a "successful" write to a poisoned path lies. Poisoned-path tells, verify-before-done, retry discipline, junk-tree recovery. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(conventions): exec-output — buffer to file, read bounded slices Empty exec results usually mean truncation, not a broken shell. The buffer-then-slice rule, shell patterns, and the diagnostic ladder, generalized to any harness. Conventions half of the durable-execution doctrine (the minion-orchestrator half lands separately). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(skills): concept-synthesis 0.2.0 — curation cull phase Adds the keep/delete/merge muscle the synthesis loop lacked: minimum substance gates (style cannot rescue hollow ideas), weighted verdict bands with hard delete triggers, grounding labels wired to the citation conventions, a reputational-risk gate, cluster budgets, and reversible merge-with-backlinks with merge-count as an emergent-salience signal. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(skills): measure-before-you-fix — measure-first ops triage Stopwatch-verify before raising timeouts; act-line vs alert-line. Trigger vocabulary maps onto the shipped doctor/autopilot/sync alert classes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(skills): data-loss-gate — confirmation gate for irreversible operations Recoverability checklist + alternatives + explicit-yes before any bulk delete, forget sweep, source/mount removal, or history rewrite. No category-level exception classes: disposability is a property of the inspected target. Routing-convention honesty: the native operation- boundary confirm remains a filed follow-up. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(skills): fact-check — pre-publication claim verification Breadth gate: extract every verifiable claim, verify each against live sources with a confidence scale. Data-derived claims section carries the two laws: producer is never verifier (independent re-derivation via a different query path) and affiliation is not authorship (typed edges, not co-occurrence). Hard-block on unsupported claims. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(skills): resolve-before-asking — exhaust the brain before interrupting the human Query, timeline, and every mounted source before asking; when asking is justified, ask WITH a hypothesis (pairs with ask-user). Includes the no-placeholders-at-ingest rule. Owns WHETHER asking is justified; query owns the lookup verb, brain-ops the read-write cycle. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(skills): brain-ingest-gate — ingest entry gate (no raw copies, entity resolution, dedup) No-raw-copies entry rule; the Named-Entity Resolution Gate (registry- first + alias expansion — a vector score is a floor for prose, never a gate for named things); the qualitative dedup decision tree (open and read the top hit before writing). The gate keeps full dedup responsibility: native import skips only matching frontmatter IDs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(skills): correction-pipeline — trace corrections to the contamination source When the user corrects a fact: root-cause it (7-class taxonomy), fix the source surface (facts table via recall/forget/remember; pages via edit + re-sync; identity files via bootstrap templates), then run the propagation check. Never just note it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(skills): company-brainify — personal-to-team brain sanitization Strip/keep tables, triage grep pipelines, verification greps, and the history-purge procedure for publishing a sanitized team brain from a personal one. Scan scope covers meetings, dailies, facts, and takes, not just people pages. History rewrite is gated through data-loss-gate with a mirror-clone backup. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(skills): citation-graph-ingest — typed inter-document citation edges Extract every inter-document reference from an ingested corpus and classify the edge type (overrules/distinguishes/relies-on/extends...), written via native link ops and queryable via graph-query. States plainly that natural-language relational retrieval walks a fixed edge- type set today; wiring these types in is a filed follow-up. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(skills): two-tier-extraction — triage/deep-read model routing for archives Cheap-model triage pass, gate, then strong-model deep read for archive ingestion, expressed through the model-routing conventions. Deterministic privacy wall: named-entity and sensitive-pattern classes are stripped before any model call; ambiguous content fails closed to human review. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(skills): brain-link-discipline — verified links in every deliverable Composite: same-message link rule, path derivation via git ls-files, push-before-link ordering, subagent-relay path rewrite, bulk-list formatting. Fallback chain: verified hosted-remote URL, then repo- relative path with a scope note, then publish output offered as an attachable artifact (never promised as a URL). Extends _output-rules.md with the verified-deliverable-link canon and the in-page (relative) vs in-message (absolute) scope split. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(skills): draft-in-voice — memory-grounded ghostwriting from validated profiles Load a validated voice profile, obey its hard rules, produce 2-3 drafts, run the voice-fidelity self-check, never auto-post. Ships the build-a- profile appendix (corpus threshold, fingerprint schema, profile page schema with provenance/consent/versioning) so users without a donor profile aren't dead-ended. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(skills): bulk-ingestion — the disciplined bulk pipeline lifecycle + manifest substrate Composite: the schema-trial-evaluate-codify-bulk-monitor lifecycle spine plus the manifest.json durable-state substrate (stable source-derived ids, derived-not-asserted status, idempotent workers, per-worker progress-file merge, never trust a subagent's 'done' — verify on disk). Codify delegates to skillify; execution to jobs/minions; ramping to test-before-bulk. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(scripts): check-skill-refs reads cliHints from operations source --tools-json does not serialize every op's cliHints, so real commands (get, timeline, backlinks, graph, link) warned as unknown. The checker now also derives the known-command set from operations.ts cliHints names + aliases. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(skills): meeting-ingestion 2.0.0 — recorder-agnostic pipeline backport Normalized transcript record as the any-recorder contract, multi-meeting split detection, cross-recorder dedup, evidence-based speaker resolution (never guess; ASR-variant search-before-create), required page sections with explicit-reason-for-empty, claim-verification table, and the brain- contradiction consistency check. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(skills): meeting-ingestion 2.1.0 — verify-before-done phase Hard gate before reporting ingested: required sections have substance, every people/companies slug has a page + timeline backlink, speaker map resolved, every quote grounded verbatim in the transcript (deterministic sidecar checks where transcripts are retained), fabricated-attendee sanity checks. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(skills): meeting-ingestion 2.2.0 — sequence verify (chronology checks) Deterministic order lint before a meeting page ships: phase-inversion and impossible-transit checks, day-timeline gaps corroborated against `gbrain day`, with a pass/block/explicit-waive contract (waives logged as acknowledged, not resolved). Adds routing-eval fixtures for the upgraded skill. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(skills): reports 1.1.0 — Actionability Gate Four link failure modes (Broken/Dead/Indirect/Missing) with the rule "a missing link is honest, an indirect link is a broken promise", the blocked-vs-warning table, retry loop as agent-run checks, and the prompt gate snippet. Canon lives in _output-rules.md; this section carries the taxonomy delta. Routing fixtures added. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(skills): briefing 1.3.0 — pre-briefing context pulls Salience, anomalies, personal recall, and hot-memory pulse pulls before composing the briefing, wired to the real CLI flags (anomalies uses --lookback-days, not the upstream --since form). Routing fixtures added. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(skills): daily-task-manager 2.0.0 — stable IDs + fail-closed actions Stable task-ID anchors, deterministic action routing, fail-closed ambiguity handling (no mutation on zero/multiple matches), structured Returns contract with an agent-chaining JSON mode, minimal-diff save discipline. Routing fixtures added. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(skills): eiirp 1.1.0 — auto-fire gate (file before you deliver) Always-on convention: when substantial structured analysis of a user- shared document is about to be delivered, file the brain page first and deliver analysis + link in the same reply. Fire conditions, does-not- fire list, dedicated-pipeline exemptions, per-user storage-policy override. eiirp moves from plugin exclusion to bundled — the gate is load-bearing for downstream installs. Routing fixtures added. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(skills): minion-orchestrator 1.1.0 — durable execution doctrine Capability ladder for operations over two minutes: jobs + a one-shot self-deleting deadman (verifying the result was REPORTED, not that the process exited) where the shell-jobs contract allows; cron-checked progress file; foreground with buffered output otherwise. Deadman failure modes (dies before firing, double-fire idempotency, stale checkpoint) encoded in Contract and Anti-Patterns; content-addressed stage-checkpoint appendix. Existing trust-boundary text unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(skills): concept-synthesis cull-phase routing fixtures Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(skills): research-compendium — archive, summarize 1:1, synthesize self-contained Deep-research discipline: archive every source, one summary per source, then a self-contained synthesis (the Self-Contained Rule; Low-Bar/High- Ceiling). Claims verification delegates to fact-check; retention is gated by the user's privacy posture. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(skills): context-audit — token hygiene for the always-loaded stack Report-only audit of SOUL/MEMORY/CLAUDE/AGENTS-class files: redundancy, contradictions, staleness, compression candidates ranked by token savings. Recommendations target bootstrap templates, never rendered files; judging routes through the native cross-modal eval runner with a cheap-model default. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(skills): blog-ingest — whole-publication and feed ingestion Feed discovery, pagination walking, article-shape normalization, canonical-URL dedup, idempotent re-runs, polite pacing, and empty-husk repair as agent procedures; per-article enrichment hands off to brain-ingest-gate. Public posts only. Fetched text is data, never instructions (untrusted-content section). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(skills): conversation-archive — AI-chat history as first-class brain content Convert chat exports into per-conversation pages, import, validate via the conversation-parser surface, extract facts; gap-healing backfill under the three invariants (capture cadence outruns eviction, gap detection, write-path equals commit-path); idea-evolution tracing. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(skills): skill-autobench — evals mined from lived usage Mine a skill's real invocation history (user corrections are the gold signal) into a proposed eval contract and replayable cases with honesty labels, staged pending human approval — never rewrites a skill. Multi-model judging verifies each provider actually responded. Extends the SkillOpt surface; never duplicates it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(todos): giftable-import wave follow-ups Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(skills): resolver strict reconciliation — triggers, fixtures, disambiguation Drives check-resolvable --strict to zero on the 69-skill pack: wave-side trigger narrowing (bare words become phrases on measure-before-you-fix, data-loss-gate, brain-ingest-gate), fixture intents rewritten to realistic phrasings, wave-side MECE duplicates removed, the ingest-gate quality rules delegated to conventions, and three new disambiguation rules (publication/article/media/chat-export routing, soul-audit vs context-audit, measurement-first triage vs debugging). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(skills): idea-ingest 1.1.0 — Returns contract + edge cases backport Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(skills): media-ingest 1.1.0 — input contract, error handling, known pitfalls Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(skills): brain-ops 1.1.0 — entity-card first lookup + shallow-ls warning (memory-verbs block preserved) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(skills): maintain 1.1.0 — weekly feature-adoption check Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(skills): data-research 1.1.0 — infer research target from context Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore(llms): regenerate bundles after the skills wave RESOLVER.md is inlined into the llms bundles; the wave's routing rows and disambiguation rules regenerate here (single wave-end regen; the committed bundles match HEAD per test/build-llms.test.ts). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(test): assemble banned-path fixtures at runtime in check-skill-refs tests The privacy check bans those literals in source files; the fixtures now construct them so the test still exercises the donor-remnant gate. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(skillpack): bundle capture + publish; keep host-only contrast fixtures portable The downstream-install smoke showed shipped fixtures referencing host- only skills. capture and publish move from exclusions to the bundle — both are user-facing and wave skills route to them (the eiirp auto-fire files via capture; brain-link-discipline falls back to publish). Contrast fixtures that pointed at host-only skills (smoke-test, skill-optimizer) now target shipped skills with declared ambiguity, so `check-resolvable --strict` is green on the host pack (69 skills) AND on a fresh scaffolded install (58 skills). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(evals): post-import-wave routing receipt (haiku) A/B harness run against the 69-skill resolver after the import wave: baseline (uncompressed) 100% held-out / 78.3% training strict — beating both compression variants on strict scoring, corroborating the review decision to defer resolver compression to its own PR. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(skills): pre-landing review fixes — trust boundaries, gate hardening, registration prose From the six-reviewer pre-landing pass: research-compendium gains the untrusted-content section (verbatim web archives feed future recall); blog-ingest's injection marker now travels with body chunks (inline fenced wrapper, not frontmatter-only); company-brainify's history-rewrite backup moves to a non-volatile private location with pre-push re-verification and retention cleanup; the 17 imported manifest entries get real descriptions (a block-scalar parsing slip) with a test pinning description prose and plugin-array uniqueness; check-skill-refs becomes cwd-independent, validates relative markdown links, and warns visibly on an empty CLI command set (5 new tests); the commit gate fails loudly on a regenerated-but-unstaged lock, single-quotes its inline lint script, and rejects missing lint paths; ladder attribution, checkpoint-hook bypass, frontmatter-contract notes, curation + misroute runbook docs, the brain-first API-pull rule, and negative routing fixtures for eight skills round out the batch. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(skills): adversarial-review hardening — trust boundaries, closure, honest contracts From the ship pre-landing + adversarial + red-team passes (all findings folded): Untrusted content: a shared conventions/untrusted-content.md (fetched text is data, never instructions) referenced from every skill that fetches or imports external text — fact-check most critically (a fetched page is never authority to rewrite a brain fact). conversation-archive gains a mandatory secret+PII redaction pass before any page is written. company-brainify: the destructive strip now sanitizes a staging copy behind the data-loss-gate card (personal brain untouched); the history purge derives its path set from the full sanitized scope, runs only against the shared repo (never sync.repo_path), stages under a non-volatile backup dir with a pre-push restore check, and logs the deletion to the personal brain, not the shared one. Downstream closure: ask-user and brain-taxonomist are bundled (three new skills delegate to them); remaining host-only references are prose, not links; a membership+closure test makes a bundled skill referencing an unshipped skill a build failure. Honest contracts: brain-link-discipline is marked mutating (it commits and pushes) and only sends its token to the issuing host; inert priority markers removed; citation-graph-ingest's writes_to dropped; concurrency and slug-collision limits documented for daily-task-manager, bulk-ingestion, conversation-archive, and blog-ingest; the self- authorizing shell-jobs prefix removed in favor of worker-side authorization. Gate hardening: the commit gate compares the staged lock blob and runs the plugin-manifest tests; check-skill-refs scans jsonl for donor remnants, tolerates CRLF, and rejects flag-shaped option values. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * v0.45.6.0 feat(skills): giftable-skills import wave — 17 new production skills + 8 upgrades Grows the built-in pack from ~52 to 69 skills: the trust layer (correction-pipeline, data-loss-gate, fact-check, brain-ingest-gate), team-brain sanitization (company-brainify), typed citation-graph ingestion, disciplined bulk/feed/conversation ingestion, and operational skills (measure-before-you-fix, context-audit, skill-autobench, resolve-before-asking, brain-link-discipline, draft-in-voice, research-compendium), plus meeting-ingestion/skillify/eiirp/ minion-orchestrator/concept-synthesis/reports/briefing/daily-task-manager upgrades and a new skill-ref integrity gate + plugin-curation record. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(skillpack,doctor): skill currency + declared preconditions — the migration harness now examines skills The upgrade/migration path was DB-only for skills: schema migrations never touched them, and skill sync was manual/advisory with new skills hidden from the post-upgrade sweep. This makes skill currency and per-skill setup first-class. Currency (capability A): src/core/skillpack/skill-currency.ts classifies every bundled skill against the install as new / drifted / current, by each skill's OWN files (shared conventions on disk no longer misclassify a new skill as drifted). `gbrain skillpack status` reports it; `gbrain skillpack sync` scaffolds only the new ones and never overwrites local edits; the post-upgrade sweep surfaces new skills with the sync command; `gbrain doctor` gains a `skill_currency` check (WARN when new skills are available, drift stays advisory). Preconditions (capability B): a `requires:` frontmatter field (source / dir:<path> / config:<key> / pages:<n>), a pure DI-seam checker in src/core/skillpack/preconditions.ts, `gbrain skillpack setup <skill>` for the static list + hints, and a `skill_preconditions` doctor check that verifies them live against the connected brain. Annotated only the two skills with genuine preconditions (citation-graph-ingest needs a corpus; skill-autobench needs the conversations/ archive). Semantic skill search (capability C) is deliberately deferred as its own subsystem (tenancy + source isolation + embedding backfill) — filed in TODOS, not crammed in unreviewed. Tests: skill-currency, skill-preconditions, skillpack-status (31 new assertions). doctor + skillpack stay engine-safe (currency is filesystem- only; live precondition checks skip cleanly without a brain). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(doctor,skillpack): categorize new skill checks, refresh flag registry, sweep test + noise gate CI on the pushed feature surfaced three real in-branch failures: - doctor-categories: register skill_currency + skill_preconditions under SKILL_CHECK_NAMES (every doctor check must be categorized). - flag registry: doctor's import of findGbrainRoot pulls bundle.ts (which runs git rev-parse --verify) into its flag scan; regenerated the known-flags allowlist to match (harmless over-inclusion, per the generator's design). - upgrade sweep: header renamed to 'Skillpack sweep (post-upgrade)'; new-skill surfacing gated on the host having scaffolded at least one skill, so a zero-skill (opted-out) host stays silent instead of getting 'N new skills' on every upgrade. Tests updated + a case added locking the surfacing. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
14 KiB
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:
- **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
you can use as a reference shape. The skillpack story for distributing
your own resolvers across machines is covered in
skillpacks as scaffolding.
The compact list format
GBrain's resolver parser originally required markdown tables:
| 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:
- **gift-advisor**: gift idea | what should I bring | birthday gift
- **flight-tracker**: track my flight | flight status | when does my flight land
When gbrain doctor only spoke the table dialect, a 306-skill
compact-format resolver reported every skill as unreachable: 238 FAIL
errors on every doctor run. The parser was silently treating the compact
dialect as zero skills.
Today the parser supports both. The same parseResolverEntries
function reads table rows and list rows in the same file, with the
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 intoconventions/, anything that isn'tskills//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**: triggersis preferred.- name: triggersworks 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:
gbrain doctor
For CI gates, use the JSON-emitting variant:
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:
{
"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:
- **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 — and now it does.
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.
Plugin bundling is a curation decision
Not every skill in skills/ reaches downstream installs. The plugin
manifest (openclaw.plugin.json) is the bundled set; everything else is a
recorded exclusion in skills/plugin-exclusions.json, each with a reason.
The two are test-pinned in both directions: every manifest skill is either
bundled or a recorded exclusion, and no skill is both. Adding a skill to
the tree does NOT ship it — bundling is an explicit decision, and an
unbundled skill never reaches a downstream install. When you write a new
skill, decide (and record) which side of that line it lives on.
bun run gate:skills (scripts/skills-commit-gate.sh) is the per-commit gate
for any change under skills/. It runs the conformance + resolver +
plugin-manifest tests, check-resolvable --strict, the skills.lock.json
regen + freshness check, and check-skill-refs in seconds — run it before
committing a skills change so the membership/closure and plugin.version
assertions fail locally instead of in CI.
When a skill misroutes
Treat a misroute like a failing test, because it becomes one. First
reproduce it as a fixture in the skill's routing-eval.jsonl — the utterance
that misrouted, with the expected skill (or null). Rewrite the misrouted
utterance onto placeholder entities (alice-example, acme-example) before
committing the fixture — same rule as skill-autobench; a routing fixture is a
public artifact and must not carry a real contact or company name. Only then
fix the cause:
usually a trigger in the skill's frontmatter or its row in
skills/RESOLVER.md. Regenerate the lock (bun run scripts/generate-skills-manifest.ts) and the llms bundles (bun run build:llms), verify with gbrain check-resolvable --strict, and ship it as
a MICRO release. Downstream installs heal on their next upgrade — the fix
travels with the skillpack, not with a support thread.
Related
- Skill development cycle — the 5-step loop for turning a repeated task into a real skill.
- Skillpacks as scaffolding — how to distribute a coherent set of skills across machines and agents.
- Sub-agent routing — when to delegate to a sub-agent vs handle in-line, and the model routing table for each path.
GBrain: github.com/garrytan/gbrain.
The parseResolverEntries parser lives at
src/core/check-resolvable.ts;
the bundled resolver lives at skills/RESOLVER.md.