Skip to content

fix(compression): couple pruned-skill reload instruction to the preserved todo snapshot - #86852

Merged
teknium1 merged 1 commit into
mainfrom
fix/84718-skill-retention
Aug 15, 2026
Merged

fix(compression): couple pruned-skill reload instruction to the preserved todo snapshot#86852
teknium1 merged 1 commit into
mainfrom
fix/84718-skill-retention

Conversation

@teknium1

Copy link
Copy Markdown
Contributor

Summary

Part of #84718 — the retention asymmetry at the compaction boundary: compaction re-injects the todo list verbatim (TODO_INJECTION_HEADER + TodoStore.format_for_injection) while skill instructions are pruned down to [SKILL_PRUNED: ...] markers. The imperative crosses the boundary; the policy that governed it does not. In the issue's forensic trace (T6), the agent kept executing a stale remove task whose justification, refuting evidence, and governing skill were all deleted.

This PR closes the asymmetry at the injection site with a scoped, zero-recurring-cost coupling: when the compressed transcript carries [SKILL_PRUNED: ...] markers AND a todo snapshot is being re-injected, the snapshot block also carries a bounded reload notice naming each pruned skill with its exact skill_view(name='...') call, plus a one-line instruction to re-check that preserved tasks are still justified after reloading. Skill guidance recovery now travels in the same boundary artifact as the imperative.

Mechanism

  • _pruned_skill_reload_notice(compressed) (new, agent/conversation_compression.py) scans the post-compression transcript for canonical markers via the existing _extract_pruned_skill_names — covering both the summary's ## Pruned Skills section and pruned tool rows surviving in the protected tail. First-seen order, deduplicated, capped by the shared _MAX_PRUNED_SKILL_MARKERS.
  • The injection site appends the notice after TODO_INJECTION_HEADER inside the same block, so:

Invariants held

  • Cache/byte-stability: the notice is derived only from the compressed transcript — same input, same bytes; no per-turn nondeterminism in the rebuilt prompt. Compression remains the only sanctioned mutation.
  • Zero recurring cost: sessions with no pruned skills are byte-identical to before. No per-item rationale storage, no execution gate (cron/subagent sessions keep executing), no verbatim 18KB skill carry that would defeat compression.
  • No marker self-feed: the notice references skills without the canonical [SKILL_PRUNED: prefix, so it can never re-trigger the marker extractor at a later boundary (test-pinned).
  • Role alternation: text-only change inside the existing snapshot block; merge/standalone-append logic untouched.

Tests

tests/agent/test_skill_todo_retention_parity.py (12 tests):

  • unit contract of the notice builder — naming every pruned skill with its reload call, tail tool-row collection, dedup + first-seen order, _MAX_PRUNED_SKILL_MARKERS cap, byte-determinism, no marker self-feed;
  • behavioral compaction runs through the real _compress_context path against a real SessionDB — notice travels with the snapshot into the trailing user message, absent when nothing was pruned, synthetic-row classification unbroken, strip lifecycle keeps exactly one snapshot+notice across repeated boundaries.

Sabotage-verified: disabling only the append flips the 3 behavioral tests red.

Also ran green locally: tests/agent/ -k "compress or compaction or todo or ghost or skill" (691 passed), tests/tools/test_todo_tool.py, ghost-skill pruning, zero-user provenance, rotation-state suites; ruff + Windows-footgun checks clean.

Relationship to other work

Complementary to draft #86267 (todo reframe + digest work): this PR is deliberately minimal and touches only the injection-site coupling — it does not add skill digests, provenance fields, or execution gates, which remain the design fork tracked in #84718.

Infographic

Retention parity at the compaction boundary

…rved todo snapshot

Compaction re-injects the todo list verbatim (TODO_INJECTION_HEADER +
TodoStore.format_for_injection) while skill instructions are pruned down
to [SKILL_PRUNED: ...] markers — the imperative crosses the boundary
without the policy that governed it, and the agent keeps executing
preserved tasks with the guidance deleted (#84718's T6 pattern).

Close the retention asymmetry at the injection site: when the compressed
transcript carries [SKILL_PRUNED: ...] markers AND a todo snapshot is
being re-injected, append a bounded reload notice to the snapshot naming
each pruned skill with its exact skill_view() reload call, plus a
one-line instruction to re-check that preserved tasks are still
justified. Skill guidance recovery now travels in the SAME boundary
artifact as the imperative — same message, same stale-snapshot strip
lifecycle, so repeated compactions refresh rather than accumulate.

Properties:
- deterministic: derived only from the compressed transcript (same input,
  same bytes) — no per-turn nondeterminism in the rebuilt prompt
- zero recurring cost when nothing was pruned (clean sessions unchanged)
- bounded: shares _MAX_PRUNED_SKILL_MARKERS with the summary re-injection
  cap; the notice text never contains the canonical marker prefix, so it
  can never feed the marker extractor at the next boundary
- rides after TODO_INJECTION_HEADER, so _strip_stale_todo_snapshot
  removes snapshot + notice together and the synthetic-row classifier
  (_is_synthetic_compression_user_turn) is unaffected

Tests: tests/agent/test_skill_todo_retention_parity.py — unit contract of
the notice builder (naming, dedup/order, cap, determinism, no marker
self-feed) and behavioral compaction runs through the real
_compress_context path (notice travels with the snapshot, absent when
nothing pruned, synthetic-row classification unbroken, strip lifecycle
across repeated boundaries). Sabotage-verified: disabling the append
flips the 3 behavioral tests red.

Part of #84718
@github-actions

github-actions Bot commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

૮ >ﻌ< ა ci review

ran on a6f911f — fix(compression): couple pruned-skill reload instruction to

⚠️ Warnings

CI timings · View report · View job

Wall time 6m23s vs 3m43s (+71.7%). 15 job(s) slower, 9 faster,

  • Python tests / Run tests slice 5/12: -59.0s
  • Python tests / Run tests slice 7/12: -42.0s
  • Python tests / Run tests slice 9/12: +39.0s
  • Python tests / Run tests slice 12/12: -37.0s
  • Python tests / Run tests slice 4/12: -30.0s

OSV vulnerability scan · View job

5 known vulnerabilities found in pinned dependencies.

How to fix:

Review the findings in the Security tab. Update the affected dependencies if a patched version is available.

@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint tool/skills Skills system (list, view, manage) sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state labels Aug 15, 2026
@teknium1
teknium1 merged commit 7a16840 into main Aug 15, 2026
49 checks passed
@teknium1
teknium1 deleted the fix/84718-skill-retention branch August 15, 2026 09:45
JoaoMarcos44 added a commit to JoaoMarcos44/hermes-agent that referenced this pull request Aug 15, 2026
… reload pointer (NousResearch#84718)

The pruned-skill tombstone marker tells the model a skill was cut and how
to reload it, but not what the skill said to do in the meantime — the
"policy" half of the retention asymmetry stayed unsolved after NousResearch#86852
closed the reload-notice gap. Extract a bounded MUST/NEVER + Workflow
digest from the skill_view body at the exact moment it's pruned (the only
point the raw content is still available), embed it alongside the marker,
and restore it deterministically if an LLM summarizer paraphrases it away
— mirroring the marker's own P2 re-injection lifecycle exactly, capped
per-skill (1200 chars) and across a boundary (5 skills) so it can never
approach the budget compaction pruned the skill to save.
JoaoMarcos44 added a commit to JoaoMarcos44/hermes-agent that referenced this pull request Aug 15, 2026
JoaoMarcos44 added a commit to JoaoMarcos44/hermes-agent that referenced this pull request Aug 15, 2026
…yed head turns

_capture_origin_anchor scanned messages from index 0, so a handoff
summary embedded mid-transcript (a prior compaction's fossil
boundary) never stopped it from resurrecting pre-boundary turns as
the permanent "## Originating Request" anchor -- exactly the
fossilization NousResearch#57814's decay contract exists to prevent. Only scan
turns after the newest embedded handoff.

Also updates the __new__-constructed test fixtures (which bypass
__init__) and stale assertions in tests whose expectations predate
the anchor/reload-notice/write-approval-default features this PR
introduces, so the branch matches the behavior teknium1 confirmed
in NousResearch#84718 after merging in main (which carries NousResearch#86852's coupled
skill-reload notice).
JoaoMarcos44 added a commit to JoaoMarcos44/hermes-agent that referenced this pull request Aug 15, 2026
teknium1 closed NousResearch#84808 on design direction: per-item rationale (extra
output tokens on every plan write) and a needs_reconfirmation downgrade
both add recurring cost to steer an edge case, and the todo tool schema
ships on every API call. Keep only the zero-cost fix already accepted
in NousResearch#86852 (static, one-time reconciliation guidance appended to the
injection block regardless of item count) plus the reload-notice
coupling. Drops the rationale field from TODO_SCHEMA, MAX_TODO_RATIONALE_CHARS,
_cap_rationale, NEEDS_RECONFIRMATION_MARKER, and the tests that only
covered them.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint P2 Medium — degraded but workaround exists sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state tool/skills Skills system (list, view, manage) type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants