12 Commits
Author SHA1 Message Date
Teknium 882482b58a fix(compression): the active-request bridge obeys the post-handoff progress floor
When the first post-handoff tool group ends right before the active user
request, the progress floor lands on that request, and _compress_window's
assistant/tool bridge then pulled the cut back onto the group's assistant:
the window held only the handoff and compress() no-oped with
empty_post_handoff_window. The bridge now moves the cut back only down to
tail_cut_progress_floor (one rule); when the floor sits on the active request
the summary merges into that row through the existing merge path.
2026-10-28 20:39:24 -07:00
Teknium f6a84fd6d9 fix(compression): a tail cut inside the first post-handoff tool group still compacts
After a compaction the transcript opens with the handoff followed by a tool
group. When the raw tail budget broke inside that group, the cut was aligned
back onto the group's assistant, leaving a window that held only the handoff.
The handoff scan drops standalone handoff rows, so compress() returned the
empty_post_handoff_window no-op and the session stayed over the trigger with
no warning. Since #144824 priced pre-handoff rows at zero, the whole-transcript
re-cut takes this path routinely.

The progress floor now requires one row the scan keeps: when every row before
the cut is a handoff, the cut moves past the next tool group, unless that
would take the newest row or the active user request out of the tail.

Found on the C4 e2e suite: test_good_summarizer_compacts_and_keeps_every_invariant[11]
failed deterministically since 52a01bd2f2.
2026-10-28 20:39:24 -07:00
Teknium 52a01bd2f2 fix(compression): a handoff row no longer collapses the tail cut onto itself
The tail-cut walk charged in-transcript handoff rows as protected-tail
candidates, but the handoff scan folds every row up to the newest handoff
into the summary window: none of them can stay in the tail. When the
handoff row alone pushed the transcript over the soft ceiling, the walk
stopped on it and the planned window held only the handoff, so compress()
returned the empty_post_handoff_window structural no-op at every trigger.

Live on main (gemini-2.5-flash, context_length 64000, prepare_ahead off):
a Desktop manual compress, then one automatic compaction (handoff now at
index 0), then four consecutive empty_post_handoff_window no-ops while the
prompt grew 47,085 -> 57,143 -> 66,716 tokens, past the 64k window.

The tail-cut walk now prices rows at or before the newest handoff at zero,
so the cut is decided by the rows that can really stay in the tail and the
window covers the old handoff plus the turns after it. Same scenario on
this branch: the second automatic compaction summarises 17 rows and the
next prompt is 18,156 tokens.
2026-10-25 22:29:15 -07:00
Teknium 8373d67547 fix(compression): hold only the floor when the trigger cannot fit a summary
At or below _MIN_SUMMARY_TOKENS (2K) the summary alone fills the trigger, so
no tail size leaves room under it; clamping the 2.5%-of-window term there only
drops recency context (a 1K test trigger on a 256K window merged the handoff
into a tail row). Keep #144177's floor-only clamp in that regime. The lean
budget moves to context_compressor_tail.lean_tail_budget.
2026-10-24 18:45:02 -07:00
Teknium 0668851519 fix(compression): prune-only then full compaction keeps one Skill Safety Rule
Prune-only rules a skill_view stub row (count-protected tail); a later full
compaction keeps that row in its token-budget tail and appended the rule to the
handoff too. Compaction now moves a kept stub's copy onto the handoff, so the
transcript carries exactly one copy while a marker remains.

Follow-up to #144177 (independent review D4).
2026-10-24 18:45:02 -07:00
Teknium a56af5699f fix(compression): the trigger cap covers the whole lean tail budget
#144177 capped only the 10K floor at half a configured trigger; the
2.5%-of-window term still kept 25K at 1M, so the tail stayed at 123%/216%/86%
of a 12K/12K/30K trigger on 400K/1M windows and the refusal loop remained.
Clamp the final lean budget instead. Default-config budgets are unchanged on
every window from 16K to 1M (threshold_percent 0.5 already exceeds 5%).

Follow-up to #144177 (independent review D3).
2026-10-24 18:45:02 -07:00
Teknium 29c0179d64 fix(compression): the Skill Safety Rule survives prune-only and salvage rewrites
Greptile "History rewrites lose skill guidance": the rule only rode the handoff
built in _assemble_compressed, so two rewrites left [SKILL_PRUNED: markers in
context without the "one reload is enough, ignore older markers" rule:
- prune_tool_results_only turns skill_view results into marker stubs and has no
  handoff: the rule now rides the first row it turned into a marker (once per
  transcript), and _is_summary_stub ignores that suffix so a later prune leaves
  the ruled stub alone;
- anti-growth salvage cut a long handoff to its head, dropping the rule at its
  end: the elision now keeps an attached rule.
2026-10-24 17:31:52 -07:00
Teknium 44e9779659 fix(compression): lean tail floor never claims more than half a configured trigger
The prompt trims shrank the CLI system prompt by 531 chars, which moved the
loopback overflow in test_worker_compression_admission / test_journey_compress
_restart_fifo from turn 4 to turn 5. Main fails identically at turn 5 (proved by
shifting the stub's 50,000-char overflow line to 50,300 on an origin/main tree):
with compression.threshold_tokens=12000 on a 64K window the lean tail floor
(10K) kept 11.8K of verbatim tail, so the rebuilt head + summary + tail stayed
over the trigger, the overflow compaction went 9->9 messages and every pre-API
pass refused as "would grow"; the turn died with no post-summary inference.

The lean floor is now min(10K, threshold_tokens * 0.50), the trigger-side twin
of TAIL_MAX_CONTEXT_FRACTION. Default percent triggers never bind it (the
75% small-window floor puts half the trigger above the 20% window cap); only a
configured threshold_tokens or aux ceiling far below the window does.
2026-10-24 17:31:52 -07:00
Teknium c422d69189 fix(prompt): one-shot runs drop steering, clarify and delegate live-control; skill-safety rule moves to compaction
Maintainer-approved prompt-footprint trims (measured on the Nous endpoint, anthropic/claude-opus-5.5):

All sessions
- The [SKILL_PRUNED] "Skill Safety Rule" (126 tok) left the system prompt: markers only exist after a
  compaction, so the compaction handoff now carries the rule whenever a marker survives in the
  compacted transcript (context_compressor_tail.with_skill_safety_rule, applied in
  _assemble_compressed for LLM and fallback summaries alike). The "## Pruned Skills" block drops
  its own parenthetical, which the rule now covers. `hermes sessions repair-prompts` keyed its
  "full build" detector on the rule's heading; it now keys on SKILLS_GUIDANCE (present in prompts
  built before and after this change).
- The CLI/TUI cron "LOCAL-ONLY" caveat (154 tok) moved from the platform hints into
  cronjob_manage's deliver description (a deferred tool: paid only when loaded).
- The Hermes help pointer no longer restates identity ("You run on Hermes Agent (by Nous
  Research)") after SOUL.md's "You are Hermes Agent, built by Nous Research."; it now opens "For
  help with Hermes Agent itself — ...", which still tells a custom persona what it runs on (22 tok).

One-shot sessions only (-q/--oneshot/-z, and gateway-owned `oneshot` policy sessions)
- No "## Mid-turn user steering" note (253 tok): nobody can steer a one-shot.
- No clarify tool (590 tok): ONESHOT_DISABLED_TOOLSETS is folded into session_disabled_toolsets
  (oneshot=...), so the agent tool build, MCP refresh, the tool_search bridge, delegate children and
  the gateway's session inventory / tool listings all drop it together. skill_manage stays.
- delegate_task without live control (425 tok): action/subagent_id/message and the
  background-delivery paragraph are removed (children are joined inside the call), applied via
  toolsets.agent_tool_surface at load and MCP refresh. The one-shot fact is frozen on the agent at
  its first tool build so refreshes stay byte-stable.

Interactive tool schemas are byte-identical to main.
2026-10-24 17:31:52 -07:00
Teknium 297552a9d6 fix(compression): fallback summaries, multiline setup notes and parallel loads keep skill markers right
Independent review follow-up:

- The deterministic fallback summary re-summarizes Phase-1 stubs, and
  _sum_skill_view re-decided the marker from the stub text, so a failed view's
  stub gained a reload marker there. A stub now keeps its own verdict (marker
  only if it already carries one), and a failed stub keeps FAILED.
- The auto-load describer ended a setup note at its first newline, so a
  multiline setup hint leaked into the recovered request. The note now ends
  at "]" followed by the blank line that separates generated blocks.
- _collect_ghosted_skill_names mapped every parallel skill_view call on one
  assistant row to the row's last skill, so two raw loads listed only one. It
  now uses the per-call mapping the lean-tail demotion already had.
- Fallback "User asked" anchors render a /skill turn as typed, like the other
  summary sections.
2026-10-14 14:56:35 -07:00
Teknium a36fb64f28 fix(compression): failed skill views and auto-load file lists are not reloaded or quoted
Review follow-up (Greptile, two P2s):

- A failed skill_view stub kept no failure outcome, so a later compaction
  counted it as a lost skill and asked the model to reload a skill that never
  loaded. The stub now carries " FAILED: <error>" like skills_list and
  skill_manage, failed raw payloads and stubs are both skipped when collecting
  pruned skills, and the lean tail adds no reload marker to a failed view.
- The auto-load describer took everything after the last directory footer as
  the typed request, which included the config, setup-note and supporting-file
  blocks _build_skill_message appends after that footer. Those blocks are now
  skipped by their fixed openings and endings, so previews, titles and the
  compaction quotes show only what the user typed.
2026-10-14 14:56:35 -07:00
Eva 036c31d24c fix(compression): a demoted tail skill_view result keeps its reload marker 2026-10-14 14:56:35 -07:00