fix(compression): couple pruned-skill reload instruction to the preserved todo snapshot - #86852
Merged
Conversation
…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
Contributor
૮ >ﻌ< ა ci reviewran on a6f911f — fix(compression): couple pruned-skill reload instruction to
|
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.
15 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 staleremovetask 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 exactskill_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 Skillssection and pruned tool rows surviving in the protected tail. First-seen order, deduplicated, capped by the shared_MAX_PRUNED_SKILL_MARKERS.TODO_INJECTION_HEADERinside the same block, so:_strip_stale_todo_snapshotremoves snapshot + notice together at the next boundary (no accumulation, Prevent todo snapshot injection after compression #26981 parity);_is_synthetic_compression_user_turnkeys on the header prefix) is unaffected — the row still never counts as real human intent (fix(compression): preserve zero-user provenance in compaction summaries #69292 provenance preserved).Invariants held
[SKILL_PRUNED:prefix, so it can never re-trigger the marker extractor at a later boundary (test-pinned).Tests
tests/agent/test_skill_todo_retention_parity.py(12 tests):_MAX_PRUNED_SKILL_MARKERScap, byte-determinism, no marker self-feed;_compress_contextpath against a realSessionDB— 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