irregular6612 commited on
Commit
c1f2ff2
·
1 Parent(s): 6491bd0

feat(cp4): JSONL trace persistence (append_trace / read_traces)

Browse files
proteus/runtime/__init__.py CHANGED
@@ -1,7 +1,15 @@
1
- """proteus.runtime — session orchestration, trace, and metrics."""
2
 
3
  from proteus.runtime.session import SessionRunner
4
  from proteus.runtime.trace import SessionTrace, TurnTrace
5
  from proteus.runtime.metrics import compute_metrics
 
6
 
7
- __all__ = ["SessionRunner", "SessionTrace", "TurnTrace", "compute_metrics"]
 
 
 
 
 
 
 
 
1
+ """proteus.runtime — session orchestration, trace, metrics, and I/O."""
2
 
3
  from proteus.runtime.session import SessionRunner
4
  from proteus.runtime.trace import SessionTrace, TurnTrace
5
  from proteus.runtime.metrics import compute_metrics
6
+ from proteus.runtime.io import append_trace, read_traces
7
 
8
+ __all__ = [
9
+ "SessionRunner",
10
+ "SessionTrace",
11
+ "TurnTrace",
12
+ "compute_metrics",
13
+ "append_trace",
14
+ "read_traces",
15
+ ]
proteus/runtime/io.py ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Persist and reload :class:`SessionTrace` as JSONL.
2
+
3
+ A run file is JSON Lines: one ``SessionTrace`` JSON object per line. Appending
4
+ lets a batch of sessions accumulate into a single file; one CLI ``run`` writes
5
+ exactly one line. This is the analysis/handoff boundary described in the spec
6
+ (§7: "세션당 JSONL 1개").
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from pathlib import Path
12
+
13
+ from proteus.runtime.trace import SessionTrace
14
+
15
+
16
+ def append_trace(trace: SessionTrace, path: str | Path) -> Path:
17
+ """Append one ``SessionTrace`` as a JSON line to *path*.
18
+
19
+ Creates parent directories if needed.
20
+
21
+ Args:
22
+ trace: The session trace to persist.
23
+ path: Destination ``.jsonl`` file (created/appended).
24
+
25
+ Returns:
26
+ The :class:`~pathlib.Path` written to.
27
+ """
28
+ path = Path(path)
29
+ path.parent.mkdir(parents=True, exist_ok=True)
30
+ with path.open("a", encoding="utf-8") as handle:
31
+ handle.write(trace.model_dump_json() + "\n")
32
+ return path
33
+
34
+
35
+ def read_traces(path: str | Path) -> list[SessionTrace]:
36
+ """Read all ``SessionTrace`` records from a JSONL *path*.
37
+
38
+ Blank lines are skipped.
39
+
40
+ Args:
41
+ path: A ``.jsonl`` file written by :func:`append_trace`.
42
+
43
+ Returns:
44
+ The traces in file order.
45
+ """
46
+ path = Path(path)
47
+ return [
48
+ SessionTrace.model_validate_json(line)
49
+ for line in path.read_text(encoding="utf-8").splitlines()
50
+ if line.strip()
51
+ ]
tests/runtime/test_io.py ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from proteus.agents import VanillaAgent
2
+ from proteus.providers import FakeProvider
3
+ from proteus.runtime import SessionRunner, append_trace, read_traces
4
+
5
+
6
+ def _trace(seed):
7
+ agent = VanillaAgent(FakeProvider(responses=["ACTION: up"], model_name="fake-1"))
8
+ return SessionRunner(
9
+ "predator_evade", agent, seed=seed, play_turns=4, use_probe=False,
10
+ ).run()
11
+
12
+
13
+ def test_append_then_read_roundtrips_one_trace(tmp_path):
14
+ path = tmp_path / "runs" / "session.jsonl"
15
+ written = append_trace(_trace(42), path)
16
+ assert written.exists()
17
+ traces = read_traces(path)
18
+ assert len(traces) == 1
19
+ assert traces[0].scenario == "predator_evade"
20
+ assert traces[0].model == "fake-1"
21
+
22
+
23
+ def test_append_accumulates_multiple_sessions(tmp_path):
24
+ path = tmp_path / "session.jsonl"
25
+ append_trace(_trace(1), path)
26
+ append_trace(_trace(2), path)
27
+ traces = read_traces(path)
28
+ assert len(traces) == 2
29
+ assert [t.seed for t in traces] == [1, 2]
30
+
31
+
32
+ def test_read_ignores_blank_lines(tmp_path):
33
+ path = tmp_path / "s.jsonl"
34
+ append_trace(_trace(7), path)
35
+ with path.open("a", encoding="utf-8") as f:
36
+ f.write("\n") # trailing blank line
37
+ assert len(read_traces(path)) == 1
38
+
39
+
40
+ def test_append_creates_parent_dirs(tmp_path):
41
+ path = tmp_path / "deep" / "nested" / "s.jsonl"
42
+ append_trace(_trace(3), path)
43
+ assert path.exists()