feat: parseResolverEntries supports list-based resolver format#1370
feat: parseResolverEntries supports list-based resolver format#1370garrytan-agents wants to merge 1 commit into
Conversation
OpenClaw compact resolver format uses markdown lists instead of tables: - **skill-name**: trigger1 | trigger2 | trigger3 - skill-name: trigger phrase This is the format OpenClaw agents actually use. The table format (| trigger | skill path |) continues to work — both formats are parsed in the same pass. Handles: - Bold skill names: - **name**: triggers - Plain skill names: - name: triggers - Pipe-delimited triggers: trigger1 | trigger2 | trigger3 - Optional path suffix: → `skills/<name>/SKILL.md` (stripped) - Ellipsis markers: ... (filtered out) - Section headings: ## Section Name (tracked as before)
|
Thanks for catching this — the diagnosis was exactly right. The compact list-format resolver that OpenClaw agents actually write was being silently treated as zero skills by Absorbed the fix into v0.41.7.0 (#1407) with the same parser branch you proposed, expanded in a few places after a Codex outside-voice review on the plan:
Closing this PR in favor of v0.41.7.0 (#1407). Credit preserved in the v0.41.7.0 CHANGELOG entry. Thanks for surfacing the gap. |
…rial (#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>
…rial (garrytan#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 garrytan#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>
Problem
parseResolverEntriesonly handles markdown table format:But OpenClaw agents use a compact list format:
This causes
checkResolvableto find 0 reachable skills → 238 FAIL errors ingbrain doctor.Fix
Adds a second parser branch for list-format entries. Both formats are handled in the same pass:
| trigger | \skills/name/SKILL.md` |` — unchanged- **name**: trigger1 | trigger2— new→ \skills/...`` suffix (stripped)Table format continues to work identically. Tested against both formats.
Impact
gbrain doctorresolver_health: FAIL (238 errors) → 0 errors