diff --git a/hermit/agent/base/records.py b/hermit/agent/base/records.py index 225360d0..1159eea6 100644 --- a/hermit/agent/base/records.py +++ b/hermit/agent/base/records.py @@ -135,5 +135,5 @@ def parse_claude(log: Path, model: str) -> tuple[str | None, list[Usage]]: def parse_log(log: Path, backend: str, model: str) -> tuple[str | None, list[Usage]]: - """Session handle and usage from the agent's JSON event stream: Codex's, else Claude Code's.""" - return (parse_codex if backend == "codex" else parse_claude)(log, model) + """Session handle and usage from the agent's JSON event stream: Codex's, Pi's, else Claude Code's.""" + return {"codex": parse_codex, "pi": parse_pi}.get(backend, parse_claude)(log, model) diff --git a/hermit/agent/evolver/methods/ahe_adapter/debugger.py b/hermit/agent/evolver/methods/ahe_adapter/debugger.py index 39ba9e9b..655a6bf0 100644 --- a/hermit/agent/evolver/methods/ahe_adapter/debugger.py +++ b/hermit/agent/evolver/methods/ahe_adapter/debugger.py @@ -181,7 +181,11 @@ def trace_source(log: Path | None) -> tuple[Path, str] | None: ("the raw Codex event stream", "the raw Claude Code event stream"), ("the Codex event stream", "the Claude Code event stream"), ("`codex exec --json` event stream", - "`claude -p --output-format stream-json` event stream"))} + "`claude -p --output-format stream-json` event stream")), + "agent-pi.jsonl": (("agent-codex.jsonl", "agent-pi.jsonl"), + ("the raw Codex event stream", "the raw Pi event stream"), + ("the Codex event stream", "the Pi event stream"), + ("`codex exec --json` event stream", "`pi --mode json` event stream"))} def stream_of(logs) -> str: diff --git a/tests/core/test_pi_agent.py b/tests/core/test_pi_agent.py index b2f38218..a6e82217 100644 --- a/tests/core/test_pi_agent.py +++ b/tests/core/test_pi_agent.py @@ -220,7 +220,8 @@ def test_system_instructions_are_appended_and_a_long_one_goes_in_a_file(fake): def test_a_pi_stream_is_read_back_by_its_name_or_by_its_events(tmp_path): - """The learning material keeps the acting agent's log: an Evolver reads Pi's steps from it like Codex's.""" + """The learning material keeps the acting agent's log: an Evolver reads Pi's steps from it like Codex's, and + records.parse_log reads its session and usage as Pi's (it read a Pi stream as Claude Code's: none of either).""" named = tmp_path / "log" named.mkdir() shutil.copy(STREAMS / "success.jsonl", named / trajectory.PI_STREAM) @@ -231,6 +232,9 @@ def test_a_pi_stream_is_read_back_by_its_name_or_by_its_events(tmp_path): raw = (STREAMS / "failure.jsonl").read_text() assert not base_cli.served("pi", raw) and base_cli.notices("pi", raw).startswith("503: ") assert base_cli.served("pi", (STREAMS / "success.jsonl").read_text()) + from hermit.agent.base.records import parse_log + session, [usage] = parse_log(STREAMS / "success.jsonl", "pi", "m") + assert session == SESSION and (usage.calls, usage.input_tokens, usage.output_tokens) == (4, 4000, 80) def test_the_pi_acting_agent_deploys_the_adapter_and_trusts_its_project_for_the_session(fake): diff --git a/tests/methods/ahe/test_ahe_hosts.py b/tests/methods/ahe/test_ahe_hosts.py index 441b1805..6f1a7492 100644 --- a/tests/methods/ahe/test_ahe_hosts.py +++ b/tests/methods/ahe/test_ahe_hosts.py @@ -701,6 +701,22 @@ def test_the_evolvers_texts_name_the_acting_agents_stream(tmp_path): assert "`claude -p --output-format stream-json` event stream" in question and "codex exec --json" not in question +def test_the_evolvers_texts_name_every_acting_agents_stream_it_reads(): + """A Pi acting agent's sessions keep their own stream (agent-pi.jsonl), which rollout_stream finds: the learners' + texts name it, not Codex's file, which is not there.""" + from hermit.agent.base.trajectory import CODEX_STREAM, STREAMS + from hermit.agent.evolver.methods.ahe_adapter import debugger as D + methods = T.ROOT / "hermit" / "agent" / "evolver" / "methods" / "ahe_adapter" + texts = [(methods / name).read_text() for name in ("START.md", "DEBUGGER.md")] + for stream in set(STREAMS) - {CODEX_STREAM}: + for text in texts: + words = D.for_stream(text, stream) + assert "agent-codex.jsonl" not in words and "Codex event stream" not in words, stream + assert "codex exec --json" not in words, stream + assert stream in D.for_stream(texts[0], stream) + assert "`pi --mode json` event stream" in D.for_stream(texts[1], "agent-pi.jsonl") + + # --- the real CLIs, each on a scripted local model ---------------------------------------------------------------- # A session of each host in a workspace the framework deployed, its model a script served on 127.0.0.1 (every proxy # pointed at a closed port): which text reaches a request is what each channel delivers. Skipped without the CLI.