Skip to content

fix(core): stop injecting unfiltered full-body skill dumps outside per-turn matching#6422

Merged
bug-ops merged 1 commit into
mainfrom
fix/6413-startup-context-token-gauge
Jul 17, 2026
Merged

fix(core): stop injecting unfiltered full-body skill dumps outside per-turn matching#6422
bug-ops merged 1 commit into
mainfrom
fix/6413-startup-context-token-gauge

Conversation

@bug-ops

@bug-ops bug-ops commented Jul 17, 2026

Copy link
Copy Markdown
Owner

Summary

  • Agent::new_with_registry_arc and reload_skills() built the initial/reloaded system prompt from the entire skill registry with full SKILL.md bodies, bypassing the matcher/disambiguation_threshold/max_active_skills cap entirely — the visible symptom was the TUI//status startup token gauge showing ~100k tokens (full registry size) before any turn ran, contradicting the project's resource-efficiency positioning.
  • Both call sites now build a catalog-only prompt (name + description via format_skills_catalog) straight from SkillMeta, with no per-skill disk read, deferring full-body injection to the first per-turn rebuild_system_prompt(query) call — which already applies the matching/cap correctly.
  • reload_skills() now recomputes cached_prompt_tokens after mutating messages[0], so the gauge never goes stale between a reload and the next turn.
  • Closes the spec gap that allowed this: added a "Construction-Time / Reload-Time Skill Prompt Contract" section to specs/005-skills/spec.md and a cross-referenced NEVER clause to specs/001-system-invariants/spec.md §7 — all prior skill-injection invariants were framed strictly "per turn" and silent on the pre-first-turn prompt.
  • Verified the subagent spawn path (crates/zeph-subagent/src/agent_loop.rs run_turn) does NOT share this defect — it resolves skills once at spawn time via a static include/exclude filter, architecturally distinct from the per-turn matcher. However, adversarial review found subagents with the default (empty skills.include) configuration force-load every registered skill's full body into their one-shot prompt — same defect class, worse magnitude since it actually reaches the LLM. Correctly scoped out of this PR; filed separately as subagent default skills.include (empty) force-loads every skill's full body into the one-shot prompt #6421.

Root cause investigation

Diagnosed via a dedicated diagnose-only session (debugger + live-tester + architect, no code changes) before this fix was implemented. Full evidence trail carried over into this worktree: .local/handoff/2026-07-17T21-12-03-debug.md, .local/handoff/2026-07-17T21-14-58-live-tester.md, .local/handoff/2026-07-17T21-19-59-architect.md.

Test plan

  • 6 new regression tests in crates/zeph-core/src/agent/tests/agent_tests/skill_prompt_bloat_tests.rs: construction catalog-only + token ceiling, reload catalog-only + cached_prompt_tokens recompute proof, last_skills_prompt catalog-only, 0-skill registry (construction + reload), Blocked-trust skill filtered at reload
  • Verified tests are genuine regressions (fail against pre-fix code, not a wrong-layer bypass)
  • cargo +nightly fmt --check — clean
  • cargo clippy --profile ci --workspace --all-targets --features "desktop,ide,server,chat,pdf,scheduler,testing" -- -D warnings — clean
  • cargo nextest run --config-file .github/nextest.toml --workspace --features "desktop,ide,server,chat,pdf,scheduler" --lib --bins — 14182 passed, 0 failed
  • RUSTFLAGS="-D warnings" RUSTDOCFLAGS="--deny rustdoc::broken_intra_doc_links" cargo doc --no-deps --workspace --features "desktop,ide,server,chat,pdf,scheduler" — clean
  • RUSTFLAGS="-D warnings" cargo check --workspace --all-targets --features desktop,ide,server,chat,pdf,scheduler --locked — clean
  • cargo test --doc --workspace --features "desktop,ide,server,chat,pdf,scheduler" — 40 passed
  • gitleaks protect --staged — no leaks
  • Rebased onto latest origin/main, re-ran full check suite post-rebase (no conflicts, no source touched by rebase)
  • Live-testing playbook updated: .local/testing/playbooks/skills.md (new scenario), .local/testing/coverage-status.md (new row)
  • CHANGELOG.md updated under [Unreleased]/Fixed

Closes #6413

@github-actions github-actions Bot added bug Something isn't working documentation Improvements or additions to documentation rust Rust code changes core zeph-core crate size/L Large PR (201-500 lines) and removed bug Something isn't working labels Jul 17, 2026
@bug-ops
bug-ops enabled auto-merge (squash) July 17, 2026 20:49
…r-turn matching

Agent::new_with_registry_arc and reload_skills() built the system prompt
from the entire skill registry with full SKILL.md bodies, bypassing the
matcher/disambiguation_threshold/max_active_skills cap entirely. This
force-loaded every skill's body via the registry's lazy accessor and
inflated the startup/hot-reload token gauge to the size of the whole
registry (e.g. ~100k tokens for 157 skills) before any turn ran.

Both call sites now build a catalog-only prompt (name + description via
format_skills_catalog) straight from SkillMeta, deferring full-body
injection to the first per-turn rebuild_system_prompt(query) call, which
already applies the cap correctly. reload_skills() also recomputes
cached_prompt_tokens after mutating messages[0] so the gauge never goes
stale between a reload and the next turn.

Adds an explicit construction-time/reload-time contract to
specs/005-skills/spec.md and specs/001-system-invariants/spec.md closing
the spec gap that allowed this (all existing skill-injection invariants
were framed per-turn and silent on the pre-first-turn prompt).

Closes #6413
@bug-ops
bug-ops force-pushed the fix/6413-startup-context-token-gauge branch from dff27b4 to c6dff00 Compare July 17, 2026 20:49
@github-actions github-actions Bot added the bug Something isn't working label Jul 17, 2026
@bug-ops
bug-ops merged commit 22e8d15 into main Jul 17, 2026
43 checks passed
@bug-ops
bug-ops deleted the fix/6413-startup-context-token-gauge branch July 17, 2026 20:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working core zeph-core crate documentation Improvements or additions to documentation rust Rust code changes size/L Large PR (201-500 lines)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

startup context gauge shows unfiltered 100k-token skill dump instead of real prompt size

1 participant