16 Commits
Author SHA1 Message Date
Anthony Ruiz f3545da572 fix(lineage): keep tool children separate across readers 2026-09-24 11:53:47 +00:00
Anthony Ruiz a41bfe8690 fix(lineage): honor _reset_from, fail closed on untrusted markers, hash full watcher payload
Layer three additive pieces on top of the #7021 / #6931 continuation
predicate without changing its shape or CONTINUATION_STARTED_AT_TOLERANCE_SECONDS:

- _reset_from is a boundary marker alongside _branched_from/_delegate_from
  (Hermes Agent stamps it in gateway/session_recovery.py and filters it in
  _NON_CONTINUATION_CHILD_FILTER_SQL). The same predicate drives the sidebar
  listing, lineage metadata/report and the transcript stitcher.
- _branch_markers() returns ('none' | 'markers' | 'unknown', markers);
  unparsable, non-object, too-deep, or non-string marker payloads are
  'unknown' and fail closed as a lineage boundary instead of degrading to
  "no markers" (fail open).
- gateway_watcher._snapshot_hash() hashes the complete canonicalized published
  payload, and the cheap fingerprint covers model_config, so a projection change
  to title/created_at/metadata with fixed (id, updated_at, message_count) emits
  sessions_changed.
2026-09-24 11:53:47 +00:00
webtecnica 2e12075400 fix(session): apply branch-marker guard to transcript-stitcher lineage query (#7021 r2)
The open/import transcript stitcher (get_state_db_session_messages with
stitch_continuations=True) runs its own lineage SELECT that omitted
session_source/model_config, so the shared _is_continuation_session guard
added for the listing path never fired there: an explicit branch/delegate
child (model_config._branched_from/_delegate_from) starting within the 2s
tolerance window got its rows stitched into the parent transcript on open.

Select both identity columns when the schema has them (NULL AS fallback for
older state.db schemas), so the same guard applies in the stitch path.

Tests (tests/test_session_lineage_metadata_api.py):
- branched/delegate children inside the tolerance stay out of the parent
  transcript on open/import (fail before this fix, pass after);
- old-schema rows without the identity columns still stitch genuine
  compression continuations (no crash, no behavior change).
2026-08-30 21:37:05 -03:00
webtecnica 6f0028cfa1 fix(session): exclude model_config branches before tolerance; drop title fallback
Re-gate #7021: two silent sidebar regressions on the #6931 continuation
tolerance.

1. MUST-FIX 1 — branch marker in model_config. The session_source=='fork'
   guard only covers WebUI-created forks (/api/session/branch). Explicit
   Agent branches/delegates carry no session_source: they are marked in the
   model_config JSON column (_branched_from / _delegate_from). The caller
   queries did not select model_config at all, so an Agent branch starting
   inside the 2s tolerance window of its parent's compression passed the
   widened check and vanished from the sidebar. Fix: select model_config in
   the classifier's caller queries (get_cli_sessions, lineage report, lineage
   metadata) and reject a child whose _branched_from / _delegate_from points
   at the parent being classified, BEFORE the tolerance is applied. The
   marker must reference this parent: compression continuations inherit the
   rotated agent's model_config verbatim (mirrors hermes-agent
   _NON_CONTINUATION_CHILD_FILTER_SQL).

2. MUST-FIX 2 — unbounded title fallback removed. Titles are user-controlled
   and non-unique, so parent_title == child_title beyond the tolerance window
   collapsed independent same-title branches arbitrarily far apart. The
   fallback is gone entirely; only the bounded 2s early-side tolerance
   (CONTINUATION_STARTED_AT_TOLERANCE_SECONDS) remains, after the reliable
   non-continuation guards.

Tests: same-title independent child outside tolerance stays visible (was
collapsed); _branched_from/_delegate_from children inside the tolerance stay
visible (reviewer's adversarial probe); unit coverage for marker parsing
(JSON string, dict, unparsable) and marker-points-at-other-session
preservation. All 4 new tests are RED on the previous head and GREEN here.

Closes #6931
2026-08-30 21:37:04 -03:00
webtecnica 52201ac9f7 fix(session): recognize compression continuation across sub-second timestamp overlap
_is_continuation_session() required float(child.started_at) >= float(parent.ended_at).
A compression/cli_close handoff can persist the continuation row a few ms BEFORE
the parent's ended_at lands (write-order race), so the strict comparison returned
False despite every other piece of evidence (direct parent_session_id link, same
source, end_reason=compression/cli_close, non-fork). The backend then labeled the
continuation as relationship_type=child_session, causing duplicate same-title
sidebar rows and spurious child-session entries (#6931).

Allow a small tolerance (2s) for handoff-consistent timing; beyond it, an exact
title match on the parent/child rows still confirms the same handoff. Fork,
cross-source, and non-compression/cli_close guards are preserved, and materially
overlapping children remain separate child sessions.

Adds API-level regression tests (sub-second overlap forms one lineage; material
overlap stays a child session; same-title evidence beyond tolerance) plus focused
unit coverage of the classifier.

Closes #6931
2026-08-30 21:37:04 -03:00
Rod Boev ab48096de1 feat(#6033): show delegated goal titles for child sessions 2026-07-14 04:01:06 -04:00
Rod Boev 80ee386827 Preserve lineage authority through serializer and refresh pass 2026-07-06 05:08:23 -04:00
Rod Boev 409be9bcd4 fix(#5598): stabilize lineage grouping across live refreshes 2026-07-06 04:49:24 -04:00
74d7732778 stage #4597 (santastabber): reduce session-list work for archived histories (perf) (#4665)
Converged after deep bounce cycles: Finding A (production regression — skipped the
state.db source correction before the CLI filter, dropping WebUI sessions with stale-CLI
sidecar flags) FIXED via _apply_sidebar_state_db_overrides(result) before the routes.py
filter on both index + full-scan paths; Finding B (flaky lineage test) FIXED via hermetic
_persisted_session_ids_snapshot fixture. 16 own tests deterministic. Code byte-faithful
from PR head e1a3ecf22eca.

Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
Co-authored-by: santastabber <santastabber@users.noreply.github.com>
2026-06-21 22:57:28 -07:00
ai-ag2026 9650b387fd fix: keep webui mirrored sessions out of cli filter 2026-05-28 00:51:12 +02:00
Dennis Soong 143d9d8ef7 fix: reconcile stale sidebar display titles 2026-05-14 16:18:53 +08:00
Dennis Soong 5efd287264 fix: align fork lineage projection paths 2026-05-11 17:15:22 +08:00
ai-ag2026 7d5704c3bc fix: keep cross-surface session continuations visible 2026-05-07 16:58:39 +00: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
Dennis Soong 7da1e074e4 fix: expose session lineage metadata in API (#1370)
PR #1358 added the client-side lineage collapse helper, but
/api/sessions often did not include _lineage_root_id for the WebUI
JSON sessions visible in the sidebar. In that case the helper has no
grouping key and multiple same-title continuation rows remain visible.

This PR:
- Reads parent_session_id and end_reason from state.db.sessions for
  the WebUI sidebar's session ids
- Walks the parent chain when end_reason is 'compression' or
  'cli_close', producing _lineage_root_id and _compression_segment_count
- Cycle-detects via a 'seen' set
- Preserves projected lineage metadata on imported/gateway session rows
- Allows sidebar collapse to group cross-surface continuation chains
  (CLI-close → WebUI continuation) while keeping non-continuation
  parent rows flat

Co-authored-by: Dennis Soong <dso2ng@gmail.com>
2026-04-30 23:04:49 +00:00