From 7b88fcd8091ef673d8a1c92f34b2557c7b51119c Mon Sep 17 00:00:00 2001 From: cyre Date: Fri, 7 Aug 2026 10:29:35 +0800 Subject: [PATCH 1/2] fix(llm): forward max_tokens on the MiniMax OpenAI wire call_model()'s MiniMax path silently dropped the caller's max_tokens entirely on the OpenAI-compatible wire -- the chat.completions.create() call passed model/temperature/messages but nothing for output length. Every MiniMax call through this wire has been unbounded regardless of what max_tokens the caller specified. The MiniMax OpenAI-compatible Chat Completions API deprecated bare max_tokens in favor of max_completion_tokens; add max_completion_tokens=max_tokens to close the gap. The Anthropic wire is untouched -- it already forwards max_tokens correctly and Anthropic's API keeps that parameter name. Extends the existing test_call_model_openai_wire_global coverage with an assertion on the new kwarg. Verified in both directions: fails with KeyError against the pre-fix code, passes with the fix. --- .agent/harness/llm.py | 1 + tests/test_llm_provider.py | 1 + 2 files changed, 2 insertions(+) diff --git a/.agent/harness/llm.py b/.agent/harness/llm.py index d2aecde..1050bb4 100644 --- a/.agent/harness/llm.py +++ b/.agent/harness/llm.py @@ -73,6 +73,7 @@ def _call_minimax(system, user, *, temperature, max_tokens, model): r = c.chat.completions.create( model=model, temperature=temperature, + max_completion_tokens=max_tokens, messages=[{"role": "system", "content": system}, {"role": "user", "content": user}], ) diff --git a/tests/test_llm_provider.py b/tests/test_llm_provider.py index 02fe0ab..8445072 100644 --- a/tests/test_llm_provider.py +++ b/tests/test_llm_provider.py @@ -102,6 +102,7 @@ def test_call_model_openai_wire_global(self): wire, kwargs = rec.calls[0] self.assertEqual(wire, "openai") self.assertEqual(kwargs["model"], "MiniMax-M3") + self.assertEqual(kwargs["max_completion_tokens"], 4096) def test_call_model_anthropic_wire_cn(self): os.environ["AGENT_PROVIDER"] = "minimax" From 20441c238304c3ea76e78c3c961f6f0b7c7095d6 Mon Sep 17 00:00:00 2001 From: cyre Date: Fri, 7 Aug 2026 10:30:29 +0800 Subject: [PATCH 2/2] fix(tests): stop leaking learn.py's module stubs, force UTF-8 read _load_learn() stubs sys.modules["text"]/["cluster"] to isolate learn.py from its two sibling modules, but never removed the stubs after loading -- they stayed process-wide for the rest of the test run. Verified this is a real leak, not a theoretical one: after calling _load_learn() once, both "text" and "cluster" remain in sys.modules; any later test or import in the same process would silently get the throwaway lambda stand-ins instead of the real modules. Save whatever was previously in sys.modules for those two names (or note there was nothing) before stubbing, and restore that exact prior state in a finally block around exec_module() so the stubs never outlive the one learn.py load they exist for. Also adds encoding="utf-8" to the one read_text() call in this file that was using the platform default encoding, per this repo's own "force UTF-8 when reading/writing tracked text" convention (already followed everywhere else _episodic() and learn.py itself read files). The third test in this file, test_append_mirror_fails_open_on_write_error, is untouched: _append_episodic_mirror()'s fail-open behavior on a write error is this file's own explicit, documented design (its docstring says so directly, matching _lesson_already_appended's read-only fail-open posture) -- not a bug. --- .agent/tools/test_learn_episodic_mirror.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/.agent/tools/test_learn_episodic_mirror.py b/.agent/tools/test_learn_episodic_mirror.py index 808e3cc..fccfde5 100644 --- a/.agent/tools/test_learn_episodic_mirror.py +++ b/.agent/tools/test_learn_episodic_mirror.py @@ -24,12 +24,18 @@ def _load_learn(base_dir): """Load .agent/tools/learn.py with BASE/CANDIDATES pointed at base_dir. Sibling modules (text.word_set, cluster.pattern_id) are stubbed so the - test needs no part of the harness beyond learn.py itself. + test needs no part of the harness beyond learn.py itself. The stubs are + process-wide (sys.modules), so any previous entry for "text"/"cluster" + is saved and restored (or removed, if there was none) once exec_module + finishes -- otherwise a later test or import in the same process would + silently pick up these throwaway stand-ins instead of the real modules. """ + previous_modules = {} for name, attrs in [ ("text", {"word_set": lambda *a, **k: set()}), ("cluster", {"pattern_id": lambda claim, cond: "testcid" + str(abs(hash((claim, tuple(cond)))))[:6]}), ]: + previous_modules[name] = sys.modules.pop(name, None) m = types.ModuleType(name) for k, v in attrs.items(): setattr(m, k, v) @@ -38,7 +44,14 @@ def _load_learn(base_dir): module_path = Path(__file__).with_name("learn.py") spec = importlib.util.spec_from_file_location("learn_under_test", module_path) mod = importlib.util.module_from_spec(spec) - spec.loader.exec_module(mod) + try: + spec.loader.exec_module(mod) + finally: + for name, previous in previous_modules.items(): + if previous is None: + sys.modules.pop(name, None) + else: + sys.modules[name] = previous mod.BASE = base_dir mod.CANDIDATES = os.path.join(base_dir, "memory", "candidates") os.makedirs(mod.CANDIDATES, exist_ok=True) @@ -68,7 +81,7 @@ def test_stage_writes_one_episodic_mirror(self): def test_evidence_id_resolves_to_the_mirror(self): mod = _load_learn(self.tmp) cid, path = mod.stage("Serialize timestamps in UTC", ["timestamps", "utc"]) - candidate = json.loads(Path(path).read_text()) + candidate = json.loads(Path(path).read_text(encoding="utf-8")) evidence_ts = candidate["evidence_ids"][0] matching = [e for e in _episodic(self.tmp) if e["timestamp"] == evidence_ts] self.assertEqual(len(matching), 1)