Commit Graph
7355 Commits
Author SHA1 Message Date
Frank Song 59b6022dd2 fix(streaming): harden recovered reasoning context ownership 2026-07-18 03:23:45 +08:00
Frank Song b741e17275 fix(streaming): recover interrupted reasoning 2026-07-18 03:23:45 +08:00
nesquena-hermes 920cdd56c0 Merge pull request #6191 from Gerkinfeltser/fix/codex-handoff-summary-current
fix: omit unsupported Codex handoff token cap
2026-07-17 12:05:10 -07:00
mol 04783d11d8 fix: normalize Codex handoff base URL 2026-07-17 16:33:52 +00:00
mol 6c863ae9c9 fix: omit unsupported Codex handoff token cap 2026-07-17 16:17:19 +00:00
nesquena-hermes f6265cc962 Merge pull request #6182 from starship-s/perf/session-switch-merge-key-cache
perf(sessions): cache reconciliation message keys
2026-07-17 03:04:15 -07:00
nesquena-hermes a3d27eb7ca Merge remote-tracking branch 'origin/master' into pr6182-g 2026-07-17 09:47:27 +00:00
nesquena-hermes d6c23582e2 Merge pull request #6181 from rodboev/pr/6108-sessiondb-retry
fix(#6108): retry transient SessionDB init for WebUI session_search
2026-07-17 02:46:30 -07:00
Rod Boev 73305706c6 fix(#6108): bound SessionDB retries to transient sqlite contention 2026-07-17 05:42:00 -04:00
Hermes Agent 1bcf8f8b46 perf(sessions): cache reconciliation message keys 2026-07-17 02:11:31 -06:00
Rod Boev 2e1867c6e5 fix(#6108): keep session_search alive through transient state.db lock races 2026-07-17 03:55:22 -04:00
nesquena-hermes 92ebc10cac Merge pull request #6134 from franksong2702/franksong2702/fix-6131-provider-aware-model-selection
Settings: preserve provider identity for duplicate model IDs
2026-07-16 20:05:09 -07:00
Frank SongandPaperclip 765b691b91 merge master into provider-aware model selection
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-07-17 09:28:09 +08:00
nesquena-hermes 650810dbb1 Merge pull request #6153 from rh-id/fix/mem-state-db-read-backstop
fix(models): add defensive row backstop to display-path state.db message read
2026-07-16 18:20:34 -07:00
Frank SongandPaperclip 484f00aa62 merge master into provider-aware model selection
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-07-17 09:18:46 +08:00
nesquena-hermes c8edc97e89 Merge remote-tracking branch 'origin/master' into p6153b 2026-07-17 01:16:08 +00:00
nesquena-hermes 196f6e261c Merge pull request #6137 from rh-id/fix/mem-streamchannel-queue
fix(stream): cap StreamChannel per-subscriber queues (drop-oldest on full)
2026-07-16 18:15:22 -07:00
nesquena-hermes 13efcbe06c Merge remote-tracking branch 'origin/master' into p6153b 2026-07-17 01:10:27 +00:00
nesquena-hermesandrh-id 5832b1e06c test(stream): drop unused MagicMock import (ruff F401)
Co-authored-by: rh-id <rh-id@users.noreply.github.com>
2026-07-17 01:10:05 +00:00
nesquena-hermes a8143affe2 Merge remote-tracking branch 'origin/master' into p6137b 2026-07-17 01:09:51 +00:00
Ruby Hartono f445c3ae39 fix(routes): gate state.db row backstop on absence of truncation_boundary prefix
Reviewer regression (reproduced): _STATE_DB_DISPLAY_ROW_BACKSTOP (newest-50k SQL
cap) was applied UNCONDITIONALLY to the GET /api/session display path's state.db
read. But merge_session_messages_append_only needs the rows at/around the
session's truncation_boundary (the preserved prefix) to reconcile correctly. A
newest-N-only cap drops those boundary rows for a >N-row session and corrupts
the merge (silently losing the preserved prefix), and also prevents older-page
(msg_before) requests from ever reaching them. So a pathological-but-real >50k-
row compressed session got a corrupted merge / unreachable older history rather
than a clean bound.

Fix: gate the backstop on the SAME conditions
_state_db_since_timestamp_for_limited_display uses to decide a read is
boundary-free — not msg_before paging, and no truncation_watermark /
truncation_boundary. Those cases stay on the full (uncapped) read; the backstop
is now a defensive net for the common uncompressed initial-tail path only.
Extracted into _state_db_backstop_limit_for_display for direct test coverage.

The get_state_db_session_messages(limit=) signature + newest-rows subquery are
unchanged — only the display-path APPLICATION of the cap is gated.

Test: tests/test_state_db_backstop_boundary_gate.py — backstop applies with no
boundary prefix; SKIPPED when truncation_boundary / truncation_watermark set or
msg_before paging; empty-string markers treated as unset (matching the existing
helper). Existing backstop + reconciliation suites (53 tests) unchanged.

Model Used: builtin:zai-coding-plan/GLM-5.2 (ZCode agent).
2026-07-17 07:19:47 +07:00
Ruby Hartono d57fa7ba3b fix(stream): retry (not break) on queue.Empty so terminal frames survive a concurrent drain
Reviewer regression (reproduced): the broadcast drop-oldest loop did
put_nowait -> on Full, get_nowait -> on Empty, break. But Empty here means a
concurrent consumer (the SSE handler thread) DRAINED the queue between our Full
and get_nowait — the queue now has space, which is exactly the condition to
retry the put. `break` silently discarded `item`, and when `item` was a terminal
frame (stream_end/error/cancel) the subscriber never received it, leaving the
client attached indefinitely (spinner-forever). The "# cap 0 defensive guard;
never reached" comment was the flawed assumption: any concurrent drain makes
the Empty branch reachable at any cap.

Fix: on queue.Empty, `continue` (retry the put into the now-empty queue) in
both the broadcast path and the replay path (for consistency; the replay path
runs under self._lock with a fresh queue so it's unreachable in practice, but
the same race-safe rule applies). No infinite-loop risk: each iteration either
succeeds a put (break) or succeeds a get (progress); the continue only fires on
Empty, after which the next put has space.

Test: test_terminal_frame_survives_concurrent_drain_during_drop_oldest
deterministically simulates the interleaving with a custom queue whose first
post-Full get_nowait raises Empty (as if a concurrent consumer drained it),
then runs BOTH the pre-fix `break` logic (drops the terminal frame — proving
the test bites) and the post-fix `continue` logic (delivers it).

Model Used: builtin:zai-coding-plan/GLM-5.2 (ZCode agent).
2026-07-17 07:04:04 +07:00
Frank SongandPaperclip 17834a6e07 merge master into provider-aware model selection
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-07-17 05:44:58 +08:00
nesquena-hermes eac20f2131 Merge pull request #6092 from pxxD1998/fix/webui-agent-runtime-update-guard
fix: block stale Agent runtime after source updates
2026-07-16 14:32:40 -07:00
nesquena-hermes ea3cba3823 Merge remote-tracking branch 'origin/master' into pr6092-gate 2026-07-16 21:05:14 +00:00
Frank SongandPaperclip f6d22641cc merge master into provider-aware model selection
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-07-17 04:20:34 +08:00
nesquena-hermesandnesquena-hermes 11773af0e3 docs(changelog): add #6143 (SessionDB subagent use-after-close) + #6140 (CLI-cache LRU cap) bullets (#6158)
Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
2026-07-16 13:17:42 -07:00
Frank SongandPaperclip c5ff4f5f3f merge master into provider-aware model selection
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-07-17 04:10:58 +08:00
nesquena-hermes b38586da38 Merge pull request #6140 from rh-id/fix/mem-cli-sessions-cache-cap
fix(models): cap _CLI_SESSIONS_CACHE with LRU eviction (drop-oldest)
2026-07-16 13:08:41 -07:00
nesquena-hermes 1101da4663 Merge remote-tracking branch 'origin/master' into p6140 2026-07-16 20:04:54 +00:00
nesquena-hermesandrh-id ecb66536ec test(diagnostics): add subscriber_dropped_events to diagnostic_snapshot exact-dict assertion
Co-authored-by: rh-id <rh-id@users.noreply.github.com>
2026-07-16 19:48:08 +00:00
nesquena-hermesandrh-id b537be3008 test(core): accept new limit kwarg in get_state_db_session_messages stub
Co-authored-by: rh-id <rh-id@users.noreply.github.com>
2026-07-16 19:47:36 +00:00
Frank SongandPaperclip 3bc757d272 merge master into provider-aware model selection
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-07-17 03:43:09 +08:00
nesquena-hermes fbe10fd535 Merge pull request #6143 from carlotestor/fix/sessiondb-close-under-live-subagents
fix: stop closing SessionDB under live background subagents
2026-07-16 12:39:47 -07:00
nesquena-hermes 3bb4fc8c20 Merge remote-tracking branch 'origin/master' into pr6143-gate 2026-07-16 19:34:40 +00:00
nesquena-hermesandcarlotestor dcdcfb9977 fix(streaming): degrade self-heal SessionDB to None on failed rebuild
Codex gate on #6143 found a SILENT regression the reuse-path fix introduced:
_replace_session_db_in_kwargs early-returned the OLD handle when the rebuild
failed, but master assigned None. If the old handle was already CLOSED, both
credential self-heal callers (streaming.py:9032/10253) then built a fresh agent
around a closed SessionDB -> every persist/search fails with 'NoneType has no
attribute execute' while the chat keeps going. Guard on _session_db_is_open:
keep a live handle (subagents hold it by ref), else degrade to None (clean lazy
reinit, master behaviour). +2 regression tests.

Co-authored-by: carlotestor <carlotestor@users.noreply.github.com>
2026-07-16 19:14:53 +00:00
Ruby Hartono dec041997f fix(models): add defensive row backstop to display-path state.db message read
Memory: get_state_db_session_messages had no SQL LIMIT — it full-scanned the
state.db messages table and fetchall()'d into a Python list. The display path
(GET /api/session) already has a careful since_timestamp optimization that
bounds the common uncompressed tail load, but it deliberately bails (returns
None) on compressed sessions (truncation_watermark/boundary) and msg_before
paging — in those cases the read used to full-scan with no cap. A
pathological/huge state.db could then materialize unbounded rows into memory.

Add an opt-in `limit` parameter to get_state_db_session_messages (applied as a
SQL LIMIT that keeps the NEWEST rows via a DESC subquery re-sorted ASC). It is
a BACKSTOP, not a semantic window: the display window counts visible rows
post-reconciliation, so a true window LIMIT here would corrupt the
sidecar/state.db merge (see _state_db_since_timestamp_for_limited_display,
which deliberately does NOT SQL-LIMIT raw rows for exactly that reason).

Thread a generous _STATE_DB_DISPLAY_ROW_BACKSTOP (50000) from the GET /api/session
display caller only. The full-history model-context callers (reconciliation at
models.py:3437, context reconstruction at :8576, new-turn context at
streaming.py:8443) do NOT pass a limit — their semantics require the full
history, unchanged.

Test: tests/test_state_db_read_backstop.py — no-limit returns full history
oldest-first (model-context contract unchanged); limit=N keeps the newest N
re-sorted oldest-first; limit > row count returns all; limit<=0 clamps to 1
(newest); invalid limit falls back to unbounded; limit composes with
since_timestamp. Existing state.db active-filter + reconciliation suites (54
tests) unchanged.

Contract Routing
- Task type: memory-bound fix (unbounded state.db row materialization on the
  display path's bail cases).
- Touched areas: state.db message reader (api/models.py) + GET /api/session
  display caller (api/routes.py). NOT the model-context callers.
- Relevant public docs: AGENTS.md, CONTRIBUTING.md, docs/CONTRACTS.md,
  docs/rfcs/webui-run-state-consistency-contract.md (Model context layer).
- State layer mutated: none (read-only). The model-context read path is
  UNCHANGED (no limit passed) — Invariant 1 (visible current turns enter model
  context) and the reconciliation contract are preserved. Only the display path
  gets the defensive cap, which does not affect what the agent receives.
- Scope boundaries: this is narrower than a naive "add LIMIT everywhere" —
  _state_db_since_timestamp_for_limited_display's docstring explains why a
  semantic window LIMIT would corrupt the merge; this cap is purely a
  pathological-input safety net above any legitimate session size.

Release note: GET /api/session's state.db read now has a defensive row backstop,
capping memory when the bounded since_timestamp optimization bails on
compressed/paged sessions.

Model Used: builtin:zai-coding-plan/GLM-5.2 (ZCode agent).
2026-07-16 23:40:26 +07:00
Frank SongandPaperclip 6e3543de16 merge master into provider-aware model selection
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-07-16 23:44:01 +08:00
Frank SongandPaperclip aafa6735aa test(models): harden provider selection harness
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-07-16 23:24:42 +08:00
df0c204d73 docs(rfc): reconcile Live-to-Final assistant-reply contracts with shipped behavior (#6144) (#6147)
Docs-only reconciliation of the Stable Assistant Turn Anchors / Live-to-Final
RFCs + CONTRACTS.md + Phase 0 inventory from proposed/scaffold to
accepted/implemented framing. No runtime/CI change; one comment-header fix in
static/assistant_turn_anchors.js. Remaining hardening tracked under #3400.

Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
Co-authored-by: franksong2702 <franksong2702@users.noreply.github.com>
2026-07-16 08:19:43 -07:00
pxxD1998 aae2830459 fix(compression): preserve running job reuse 2026-07-16 22:56:41 +08:00
carlotestor 7d2ca8cabb fix: address greptile review on SessionDB adopt helper
- Log failed closes in _adopt_session_db_for_cached_agent with
  logger.debug(..., exc_info=True) instead of bare pass, matching
  _replace_session_db_in_kwargs so a failed close (EMFILE risk) is
  diagnosable.
- Drop the fragile exec/source-slice fallback in the test helper import;
  real import or pytest.skip. Source-level pins still cover reverts
  without importing streaming.
2026-07-16 16:53:42 +02:00
pxxD1998 798bc5fb51 fix(compression): reject stale runtime before async job 2026-07-16 22:43:19 +08:00
carlotestor 0cee7dba47 fix: stop closing SessionDB under live background subagents
Background subagents (delegate_tool) capture parent_agent._session_db by
reference. The cached-agent reuse path created a fresh SessionDB every
stream request and unconditionally closed the previous handle (PR #1421
EMFILE fix). A server-side wakeup / new turn for the same parent session
could therefore close the shared handle while children were still
flushing messages, producing:

  Session DB append_message failed: 'NoneType' object has no attribute 'execute'

Reproduce (observed in production):
1. Parent WebUI session runs delegate_task with N background subagents
2. Parent turn tears down / idle-hook redelivers a deferred wakeup
3. New stream for the parent adopts a fresh SessionDB and closes the old one
4. Live subagents' append_message hits conn=None

Fix: _adopt_session_db_for_cached_agent reuses a still-open handle (and
closes the unused new one — still EMFILE-safe) and only replaces when
the existing handle is missing or already closed. Same guard applied to
_replace_session_db_in_kwargs (credential self-heal). LRU eviction still
closes at true session boundaries.

Tests updated to pin the adopt helper and the subagent-safe semantics.
2026-07-16 16:38:43 +02:00
pxxD1998 2415995204 fix(runtime): ignore in-memory agent module swaps 2026-07-16 22:28:35 +08:00
pxxD1998 0bab6a3d7a fix: handle remaining stale runtime entrypoints 2026-07-16 22:28:35 +08:00
pxxD1998 66dceb10e5 fix: ignore untracked enclosing agent repositories 2026-07-16 22:28:35 +08:00
pxxD1998 f722f68738 fix: scope stale runtime guard by execution owner 2026-07-16 22:28:35 +08:00
pxxD1998 5f1791e08d fix: reject stale runtime before chat mutation 2026-07-16 22:28:35 +08:00
pxxD1998 5df6b1fe8a fix: stop stale agent runtime after source updates 2026-07-16 22:28:35 +08:00