Repository navigation
[Bug]: A run's last conversation events never reach enterprise because the sandbox is deleted before the agent-server flushes them #616
Description
Bug Description
On OHE and Cloud, enterprise never receives the last events of an automation run's conversation. Its copy of the conversation stops before the agent's final reply and the finished state. The cause is a race:
- The agent-server sends events to enterprise's webhook in batches, and flushes a partial batch only after a delay.
- The service deletes the run's sandbox within seconds of the run completing, so that batch is never sent.
This affects every run whose last events don't fill a batch. Since #614, a plugin-spec run's conversation is started through enterprise, so this truncated copy is the record users open from their conversation list.
Expected Behavior
Once a run is COMPLETED or FAILED, enterprise has the run conversation's full event history, including the agent's final message and the last state update.
Actual Behavior
Reproduced on OHE Replicated (fleet instance shared-8, 2026-10-20):
- OHE 0.84.0: enterprise 1.73.0, agent-server 1.56.0.
- The automation image was feat: let users choose a plugin-spec automation's agent profile #615's build,
sha-825c090(1.22.0 plus feat: copy the run's agent profile into its cloud sandbox #614 and feat: let users choose a plugin-spec automation's agent profile #615). Teardown on that build is the same as onmain.
A plugin-spec prompt-task automation was dispatched manually. While it ran, I read its events from the sandbox's agent-server (GET {AGENT_SERVER}/api/conversations/{id}/events/search). After it completed, I read enterprise's copy (GET /api/v1/conversation/{id}/events/search).
| Sandbox agent-server (just before completion) | Enterprise (after completion) | |
|---|---|---|
| events | 13: 12 listed by the agent-server, plus the start-time state update that only enterprise lists | 10 |
| last events | ObservationEvent (task) 14:25:02, ConversationStateUpdateEvent 14:25:04, agent MessageEvent 14:25:04 (the final reply), ConversationStateUpdateEvent finished 14:25:04 |
ends at the ObservationEvent at 14:25:02 |
The run completed at 14:25:09, and the sandbox was gone right after. An earlier run of the same automation also stopped at exactly 10 stored events, before the final reply.
Why this happens:
- Agent-server. The agent-server's
WebhookSpecdefaults toevent_buffer_size=5andflush_delay=30.0s (config.py).- It sends a batch when 5 events are queued, and sends a partial batch only after 30 s without a new event (
__call__). - OHE doesn't override either setting; it only sets
OH_WEBHOOKS_0_BASE_URL. - So in the run above, 13 events made 2 full batches and the last 3 were still queued.
- It sends a batch when 5 events are queued, and sends a partial batch only after 30 s without a new event (
- Service.
POST /v1/runs/{run_id}/completestarts sandbox deletion right away (router.py). Withsandbox_cleanup_delay_secondsset it pauses the sandbox instead; I haven't checked whether a pause lets the agent-server flush.- The runner calls
/completeas soon as the conversation finishes, about 5 s after the last event.
- The runner calls
Acceptance Criteria
- After a run completes on OHE/Cloud, enterprise's events for the run's conversation include the agent's final message and the terminal
ConversationStateUpdateEvent. - This holds both when the sandbox is deleted at completion and when
sandbox_cleanup_delay_secondsis set. - A regression test covers the ordering: the sandbox is not deleted or paused before the conversation's buffered events have been delivered.
Additional Context
Places the fix could go, for discussion:
- Service: don't delete or pause the sandbox until a buffered batch would have been sent, for example by deferring deletion past
flush_delay. This is simple but couples the service to an agent-server default. - Runner: before calling
/complete, wait until enterprise's copy has the conversation's final event. - Agent-server (software-agent-sdk): flush webhook subscribers as soon as a conversation reaches a terminal execution status. This fixes it for every consumer, but needs an SDK release that OHE then ships.
Related: #600, #614 (its "Known gap" note).
I'm an AI agent (Claude Code) helping @simonrosenberg.
Activity
all-hands-bot commentedon Oct 20, 2026
The following comments were added by the OpenHands AI agent.
Decision needed
The bug, its root cause, and the reproduction are clear. What is not settled is which of the three candidate fix locations owns the change, and that choice determines this issue's scope and whether it can land in this repository at all.
Which location should own the fix? The service's teardown (
complete_runinrouter.py/ the watchdog deleting or pausing the sandbox), the plugin-spec runner waiting for enterprise's copy before it calls/complete, or the agent-server flushing webhook subscribers when a conversation reaches a terminal execution status. The first two land here; the third needs asoftware-agent-sdkchange plus an OHE release, which would make this issue wait on that release. The body lists all three "for discussion" and notes a different downside for each, so the choice is yours to make.On the
sandbox_cleanup_delay_secondspath: should the fix also guarantee the tail is delivered there, or is deferring to the agent-server's flush window acceptable? The body records that whether a pause lets the agent-server flush was not checked, so the expected behavior on that path is currently unknown.Once the location and the pause-path expectation are settled, observable acceptance criteria can be written for the outcome the body already describes, without prescribing an approach.