From 5e38f0152d08188061796873404d2df29512b378 Mon Sep 17 00:00:00 2001 From: cyre Date: Fri, 17 Jul 2026 02:06:32 +0000 Subject: [PATCH 1/2] fix(io): force UTF-8 on stdout/stderr and candidate writes Two related Windows-compatibility fixes: - Reconfigure stdout/stderr to UTF-8 (errors=replace) at import, so a lesson claim containing non-ASCII (arrows, em-dashes, accented text) doesn't raise UnicodeEncodeError under a cp1252 console on Windows. - Write the candidate JSON file with explicit encoding="utf-8" rather than the platform default, so candidates round-trip identically across OSes. Both are additive and platform-safe (the reconfigure is guarded by hasattr, a no-op where unavailable). Found and applied downstream in Perpetua-Tools; contributing back. Stacked on top of the episodic-mirror fix (atomic-01). --- .agent/tools/learn.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.agent/tools/learn.py b/.agent/tools/learn.py index 3154b0e..2b9cbec 100644 --- a/.agent/tools/learn.py +++ b/.agent/tools/learn.py @@ -20,6 +20,10 @@ """ import argparse, datetime, json, os, subprocess, sys +if hasattr(sys.stdout, "reconfigure"): + sys.stdout.reconfigure(encoding="utf-8", errors="replace") + sys.stderr.reconfigure(encoding="utf-8", errors="replace") + BASE = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) CANDIDATES = os.path.join(BASE, "memory/candidates") sys.path.insert(0, os.path.join(BASE, "harness")) @@ -105,7 +109,7 @@ def stage(claim, conditions, source="learn", importance=7): "rejection_count": 0, } path = os.path.join(CANDIDATES, f"{cid}.json") - with open(path, "w") as f: + with open(path, "w", encoding="utf-8") as f: json.dump(candidate, f, indent=2) _append_episodic_mirror(cid, claim, now, source) return cid, path From f685a08027eff54a61e5c426b630c41adc3c3e14 Mon Sep 17 00:00:00 2001 From: cyre Date: Fri, 17 Jul 2026 02:06:52 +0000 Subject: [PATCH 2/2] fix(io): use a context manager in _lesson_already_appended The read-only probe opened lessons.jsonl with a bare open() and relied on GC to close the handle. Wrap it in a with-statement (and add explicit encoding="utf-8" for cross-platform consistency) so the file descriptor is released deterministically. Behavior is otherwise unchanged. Found and applied downstream in Perpetua-Tools; contributing back. Stacked on top of atomic-02 (UTF-8 fixes). --- .agent/tools/learn.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/.agent/tools/learn.py b/.agent/tools/learn.py index 2b9cbec..8cb2620 100644 --- a/.agent/tools/learn.py +++ b/.agent/tools/learn.py @@ -44,16 +44,17 @@ def _lesson_already_appended(cid): return False target = f"lesson_{cid}" try: - for line in open(lessons_path): - line = line.strip() - if not line: - continue - try: - row = json.loads(line) - except json.JSONDecodeError: - continue - if row.get("id") == target: - return True + with open(lessons_path, encoding="utf-8") as f: + for line in f: + line = line.strip() + if not line: + continue + try: + row = json.loads(line) + except json.JSONDecodeError: + continue + if row.get("id") == target: + return True except OSError: return False return False