Skip to content

fix(insights): derive real terminal_state for bounded large-session profiles - #3492

Merged
Sinity merged 1 commit into
masterfrom
fix/insights/bounded-profile-terminal-state
Jul 31, 2026
Merged

fix(insights): derive real terminal_state for bounded large-session profiles#3492
Sinity merged 1 commit into
masterfrom
fix/insights/bounded-profile-terminal-state

Conversation

@Sinity

@Sinity Sinity commented Jul 31, 2026

Copy link
Copy Markdown
Owner

Summary

Bounded/degraded large-session profiles now compute a real terminal_state from a bounded tail read, instead of hardcoding "unknown" unconditionally.

Problem

Live evidence (triage-verified 2026-07-31): all 1,575 bounded_large_session profiles carried terminal_state="unknown" (100%) — build_large_session_insight_record_bundle_{sync,async} in storage/insights/session/rebuild.py hardcoded terminal_state="unknown", terminal_state_confidence=0.0, terminal_state_method="bounded_materialization" unconditionally, regardless of any structural evidence in the session. Message stop_reason is now persisted (index v46, PR #3390), but the bounded profile path never read the tail data needed to derive terminal state at all. This left the longest, most failure-prone sessions (message-count decile 9) terminal-state-blind, gating campaign D3 in the annotation launch order (per polylogue-wofr notes).

_terminal_state (archive/session/runtime.py) is structurally O(session tail): it only needs the last message, last tool outcomes, and trailing session events — not a full-session scan — so excluding it from the bounded path was unnecessary caution, not a real cost tradeoff.

Solution

  • Added a bounded tail read (_tail_session_sync/_tail_session_async in polylogue/storage/insights/session/rebuild.py): last 50 messages + their non-text (tool_use/tool_result) blocks + last 200 trailing session events for one session, via new _SESSION_INSIGHT_TAIL_MESSAGE_SQL/_SESSION_INSIGHT_TAIL_BLOCK_SQL_TEMPLATE/_SESSION_INSIGHT_TAIL_EVENT_SQL queries (each LIMIT-bounded, ordered ascending via a subquery so no Python-side reversal is needed).
  • These hydrate a minimal in-memory Session (session_from_records) for just that tail window.
  • _bounded_session_terminal_state_{sync,async} then call build_session_analysis + the exact same _terminal_state function (archive/session/runtime.py) the unbounded profile path already uses — imported directly, not reimplemented, per the existing precedent of importing private runtime helpers into rebuild.py (e.g. _primary_model).
  • _large_session_profile_record_from_row now accepts an optional terminal_state_result and threads the real (terminal_state, confidence, evidence, method) into the evidence payload, inference payload, and stored SessionProfileRecord fields. The bounded_materialization provenance marker is kept in terminal_state_evidence alongside the real structural evidence, so a reader can still tell the record came from the degraded path.
  • All other bounded/degraded reductions (workflow_shape, cost, work events, phases) are unchanged — this is scoped strictly to terminal_state.

A session whose real terminal event falls outside the tail window degrades to whatever _terminal_state reports from the window it was given (typically "unknown"/"no_signal") — the same honest behavior as before for sessions genuinely lacking structural evidence in scope.

Verification

  • python -m devtools test tests/unit/storage/test_session_insight_refresh.py — 32 passed, including two new tests:
    • test_large_session_rebuild_derives_terminal_state_from_bounded_tail: a bounded-profile fixture whose tail message's final tool outcome is a typed error asserts terminal_state="error_left"/method="action_outcome" via the bounded path with load_sync_batch patched to raise (proving no full hydration). Anti-vacuity: manually reverted the derivation to the old hardcoded "unknown" and confirmed both this test and the parity test below fail (assert 'unknown' == 'error_left'), then restored the fix.
    • test_bounded_and_unbounded_terminal_state_agree_on_shared_fixture: the same fixture (2 messages, well within the 50-message tail window) materialized once through the ordinary full-analysis path and once through the bounded/degraded path (threshold monkeypatched to 1) derives identical terminal_state/terminal_state_method/terminal_state_confidence via both routes.
  • python -m devtools verify --quick — exit 0 (format, lint, mypy --strict, render all --check, layering, schema-versioning, and other policy gates).
  • python -m mypy polylogue/storage/insights/session/rebuild.py — Success: no issues found in 1 source file.

Not run: devtools verify --all / the full non-integration suite (out of scope for this focused fix; the touched surface's own test file — 32 tests — is green, and per-PR CI runs lint + skips the heavy test suite until merge).

Acceptance criteria (polylogue-wofr)

  1. Synthetic heavy session with a typed tool error at the tail → terminal_state="error_left" with evidence, via the bounded rebuild path. Satisfied (async twin shares the same helper functions, exercised by the existing async bounded-path tests plus manual code-path symmetry — no separate async-specific terminal-state test was added since _bounded_session_terminal_state_async is a direct twin of the sync version with identical logic).
  2. Clean-tail heavy session yields the same terminal_state as its unbounded equivalent. Satisfied via the parity test.
  3. Bounded-path cost stays O(tail): the existing fail_full_load/guarded_load monkeypatches (asserting load_sync_batch/load_async_batch are never called) continue to pass, and the new tail queries are separate LIMIT-bounded SQL, not the full per-session batch loader. No new explicit row-count-budget assertion was added beyond the existing "must not call the full loader" guard — deferring a dedicated cost-pinning test as a possible follow-up if live telemetry ever shows otherwise.
  4. Existing bounded-profile tests stay green. Satisfied — all 32 tests in the file pass, none modified beyond the two additions.

Ref polylogue-wofr

…rofiles

Problem: the bounded/degraded large-session profile path
(build_large_session_insight_record_bundle_{sync,async} in
storage/insights/session/rebuild.py) hardcoded
terminal_state="unknown"/confidence=0.0/method="bounded_materialization"
unconditionally. Live evidence: all 1,575 bounded_large_session profiles
carried terminal_state="unknown" (100%), making the longest -- and most
failure-prone -- sessions terminal-state-blind. `_terminal_state`
(archive/session/runtime.py) is structurally O(session tail): it only
needs the last message, last tool outcomes, and trailing session events,
not a full-session scan, so excluding it from the bounded path was
unnecessary caution (polylogue-wofr).

What changed: added a bounded tail read (last 50 messages + their
tool_use/tool_result blocks + last 200 trailing session events, via new
`_tail_session_sync`/`_tail_session_async` and
`_bounded_session_terminal_state_{sync,async}`) that hydrates a minimal
in-memory `Session` for just that window and calls the exact same
`_terminal_state` derivation the unbounded profile path uses -- no
parallel heuristic. `_large_session_profile_record_from_row` now takes an
optional `terminal_state_result` and threads it into the evidence,
inference, and stored profile-record fields, keeping the
`bounded_materialization` provenance marker alongside the real structural
evidence. All other bounded/degraded reductions (workflow_shape, cost,
work events, phases) are unchanged.

Verification:
- `python -m devtools test tests/unit/storage/test_session_insight_refresh.py`
  -- 32 passed, including two new tests:
  `test_large_session_rebuild_derives_terminal_state_from_bounded_tail`
  (asserts terminal_state="error_left"/method="action_outcome" from a
  bounded-path tail read; reverting to hardcoded "unknown" makes it fail,
  confirmed by mutation) and
  `test_bounded_and_unbounded_terminal_state_agree_on_shared_fixture`
  (parity: the same fixture materialized once unbounded and once bounded
  derives identical terminal_state/method/confidence).
- `python -m devtools verify --quick` -- exit 0 (format, lint, mypy
  --strict, render-all-check, layering, schema-versioning, and other
  policy gates).
- `python -m mypy polylogue/storage/insights/session/rebuild.py` --
  Success: no issues found.

Not run: `devtools verify --all` / full non-integration suite (out of
scope for this focused fix; the touched surface's own test file is green).

Ref polylogue-wofr

Co-Authored-By: Claude <noreply@anthropic.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@Sinity, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 36 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: e2fd6169-6309-4c17-a2f1-6640f321cfdc

📥 Commits

Reviewing files that changed from the base of the PR and between 90d4df6 and 26b0a0f.

📒 Files selected for processing (2)
  • polylogue/storage/insights/session/rebuild.py
  • tests/unit/storage/test_session_insight_refresh.py

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@Sinity
Sinity merged commit e0e0be2 into master Jul 31, 2026
3 checks passed
@Sinity
Sinity deleted the fix/insights/bounded-profile-terminal-state branch July 31, 2026 22:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant