Skip to content

fix(review): isolate the background-review fork from the canonical session store - #50296

Closed
arminanton wants to merge 1 commit into
NousResearch:mainfrom
arminanton:fix/background-review-session-isolation
Closed

fix(review): isolate the background-review fork from the canonical session store#50296
arminanton wants to merge 1 commit into
NousResearch:mainfrom
arminanton:fix/background-review-session-isolation

Conversation

@arminanton

@arminanton arminanton commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

What this fixes

If you use the background skill/memory review feature, you may have hit a strange failure where, on the turn after a review fires, the agent suddenly stops doing what you asked and instead starts acting like a librarian: "Let me review the conversation and update the skill library…", refusing the actual task. I kept hitting this and finally traced it.

Root cause: the background review runs in a forked agent that shares the parent's session_id (deliberately, for prompt-cache warmth). But the fork also wrote its own turns into that shared session. So the harness prompt it runs under ("Review the conversation above and update the skill library…") plus its curator-mode reply landed in your real session history. On your next live turn the agent re-read that injected user message as a standing instruction and "became" the curator. The session was effectively hijacked by its own background helper.

The fix (two layers)

Prevention (_persist_disabled): a per-agent flag set on the review fork that hard-stops every path that could write to or lazily open the canonical session store. _flush_messages_to_session_db, _ensure_db_session, and _get_session_db_for_recall all short-circuit when it's set. The fork still does its real job (writing to the skill and memory stores via its tools); it just can't touch state.db or the JSON snapshot anymore.

Defense-in-depth (_strip_background_review_harness): a load-on-read filter so any session already polluted by an older build resumes clean. It drops the harness message and the curator-mode assistant reply that immediately followed it, leaving everything else untouched and in order.

Tests

tests/test_background_review_session_isolation.py covers the matcher (skill + memory prompt variants, leading-whitespace tolerance, role/non-string guards, no false positives on normal "please review my PR…" messages) and the stripper (removes harness + its reply, preserves order, handles the empty and no-match cases). 13 tests, all green.

Relation to existing work

Related: #27190 (merged) isolates the review fork from external memory plugins, the sibling layer. #32858 is the underlying "background curation prompts leak into user stores" bug report. This PR adds the missing session-store isolation layer specifically: the _persist_disabled write/lazy-open hard-stop plus the read-side harness stripper. It's complementary to #27190, not a duplicate, the two cover different stores.

@alt-glitch alt-glitch added type/bug Something isn't working comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint P1 High — major feature broken, no workaround labels Jun 21, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

Related: #27190 (merged — isolates the review fork from external memory plugins, the sibling layer) and #32858 (the underlying "background curation prompts leak into user stores" bug). This PR adds the missing session-store isolation layer (_persist_disabled write/lazy-open hard-stop + read-side harness stripper).

@teknium1 teknium1 added sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-broad Sweeper blast radius: broad — a core path most sessions hit labels Jun 21, 2026
@arminanton

Copy link
Copy Markdown
Contributor Author

Thanks @alt-glitch — agreed, this is the session-store isolation layer (_persist_disabled write/lazy-open hard-stop + read-side harness stripper), complementary to #27190 (external-memory-plugin isolation) and addressing #32858. Distinct layer, not a duplicate.

arminanton added a commit to arminanton/hermes-agent that referenced this pull request Jun 22, 2026
…n v0.17.0 (3-way merge)

Real maintainer operation (per-PR 3-way merge, not naive git apply): 37 of 39 PRs
merge clean. 2 combine-conflicts: NousResearch#50457 (redundant 100-file bundle, 94 files dup
other PRs -> recommend slim/close) + NousResearch#50296 (ordinary 1-file overlap). The '6
compile failures' are a new-file-absent artifact of excluding the creating PR, not
breakage. Each PR also individually clean on v0.17.0 (41/41).
arminanton added a commit to arminanton/hermes-agent that referenced this pull request Jun 22, 2026
… 0 markers, 0 real compile-fail

Engineering this round: slimmed NousResearch#50457 (100->4 files, removed the combinability
blocker, 9d3b3b9); proved NousResearch#50296 is overlap-only (clean alone on v0.17.0).
Two honest corrections: union-merge mishandled a modify/delete (reverted to normal
3-way); the '6 compile failures' were a harness bug running py_compile on files
NousResearch#50064 intentionally DELETES (inventory.py). Corrected: 324 .py, 0 real fails.
arminanton added a commit to arminanton/hermes-agent that referenced this pull request Jun 22, 2026
…rs) + CI-lint green

- combine_and_verify_v017.sh: reproducible, pins each PR to current head SHA.
- PINNED-SHAS.txt: every PR's head SHA (incl slimmed NousResearch#50457).
- PER-PR-CLEAN-v017.txt: 41/41 individually CLEAN on v0.17.0, 0 compile-fail.
- COMBINE-v017.txt: 39 combine via 3-way merge, NousResearch#50296 overlap, 0 markers, 0 real fail.
- CI-LINT-RESULT.txt: 'ruff check .' (the blocking CI gate) PASSES on integration tree.
- CI-STATUS.md: upstream fork-PR CI is maintainer-gated; local equivalent runs green.
arminanton added a commit to arminanton/hermes-agent that referenced this pull request Jun 22, 2026
…uivalent + NousResearch#50296 rebase

- RECONCILIATION-41-vs-39.md: 41 = 40 candidates + 1 manifest; 40 = 39 merged + NousResearch#50296.
- PER-PR-STATE.md: all 41 base=main, 8 ready/33 draft, head SHAs + mergeable state.
- NousResearch#50296 REBASED onto current origin/main (agent_init.py drift: kept both attrs);
  now GitHub-MERGEABLE (084b79bed). The genuine rebase the prior rounds missed.
- CI-EQUIVALENT-RESULTS.txt: ruff check . PASS + ty check PASS + 285 PR tests PASS
  on the integration tree (warnings = upstream deprecations, not our code).
arminanton added a commit to arminanton/hermes-agent that referenced this pull request Jun 22, 2026
…LE) + reproducible delta map

- GITHUB-MERGEABLE-AUDIT.md: GitHub mergeable=40/41 (only NousResearch#50111 manifest conflicts).
  6 PRs (NousResearch#50296/NousResearch#49644/NousResearch#50041/NousResearch#50073/NousResearch#50064/NousResearch#50033) genuinely conflicted on current
  origin/main (drifted past v0.17.0); each rebased (1-file complementary conflict),
  now MERGEABLE.
- DELTA-MAP-v017.md: reproducible per-file map (PR diffs vs v0.17.0, fresh tips):
  160 = 137 in-PR + 21 DISCARD + 2 upstream-NousResearch#29433 + 0 orphans, sum verified.
arminanton added a commit to arminanton/hermes-agent that referenced this pull request Jun 22, 2026
…afety proof

Addresses the Council demand for SEMANTIC correctness (tests pass after resolution),
not just compile-clean.

Per-PR own-test results on resolved v0.17.0 (V017-PER-PR-TEST-RESULTS.txt):
  NousResearch#49644 10 passed | NousResearch#50033 165 gemini passed | NousResearch#50056 218 passed
  NousResearch#50073  9 passed (INTENT-INFERRED resolution PROVEN correct: hygiene=400 kept)
  NousResearch#50296 13 passed | NousResearch#50064 61 passed, 1 FAILED

The single NousResearch#50064 failure is characterized precisely (not hacked): test
test_routed_client_preserves_openai_sdk_default_headers asserts pre-v0.17.0 copilot
routing internals that v0.17.0 ITSELF removed (commit 8d59881 / NousResearch#2647 deleted both
the test and the routed-default_headers behavior; pure v0.17.0 has 0 occurrences).
Documented as a forward-compat test-removal on rebase, not a regression. NousResearch#50064's
feature is intact (61/62).

DISCARD ratification (NON-CONTRIBUTABLE.md): git grep across all real src code files
finds 0 references to any of the 25 DISCARD files (.bak/.project-intel/transcripts) —
proof they are safely droppable.

v017-conflict-resolutions/README.md documents all 6 resolution strategies.
arminanton added a commit to arminanton/hermes-agent that referenced this pull request Jun 22, 2026
…open questions

Addresses the Council demand for platform/review state, not just local repro.

Pulled directly from GitHub for all 42 PRs (PER-PR-PLATFORM-STATUS.txt):
- 42/42 OPEN (8 ready-for-review, 34 draft).
- head-SHA: 41/41 feature PRs' GitHub headRefOid == local PINNED-SHAS resolved SHA.
  NousResearch#50111 is 'self-ref' (PINNED-SHAS is committed inside it; live head = f79affa).
- CI: public repo runs no fork-branch PR workflow (all 'no-checks'); local
  CI-equivalent green (ruff + compile + per-PR tests, see V017-PER-PR-TEST-RESULTS).
- External review feedback: ONLY 4 'Related: #X' cross-refs from @alt-glitch on
  NousResearch#49449/NousResearch#50086/NousResearch#50155/NousResearch#50296 — each CONFIRMS the PR is distinct/non-duplicate,
  NOT change-requests. 0 unaddressed review threads, 0 submitted reviews.
- The 6 conflict PRs are MERGEABLE on their own base (origin/main); the v0.17.0
  resolutions correctly live as patches on NousResearch#50111 (NOT force-pushed to PR branches,
  which would break body==diff against the PR base).

Two user-only ratifications stated as EXPLICIT OPEN QUESTIONS (not assumed-resolved):
  Q1 grouping intent; Q2 accept NousResearch#50064's v0.17.0 forward-compat test drop (NousResearch#2647).
arminanton added a commit to arminanton/hermes-agent that referenced this pull request Jun 22, 2026
…script

Addresses Council items 2 & 3:

Item 3 (justify every resolution against PR intent): V017-RESOLUTION-JUSTIFICATION.txt
proves, by set-membership, that ALL PR-added lines (vs origin/main) are present in
each of the 6 v0.17.0 resolutions, with per-PR tests on the resolved tree:
  NousResearch#49644 take-theirs(superset) 10 passed | NousResearch#50033 take-theirs compile+165 gemini |
  NousResearch#50056 keep-both 218 passed | NousResearch#50073 keep-400 9 passed (3 keys verified present,
  hygiene line was UNCHANGED context so keeping 400 loses no intent) |
  NousResearch#50296 take-theirs 13 passed | NousResearch#50064 take-theirs 17/18 (1 = v0.17.0 upstream
  removal NousResearch#2647, NOT lost intent = Q2).

Item 2 (delivery mechanism): APPLY-RESOLUTIONS-ON-v0.17.0.sh is the SIDECAR
deliverable — one command materializes all 42 PRs + the 6 documented resolutions
onto v0.17.0. Verified end-to-end: 0 residual markers, 0 compile failures.
Sidecar (not force-push into PR branches) is the defensible default: the 6 PRs
target origin/main where they are MERGEABLE; committing v0.17.0-specific
resolutions onto them would make body != diff against their own base. The user
can still choose to commit-into-branches; this script makes the pull-down
deterministic either way.
arminanton added a commit to arminanton/hermes-agent that referenced this pull request Jun 22, 2026
Each PR-<n>-onto-v0.17.0.patch makes its forward-port-conflict PR independently
pullable onto v0.17.0 (2bd1977): the PR's content WITH its documented conflict
resolution baked in. Verified APPLIES-CLEAN on a fresh v0.17.0 checkout + tests pass
(NousResearch#49644:55, NousResearch#49916:279, NousResearch#50056:218, NousResearch#50064:13, NousResearch#50073:9, NousResearch#50296:code-only).
Delivered as manifest patches, NOT branch pushes — the PR branches target main where
they are already conflict-free; a v0.17.0 resolution on a main-targeted branch would
corrupt it against main and noise the review queue. 0 private leaks.
…n store

The background skill/memory review fork shares the parent agent's session_id
(for prompt-cache warmth), so without isolation it wrote its harness turn
("Review the conversation above and update the skill library…") plus its
curator-mode reply straight into the user's REAL session in state.db. On the
user's next live turn the agent re-read that injected user message as a
standing instruction and "became" the curator, refusing the actual task.

Two layers:
1. Prevention: an `_persist_disabled` flag on the fork hard-stops every DB
   write/lazy-open path (_flush_messages_to_session_db, _ensure_db_session,
   _get_session_db_for_recall). The review writes only to the skill/memory
   stores via its tools, which is all it needs.
2. Defense-in-depth on read: _strip_background_review_harness drops any stray
   harness message (and the curator reply that followed it) when loading
   session history, so a session polluted by an older build resumes clean.

Adds 13 unit tests for the read-side stripper.
@arminanton
arminanton force-pushed the fix/background-review-session-isolation branch from 084b79b to 15bebf3 Compare June 23, 2026 01:03
@arminanton arminanton changed the title fix(review): isolate background-review fork from the canonical session store fix(review): isolate the background-review fork from the canonical session store Jun 23, 2026
kshitijk4poor pushed a commit that referenced this pull request Jul 1, 2026
…ssion

The forked skill/memory review agent shares the parent's session_id for
prompt-cache warmth. Without isolation it wrote its harness turn ('Review the
conversation above and update the skill library…') plus its curator-mode reply
straight into the user's REAL session in state.db; the next live turn re-read
that injected user message as a standing instruction and the agent 'became' the
curator, refusing the actual task.

Root fix: a _persist_disabled flag on the fork that hard-stops every DB write
and lazy-open path (_flush_messages_to_session_db, _ensure_db_session,
_get_session_db_for_recall) — the review writes only to the skill/memory stores
via its tools. Defense-in-depth: _strip_background_review_harness drops any
stray harness message (and the assistant reply that followed) at load time in
get_messages_as_conversation, so an already-polluted session resumes clean.

Salvaged from #50296.

Co-authored-by: arminanton <29869547+arminanton@users.noreply.github.com>
hashbender added a commit to hashbender/hermes-agent that referenced this pull request Jul 1, 2026
…n (salvage NousResearch#50296) (#306)

Co-authored-by: qbit-mirror-bot <qbit-mirror-bot@users.noreply.github.com>
waefrebeorn pushed a commit to waefrebeorn/slermes that referenced this pull request Jul 2, 2026
…ssion

The forked skill/memory review agent shares the parent's session_id for
prompt-cache warmth. Without isolation it wrote its harness turn ('Review the
conversation above and update the skill library…') plus its curator-mode reply
straight into the user's REAL session in state.db; the next live turn re-read
that injected user message as a standing instruction and the agent 'became' the
curator, refusing the actual task.

Root fix: a _persist_disabled flag on the fork that hard-stops every DB write
and lazy-open path (_flush_messages_to_session_db, _ensure_db_session,
_get_session_db_for_recall) — the review writes only to the skill/memory stores
via its tools. Defense-in-depth: _strip_background_review_harness drops any
stray harness message (and the assistant reply that followed) at load time in
get_messages_as_conversation, so an already-polluted session resumes clean.

Salvaged from NousResearch#50296.

Co-authored-by: arminanton <29869547+arminanton@users.noreply.github.com>
Jasper6439 pushed a commit to Jasper6439/hermes-agent that referenced this pull request Jul 5, 2026
…ssion

The forked skill/memory review agent shares the parent's session_id for
prompt-cache warmth. Without isolation it wrote its harness turn ('Review the
conversation above and update the skill library…') plus its curator-mode reply
straight into the user's REAL session in state.db; the next live turn re-read
that injected user message as a standing instruction and the agent 'became' the
curator, refusing the actual task.

Root fix: a _persist_disabled flag on the fork that hard-stops every DB write
and lazy-open path (_flush_messages_to_session_db, _ensure_db_session,
_get_session_db_for_recall) — the review writes only to the skill/memory stores
via its tools. Defense-in-depth: _strip_background_review_harness drops any
stray harness message (and the assistant reply that followed) at load time in
get_messages_as_conversation, so an already-polluted session resumes clean.

Salvaged from NousResearch#50296.

Co-authored-by: arminanton <29869547+arminanton@users.noreply.github.com>
habarmc1223-sudo pushed a commit to habarmc1223-sudo/hermes-agent-fluxmem that referenced this pull request Jul 8, 2026
…ssion

The forked skill/memory review agent shares the parent's session_id for
prompt-cache warmth. Without isolation it wrote its harness turn ('Review the
conversation above and update the skill library…') plus its curator-mode reply
straight into the user's REAL session in state.db; the next live turn re-read
that injected user message as a standing instruction and the agent 'became' the
curator, refusing the actual task.

Root fix: a _persist_disabled flag on the fork that hard-stops every DB write
and lazy-open path (_flush_messages_to_session_db, _ensure_db_session,
_get_session_db_for_recall) — the review writes only to the skill/memory stores
via its tools. Defense-in-depth: _strip_background_review_harness drops any
stray harness message (and the assistant reply that followed) at load time in
get_messages_as_conversation, so an already-polluted session resumes clean.

Salvaged from NousResearch#50296.

Co-authored-by: arminanton <29869547+arminanton@users.noreply.github.com>
santhreal pushed a commit to santhreal/hermes-agent that referenced this pull request Jul 13, 2026
…ssion

The forked skill/memory review agent shares the parent's session_id for
prompt-cache warmth. Without isolation it wrote its harness turn ('Review the
conversation above and update the skill library…') plus its curator-mode reply
straight into the user's REAL session in state.db; the next live turn re-read
that injected user message as a standing instruction and the agent 'became' the
curator, refusing the actual task.

Root fix: a _persist_disabled flag on the fork that hard-stops every DB write
and lazy-open path (_flush_messages_to_session_db, _ensure_db_session,
_get_session_db_for_recall) — the review writes only to the skill/memory stores
via its tools. Defense-in-depth: _strip_background_review_harness drops any
stray harness message (and the assistant reply that followed) at load time in
get_messages_as_conversation, so an already-polluted session resumes clean.

Salvaged from NousResearch#50296.

Co-authored-by: arminanton <29869547+arminanton@users.noreply.github.com>
Gravezzz pushed a commit to Gravezzz/hermes-agent that referenced this pull request Jul 21, 2026
…ssion

The forked skill/memory review agent shares the parent's session_id for
prompt-cache warmth. Without isolation it wrote its harness turn ('Review the
conversation above and update the skill library…') plus its curator-mode reply
straight into the user's REAL session in state.db; the next live turn re-read
that injected user message as a standing instruction and the agent 'became' the
curator, refusing the actual task.

Root fix: a _persist_disabled flag on the fork that hard-stops every DB write
and lazy-open path (_flush_messages_to_session_db, _ensure_db_session,
_get_session_db_for_recall) — the review writes only to the skill/memory stores
via its tools. Defense-in-depth: _strip_background_review_harness drops any
stray harness message (and the assistant reply that followed) at load time in
get_messages_as_conversation, so an already-polluted session resumes clean.

Salvaged from NousResearch#50296.

Co-authored-by: arminanton <29869547+arminanton@users.noreply.github.com>
leewenjie pushed a commit to leewenjie/hermes-agent that referenced this pull request Aug 7, 2026
…ssion

The forked skill/memory review agent shares the parent's session_id for
prompt-cache warmth. Without isolation it wrote its harness turn ('Review the
conversation above and update the skill library…') plus its curator-mode reply
straight into the user's REAL session in state.db; the next live turn re-read
that injected user message as a standing instruction and the agent 'became' the
curator, refusing the actual task.

Root fix: a _persist_disabled flag on the fork that hard-stops every DB write
and lazy-open path (_flush_messages_to_session_db, _ensure_db_session,
_get_session_db_for_recall) — the review writes only to the skill/memory stores
via its tools. Defense-in-depth: _strip_background_review_harness drops any
stray harness message (and the assistant reply that followed) at load time in
get_messages_as_conversation, so an already-polluted session resumes clean.

Salvaged from NousResearch#50296.

Co-authored-by: arminanton <29869547+arminanton@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint P1 High — major feature broken, no workaround sweeper:blast-broad Sweeper blast radius: broad — a core path most sessions hit sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants