Commit Graph

36 Commits

Author SHA1 Message Date
Maciej Lisiewski
d76cce4f11 ci: apply all automatic linter fixes available 2026-10-08 17:22:54 +02:00
kshitijk4poor
f432912636 refactor(compaction): drop a dead budget guard and read the user content once
The only caller passes a positive budget and a zero remainder already yields an empty head via _head_within_budget, so the early break never changed the result; the boundary branch now binds content once. Final-head gate (quality Lows).
2026-10-05 20:01:03 +05:30
kshitijk4poor
4018869d37 refactor(compaction): ASCII fast path and CHARS_PER_TOKEN in the budgeted head bisect
Gate r1 Low: _head_within_budget hard-coded the 4 chars/token ratio that
agent.model_metadata already names CHARS_PER_TOKEN, and bisected even for
all-ASCII heads, whose estimate is exactly ceil(len / CHARS_PER_TOKEN) so the
budget-sized slice always fits. Return that slice directly when it is ASCII;
clamp the budget at 0 so a non-positive budget still yields ''.

Equivalence vs the previous implementation: 3000 random cases (a/ж/中/é/space,
budgets -2..20), 0 mismatches.
2026-10-05 20:01:03 +05:30
kshitijk4poor
40eebc785a refactor(compaction): share the budgeted head bisect across string and typed boundaries
#132147 re-implemented the budget*4-capped estimator bisect inside
_truncate_input_text_parts, while FroRaut's #127458 introduced the same
search as _head_within_budget for the plain-string boundary. Two copies of
one search drift; the typed input_text path now calls the shared helper so
string and typed boundary items are cut by the identical rule.

Co-authored-by: FroRaut <79215537+Froraut@users.noreply.github.com>
2026-10-05 20:01:03 +05:30
FroRaut
1f75ef5137 fix(compression): keep retained non-ASCII user text within budget
When a native compaction checkpoint is replayed, the oldest
pre-checkpoint user message that only partly fits the retained-user
budget is cut at the front. The cut was content[: user_remaining * 4],
which counts characters, but the budget is charged with
estimate_tokens_rough, which counts UTF-8 bytes / 4 and one token per
CJK character. A Cyrillic boundary message kept about 2x the remaining
budget and a CJK one about 4x.

agent/native_compaction.py:prune_pre_checkpoint_items now cuts with a
new _head_within_budget helper, which binary-searches the longest
prefix whose _approx_tokens fits the remaining budget. ASCII text
still keeps user_remaining * 4 characters, and the str slice never
splits a UTF-8 character.

(cherry picked from commit 95b069263440372bec619bb195aa68ba42d213ca)
2026-10-05 20:01:03 +05:30
kshitijk4poor
4d1b2dcf65 fix(compaction): tolerate content-less user item at over-budget boundary
BN01 fold2 finding (2c Low): collapsing the over-budget branch in
d24e5875b7 read `item["content"]` unguarded. A user message whose text
is measured only via `output_text` (no `content` key) reaches that
branch when it exceeds the budget and raised KeyError, aborting the
prune. Use `item.get("content")` so it falls through to the
non-truncatable path (keep nothing, budget reset) as before the
refactor. The typed-boundary parametrize gains a row for it.
2026-10-05 20:01:03 +05:30
kshitijk4poor
df3d72e8ac refactor(compaction): collapse the over-budget boundary branch
BN01 quality finding (Low): the over-budget user path had a string
branch and an else branch that each ended with their own
`user_remaining = 0`, and the else re-tested `parts_cost is not None`.
Fold them into one else that picks the head (string slice, typed-part
head, or nothing for a non-truncatable boundary), appends it when it
has non-blank text, and resets the budget once. Behaviour is unchanged:
a snapshot probe over string, blank, typed, blank-typed and text+image
boundaries prints identical output before and after.
2026-10-05 20:01:03 +05:30
kshitijk4poor
98c77d0657 perf(compaction): cap the typed-boundary bisect at budget*4 chars
BN01 efficiency finding (Low): the head search over an oversized
input_text part started with hi = len(text), so each of its ~20 probes
re-estimated slices up to half the whole part on every native-compaction
request. estimate_tokens_rough charges every char at least 1/4 token
(ASCII (n+3)//4, other non-CJK >= 1 UTF-8 byte, CJK-dense 1), so no
prefix longer than budget*4 chars can fit and the cap never changes the
result. Proven with a randomized real-import check (2000 cases across
ASCII, Cyrillic, CJK, emoji and mixed text: 0 mismatches); 1M Cyrillic
chars at budget 64k: 40.8ms -> 28.0ms.
2026-10-05 20:01:03 +05:30
kshitijk4poor
a304e716bf refactor(compaction): append fitting input_text parts without copying
BN01 quality finding (Low): _truncate_input_text_parts copied each
fully-fitting part with {**part} although it never mutates it. The
fully-fitting item path already appends the original item uncopied,
and the clipped boundary part is still a new dict. No caller mutates
the returned parts (the result is only placed into the retained
message), so the copy was dead work.
2026-10-05 20:01:03 +05:30
kshitijk4poor
fe422a690a test(compaction): trim typed-retention tests to two invariants
Keep the converter->pruner checkpoint/no-checkpoint case (the issue's
headline: an older completed ask must not survive while the newest
oversized typed ask is dropped) and one boundary case covering multipart
clipping, the token-estimator budget for CJK text, and an oversized
mixed text+image boundary that now retains no older ask (behaviour change:
previously the oversized boundary was skipped and an older ask kept).

Also drop the provenance-footer disclaimer from the docstring.

Parallel fix in #131819 by liuhao1024 credited here.

Co-authored-by: liuhao1024 <sunsky.lau@gmail.com>
2026-10-05 20:01:03 +05:30
Xipong
5af47ddcd2 fix(compaction): retain oversized typed user text at checkpoint boundary
(cherry picked from commit b2b8cf5282cfa6649aa2d156369e057bd9342f21)
2026-10-05 20:01:03 +05:30
kshitijk4poor
23200e75e1 fix(agent): keep user image parts through typed Responses preflight
Stamping type=message on user items routed them from the untyped role
validator to _preflight_message, whose new user branch accepted text parts
only. Every turn with an image in history then failed preflight with
"unsupported type 'input_image'" on all codex_responses routes.

_preflight_message now keeps its strict output_text path for replayed
assistant output (a content list carrying id/status/phase) and hands every
other typed message to _preflight_role_message, the single validator for
role items (string content, user image parts). Preflight is therefore a
no-op on the converter's own output: it no longer synthesizes a status the
converter never sent (plain assistant items on custom endpoints, typed
output_text items on the ChatGPT Codex backend). The content annotations
widened to Any go back to lists.

native_compaction's retained summary carrier gets the same explicit type.

The PR's nine new adapter tests collapse into one converter -> preflight
invariant, parametrized over the default and codex_backend issuers, with a
user image turn; it fails on the contributor head and on main. The empty
assistant-carrier guard in test_responses_empty_tool_replay matches any
empty assistant item again rather than one exact dict.
2026-10-01 16:18:23 +05:30
kshitijk4poor
11c50f05d0 refactor(compression): Astra native-compaction gate reuses is_astra_model
The -900k alias fix hand-rolled a second copy of the Astra slug set and its
vendor-prefix normalization. agent/reasoning_effort.py::is_astra_model is the
documented single home for that set (picker, effort vocabulary and request
sanitizer already key off it), so the gate now calls it and a future Astra
alias stays a one-line edit. The gpt-5.6 marker check is back to main's exact
form.

Tests move into the existing parametrized Astra gate table, which checks both
the capability resolver and the per-request gate: -900k on official Codex OAuth
is eligible; -900k through a relay or on provider openai is not. Docs and the
config example no longer say "exact gpt-6-astra".
2026-09-26 19:27:41 +05:30
qqx
60b99e1932 fix(compression): enable native compaction for Astra 900K alias
(cherry picked from commit 8ce2851f0495db8afb44118928f4c3695bb12e06)
2026-09-26 19:27:41 +05:30
GodsBoy
33b2461be4 fix(compression): enable Astra native compaction on official Codex OAuth 2026-09-19 00:53:26 -07:00
teknium1
3f59b5c594 refactor(agent): /context breakdown and native-compaction retention use the canonical token estimator
context_breakdown._chars_to_tokens and native_compaction._approx_tokens did raw
chars//4, under-counting CJK/Cyrillic by 2-4x next to the conversation slice
that already used estimate_tokens_rough — the /context pie chart mixed two
estimators. Both now call the canonical. The four private `= 4` ratio
constants import one CHARS_PER_TOKEN from agent/model_metadata.py. Estimates
only feed UI and budgets; no prompt or message bytes change.
2026-09-13 05:09:43 -07:00
Benjamin Brumbaugh
cd71ee0708 fix(compression): defer local preflight after native checkpoint
A native Responses compaction checkpoint is opaque ciphertext; the rough
preflight estimator counts it as text (5.17M chars -> ~1.29M tokens against
a 204K trigger) and fires local compression on a request whose real prompt is
~116K. Arm the existing one-response real-usage latch when a replayable
checkpoint is captured (build_assistant_message) or restored into a fresh
agent (_hydrate_from_history), honor it in the post-tool gate and idle
compaction, and require non-empty encrypted_content for a checkpoint.

Squash of the author's source commits from #100642 (0e3c234ea0, 771e1b3365,
bb1505a119) plus the fdf140c81d test refresh, re-based onto current main by
patch application. Source delta is byte-identical to the PR head d6ce3e236d.

Fixes #100611
2026-09-06 09:09:00 -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
acfa6e3691 refactor(agent): pass-2 structural compaction of prompt_caching and native_compaction
- prompt_caching: fold _count_cache_markers' nested _marked into one sum over
  [*messages, *parts, *tools]; collapse the open-turn guard + endpoint append in
  _completed_transaction_endpoint_indexes into one predicate; enumerate() in
  apply_anthropic_cache_control's non_sys scan; effective_cache_ttl tail -> one
  conditional; pack wrapped calls/comprehensions to <=120 cols; tighten
  docstrings (every WHY kept).
- native_compaction: resolve_compact_threshold upper -> one conditional expr;
  warn-once guard -> single if; _extract_item_text part loop drops the str/dict/
  else ladder (candidates default (part,), non-str filtered by the same isinstance
  check); _retain_summary early-return -> guarded body; pack wrapped calls and
  docstrings.
Zero behavior change: E corpus (224MB) and C corpus byte-identical vs base
113f04616bd; extra corpus for warn-once/_extract_item_text edges identical;
68 test files / 1359 tests green; no symbol added/removed vs pass 1.
2026-09-02 21:44:48 -07:00
Teknium
3b5aa80473 refactor(agent): finish memory/compaction/prompt-cache compaction pass (>=25% LOC) 2026-09-02 19:50:13 -07:00
Teknium
21cbb27d89 refactor(agent): compact memory/compaction/prompt-cache modules (pass 2, corpus parity) 2026-09-02 19:07:30 -07:00
Teknium
4f20954c5f refactor(agent): tighten memory/compaction/prompt-cache helpers (pass 1, corpus parity) 2026-09-02 18:28:38 -07:00
Teknium
44982309b8 refactor(agent/prompt): dispatch tables and helper extraction in display, context refs, breakdown, compaction
build_tool_preview -> _PREVIEW_BUILDERS per-tool table; git @refs -> _GIT_REFERENCE_ARGS; context_breakdown
_skills_block/_append_overflow dedupe; prune_pre_checkpoint_items summary retention folded into one closure;
build_skill_invocation_message reuses _render_skill_block; ruff SIM collapses; restored two compacted
cache-policy invariant comments.
2026-09-02 13:53:58 -07:00
Teknium
be5c6a2fd8 refactor(agent/prompt): remove dead code, unify duplicated helpers, compact docstrings across prompt/skill/redaction modules
Dead (zero refs): coding_system_blocks, get_friendly_tool_labels, get_scan_ordered_skills_dirs,
_project_quarantine_cache_clear, clear_stable_prefixes, _redact_http_request_target_query_params,
_has_http_method_substring, PromptCachePlan.marker_count, display _diff_* colour thunks (-> _diff_ansi),
pass-through RedactingFormatter.__init__.
Unified: _slugify -> slugify_skill_name; reload diff -> diff_command_snapshots; _is_summary_item ->
is_compaction_summary_message alias; sanitizer walkers -> _sanitize_messages/_sanitize_structure;
assignment redaction passes -> _redact_assignments/_should_redact_assignment; quiet-mode tool lines -> _CUTE_LINES table.
2026-09-02 13:53:57 -07:00
Stephen Chin
c9b9b5e6c7 fix(gateway): preserve native compaction capability on resume 2026-08-30 05:16:10 -07:00
Stephen Chin
5247a6f07f fix(compaction): clarify runtime capability state
Use a distinct runtime_capabilities field on agents, preserve compatibility with earlier snapshots, and resolve the canonical direct OpenAI endpoint when a cross-provider switch omits base_url. Keep ambiguous proxy routes fail-closed.
2026-08-30 05:16:10 -07:00
Stephen Chin
08c7879ca1 fix(compaction): preserve native capability across runtime switches
Stage destination native-compaction capabilities until the complete runtime and context setup succeeds, and restore them with primary and fallback runtimes. Keep native compaction default-deny across live switches and session reconstruction.\n\nVerification: uv run --with pytest --with pyyaml python -m pytest tests/run_agent/test_switch_model_context.py tests/run_agent/test_native_compaction.py tests/run_agent/test_native_compaction_switch_capabilities.py tests/run_agent/test_switch_model_rollback.py tests/run_agent/test_fallback_reasoning_override.py tests/run_agent/test_primary_runtime_restore.py tests/run_agent/test_provider_fallback.py -q -o 'addopts='; uv run --with ruff ruff check <touched files>; git diff --check
2026-08-30 05:16:10 -07:00
Andrex Ibiza, MBA
532b2d8874 fix(native-compaction): retain image-only user content
Preserve valid normalized input_image user messages across native-compaction checkpoints at bounded one-token retention cost. Keep text extraction text-only, reject malformed or unknown multipart placeholders, and prove the production adapter path without claiming unsupported input_file behavior.

Republish the identical source tree after an unrelated nondeterministic focus-redraw test failure; this commit contains no source delta from the previously verified object.

Refs #90976 and #91477.
2026-08-30 05:15:54 -07:00
Marco Fernstaedt
a2af8405d1 fix(compression): derive native threshold from local trigger 2026-08-30 05:15:37 -07:00
Jan-Stefan Janetzky
8cc379b528 fix(memory): bind the checkpoint gate to every compaction authority
The fail-closed gate lived only in compress_context(), but two native
lossy owners compact without ever crossing it (review on #93996):

- codex app-server: in "native"/"off" auto-compaction mode (native is the
  default) Hermes preflight is skipped and the codex agent compacts its
  own thread inside run_turn() — the compress_context() rejection was
  unreachable. init_agent now refuses checkpoint_required together with
  api_mode=codex_app_server (BLOCKED_MISSING_PREREQUISITE, extracted as a
  testable guard), and run_codex_app_server_turn() fails closed as
  defense in depth before a turn can reach the codex-owned boundary.
- Responses server-side native compaction:
  native_compaction_context_management() now returns None while the gate
  is armed, so context_management never goes on the wire and the
  checkpoint-aware Hermes compressor stays authoritative. The suppression
  is logged once per process, not silently applied.

Regressions: checkpoint_required + app-server raises before run_turn()
(the session is never created); checkpoint_required keeps
context_management off the wire while the plain configuration still
produces it; the init guard refuses exactly the incompatible pair. Docs
and cli-config.yaml.example describe both bindings.

Refs #93986
2026-08-25 03:55:55 -07:00
JoaoMarcos44
fb27614add fix(native_compaction): preserve compression summary messages during pre-checkpoint pruning
prune_pre_checkpoint_items() had a hardcoded role=='user' filter that
discarded all non-user messages before a checkpoint — including Hermes'
own compression summaries (role='assistant'), causing total context amnesia
about past conversation summaries.

The fix:
- _is_summary_item delegates to the canonical
  agent.context_compressor.is_compaction_summary_message provenance check
  (not an ad-hoc heuristic)
- Summaries are retained whole (never byte-sliced) within a 32k token budget
- Idempotent across repeated checkpoints (dedup by identical text)
- _chat_messages_to_responses_input threads item_sources (raw chat messages)
  through to the pruner, so it can read summary content directly from the
  source when the Responses conversion shape is lossy (tool-result carrier
  becomes function_call_output, or stale codex_message_items replay shadows
  merged content)

Fixes #90975.

Salvage of #90976 by @JoaoMarcos44.
2026-08-21 17:24:22 +05:30
Drexuxux
6c2d4efd02 fix(agent): keep native compaction checkpoints out of the thinking-only drop
A type="compaction" item is the server-side stand-in for history that has
already been pruned, and it rides the same codex_reasoning_items sidecar as
per-turn reasoning. e00965a7e taught compaction pruning to filter that
sidecar instead of popping it so checkpoints survive on every retained
message.

The thinking-only sanitizer reaches the same sidecar from the other
direction and asks a coarser question: does any item have type ==
"reasoning"? A commentary turn carrying both a reasoning item and a
checkpoint answers yes, so the whole message is dropped from the wire copy
and the only copy of the checkpoint goes with it. The request then carries
neither the compacted history nor the checkpoint standing in for it.

Extract has_compaction_checkpoint() into agent/native_compaction.py — the
module that owns the concept, and where merge_interim_reasoning_items()
already spelled the same predicate inline — and consult it before the
thinking-only verdict. A reasoning-only carrier is still dropped.
2026-08-13 03:04:45 -07:00
Teknium
e029e300ca fix(compression): harden native compaction rejection matcher + config coercion (#82777)
Two reliability gaps from #82777:

1. Rejection matcher required only a field-name mention, so a transient
   5xx/timeout whose body echoed the request (which contains
   context_management) permanently downgraded native compaction for the
   session. Now requires rejection language (unknown/unsupported/invalid/...)
   alongside the field name, and when a parsed HTTP status is available,
   400 specifically — non-400 statuses never match. Message-only transports
   (no status attribute) keep working unchanged.

2. compression.codex_responses_native was coerced with bool(), so the
   strings "false"/"off" enabled the feature. Now uses the shared
   utils.is_truthy_value helper.

Conversation-loop call site passes api_error.status_code through.
Sabotage-verified: reverting the matcher to field-name-only fails the new
echo and non-400 tests.
2026-08-13 03:04:31 -07:00
Teknium
e4b3b91b62 fix(compression): prune pre-checkpoint history on native compaction replay
Live verification (gpt-5.6 @ api.openai.com) proved the Responses server
renders NOTHING placed before a replayed compaction checkpoint: a fact
stated in a pre-checkpoint input item is invisible to the model, while the
same item after the checkpoint recalls perfectly. Hermes was replaying the
full pre-checkpoint transcript anyway — dead upload weight, and worse, every
plaintext user ask from before the boundary silently vanished from the
model's view, surviving only inside the opaque server summary. That is the
goal-drift failure mode reported against native compaction sessions.

Codex CLI never hits this because it rebuilds history client-side after
compaction, retaining user messages verbatim under a token budget. This
change is the wire-level equivalent: when a replayed checkpoint is present,
_chat_messages_to_responses_input restructures the input as

  [newest checkpoint run] + [retained pre-checkpoint user messages,
  newest-first within a 64K-token budget] + [post-checkpoint tail]

Histories without a checkpoint are returned unchanged, so non-native
sessions see a byte-identical wire.
2026-08-13 01:51:24 -07:00
Teknium
e00965a7e8 fix(compression): correct prune boundary + exempt native compaction checkpoints
Two corrections on top of the #71077 base (the whole bug class):

1. Turn boundary = last USER message, not last assistant message. A Codex
   turn spans several assistant messages (assistant+tool_calls -> tool ->
   ... -> final assistant) whose reasoning items must replay together; the
   last-assistant boundary would strip reasoning mid-chain from the active
   turn (the gap flagged in PR #71077 review).

2. type="compaction" checkpoints (native server-side compaction, PR #81747)
   are exempt: they carry already-pruned history, not per-turn reasoning.
   Pruning filters items instead of popping the sidecar key.

Sibling site fixed in the same class: the Codex incomplete-continuation
dedup path blind-overwrote codex_reasoning_items on visually-duplicate
interim messages, which would drop the only copy of a checkpoint captured
on the earlier response. Extracted merge_interim_reasoning_items() into
agent/native_compaction.py; newer reasoning wins, prior checkpoints are
preserved unless the newer payload carries its own.
2026-08-08 14:09:41 -07:00
Teknium
5e1b50115f feat(compression): native OpenAI Responses server-side compaction for gpt-5.6
Opt-in via compression.codex_responses_native (default: false). When enabled,
gpt-5.6-family models on the direct OpenAI API (api.openai.com) or a ChatGPT
Codex subscription send context_management=[{type: compaction,
compact_threshold: N}] on Responses requests. OpenAI compacts server-side and
returns an encrypted compaction output item; Hermes captures it into the
existing codex_reasoning_items sidecar and replays it on later turns in place
of the pruned history — inheriting persistence, session replay, the
cross-issuer guard, and the encrypted-replay kill switch with zero new state.

Scope is deliberately hard-gated (agent/native_compaction.py, re-checked per
request): gpt-5.6 family only — gpt-5.1/5.2 fail server-side on the field
(HTTP 500 / stream stall, no structured rejection; live-verified) — and
direct OpenAI/Codex routes only; xAI, GitHub/Copilot, OpenRouter, relays,
and local servers never see the field.

Hermes' local compression stays armed as the fallback owner: the native
threshold is clamped ~8K tokens below the local trigger so the server
compacts first, and a structured provider rejection of context_management
disables native compaction for the session and retries without it
(one-shot guard in TurnRetryState).

Live-verified E2E on api.openai.com/gpt-5.6: server compaction fired at a
4K threshold, checkpoints captured and replayed, recall preserved across
3 turns; gpt-5.1 with the flag enabled stays clean (field never sent).

Direction credit: PR #76950 by @laryhorb explored native Responses
compaction; this is a minimal reimplementation on current main.
2026-08-08 11:24:45 -07:00