Commit Graph

27 Commits

Author SHA1 Message Date
mnajafian-nv
8810cf24b2 fix(relay): record why an overflow compaction ran in overflow_reason
overflow_reason is a v1 field, but it was always null: both overflow
callers passed only trigger="overflow", although each already holds the
error classifier's verdict for the provider rejection it recovers from.
A consumer could not tell a 413 from a context-length rejection or the
Anthropic long-context tier gate.

Both callers now pass the classified reason through _compress_context
into the attempt seed, so the attempt log line and the compaction mark
carry it. The mark keeps it in a closed set (context_overflow,
payload_too_large, long_context_tier, else other) and leaves it null for
every other trigger. The Relay docs list the set instead of calling the
field reserved.

Signed-off-by: mnajafian-nv <mnajafian@nvidia.com>
(cherry picked from commit c3ad6f7fb8bfcc8f9c350f3a4272029d63333c7f)
2026-10-10 04:27:27 -07:00
Maciej Lisiewski
4278b6aa24 ci: RUF013, RUF015 2026-10-08 21:29:48 +02:00
kshitijk4poor
fbdf612724 refactor(compression): forward snapshot_is_current directly in facade
compress_context already defaults snapshot_is_current to None, so the
conditional kwarg splat in the facade's _run forwarder only added noise.
2026-10-03 16:54:06 +05:30
funky-xamarin
35c9242fcf fix(compression): reject stale manual snapshots after lease admission
(cherry picked from commit 6baa8620310d3b9df6b4c23b89ed4e12d7286e9f)
2026-10-03 16:54:06 +05:30
teknium1
a8d57be77b feat(telemetry): count compaction attempts in shared metrics
Every compression attempt already funnels its outcome through
_emit_compression_attempt_telemetry (committed, aborted, lock-contended,
pool-saturated). That choke point now also records hermes.compression.count
once per attempt: trigger (auto / overflow / manual), outcome
(success / skipped for lock contention / failed) and the context fill from
the pre-compaction token estimate against the model window.

The pending record is set on the agent in _begin_compression_attempt and
popped by the first emit, because abort paths restore the compressor
snapshot (telemetry seed included) before they emit. compress_context and
AIAgent._compress_context gain a trigger kwarg so provider-proven overflow
recovery (turn_overflow, long-context-tier recovery) is labelled overflow
instead of being inferred from bypass_cooldown, which the stall-fallback
retry also sets.
2026-09-28 12:43:03 -07:00
John Paul Soliva
526d135a96 fix(compression): keep the /compress here N tail in state.db under in-place compaction
compress_now() hands only the head to _compress_context() and rejoins the
kept exchanges in memory afterwards. With compression.in_place (the
default), the commit's archive_and_compact() archives every active row at
or below the lease watermark, which includes the kept tail's rows, and
inserts only the compacted head. Its tail_count rewind then lands on the
newest rows under the watermark, which are the kept tail itself, so those
rows end up active=0, compacted=0 with no live copy. No surface writes them
back: the CLI re-flushes only after a rotation, the gateway skips in-place
on purpose, and the TUI only swaps its in-memory history. The exchanges the
user asked to keep verbatim are gone on resume, and on the gateway from the
very next message.

compress_now() now passes copies of the tail as verbatim_tail. The in-place
commit stores head + tail in the same archive_and_compact() transaction,
joined with the same seam rejoin_compressed_head_and_tail() builds in
memory, and adds the tail to tail_count. The rewind flags then land on the
kept tail's originals and on compress()'s own carried rows, which were
left compacted=1 and shown twice in the resumed display history. The
copies are stamped as persisted and compress_now() returns the stored list
instead of rejoining the tail a second time. Rotation, no-op and
rolled-back commits are unchanged: the copies stay unstamped and the
caller's tail is rejoined as before.
2026-09-23 01:11:58 -07:00
beardthelion
09b72bc6d2 fix(compression): track commit fences as a registration stack
_compress_context published the active commit fence with a
save/restore cell: registration order was serialized by the fence
lock, but completion order is not. When attempt B registered over A
and A finished first, A's finally popped the slot, deleting B's live
fence mid-attempt (hard_interrupt lost the handle serializing cancel
admission against B's begin_commit). B's finally then republished A's
dead fence, which lingered until the next compression. The same
clobber existed in _publish_new_fence, which overwrote the slot
unconditionally when minting the stall-fallback retry fence.

Replace the cell with a stack of per-attempt registrations. The
finally removes only its own registration and republishes the newest
live entry (or clears the slot), so a dead fence can never be
restored over a live newer attempt. The stall-fallback retry swaps
its fence inside the owning registration and publishes only while
that attempt still holds the top registration. Registration moved
inside the try so an early exception cannot strand an entry.

(cherry picked from commit 574e9945cf186071c3da23c4bb517c0cbdaf73e6)
2026-09-20 15:27:11 -07:00
teknium1
303bcd804a fix(compression): a timed-out preflight compaction sends a fitting request and prune-commits an over-window one
A turn-start preflight pass whose summary stalled had no deterministic exit: the wrapper handed the
transcript back unchanged, _fail_closed_after_preflight_timeout raised for ANY over-threshold request
(even one that fits the model window — #113646: 99K of a 120K window), the loop labelled it
compression_exhausted, the messaging gateway auto-reset the session (#114594), and the existing
deterministic escalation (DETERMINISTIC_SUMMARY_ROUTE, #112420) was gated on a PRIOR stall in the same
session — unreachable once the first stall had already wiped it. /compress rode the same wrapper, so the
suggested recovery reproduced the same loop.

- request_exceeds_model_window(agent, tokens): one predicate, two consumers.
- Fits the window: the request is sent uncompressed this turn (the cooldown-blocked path already does
  exactly this every turn); the summary-failure cooldown stops the retry from repeating.
- Above the window: the stall retry ladder escalates to the deterministic fallback summary on the FIRST
  stall (old tool results pruned, static handoff committed through the normal lease/fence pipeline).
  compression_exhausted / auto-reset is the last resort, when even that cannot shrink the transcript.
- Per-attempt observable: one INFO line when the summary call is dispatched (model, prompt chars,
  prompt build ms) so a stalled attempt is distinguishable from a slow prompt build.
- Deterministic-rung wording no longer claims "again after a stall backoff".

Live repro (real AIAgent + SessionDB + local OpenAI-compatible stand-in whose summariser never answers
within the idle budget): before — FITS(73K/200K) and OVER(73K/64K) both end failed=True,
compression_exhausted=True, main_calls=0; after — FITS completes with the request sent uncompressed
(main_calls=1), OVER commits the deterministic fallback (103->25 rows) and completes.
2026-09-18 10:36:11 -07:00
teknium1
20d2903e39 fix(compression): slim the same-turn fallback worker and pin it with a real-path test
Fold the duplicated fallback worker into the existing snapshot worker (one
parameter, functools.partial) and replace the patched-compress_context test
with an invariant test that drives the real AIAgent facade, timeout wrap and
compress_context: the primary summary route stalls silently until the host
cancels its fence, the cancelled worker persists its stall_interrupted
backoff, and the pinned fallback_chain retry must still compress.

Why: the contributor test only asserted the kwarg handoff with compress_context
patched out, so it could not catch the real gate/cooldown race that #112387
reports (retry returned the caller's own list in 15 ms).

Co-authored-by: KoNit-K <124019182+KoNit-K@users.noreply.github.com>
2026-09-16 17:12:33 -07:00
KoNit-K
8e64cc3347 fix(compression): retry stalled fallback in the same turn 2026-09-16 17:12:33 -07:00
Teknium
e83816a4d1 review-fix(comments): restore lost #NNNN rationale comments across non-test source (mechanical sweep, condensed, code unchanged)
For each issue anchor present in BASE 63279301bc non-test .py and absent on HEAD, the BASE comment/docstring block was re-attached at the HEAD location of the code it explained (matched by the distinctive code line / enclosing def). Sentences already covered by an existing HEAD comment were deduped; the issue number always survives. Insert-only: no code lines changed.
2026-09-03 09:44:26 -07:00
Teknium
bcdd78e3c3 refactor(agent/conversation_compression): compact fence/constant docstrings and comments; hug remaining docstring bodies 2026-09-02 23:38:31 -07:00
Teknium
566adb178d refactor(agent/conversation_compression): compact docstrings — summary line hugs its prose body (text unchanged) 2026-09-02 23:30:28 -07:00
Teknium
58d9f24663 refactor(agent/conversation_compression): AST-identical — drop paragraph blanks inside function bodies 2026-09-02 23:26:28 -07:00
Teknium
8998c13bcd refactor(agent/conversation_compression): reflow prose docstrings to 110 cols (text unchanged) 2026-09-02 23:06:46 -07:00
Teknium
e0b3879d6b refactor(agent/conversation_compression): AST-identical layout — hug bracket spans, merge split string literals 2026-09-02 22:51:28 -07:00
Teknium
a6dd9dd07a refactor(agent/conversation_compression): collapse redundant guards and defensive layers 2026-09-02 22:46:53 -07:00
Teknium
6082f7d442 refactor(agent/compression): join remaining fragmented log/warning literals (AST-identical) 2026-09-02 20:13:50 -07:00
Teknium
3a1e4a7661 refactor(agent/compression): fold boundary bookkeeping locals; pack one-item-per-line call/def argument lists (AST-identical) 2026-09-02 19:51:06 -07:00
Teknium
fa4fc3ea1b refactor(agent/compression): reflow docstrings/comment blocks to 118 cols (word-identical) 2026-09-02 19:39:34 -07:00
Teknium
e0d7f18fc4 refactor(agent/compression): drop blank lines after function-local imports (AST-identical) 2026-09-02 19:31:44 -07:00
Teknium
4fc0d74fe3 refactor(agent/compression): try/except-pass -> contextlib.suppress (21 sites) 2026-09-02 19:24:14 -07:00
Teknium
7bd7c1114a refactor(agent/compression): join fragmented adjacent string literals (AST-identical) 2026-09-02 19:19:41 -07:00
Teknium
4051b537e5 refactor(agent/compression_facade): lift pooled timeout run, marker mirror and caller rebind out of _compress_context 2026-09-02 19:16:38 -07:00
Teknium
ec16f113a4 refactor(agent/compression): reflow to 120 columns (AST-identical); joins fragmented call/string continuations 2026-09-02 19:08:36 -07:00
Teknium
7524e9ec8a refactor(agent/compression): unify session rebind, scoped-twin stamping; lift facade timeout callbacks to module helpers 2026-09-02 18:17:52 -07:00
Teknium
f0526e6d74 refactor(run_agent): extract CompressionFacadeMixin; collapse 10 more lazy forwarders 2026-09-02 13:29:40 -07:00