Skip to content

Commit fdfc820

Browse files
authored
Merge pull request #202 from SentienceAPI/tighten
tighten debugger lifecycle ergonomics
2 parents 146638d + 7d7f658 commit fdfc820

File tree

4 files changed

+40
-1
lines changed

4 files changed

+40
-1
lines changed

sentience/agent_runtime.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -782,6 +782,14 @@ async def emit_step_end(
782782
self.tracer.emit("step_end", step_end_data, step_id=self.step_id)
783783
return step_end_data
784784

785+
async def end_step(self, **kwargs: Any) -> dict[str, Any]:
786+
"""
787+
User-friendly alias for emit_step_end().
788+
789+
This keeps step lifecycle naming symmetric with begin_step().
790+
"""
791+
return await self.emit_step_end(**kwargs)
792+
785793
async def _capture_artifact_frame(self) -> None:
786794
if not self._artifact_buffer:
787795
return

sentience/debugger.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,10 @@ class SentienceDebugger:
2222
Verifier-only sidecar wrapper around AgentRuntime.
2323
"""
2424

25-
def __init__(self, runtime: AgentRuntime) -> None:
25+
def __init__(self, runtime: AgentRuntime, *, auto_step: bool = True) -> None:
2626
self.runtime = runtime
2727
self._step_open = False
28+
self._auto_step = bool(auto_step)
2829

2930
@classmethod
3031
def attach(
@@ -65,5 +66,9 @@ async def snapshot(self, **kwargs: Any):
6566

6667
def check(self, predicate, label: str, required: bool = False):
6768
if not self._step_open:
69+
if not self._auto_step:
70+
raise RuntimeError(
71+
f"No active step. Call dbg.begin_step(...) or use 'async with dbg.step(...)' before check(label={label!r})."
72+
)
6873
self.begin_step(f"verify:{label}")
6974
return self.runtime.check(predicate, label, required=required)

tests/test_agent_runtime.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -755,6 +755,21 @@ async def test_snapshot_with_legacy_browser(self) -> None:
755755
assert result is mock_snapshot
756756
assert runtime.last_snapshot is mock_snapshot
757757

758+
759+
class TestAgentRuntimeEndStep:
760+
@pytest.mark.asyncio
761+
async def test_end_step_aliases_emit_step_end(self) -> None:
762+
backend = MockBackend()
763+
tracer = MockTracer()
764+
runtime = AgentRuntime(backend=backend, tracer=tracer)
765+
766+
with patch.object(runtime, "emit_step_end", new_callable=AsyncMock) as emit_mock:
767+
emit_mock.return_value = {"ok": True}
768+
out = await runtime.end_step(action="noop")
769+
770+
emit_mock.assert_awaited_once_with(action="noop")
771+
assert out == {"ok": True}
772+
758773
@pytest.mark.asyncio
759774
async def test_snapshot_with_backend(self) -> None:
760775
"""Test snapshot uses backend-agnostic snapshot."""

tests/test_debugger.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,3 +69,14 @@ def test_check_auto_opens_step_when_missing() -> None:
6969
runtime.begin_step.assert_called_once_with("verify:has_cart", step_index=None)
7070
runtime.check.assert_called_once_with(predicate, "has_cart", required=True)
7171
assert handle == "check-handle"
72+
73+
74+
def test_check_strict_mode_requires_explicit_step() -> None:
75+
runtime = MockRuntime()
76+
77+
from sentience.debugger import SentienceDebugger
78+
79+
debugger = SentienceDebugger(runtime=runtime, auto_step=False)
80+
81+
with pytest.raises(RuntimeError, match="No active step"):
82+
debugger.check(predicate=MagicMock(), label="has_cart", required=True)

0 commit comments

Comments
 (0)