Skip to content

Commit 62b0b41

Browse files
committed
fix(harness): heartbeat while auto_send delivers a turn
The provider helpers heartbeat once when a streamed turn starts, then hand the stream to UnifiedEmitter.auto_send_turn, which never heartbeated. A streamed OpenAI turn (or any harness turn delivered from an activity) that outlasts a short heartbeat_timeout was timed out and retried while it was still sending messages. auto_send now calls heartbeat_if_in_activity for each event it delivers; temporalio throttles the actual RPCs, and it is a no-op outside an activity. New test: auto_send inside a temporalio ActivityEnvironment records one heartbeat per event (none before this change).
1 parent 9ddbdd4 commit 62b0b41

2 files changed

Lines changed: 34 additions & 0 deletions

File tree

‎src/agentex/lib/core/harness/auto_send.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from datetime import datetime
77

88
from agentex.types.text_delta import TextDelta
9+
from agentex.lib.utils.temporal import heartbeat_if_in_activity
910
from agentex.types.text_content import TextContent
1011
from agentex.lib.core.harness.types import TurnUsage, TurnResult, StreamTaskMessage
1112
from agentex.lib.core.harness.tracer import SpanTracer
@@ -86,6 +87,7 @@ async def _close_all() -> None:
8687

8788
try:
8889
async for event in events:
90+
heartbeat_if_in_activity("auto send")
8991
if deriver is not None and tracer is not None:
9092
for signal in deriver.observe(event):
9193
await tracer.handle(signal)

‎tests/lib/core/harness/test_auto_send.py‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -478,3 +478,35 @@ async def test_auto_send_created_at_forwarded():
478478
await auto_send(_gen(events), task_id="task1", tracer=None, streaming=streaming, created_at=dt)
479479

480480
assert all(ts == dt for ts in streaming.recorded_created_at)
481+
482+
483+
@pytest.mark.asyncio
484+
async def test_auto_send_heartbeats_per_event_inside_an_activity():
485+
"""A long streamed turn must keep heartbeating, or a short heartbeat_timeout
486+
times out an activity that is still delivering messages."""
487+
from temporalio import activity
488+
from temporalio.testing import ActivityEnvironment
489+
490+
streaming = _FakeStreaming()
491+
events = [
492+
StreamTaskMessageStart(type="start", index=0, content=TextContent(type="text", author="agent", content="")),
493+
StreamTaskMessageDelta(type="delta", index=0, delta=TextDelta(type="text", text_delta="a")),
494+
StreamTaskMessageDelta(type="delta", index=0, delta=TextDelta(type="text", text_delta="b")),
495+
StreamTaskMessageDone(type="done", index=0),
496+
]
497+
beats: list[tuple[object, ...]] = []
498+
499+
@activity.defn(name="deliver_turn")
500+
async def deliver_turn() -> None:
501+
async def _source():
502+
for e in events:
503+
yield e
504+
505+
await auto_send(_source(), task_id="task1", tracer=None, streaming=streaming)
506+
507+
env = ActivityEnvironment()
508+
env.on_heartbeat = lambda *details: beats.append(details)
509+
await env.run(deliver_turn)
510+
511+
assert len(beats) == len(events)
512+

0 commit comments

Comments
 (0)