feat(skills): two-tier index + signal-based nudge (gated) - #21841
feat(skills): two-tier index + signal-based nudge (gated)#21841zhuosama wants to merge 19 commits into
Conversation
The _Stub class fakes a minimal AIAgent for the concurrent-tool-execution tests. After Codex's signal-based-nudge work added a _observe_tool_activity call inside _execute_tool_calls_concurrent (run_agent.py:8399, 8765), the stub started failing with AttributeError because it didn't fake the new method. Add a no-op stub method to match the existing pattern (_vprint, _safe_print, _touch_activity, etc.). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Codex's refactor introduced a module-level OrderedDict LRU (_INVENTORY_CACHE in agent/skill_inventory.py) that survives across xdist worker tests, since pytest-xdist reuses workers across tests in the same module/group. Each test gets a fresh HERMES_HOME tmp dir, so the cache key (skills_dir, ...) changes — but stale entries from previous tests' tmp dirs remain in the OrderedDict until evicted by _INVENTORY_CACHE_MAX (8 entries). Clearing both caches in _reset_module_state up front matches how production behaves on a fresh process. Note: this addresses correctness hygiene, not the wider xdist flake variance — those are cross-module races that exist on main too (measured: main wider suite has 26 failures, branch has 27-47, similar variance distribution). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Thanks @zhuosama — closing without merging. This is the third PR proposing to compact the skills system prompt (after #14319 and #20644, both closed). The core token-saving idea is real, but each version has come back heavier than the last and we've consistently found the same architectural objections. The size makes the review cost worse than the prompt cost it's trying to save. On the prompt compaction (Phase A): same pattern as the two earlier PRs. The current prompt's strong "MUST load skills" framing is doing real work for models that don't tool-call aggressively, and we're not willing to trade that for prompt size. On the signal-based nudge (Phase B): this is the more interesting half but solves a problem we already patched at the right layer in #23004 (just merged). That PR added explicit "Do NOT capture" guidance to both review prompts — the reviewer now refuses transient env failures, negative tool claims, one-off task narratives, and session-specific errors directly in its own prompt. The reviewer makes context-aware decisions; the proposed S1-S4 signal gate replaces context with hardcoded heuristics (3 repeats = class of work, 30 days = novel CLI, etc.) which will be wrong often. If the reviewer's own judgment is correctly calibrated — which is what #23004 set out to do — the signal gate is unnecessary. If it's still miscalibrated after #23004, the fix belongs inside the reviewer's prompt, not as a heuristic gatekeeper outside it. Other concerns that hurt this PR independent of the design:
If you want to push specifically on the signal-based nudge as a useful idea independent of prompt compaction, the path that would actually get merged: a focused PR (~400-600 LOC) with just the S1-S4 evaluator, no Closing this so the queue stays legible. |
Summary
Two-tier skill index + signal-based skill-creation nudge, both gated behind feature flags. Targets the spec in
plans/skills-prompt-budget-and-nudge-redesign.md(~80% reduction in per-turn skill-prompt tokens at 100 skills, signal-driven nudges that fire on evidence rather than a wall-clock counter).Phase A — skill index v2 (gated
skills.index_v2, defaultfalse)agent/skill_inventory.pyas a typed source of truth for parsed skill metadata, consumed by bothbuild_skills_system_prompt()and the newskill_describetool. Snapshot version bumped to2.priority: critical | normal(default normal). Validated bytools/skill_manager_tool.py:_validate_frontmatter.skills.index_v2: true:priority: criticalentries.skill_describe(category=…)/skill_describe(names=[…]).skills.index_token_budget(default2000) with category-fold-back ranked by recent usage. Categories beyond budget are listed by name behind a… and N more categorieshint.~/.hermes/.skill_usage.jsontracker (agent/skill_usage_tracker.py) updated onskill_view/skill_describe; best-effort, falls back to empty on corruption.priority: critical(spec §9 Q1).Phase B — signal-based nudge (gated
skills.nudge_signals.enabled, defaultfalse)agent/skill_nudge_signals.pySignalEvaluatorcovering:repeated_pattern_thresholddefault 3)terminal/process(windownovel_cli_window_daysdefault 30, plus a default suppression list ofgit,python,pytest, etc.)next time,remember,from now on,记一下,下次,以后)error_repeat_thresholddefault 2)~/.hermes/.skill_known_clis.jsonpersists CLI history with daily pruning (best-effort).creation_nudge_intervalcounter remains as a fallback (kept at 15 in this PR; bumps to 50 in Phase 3)._spawn_background_reviewnow receivestriggered_signals: set[str]so the reviewer prompt can reference which signal fired./skills nudge off|on(intercepted incli.py:HermesCLI._handle_skills_commandbefore delegating to theskills_hubrouter; help line added).HERMES_SKILL_NUDGE_DISABLE=1honoured at session init.Phase 3 (separate PR after dogfooding)
skills.index_v2andskills.nudge_signals.enableddefaults totrueskills.creation_nudge_intervaldefault from 15 → 50ui-tui/src/app/slash/commands/ops.ts) and gateway parity for/skills nudge off|onTracked in
plans/skills-prompt-budget-and-nudge-phase-3-checklist.md.Files
agent/skill_inventory.py,agent/skill_usage_tracker.py,agent/skill_nudge_signals.pyagent/prompt_builder.py,tools/skills_tool.py,tools/skill_manager_tool.py,run_agent.py,cli.py,hermes_cli/skills_hub.py,cli-config.yaml.example,tests/conftest.pyplans/skills-prompt-budget-and-nudge-redesign.md(spec),plans/skills-prompt-budget-and-nudge-implementation.md(plan),plans/skills-prompt-budget-and-nudge-phase-3-checklist.md(follow-up)Test status
test_concurrent_interrupt): 216 passed, 1 skipped, 0 failed.tests/agent/ tests/tools/ tests/run_agent/ tests/cli/ tests/integration/): 27–47 failures across runs vs 26 on main with the same xdist conditions. The delta is within ambient cross-module xdist race variance; no deterministic regressions were found. Same-directory parallel runs (e.g.tests/run_agent/alone) pass cleanly: 992/0 on branch, 982/0 on main.tests/agent/test_anthropic_adapter.py(reads real~/.claudecredentials instead of fixtures),tests/tools/test_file_staleness.py/test_file_state_registry.py(/var/folderspath-validation gate),tests/tools/test_local_interrupt_cleanup.py.Dogfood checklist (complete before merging out of draft)
Phase A — index v2
Set in
~/.hermes/config.yaml:Then exercise:
priority: criticalskills with descriptions). Suggested:hermes --debug 2>&1 | grep -A 3 "## Skills".priority: critical; verify its description reappears in Tier 1.skill_describe(category="<some-cat>")from inside a session; confirm it returns the expected one-line descriptions.skill_describe(names=["<known-name>"]); confirm it returns the entry. Try an unknown name; confirmsuccess: falsewith a useful error.index_token_budgetto e.g.300); confirm the fold-back line… and N more categories: …appears with comma-separated names.~/.hermes/.skill_usage.jsonis created/updated whenskill_vieworskill_describeruns.Phase B — signal-based nudge
Set in
~/.hermes/config.yaml:Then exercise:
gh pr view 1,gh pr view 2,gh pr view 3). Confirm a background skill review fires after the response.terminal); confirm the signal fires once the call succeeds./skills nudge off; confirm subsequent signals are suppressed in that session. Run/skills nudge on; confirm they fire again.HERMES_SKILL_NUDGE_DISABLE=1in the env; confirm a fresh session is muted.creation_nudge_interval: 50; confirm the time fallback eventually triggers when no signals fired.Cleanup
~/.hermes/config.yamloverrides used during dogfooding (or leave them on if you want the new behavior — Phase 3 is what flips the defaults globally).Spec / plan
plans/skills-prompt-budget-and-nudge-redesign.mdplans/skills-prompt-budget-and-nudge-implementation.mdplans/skills-prompt-budget-and-nudge-phase-3-checklist.md🤖 Generated with Claude Code