| """ContextBuilder — the shared blackboard reaches the prompt (ADR-0023). |
| |
| Before this, an agent saw only the world text and its own past lines, so small |
| models looped on one clue and never reacted to anyone. The builder now surfaces |
| ``projection.agent_notes`` (peers' public lines) so the table is actually shared. |
| """ |
|
|
| from __future__ import annotations |
|
|
| from src.core.context import ContextBuilder |
| from src.core.projections import StageProjection |
|
|
|
|
| def test_blackboard_surfaces_peer_lines(): |
| proj = StageProjection(goal="g", current_scene="scene") |
| proj.agent_notes = ["spy-cara: a morning fuel", "spy-nil: warm and comforting"] |
| prompt = ContextBuilder().build(agent_name="spy-bex", persona="(bex)", projection=proj, all_events=()) |
| assert "WHAT'S BEEN SAID" in prompt |
| assert "a morning fuel" in prompt |
| assert "warm and comforting" in prompt |
| |
| assert "new angle" in prompt.lower() and "echo" in prompt.lower() |
|
|
|
|
| def test_blackboard_prompts_the_first_speaker_to_open(): |
| proj = StageProjection(goal="g", current_scene="scene") |
| prompt = ContextBuilder().build(agent_name="spy-cara", persona="(cara)", projection=proj, all_events=()) |
| assert "WHAT'S BEEN SAID" in prompt |
| assert "you go first" in prompt.lower() |
|
|
|
|
| def test_persona_and_goal_still_lead(): |
| proj = StageProjection(goal="catch the spy", current_scene="scene") |
| prompt = ContextBuilder().build(agent_name="a", persona="I am A.", projection=proj, all_events=()) |
| |
| assert prompt.index("I am A.") < prompt.index("catch the spy") < prompt.index("WHAT'S BEEN SAID") |
|
|
|
|
| def _spoke(actor, text, turn=1, kind="agent.spoke"): |
| from src.core.events import Event |
|
|
| return Event(run_id="r", turn=turn, kind=kind, actor=actor, payload={"text": text}) |
|
|
|
|
| def test_judge_gets_the_full_ordered_transcript_not_just_the_tail(): |
| |
| |
| events = tuple(_spoke("debater-a" if i % 2 == 0 else "debater-b", f"point number {i}", turn=i) for i in range(12)) |
| proj = StageProjection(goal="g", current_scene="scene") |
| prompt = ContextBuilder().build( |
| agent_name="debate-judge", persona="(judge)", projection=proj, all_events=events, role="judge" |
| ) |
| assert "THE EXCHANGE TO JUDGE" in prompt and "WHAT'S BEEN SAID" not in prompt |
| |
| assert "point number 0" in prompt and "point number 11" in prompt |
| assert prompt.index("point number 0") < prompt.index("point number 11") |
|
|
|
|
| def test_judge_transcript_excludes_private_thoughts(): |
| |
| events = (_spoke("a", "said aloud"), _spoke("b", "secret scheming", kind="agent.thought")) |
| prompt = ContextBuilder().build( |
| agent_name="j", |
| persona="(judge)", |
| projection=StageProjection(current_scene="s"), |
| all_events=events, |
| role="judge", |
| ) |
| assert "said aloud" in prompt |
| assert "secret scheming" not in prompt |
|
|
|
|
| def test_worker_gets_blackboard_not_transcript(): |
| proj = StageProjection(current_scene="scene") |
| proj.agent_notes = ["a: hello there"] |
| prompt = ContextBuilder().build( |
| agent_name="w", persona="(w)", projection=proj, all_events=(_spoke("a", "hello there"),), role="worker" |
| ) |
| assert "WHAT'S BEEN SAID" in prompt and "THE EXCHANGE TO JUDGE" not in prompt |
|
|
|
|
| def test_memory_does_not_repeat_a_line_already_in_the_discussion(): |
| |
| |
| proj = StageProjection(current_scene="scene") |
| proj.agent_notes = ["a: the bench needs shade"] |
| memory_text = "[turn 003][agent.spoke] the bench needs shade\n[turn 001][world.observed] an older beat" |
| prompt = ContextBuilder().build( |
| agent_name="w", persona="(w)", projection=proj, all_events=(), memory_text=memory_text, role="worker" |
| ) |
| |
| assert prompt.count("the bench needs shade") == 1 |
| assert "an older beat" in prompt |
|
|
|
|
| def test_memory_shows_a_pointer_when_fully_covered_by_the_discussion(): |
| proj = StageProjection(current_scene="scene") |
| proj.agent_notes = ["a: only line"] |
| prompt = ContextBuilder().build( |
| agent_name="w", |
| persona="(w)", |
| projection=proj, |
| all_events=(), |
| memory_text="[turn 003][agent.spoke] only line", |
| role="worker", |
| ) |
| assert "nothing beyond the exchange above" in prompt |
|
|
|
|
| def test_a_peer_thought_never_reaches_another_agent(): |
| |
| |
| from src.core.events import Event |
|
|
| proj = StageProjection(goal="g", current_scene="scene") |
| proj.apply( |
| Event( |
| run_id="r", |
| turn=1, |
| kind="agent.spoke", |
| actor="spy-nil", |
| payload={"text": "warm and comforting", "thought": "I'm the spy — keep it vague!"}, |
| ) |
| ) |
| assert any("warm and comforting" in n for n in proj.agent_notes) |
| assert not any("spy" in n.lower() and "vague" in n.lower() for n in proj.agent_notes) |
| prompt = ContextBuilder().build(agent_name="spy-bex", persona="(bex)", projection=proj, all_events=()) |
| assert "warm and comforting" in prompt |
| assert "keep it vague" not in prompt |
|
|