diff --git a/tests/test_memory_log.py b/tests/test_memory_log.py index 28a943498..d05fc3c5a 100644 --- a/tests/test_memory_log.py +++ b/tests/test_memory_log.py @@ -136,6 +136,23 @@ class TestTradingMemoryLogCore: log.store_decision("NVDA", "2026-01-10", DECISION_BUY) assert len(log.load_entries()) == 1 + def test_store_decision_idempotent_after_the_entry_resolves(self, tmp_path): + """A settled entry still blocks a duplicate. + + The guard matched only pending entries, so re-running a ticker and date + whose outcome had already been settled appended a second entry: the same + decision counted twice in past context and in any aggregate over the log. + """ + log = make_log(tmp_path) + log.store_decision("NVDA", "2026-01-10", DECISION_BUY) + log.update_with_outcome("NVDA", "2026-01-10", 0.05, 0.02, 5, "worked", "2026-01-17") + + log.store_decision("NVDA", "2026-01-10", DECISION_BUY) + + entries = log.load_entries() + assert len(entries) == 1 + assert entries[0]["pending"] is False # the settled record is kept, not replaced + def test_batch_update_resolves_multiple_entries(self, tmp_path): """batch_update_with_outcomes resolves multiple pending entries in one write.""" log = make_log(tmp_path) diff --git a/tradingagents/agents/utils/memory.py b/tradingagents/agents/utils/memory.py index 30b16339b..6abb5f631 100644 --- a/tradingagents/agents/utils/memory.py +++ b/tradingagents/agents/utils/memory.py @@ -36,11 +36,14 @@ class TradingMemoryLog: """Append pending entry at end of propagate(). No LLM call.""" if not self._log_path: return - # Idempotency guard: fast raw-text scan instead of full parse + # Idempotency guard: fast raw-text scan instead of full parse. Any entry + # for this ticker and date blocks another, pending or settled: a re-run + # after the outcome landed would otherwise count the same decision twice + # in past context and in every aggregate over the log. if self._log_path.exists(): raw = self._log_path.read_text(encoding="utf-8") for line in raw.splitlines(): - if line.startswith(f"[{trade_date} | {ticker} |") and line.endswith("| pending]"): + if line.startswith(f"[{trade_date} | {ticker} |") and line.endswith("]"): return rating = parse_rating(final_trade_decision) tag = f"[{trade_date} | {ticker} | {rating} | pending]"