Repository navigation
Conversation
This was referenced Oct 15, 2026
A cron run whose context compresses carries on in a continuation session, but only its first segment carries the run's identity (its legacy id or its scheduler admission), so run history and session detail kept describing that segment. Compression stamps its ended_at: a live run read as closed (writable on Desktop), a run the scheduler lost after compressing read as closed instead of a zombie, and detail of the continuation named no run. list_cron_job_runs now projects each row onto its live tip with the sessions list's _project_compression_tips (the tip's id, ended_at, title and transcript fields; the run's started_at and identity kept). cron_run_identity resolves a segment through its compression root, walked up the continuation edges with the forward walk's bound, so a chain deeper than the resume walk's 100 hops still names its run. Refs NousResearch#134889 Refs NousResearch#138812
A cron run that compresses goes on in a continuation, and compression stamps ended_at on the segment it ends. A Desktop tab opened on the run while that segment was the tip keeps holding it, and the write gate read the segment's own stamp as the run closing: a run the scheduler lost after compressing stayed writable instead of view-only. Polls did not correct it, because run rows now arrive under the new tip's id and verdicts were matched by exact id. Session detail of a cron segment ended by compression now carries the run at its live tip (cron_run_tip: its id, ended_at, end_reason and scheduler ownership), read through the runs list's own projection. The Desktop gate judges that instead of the segment's stamp, and a poll reaches every verdict of the run through the row's lineage. A finalized run stays writable; a backend without the field reads as before. Refs NousResearch#134889 Refs NousResearch#138812
dokterdok
force-pushed
the
fix/cron-gate-compressed-segment
branch
from
October 15, 2026 20:52
27681f0 to
ee2f628
Compare
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What does this PR do?
The problem.
668aaa94a3(fix(cron): follow a compressed run to its tip in run history and detail #139268). Onmain, run history misreads a compressed run as well; fix(cron): follow a compressed run to its tip in run history and detail #139268 fixes that part.Background for non-specialists: Hermes compresses a long conversation by ending the current session segment and carrying on in a new one. Compression stamps the old segment as ended. For a cron run, Desktop's send gate reads "ended" as "the scheduler closed this run properly, so it is an ordinary chat now", which is writable. A zombie, a run that was never closed and that nothing owns, is view-only.
Why. The tab keeps the id of the segment that was the run's newest when it opened. Before every send, Desktop re-reads that session and asks whether it is closed or still owned by the scheduler. The old segment answers "ended": that is the compression's stamp, not the run closing, so the send goes through. Polls from the Cron page did not correct the tab either. They now list the run under its newest segment's id, and Desktop matched them to open tabs by exact id only. Desktop cannot simply follow its own cached copy of the run: a tab restored after a restart may have none, and a cached copy can be one compression behind.
The fix. Judge the run, not the segment the tab holds.
Proof. Both new tests fail on the base
668aaa94a3(#139268 on currentmain) and pass here.Before / after:
GET /api/sessions/{id}of a cron segment that compression endedended_atset,scheduler_owned: falsecron_run_tip: the run at its live tip (id,ended_at,end_reason,scheduler_owned)_lineage_idsRelated Issue
Refs #134889 (CRON-1): work package #139162, lane 3.
Refs #138812
This is a draft only because the repo allows 3 ready-for-review PRs per contributor; it is ready for review.
Depends on #139268. This branch is stacked on it, so until it merges the diff here includes its commit. Only the last commit,
ee2f628dd9, belongs to this PR. #139268 names this gap as its known Desktop follow-up. #138968 itself landed via #139273.Overlap: #131434 (@dskwe, for #131433) also changes
isResumableCronRun(acron_finalizedclassifier),SessionInfoandlist_cron_job_runs. It fixes a different bug. Whichever lands second needs a small rebase, and the tip check should run before that classifier, so that a held older segment is judged by its run's tip.Type of Change
Changes Made
hermes_state_portability.py:SessionDB.cron_run_tip(session_id)returns the run that a segment belongs to, aslist_cron_job_runsserves it. It takes the compression root (_COMPRESSION_ROOT_SQL, the edgecron_run_identityalready walks) and projects it with_project_compression_tips. It returns None when that tip is the segment itself.hermes_cli/web_routers/sessions.py:get_session_detailstampscron_run_tipon a cron segment whoseend_reasonis'compression'. Ownership comes fromcron_run_scheduler_ownedapplied to the projected run plus its identity, which is the runs list's own evaluation.apps/desktop/src/types/hermes.ts: addsSessionInfo.end_reason,SessionInfo.cron_run_tipandCronRunTip.apps/desktop/src/app/cron/open-cron-run.ts:isResumableCronRunjudgescron_run_tipfor a row that compression ended.reconcileCronRunVerdictsmatches open verdicts throughsessionMatchesStoredId(the id, the lineage root and the lineage ids).apps/desktop/src/app/cron/open-cron-run.test.ts: the send-time gate and a poll for a tab held on an older segment.tests/hermes_cli/test_cron_run_history_sessions.py: detail of a twice-compressed run's segments, through the real producer, compression publish and scheduler finalizer.For reviewers:
_CHAIN_CAP), one batched rich-row read and one in-flight lookup.main: a unified-runtime run's tab restored after a restart, before any Cron surface has loaded the run's row, is not recognised as a cron run, because itslocal-*id says nothing. This PR does not change that.scheduler_ownedat the top level of the detail row is still the segment's own. The run's ownership is incron_run_tip.sessionMatchesStoredId, the one place Desktop decides whether two ids are the same conversation across compression._lineage_idsnames only that run's compression segments, so a/branchcopy or another run's verdict cannot flip._CHAIN_CAPsymmetry, branch and reset edges, a stray open sibling, older backends and test discrimination. Its findings are folded in: the older-backend and unrelated-run test cases, a single query for the root row, and theCronRunTip.scheduler_ownedtype.How to Test
668aaa94a3:test_detail_of_an_older_segment_reports_its_run_at_the_tipfails because detail of an older segment has nocron_run_tip. The other 9 pass.test_cron_run_history_sessions.py,test_cron_run_sessions.py,test_cron_run_scheduler_ownership.py) plustest_session_detail_malformed_db.pygive 30 passed.src/app/cronandsrc/app/chat/sidebar: 54 files, 429 passed.tsc -p . --noEmitand eslint on the touched files are clean.tests/hermes_state,tests/cron, and the session-detail, web-server, timeline and dashboard session tests) gave 3303 passed and 22 failed. The failing files failed the same way on the base on this Windows host: cross-process locks, WAL, lifecycle guards and scheduler timing.scripts/check --base upstream/main: 12 checks ok; code health 0 blocking, 0 advisory.Checklist
Code
pytest tests/ -qand all tests pass. Not ticked: I ran the related suites above, not the whole tree; CI is the full runpython scripts/checkpassesDocumentation & Housekeeping
SessionInfofields describe the contractcli-config.yaml.example: N/ACONTRIBUTING.md/AGENTS.md: N/A