Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 45 additions & 1 deletion tests/agent_fakes.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
"""Shared scripted fakes for the agent tests: a stub Messages client standing in behind the
fusionlab.agent._client seam, and the SSE reader a browser would run. No SDK, no network."""
fusionlab.agent._client seam, the SSE reader a browser would run, and the one-time recording
client that captures a live session into a golden fixture. No SDK, no network (imported here)."""

from __future__ import annotations

import copy
import json
from datetime import UTC, datetime
from typing import Any


Expand Down Expand Up @@ -75,6 +77,48 @@ def stream(self, **kwargs: Any) -> FakeStream:
raise RuntimeError("api down")


class RecordingClient(FakeClient):
"""The capture half of the golden-trace harness (tests/test_agent_golden.py): wraps a real
Messages client, writes down every round it streams — the deltas and the final blocks — and
serializes the session into the golden fixture format via to_trace(). Recording is a one-time,
key-bearing operation done by hand (monkeypatch fusionlab.agent._client with a lambda that
wraps the real client); CI only replays the committed fixtures and never constructs this class.

The wrapped client only needs the slice the loop uses: .messages.stream(**kwargs) as a context
manager exposing text_stream and get_final_message() — the real SDK client, or FakeClient in a
test. Nothing here imports the SDK; the wrapper is duck-typed.
"""

def __init__(self, wrapped: Any) -> None:
super().__init__([])
self.wrapped = wrapped

def stream(self, **kwargs: Any) -> FakeStream:
# override the replay pop: recording appends what the wrapped client actually returned
with self.wrapped.messages.stream(**kwargs) as stream:
deltas = list(stream.text_stream)
final = stream.get_final_message()
blocks = [b if isinstance(b, dict) else b.model_dump() for b in final.content]
self.script.append((deltas, blocks))
self.calls.append(copy.deepcopy(kwargs))
return FakeStream(deltas, blocks)

def to_trace(self, message: str, history: list[dict[str, str]],
recorded_at: str | None = None) -> dict[str, Any]:
"""The fixture format: {message, history, script: [{deltas, blocks}], recorded_model,
recorded_at}. recorded_model comes from the first recorded call — the model that actually
ran. Recording nothing first is an IndexError on purpose: no session, no trace."""
if not self.calls:
raise IndexError("nothing was recorded; to_trace needs a streamed session")
return {
"message": message,
"history": [dict(m) for m in history],
"script": [{"deltas": list(d), "blocks": [dict(b) for b in blocks]} for d, blocks in self.script],
"recorded_model": str(self.calls[0]["model"]),
"recorded_at": recorded_at or datetime.now(UTC).isoformat(),
}


def parse_sse(text: str) -> list[dict[str, Any]]:
"""The browser's reader: data lines only, one JSON payload each."""
return [json.loads(line[len("data: "):]) for line in text.splitlines() if line.startswith("data: ")]
35 changes: 35 additions & 0 deletions tests/fixtures/golden/01-read-only-chain.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"message": "Roughly how big is the archive, and how close does shot 30166 come to its operating limits? Work from the catalog, not memory.",
"history": [],
"script": [
{
"deltas": ["Let me size the catalog first."],
"blocks": [
{"type": "text", "text": "Let me size the catalog first."},
{"type": "tool_use", "id": "tu_1", "name": "search_shots", "input": {}}
]
},
{
"deltas": ["Found it. Now the shot itself."],
"blocks": [
{"type": "text", "text": "Found it. Now the shot itself."},
{"type": "tool_use", "id": "tu_2", "name": "load_shot", "input": {"shot_id": "30166"}}
]
},
{
"deltas": ["And its limit distances."],
"blocks": [
{"type": "text", "text": "And its limit distances."},
{"type": "tool_use", "id": "tu_3", "name": "limit_distances", "input": {"shot_id": 30166}}
]
},
{
"deltas": ["The catalog holds 15,969 shots; the search page shows the first 200 in ascending shot_id order — the archive has no sort control. Shot 30166's closest approaches over its time base: Greenwald density 0.243× at t = 0.365 s, Troyon β_N 0.096× and kink (q95 < 2) 0.167× at t = 0.03 s, and no operating limit is crossed — a distance, never a forecast of a disruption. As ever, the twin is compared with MAST data, not a prediction."],
"blocks": [
{"type": "text", "text": "The catalog holds 15,969 shots; the search page shows the first 200 in ascending shot_id order — the archive has no sort control. Shot 30166's closest approaches over its time base: Greenwald density 0.243× at t = 0.365 s, Troyon β_N 0.096× and kink (q95 < 2) 0.167× at t = 0.03 s, and no operating limit is crossed — a distance, never a forecast of a disruption. As ever, the twin is compared with MAST data, not a prediction."}
]
}
],
"recorded_model": "claude-opus-5-5",
"recorded_at": "2026-10-12T00:00:00Z"
}
24 changes: 24 additions & 0 deletions tests/fixtures/golden/02-run-whatif-proposal.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"message": "Then show me what raising the beams to 3 MW would do. Propose it — don't apply it.",
"history": [
{"role": "user", "content": "Which edits can the archive actually test on shot 30166?"},
{"role": "assistant", "content": "On 30166 the matched pairs can test density, beam power and beam timing; current and field edits are refused — too few matched pairs to test any response law."}
],
"script": [
{
"deltas": ["Re-flying 30166 at 3 MW of beams."],
"blocks": [
{"type": "text", "text": "Re-flying 30166 at 3 MW of beams."},
{"type": "tool_use", "id": "tu_1", "name": "run_whatif", "input": {"shot_id": "30166", "p_nbi_MW": 3.0}}
]
},
{
"deltas": ["Proposed, not applied: at 3.0 MW (1.40 × the shot's 2.14813 MW logged peak) flat-top stored energy changes +6.5% to +14.2% across the response laws, compared with MAST data. Nothing is applied yet — confirming in the what-if panel is yours to do. An educational comparison, never a prediction."],
"blocks": [
{"type": "text", "text": "Proposed, not applied: at 3.0 MW (1.40 × the shot's 2.14813 MW logged peak) flat-top stored energy changes +6.5% to +14.2% across the response laws, compared with MAST data. Nothing is applied yet — confirming in the what-if panel is yours to do. An educational comparison, never a prediction."}
]
}
],
"recorded_model": "claude-opus-5-5",
"recorded_at": "2026-10-12T00:00:00Z"
}
21 changes: 21 additions & 0 deletions tests/fixtures/golden/03-gate-refusal.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"message": "Bump the toroidal field on shot 30166 up by 10% — what does that do to confinement?",
"history": [],
"script": [
{
"deltas": ["Let me put that past the gate."],
"blocks": [
{"type": "text", "text": "Let me put that past the gate."},
{"type": "tool_use", "id": "tu_1", "name": "run_whatif", "input": {"shot_id": "30166", "bt_scale": 1.1}}
]
},
{
"deltas": ["I can't propose that edit: the archive has 3 matched pairs for toroidal-field edits, too few to test any response law — the gate refuses it rather than guessing, so I won't retry it or substitute a guess. Density, beam power and beam timing edits are testable on this shot if you want a what-if I can stand behind. Remember the twin is compared with MAST data, never a prediction."],
"blocks": [
{"type": "text", "text": "I can't propose that edit: the archive has 3 matched pairs for toroidal-field edits, too few to test any response law — the gate refuses it rather than guessing, so I won't retry it or substitute a guess. Density, beam power and beam timing edits are testable on this shot if you want a what-if I can stand behind. Remember the twin is compared with MAST data, never a prediction."}
]
}
],
"recorded_model": "claude-opus-5-5",
"recorded_at": "2026-10-12T00:00:00Z"
}
50 changes: 50 additions & 0 deletions tests/fixtures/golden/04-budget-exhaustion.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
{
"message": "List the sandbox devices, then compare each one's worst operating limit, then rank them for me.",
"history": [],
"script": [
{
"deltas": ["Starting the device walk."],
"blocks": [
{"type": "text", "text": "Starting the device walk."},
{"type": "tool_use", "id": "tu_1", "name": "list_devices", "input": {}}
]
},
{
"deltas": ["Next device."],
"blocks": [
{"type": "text", "text": "Next device."},
{"type": "tool_use", "id": "tu_2", "name": "list_devices", "input": {}}
]
},
{
"deltas": ["And the next."],
"blocks": [
{"type": "text", "text": "And the next."},
{"type": "tool_use", "id": "tu_3", "name": "list_devices", "input": {}}
]
},
{
"deltas": ["Continuing."],
"blocks": [
{"type": "text", "text": "Continuing."},
{"type": "tool_use", "id": "tu_4", "name": "list_devices", "input": {}}
]
},
{
"deltas": ["Still ranking."],
"blocks": [
{"type": "text", "text": "Still ranking."},
{"type": "tool_use", "id": "tu_5", "name": "list_devices", "input": {}}
]
},
{
"deltas": ["One more pass."],
"blocks": [
{"type": "text", "text": "One more pass."},
{"type": "tool_use", "id": "tu_6", "name": "list_devices", "input": {}}
]
}
],
"recorded_model": "claude-opus-5-5",
"recorded_at": "2026-10-12T00:00:00Z"
}
Loading
Loading