Commit graph

4 commits

Author SHA1 Message Date
nesquena-hermes
6bf455fa40
Release v0.51.306 — Release JV (stage-a2 — branchy compression lineage freshest-tip) (#3761)
* fix(sessions): resolve branchy compression lineage to the freshest tip (#3751)

Compression parents can have multiple continuation-looking children when a stale
segment is resumed after a newer compressed branch already exists. The previous
projection followed the newest DIRECT child only, so it could hide the deeper
branch with the latest real activity and make a conversation look missing/stale
after compaction or session rotation.

- compression_tip() now walks ALL reachable continuation descendants (DFS) and
  selects the freshest importable (messageful) tip by (last_activity, depth).
- read_session_lineage_metadata() expands descendants from the materialized
  ancestors via the parent index (scoped, 20-hop cap), pulls per-session message
  stats from the messages table, and exposes a canonical _lineage_tip_id so the
  WebUI sidebar collapse picks the same tip as the projection.

Backward-compat hardening (two release-gate Codex findings, both fixed + tested):
The new message-stats / tip-scoring code must not raise on older/minimal or
non-standard state.db schemas, which previously collapsed the whole projection:
  - messages table with NO `timestamp` column → MAX(timestamp) raised in SQL →
    read_session_lineage_metadata returned {} (lost all lineage metadata).
  - ISO-8601 TEXT messages.timestamp → float()/raw comparison raised TypeError;
    in read_importable_agent_session_rows that propagated through compression_tip
    and get_cli_sessions() swallowed it, hiding ALL imported agent rows.
Fixes:
  - PRAGMA table_info(messages) detection: require session_id, only SELECT
    MAX(timestamp) when present (else NULL + COUNT only); fall back to message_count.
  - new _as_score() helper (first numerically-coercible value, else next
    candidate e.g. started_at) used at EVERY tip-scoring / sort site in both
    compression_tip() and freshest_continuation_tip(), plus the projection sort.
  - regression tests: lineage metadata survives REAL/absent/TEXT messages.timestamp,
    and read_importable_agent_session_rows survives a TEXT timestamp (no empty hide).

Co-authored-by: ai-ag2026 <[email protected]>

* docs(changelog): stamp v0.51.306 — Release JV (stage-a2 #3751)

---------

Co-authored-by: nesquena-hermes <[email protected]>
2026-06-06 19:10:04 -07:00
Dennis Soong
082f3d45b7 fix: nest child sessions under lineage roots 2026-05-02 12:09:36 +08:00
nesquena-hermes
63251ad206 release: apply Opus SHOULD-FIX 1+2 + add #1372 manual-cron persistence
Opus pre-release findings on #1370 applied:

SHOULD-FIX 1: Tightened parent_session_id exposure to only emit when
the parent's end_reason is in {compression, cli_close}. Without this,
two distinct WebUI sessions sharing a non-continuation parent (e.g.
'user_stop') would get clustered by frontend's _sessionLineageKey
(which falls through to parent_session_id when _lineage_root_id is
missing) and incorrectly collapsed into a single sidebar row.

  Updated assertions in:
  - tests/test_session_lineage_metadata_api.py::
    test_non_compression_state_db_parent_does_not_create_sidebar_lineage
  - tests/test_pr1370_lineage_metadata_perf_and_orphan.py::
    test_non_compression_parent_does_not_extend_lineage

SHOULD-FIX 2: Chunked the IN-clause to 500 vars to stay under
SQLITE_MAX_VARIABLE_NUMBER. Python 3.9 ships sqlite 3.31 with the
default limit of 999. A power user with 2000+ sessions in the
sidebar would hit OperationalError, the silent except-wrapper would
swallow it, and lineage collapse would never work. Added
test_in_clause_chunked_for_large_session_set with SQL interception
to lock the invariant in source.

PR addition (per user directive — Opus + my review, no second
independent review round needed for combined batch):

#1372 from @NocGeek — fix: persist manual cron run results.
Self-contained 89 LOC fix split out from the held #1352. Mirrors the
scheduled-cron path (cron/scheduler.py:1334-1364) exactly: saves
output, marks job complete, treats empty response as soft failure
with matching error string. 2 behavioral tests using sys.modules
monkeypatch to mock cron.scheduler.run_job. CI not yet attached
because branch is brand-new; ran the new tests + adjacent suites
locally — all pass.

Final test count: 3471 passing, 0 failed.

Also adds 2 more regression tests for the perf-fix invariants:
- test_in_clause_chunked_for_large_session_set
- test_two_children_sharing_non_continuation_parent_not_collapsed
2026-04-30 23:17:54 +00:00
nesquena-hermes
571cfed180 release: v0.50.251 (#1370 perf fix + orphan-parent guard + regression suite)
Bundles:
- #1370 fix: expose session lineage metadata in API (@dso2ng)

Pre-release fixes applied:

1) Perf: replaced full table scan with parameterized WHERE id IN (...)
   query. Original code did SELECT id, parent_session_id, end_reason
   FROM sessions on every sidebar refresh. Measured 9ms cached scan at
   1000 rows in production (up to ~450ms cold-cache); scales linearly.
   New approach hits PRIMARY KEY + idx_sessions_parent — 50x faster
   at 1000 rows, ~0.2ms regardless of total row count. Depth-bounded
   to 20 hops to cap query count under pathological data.

2) Orphan-parent guard: suppress parent_session_id in API output when
   the referenced parent row doesn't exist in state.db. The frontend's
   #1358 _sessionLineageKey falls through to parent_session_id when
   _lineage_root_id is missing — orphan references would create
   never-collapsing single-row groups in the sidebar.

3) Regression suite (5 tests in
   test_pr1370_lineage_metadata_perf_and_orphan.py):
   - Pins the no-full-scan invariant by intercepting all SQL queries
     and asserting no SELECT FROM sessions without a WHERE clause
   - Pins orphan-parent suppression
   - Pins cycle termination via threading.Event watchdog (2s timeout)
   - End-to-end test for 4-segment compression chain root resolution
   - Pins non-compression end_reason boundary stops walk
2026-04-30 23:06:37 +00:00