Spaces:
Running on Zero
Running on Zero
| from __future__ import annotations | |
| from src.core.events import Event | |
| from src.core.memory import EpisodicMemory | |
| def _event(kind: str, actor: str = "x", turn: int = 1) -> Event: | |
| return Event(run_id="r", turn=turn, kind=kind, actor=actor, payload={"text": f"{actor}:{kind}"}) # type: ignore[arg-type] | |
| class TestEpisodicMemory: | |
| def test_own_events_visible(self): | |
| mem = EpisodicMemory("seedkeeper") | |
| events = (_event("agent.spoke", actor="seedkeeper"),) | |
| visible = mem.visible(events) | |
| assert len(visible) == 1 | |
| def test_world_observed_visible_to_all(self): | |
| mem = EpisodicMemory("pocket-actor") | |
| events = (_event("world.observed", actor="scene-whisperer"),) | |
| visible = mem.visible(events) | |
| assert len(visible) == 1 | |
| def test_other_agent_spoke_is_visible(self): | |
| # Peers' SPOKEN lines are the shared table β recallable across the whole run, not | |
| # just this round's blackboard tail (ADR-0023 follow-up). A late-firing judge | |
| # depends on this to read the discussion it rules on. | |
| mem = EpisodicMemory("pocket-actor") | |
| events = (_event("agent.spoke", actor="scene-whisperer"),) | |
| assert len(mem.visible(events)) == 1 | |
| def test_other_agent_thought_not_visible(self): | |
| # A private thought (the mind-reader content) rides only its own event β peers | |
| # never read another mind's thinking, even though spoken lines are now shared. | |
| mem = EpisodicMemory("pocket-actor") | |
| events = (_event("agent.thought", actor="scene-whisperer"),) | |
| assert len(mem.visible(events)) == 0 | |
| def test_oracle_spoke_visible_to_all(self): | |
| # The custom public-speech kind (oracle-grove) is shared like agent.spoke. | |
| mem = EpisodicMemory("scene-whisperer") | |
| events = (_event("oracle.spoke", actor="fortune-teller"),) | |
| assert len(mem.visible(events)) == 1 | |
| def test_user_injected_visible_to_all(self): | |
| mem = EpisodicMemory("echo") | |
| events = (_event("user.injected", actor="visitor"),) | |
| visible = mem.visible(events) | |
| assert len(visible) == 1 | |
| def test_capped_at_max_recent(self): | |
| mem = EpisodicMemory("x", max_recent=3) | |
| events = tuple(_event("world.observed", turn=i) for i in range(10)) | |
| visible = mem.visible(events) | |
| assert len(visible) == 3 | |
| def test_returns_most_recent(self): | |
| mem = EpisodicMemory("x", max_recent=2) | |
| events = tuple(_event("world.observed", turn=i) for i in range(5)) | |
| visible = mem.visible(events) | |
| assert visible[0].turn == 3 | |
| assert visible[1].turn == 4 | |
| def test_format_for_prompt_returns_string(self): | |
| mem = EpisodicMemory("x") | |
| events = (_event("world.observed", actor="narrator"),) | |
| result = mem.format_for_prompt(events) | |
| assert isinstance(result, str) | |
| def test_format_empty_returns_placeholder(self): | |
| mem = EpisodicMemory("x") | |
| result = mem.format_for_prompt(()) | |
| assert "no prior" in result.lower() or result | |
| def test_run_started_renders_goal_not_raw_payload(self): | |
| # run.started carries {seed, goal} and is globally visible; the old formatter | |
| # dumped str(payload) β leaking the raw seed into every prompt. Now it renders | |
| # the shared goal only, never the seed dict. | |
| events = ( | |
| Event( | |
| run_id="r", | |
| turn=0, | |
| kind="run.started", | |
| actor="conductor", | |
| payload={"seed": "s3cr3t-seed", "goal": "catch the spy"}, # type: ignore[arg-type] | |
| ), | |
| Event(run_id="r", turn=1, kind="agent.spoke", actor="x", payload={"text": "hello"}), # type: ignore[arg-type] | |
| ) | |
| out = EpisodicMemory("x").format_for_prompt(events) | |
| assert "s3cr3t-seed" not in out | |
| assert "catch the spy" in out | |
| assert "hello" in out | |
| class _RaisingIndex: | |
| """A memory index whose backend is down β every call throws.""" | |
| def index(self, events): | |
| raise RuntimeError("backend down") | |
| def search(self, query, k, run_id=None): | |
| raise RuntimeError("backend down") | |
| class TestSalienceIndexResilience: | |
| def test_index_failure_degrades_to_keyword(self): | |
| # ADR-0018: the index is a derived, rebuildable lens, never load-bearing. | |
| # A flaky backend must degrade to keyword relevance, not crash the turn β | |
| # this is what kept the salience-using spy agents silent on the live path. | |
| from src.core.memory import SalienceMemory | |
| events = (Event(run_id="r", turn=1, kind="agent.spoke", actor="a", payload={"text": "warm fuel"}),) # type: ignore[arg-type] | |
| mem = SalienceMemory("a", top_k=3, index=_RaisingIndex()) | |
| out = mem.format_for_prompt(events, current_turn=1, query="warm") # must not raise | |
| assert "warm fuel" in out | |