honcho/tests/dreamer/test_dream_v2_rollups.py

184 lines
6.8 KiB
Python

# pyright: reportPrivateUsage=false
"""tests: DreamSpecialistEvent + DreamRunEvent v2 fields.
Targets:
- Schema bumps to v2 (DreamRunEvent + DreamSpecialistEvent).
- Specialist rollups come from `tool_result_metadata`, NOT tool-name counting.
- DreamRunEvent carries the scheduler reasons threaded through the dream
queue payload.
"""
from __future__ import annotations
from src.telemetry.events.dream import DreamRunEvent, DreamSpecialistEvent
from src.utils.queue_payload import DreamPayload
class TestSchemaVersionsBumpedToV2:
def test_dream_run_event_at_v2(self):
assert DreamRunEvent.schema_version() == 2
def test_dream_specialist_event_at_v2(self):
assert DreamSpecialistEvent.schema_version() == 2
class TestDreamRunEventV2Fields:
def test_scheduler_fields_default(self):
"""Existing callers that don't supply scheduling fields must
still construct a valid event — all new fields default to None or 0."""
event = DreamRunEvent(
run_id="abc",
workspace_name="ws",
session_name=None,
observer="o",
observed="user",
specialists_run=["deduction", "induction"],
deduction_success=True,
induction_success=True,
total_iterations=10,
total_input_tokens=100,
total_output_tokens=20,
total_duration_ms=1000.0,
)
assert event.dream_type is None
assert event.enabled_types_count == 0
assert event.trigger_reason is None
assert event.delay_reason is None
assert event.documents_since_last_dream_at_schedule is None
assert event.document_threshold is None
def test_scheduler_reasons_round_trip(self):
event = DreamRunEvent(
run_id="abc",
workspace_name="ws",
session_name=None,
observer="o",
observed="user",
specialists_run=["deduction"],
deduction_success=True,
induction_success=False,
total_iterations=5,
total_input_tokens=50,
total_output_tokens=10,
total_duration_ms=500.0,
dream_type="omni",
enabled_types_count=1,
trigger_reason="document_threshold",
delay_reason="idle_timeout",
documents_since_last_dream_at_schedule=60,
document_threshold=50,
)
assert event.dream_type == "omni"
assert event.trigger_reason == "document_threshold"
assert event.delay_reason == "idle_timeout"
assert event.documents_since_last_dream_at_schedule == 60
assert event.document_threshold == 50
def test_threshold_and_delay_are_separate(self):
"""The two scheduler gates are intentionally separate fields. The
snapshot semantics differ: trigger_reason describes WHY the dream
was scheduled (which gate tripped); delay_reason describes WHEN it
will fire (idle vs immediate)."""
event = DreamRunEvent(
run_id="abc",
workspace_name="ws",
session_name=None,
observer="o",
observed="user",
specialists_run=["induction"],
deduction_success=False,
induction_success=True,
total_iterations=3,
total_input_tokens=30,
total_output_tokens=5,
total_duration_ms=100.0,
trigger_reason="document_threshold",
delay_reason="immediate",
)
# trigger_reason captures the WHY; delay_reason captures the WHEN.
# They are separate dimensions — flattening into one field would lose
# the gate semantics that was specifically designed to expose.
assert event.trigger_reason != event.delay_reason
class TestDreamSpecialistEventV2Rollups:
def test_rollup_fields_default(self):
event = DreamSpecialistEvent(
run_id="abc",
specialist_type="deduction",
workspace_name="ws",
observer="o",
observed="user",
iterations=3,
tool_calls_count=5,
input_tokens=100,
output_tokens=20,
duration_ms=500.0,
success=True,
)
assert event.created_observation_count == 0
assert event.deleted_observation_count == 0
assert event.peer_card_updated is False
assert event.search_tool_calls_count == 0
def test_observation_counts_are_observation_truth_not_call_counts(self):
"""The whole point of sourcing rollups from ToolResult.metadata
instead of tool-name counts: a single `create_observations` call can
produce N observations (or zero on validation failure). must
report observation truth, not call truth."""
event = DreamSpecialistEvent(
run_id="abc",
specialist_type="deduction",
workspace_name="ws",
observer="o",
observed="user",
iterations=2,
# Two create_observations CALLS, but they produced 7 observations
# together (e.g. one batch of 5, one batch of 2). reports
# 7 (the metadata-sourced truth), not 2 (the call count).
tool_calls_count=2,
input_tokens=100,
output_tokens=20,
duration_ms=500.0,
success=True,
created_observation_count=7,
)
assert event.tool_calls_count == 2
assert event.created_observation_count == 7
class TestDreamPayloadSchedulerFields:
def test_payload_defaults(self):
from src.schemas import DreamType
payload = DreamPayload(
dream_type=DreamType.OMNI,
observer="o",
observed="user",
)
assert payload.trigger_reason is None
assert payload.delay_reason is None
assert payload.documents_since_last_dream_at_schedule is None
assert payload.document_threshold is None
def test_payload_threads_scheduler_reasons(self):
from src.schemas import DreamType
payload = DreamPayload(
dream_type=DreamType.OMNI,
observer="o",
observed="user",
trigger_reason="document_threshold",
delay_reason="idle_timeout",
documents_since_last_dream_at_schedule=55,
document_threshold=50,
)
# Round-trip through serialization → deserialization mimics what
# happens between scheduler enqueue and consumer dequeue.
data = payload.model_dump(mode="json")
restored = DreamPayload(**data)
assert restored.trigger_reason == "document_threshold"
assert restored.delay_reason == "idle_timeout"
assert restored.documents_since_last_dream_at_schedule == 55
assert restored.document_threshold == 50