Skip to content

Commit 341c80c

Browse files
committed
p2
1 parent 4ce4246 commit 341c80c

File tree

3 files changed

+79
-12
lines changed

3 files changed

+79
-12
lines changed

sentience/agent_runtime.py

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -326,19 +326,42 @@ def finalize_run(self, *, success: bool) -> None:
326326
return
327327
if success:
328328
if self._artifact_buffer.options.persist_mode == "always":
329-
self._artifact_buffer.persist(reason="success", status="success")
329+
self._artifact_buffer.persist(
330+
reason="success",
331+
status="success",
332+
snapshot=self.last_snapshot,
333+
diagnostics=getattr(self.last_snapshot, "diagnostics", None),
334+
metadata=self._artifact_metadata(),
335+
)
330336
self._artifact_buffer.cleanup()
331337
else:
332338
self._persist_failure_artifacts(reason="finalize_failure")
333339

334340
def _persist_failure_artifacts(self, *, reason: str) -> None:
335341
if not self._artifact_buffer:
336342
return
337-
self._artifact_buffer.persist(reason=reason, status="failure")
343+
self._artifact_buffer.persist(
344+
reason=reason,
345+
status="failure",
346+
snapshot=self.last_snapshot,
347+
diagnostics=getattr(self.last_snapshot, "diagnostics", None),
348+
metadata=self._artifact_metadata(),
349+
)
338350
self._artifact_buffer.cleanup()
339351
if self._artifact_buffer.options.persist_mode == "onFail":
340352
self.disable_failure_artifacts()
341353

354+
def _artifact_metadata(self) -> dict[str, Any]:
355+
url = None
356+
if self.last_snapshot is not None:
357+
url = self.last_snapshot.url
358+
elif self._cached_url:
359+
url = self._cached_url
360+
return {
361+
"backend": self.backend.__class__.__name__,
362+
"url": url,
363+
}
364+
342365
def begin_step(self, goal: str, step_index: int | None = None) -> str:
343366
"""
344367
Begin a new step in the verification loop.

sentience/failure_artifacts.py

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@
44
import shutil
55
import tempfile
66
import time
7+
from collections.abc import Callable
78
from dataclasses import dataclass
89
from pathlib import Path
9-
from typing import Callable, Literal
10+
from typing import Any, Literal
1011

1112

1213
@dataclass
@@ -93,7 +94,20 @@ def _prune(self) -> None:
9394
pass
9495
self._frames = keep
9596

96-
def persist(self, *, reason: str | None, status: Literal["failure", "success"]) -> Path | None:
97+
def _write_json_atomic(self, path: Path, data: Any) -> None:
98+
tmp_path = path.with_suffix(path.suffix + ".tmp")
99+
tmp_path.write_text(json.dumps(data, indent=2))
100+
tmp_path.replace(path)
101+
102+
def persist(
103+
self,
104+
*,
105+
reason: str | None,
106+
status: Literal["failure", "success"],
107+
snapshot: Any | None = None,
108+
diagnostics: Any | None = None,
109+
metadata: dict[str, Any] | None = None,
110+
) -> Path | None:
97111
if self._persisted:
98112
return None
99113

@@ -107,8 +121,23 @@ def persist(self, *, reason: str | None, status: Literal["failure", "success"])
107121
for frame in self._frames:
108122
shutil.copy2(frame.path, frames_out / frame.file_name)
109123

110-
steps_path = run_dir / "steps.json"
111-
steps_path.write_text(json.dumps(self._steps, indent=2))
124+
self._write_json_atomic(run_dir / "steps.json", self._steps)
125+
126+
snapshot_payload = None
127+
if snapshot is not None:
128+
if hasattr(snapshot, "model_dump"):
129+
snapshot_payload = snapshot.model_dump()
130+
else:
131+
snapshot_payload = snapshot
132+
self._write_json_atomic(run_dir / "snapshot.json", snapshot_payload)
133+
134+
diagnostics_payload = None
135+
if diagnostics is not None:
136+
if hasattr(diagnostics, "model_dump"):
137+
diagnostics_payload = diagnostics.model_dump()
138+
else:
139+
diagnostics_payload = diagnostics
140+
self._write_json_atomic(run_dir / "diagnostics.json", diagnostics_payload)
112141

113142
manifest = {
114143
"run_id": self.run_id,
@@ -117,12 +146,12 @@ def persist(self, *, reason: str | None, status: Literal["failure", "success"])
117146
"reason": reason,
118147
"buffer_seconds": self.options.buffer_seconds,
119148
"frame_count": len(self._frames),
120-
"frames": [
121-
{"file": frame.file_name, "ts": frame.ts} for frame in self._frames
122-
],
149+
"frames": [{"file": frame.file_name, "ts": frame.ts} for frame in self._frames],
150+
"snapshot": "snapshot.json" if snapshot_payload is not None else None,
151+
"diagnostics": "diagnostics.json" if diagnostics_payload is not None else None,
152+
"metadata": metadata or {},
123153
}
124-
manifest_path = run_dir / "manifest.json"
125-
manifest_path.write_text(json.dumps(manifest, indent=2))
154+
self._write_json_atomic(run_dir / "manifest.json", manifest)
126155

127156
self._persisted = True
128157
return run_dir

tests/unit/test_failure_artifacts.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,26 @@ def time_fn() -> float:
3434
buf.record_step(action="CLICK", step_id="s1", step_index=1, url="https://example.com")
3535
buf.add_frame(b"frame")
3636

37-
run_dir = buf.persist(reason="assert_failed", status="failure")
37+
snapshot = {"status": "success", "url": "https://example.com", "elements": []}
38+
diagnostics = {"confidence": 0.9, "reasons": ["ok"], "metrics": {"quiet_ms": 42}}
39+
run_dir = buf.persist(
40+
reason="assert_failed",
41+
status="failure",
42+
snapshot=snapshot,
43+
diagnostics=diagnostics,
44+
metadata={"backend": "MockBackend", "url": "https://example.com"},
45+
)
3846
assert run_dir is not None
3947
manifest = json.loads((run_dir / "manifest.json").read_text())
4048
steps = json.loads((run_dir / "steps.json").read_text())
49+
snap_json = json.loads((run_dir / "snapshot.json").read_text())
50+
diag_json = json.loads((run_dir / "diagnostics.json").read_text())
4151

4252
assert manifest["run_id"] == "run-2"
4353
assert manifest["frame_count"] == 1
54+
assert manifest["snapshot"] == "snapshot.json"
55+
assert manifest["diagnostics"] == "diagnostics.json"
56+
assert manifest["metadata"]["backend"] == "MockBackend"
4457
assert len(steps) == 1
58+
assert snap_json["url"] == "https://example.com"
59+
assert diag_json["confidence"] == 0.9

0 commit comments

Comments
 (0)