Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"name": "lore",
"source": "./",
"description": "Shared memory for Claude Code, Codex, and DOXA: capped curated facts, evidence-backed beliefs, FTS5 session search, staged review, learned skills, and signed sync.",
"version": "0.62.22"
"version": "0.62.23"
}
]
}
2 changes: 1 addition & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "lore",
"description": "Memory for Claude Code that reasons about you and improves itself: Honcho-pattern belief store with deriver, dreamer and dialectic (/lore:ask), automatic skillification with a closed judge/update/retire loop, on a Hermes-pattern core \u2014 hard-capped curated memory, FTS5 session search, staged review.",
"version": "0.62.22",
"version": "0.62.23",
"author": {
"name": "Fabian Wilde",
"email": "docwilde@proton.me"
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## 0.62.23 — 2026-10-19

**Background review history**

- Fix **`worker::provider`** and **`run_claude`** saving background reviews as resumable user sessions; disable session persistence for Deriver, Dreamer and their shared review callers.
- Retain that setting during OAuth retries. Existing session files and Desktop imports require separate archival.

## 0.62.22 — 2026-10-13

**Codex memory writes**
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion lore_core/deriver.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,10 @@ def call(bare: bool) -> subprocess.CompletedProcess[str]:
# Prompt via STDIN, never argv: a dreamer prompt over a large
# belief store exceeds ARG_MAX (live E2BIG at 515 beliefs,
# 2026-08-22). `claude -p` with no inline prompt reads stdin.
cmd += ["-p", "--model", model, "--allowedTools", ""]
# Internal reviews must stay out of provider/Desktop session history,
# including the OAuth retry without --bare.
cmd += ["-p", "--no-session-persistence", "--model", model,
"--allowedTools", ""]
return subprocess.run(
cmd, capture_output=True, text=True, timeout=600,
input=prompt,
Expand Down
2 changes: 1 addition & 1 deletion plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
"name": "lore",
"version": "0.62.22",
"version": "0.62.23",
"description": "Unified user, repo, and session memory for Claude Code, Codex, and DOXA",
"author": {
"name": "Dr. Fabian Wilde",
Expand Down
2 changes: 1 addition & 1 deletion rust/lore-core/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "lore-core"
version = "0.62.22"
version = "0.62.23"
edition = "2021"
license = "AGPL-3.0-only"
description = "Native LORE memory, beliefs, review and session index"
Expand Down
4 changes: 3 additions & 1 deletion rust/lore-core/src/worker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,9 @@ fn provider(
command.arg("--bare");
}
command
.args(["-p", "--model", model, "--allowedTools", ""])
// Background reviews must never become resumable user sessions,
// including when subscription auth requires the non-bare retry.
.args(["-p", "--no-session-persistence", "--model", model, "--allowedTools", ""])
.env("LORE_SKIP", "1")
.env("LORE_DISABLE_REVIEW", "1")
.stdin(Stdio::piped())
Expand Down
30 changes: 29 additions & 1 deletion rust/lore-core/tests/worker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ const PROVIDER: &str = r#"#!/usr/bin/python3
import json, os, re, subprocess, sys, time
from pathlib import Path
root = Path(os.environ['OWNED_FIXTURE'])
assert sys.argv[1:] == ['--bare', '-p', '--model', 'owned-model', '--allowedTools', '']
bare = '--bare' in sys.argv[1:]
assert sys.argv[1:] == (['--bare'] if bare else []) + ['-p', '--no-session-persistence', '--model', 'owned-model', '--allowedTools', '']
assert os.environ['LORE_SKIP'] == '1' and os.environ['LORE_DISABLE_REVIEW'] == '1'
child = subprocess.Popen(['/bin/sleep', '30'], stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
(root / 'ready').write_text(json.dumps({'provider':os.getpid(), 'descendant':child.pid}))
Expand All @@ -43,6 +44,9 @@ role = 'dreamer' if prompt.startswith('You are the dreamer') else 'deriver'
(root / ('ready-' + role)).write_text(json.dumps({'provider':os.getpid(), 'descendant':child.pid}))
with (root / 'calls').open('a') as output:
output.write(json.dumps({'role':role,'argv':sys.argv[1:]}) + '\n')
if mode == 'dream_auth_retry' and bare:
sys.stderr.write('Not logged in')
sys.exit(1)
if mode.startswith('dream_'):
(root / ('prompt-' + role)).write_text(prompt)
if role == 'dreamer':
Expand Down Expand Up @@ -508,6 +512,7 @@ fn automatic_native_dream_merges_verified_pair_and_stages_promotions_for_review(
json!([
"--bare",
"-p",
"--no-session-persistence",
"--model",
"owned-model",
"--allowedTools",
Expand Down Expand Up @@ -571,6 +576,29 @@ fn automatic_native_dream_merges_verified_pair_and_stages_promotions_for_review(
}
}
#[test]
fn native_review_oauth_retry_keeps_both_roles_out_of_session_history() {
let fixture = Fixture::new("codex", false);
fixture.seed_pair();
let mut worker = fixture.spawn("codex", "dream_auth_retry");
worker.observe_role(&fixture.home, "deriver");
worker.observe_role(&fixture.home, "dreamer");
assert!(worker.finish(Duration::from_secs(5)).unwrap().success());
assert!(worker.stderr().is_empty() && worker.stdout().is_empty());
let calls = fixture.calls();
assert_eq!(calls.len(), 4);
assert_eq!(
calls.iter().map(|call| call["role"].as_str().unwrap()).collect::<Vec<_>>(),
["deriver", "deriver", "dreamer", "dreamer"]
);
for (index, call) in calls.iter().enumerate() {
let args = call["argv"].as_array().unwrap();
assert_eq!(args.contains(&json!("--bare")), index % 2 == 0);
assert!(args.contains(&json!("--no-session-persistence")));
}
assert_eq!(fixture.pending().len(), 2);
fixture.assert_not_curated();
}
#[test]
fn failed_automatic_dream_preserves_landed_review_and_reports_no_safe_retry() {
let fixture = Fixture::new("claude", false);
let pair = fixture.seed_pair();
Expand Down
59 changes: 59 additions & 0 deletions tests/test_worker_session_history.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# SPDX-License-Identifier: AGPL-3.0-only
"""Background reviews must not create resumable provider sessions."""
import contextlib
import io
import json
import os
from pathlib import Path
import sys
import tempfile
import unittest
from unittest import mock

sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
from lore_core.deriver import run_claude


PROVIDER = """#!/usr/bin/python3
import json, os, sys
from pathlib import Path
root = Path(os.environ['OWNED_FIXTURE'])
prompt = sys.stdin.read()
with (root / 'calls').open('a') as out:
out.write(json.dumps(sys.argv[1:]) + '\\n')
if '--no-session-persistence' not in sys.argv:
(root / 'user-session.jsonl').write_text(prompt)
if os.environ['OWNED_AUTH_RETRY'] == '1' and '--bare' in sys.argv:
sys.stderr.write('Not logged in')
sys.exit(1)
print(json.dumps({'reviewed': prompt, 'reentry_disabled': os.environ['LORE_SKIP']}))
"""


class WorkerSessionHistory(unittest.TestCase):
def test_deriver_and_dreamer_never_persist_even_after_oauth_retry(self):
for role in ('deriver', 'dreamer'):
for retry in (False, True):
with self.subTest(role=role, retry=retry), tempfile.TemporaryDirectory() as tmp:
root = Path(tmp)
provider = root / 'provider'
provider.write_text(PROVIDER)
provider.chmod(0o700)
with mock.patch.dict(os.environ, {
'OWNED_FIXTURE': tmp, 'OWNED_AUTH_RETRY': '1' if retry else '0',
}), contextlib.redirect_stdout(io.StringIO()):
result = run_claude(str(provider), 'owned review prompt', 'owned-model', role)
self.assertEqual(result.returncode, 0, result.stderr)
self.assertEqual(json.loads(result.stdout), {
'reviewed': 'owned review prompt', 'reentry_disabled': '1',
})
self.assertFalse((root / 'user-session.jsonl').exists())
calls = [json.loads(line) for line in (root / 'calls').read_text().splitlines()]
self.assertEqual(len(calls), 2 if retry else 1)
self.assertIn('--bare', calls[0])
if retry:
self.assertNotIn('--bare', calls[1])


if __name__ == '__main__':
unittest.main()
Loading