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 docs/self-evolution.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Historical verification snapshot: `51be33361422e55e1f2f00c33a0e0f8c56132a91`
(the post-#54 `main` revision, captured before this #55 documentation-only
update). Snapshot date: 2026-09-04.

Current repository test count at this snapshot: **202 unittest cases**.
Current repository test count at this snapshot: **203 unittest cases**.

## Verified surface

Expand Down
13 changes: 7 additions & 6 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -1962,12 +1962,13 @@ def _detect_user_preferences(user_input: str) -> None:
detected = {k: v for k, v in _user_preferences.items() if v}
if detected:
pref_str = "; ".join(f"{k}={v}" for k, v in detected.items())
# Only store if new or changed
recent = memory_bank.get_recent(3)
already = any(f"PREF: {pref_str}" in r for r in recent)
if not already:
learning_engine.submit("preference", f"PREF: {pref_str}", evidence_id=stable_hash(user_input),
confidence=0.5, metadata={"source": "preference_detection"})
# Each user turn is an independent observation. Reusing a content hash
# (or suppressing a repeated message) prevents the evidence threshold
# from ever promoting a preference across a process restart.
learning_engine.submit(
"preference", f"PREF: {pref_str}", evidence_id=f"turn-{uuid.uuid4().hex}",
confidence=0.5, metadata={"source": "preference_detection"},
)


def _build_preference_context() -> str:
Expand Down
37 changes: 37 additions & 0 deletions tests/test_learning_dispatcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,43 @@ def test_verified_preferences_hydrate_in_fresh_runtime_and_prompt(self):
self.assertIn("naming_style=snake_case", completed.stdout)
self.assertIn("Known user preferences", completed.stdout)

def test_detected_preference_promotes_and_survives_restart(self):
with tempfile.TemporaryDirectory(prefix="openkyrozen-detected-preference-") as directory:
root = Path(directory)
db_path = root / "state.sqlite3"
env = os.environ.copy()
env.update({
"HOME": str(root), "KYROZEN_DB_PATH": str(db_path),
"KYROZEN_DISABLE_VECTOR_INDEX": "1", "KYROZEN_WORKSPACE_ROOT": str(root),
"PYTHONPATH": str(Path(__file__).parents[1]),
})
observe = subprocess.run(
[sys.executable, "-c", (
"import main; "
f"main.configure_launch_context(project_path={str(root)!r}); "
"[main.dispatch_learning_cycle(surface='cli', trigger='turn', max_features=1, "
"user_input='Please use concise Python and snake_case names.', "
"feature_names=('detect_user_preferences',)) for _ in range(2)]; "
"print(main.learning_engine.status(100)[0]['status'])"
)],
cwd=Path(__file__).parents[1], env=env, capture_output=True, text=True, check=False,
)
self.assertEqual(observe.returncode, 0, observe.stderr)
self.assertIn("active", observe.stdout)

fresh = subprocess.run(
[sys.executable, "-c", (
"import main; "
f"main.configure_launch_context(project_path={str(root)!r}); "
"print(main._build_preference_context())"
)],
cwd=Path(__file__).parents[1], env=env, capture_output=True, text=True, check=False,
)
self.assertEqual(fresh.returncode, 0, fresh.stderr)
self.assertIn("language=python", fresh.stdout)
self.assertIn("naming_style=snake_case", fresh.stdout)
self.assertIn("verbosity=concise", fresh.stdout)

def test_feature_failure_is_recorded_without_stopping_the_cycle(self):
names = main._LEARNING_FEATURE_ORDER[:2]
registry = dict(main._LEARNING_FEATURE_REGISTRY)
Expand Down
Loading