Describe the Bug
Cloudflare direct agent prompts persist the submitted user message into canonical session history, but do not append a corresponding user message_start / message_end event to the durable agent stream.
That means consumers that rebuild UI state from GET /agents/:name/:id / client.agents.stream() do not see any role: "user" event for direct prompts. The message may appear temporarily in React because @flue/react adds a local optimistic message, but after a page refresh the hook replays only the durable stream and the user message is missing.
This appears to contradict the public durability/log contract in the beta blog:
the source of truth is the log. Durable Streams give us a guaranteed append-only record of everything an agent sees: every human prompt, model response, and tool result is recorded to a durable, replayable stream.
Code path observed on current main (df62fe375c5a5832b380a9588dff319c09cd6a56) and v1.0.0-beta.2:
packages/runtime/src/session.ts persists direct input via runPersistedDirectSubmissionInput() by appending a user message to SessionHistory with directSubmissionId.
runPersistedContextInput() then calls save() after persistInput() succeeds.
- No user
message_start / message_end event is emitted on that path.
- Cloudflare durable streams only append events emitted through
ctx.subscribeEvent(...) in packages/runtime/src/cloudflare/agent-coordinator.ts, so the persisted direct user input never reaches the event stream.
The React package seems to expect the missing server event:
packages/react/src/agent-reducer.ts has reconciliation logic for a streamed user echo keyed by submissionId.
apps/docs/src/content/docs/guide/react.md says: “The stream then reconciles that optimistic message with its durable copy.”
But the runtime does not currently emit that durable copy for Cloudflare direct prompts.
Expected Behavior
For a Cloudflare direct prompt sent through POST /agents/:name/:id or client.agents.send():
- Flue durably admits the submission.
- Flue persists the user input into canonical session history.
- Flue appends a corresponding user message event to the durable agent stream, with the same
submissionId used for the direct submission.
- Replaying
client.agents.stream(name, id, { offset: '-1' }) after a page refresh includes the user message as a role: "user" message event.
A normal stream replay should be sufficient to reconstruct the conversation transcript. Local optimistic UI state should only be temporary, not the only source of user messages.
The user message event should be emitted only once per submitted input. Retry/recovery paths that find an already-persisted direct input should not append duplicate user events.
Steps to Reproduce
-
Create or use a basic Cloudflare Flue app with an HTTP-routed agent and the React hook or SDK stream reader.
-
Send a direct prompt:
await client.agents.send('support-assistant', 'conversation-1', {
message: 'hello',
});
- Read the durable stream from the beginning, or refresh a React page that rebuilds from stream history:
for await (const event of client.agents.stream('support-assistant', 'conversation-1', {
offset: '-1',
live: false,
})) {
console.log(event.type, 'message' in event ? event.message : undefined);
}
-
Observe that assistant/model/tool events are present, but there is no message_start or message_end event whose message.role is "user" for the submitted prompt.
-
If using @flue/react, the user message may appear immediately after submit because of local optimistic state. Refresh the page and rebuild from durable stream history: the user message is gone.
Additional Context
Current package/version check:
- A downstream app pinned to
@flue/runtime@1.0.0-beta.1 shows the behavior.
@flue/runtime@1.0.0-beta.2 is the latest published runtime as of 2026-06-19, and the same code path is still present there.
- Current upstream
main (df62fe375c5a5832b380a9588dff319c09cd6a56) also still has the same path.
Implementation direction that would match the documented contract:
- When
runPersistedDirectSubmissionInput() first appends the canonical user message and save() succeeds, emit a durable user message_end event, or both message_start and message_end for consistency.
- Include the direct submission's
submissionId so React/SDK consumers can reconcile it with the optimistic local message (submission:${submissionId}:user:0).
- Do not emit on retries where
findDirectSubmissionInput(submissionId) already finds the input.
Describe the Bug
Cloudflare direct agent prompts persist the submitted user message into canonical session history, but do not append a corresponding user
message_start/message_endevent to the durable agent stream.That means consumers that rebuild UI state from
GET /agents/:name/:id/client.agents.stream()do not see anyrole: "user"event for direct prompts. The message may appear temporarily in React because@flue/reactadds a local optimistic message, but after a page refresh the hook replays only the durable stream and the user message is missing.This appears to contradict the public durability/log contract in the beta blog:
Code path observed on current
main(df62fe375c5a5832b380a9588dff319c09cd6a56) andv1.0.0-beta.2:packages/runtime/src/session.tspersists direct input viarunPersistedDirectSubmissionInput()by appending a user message toSessionHistorywithdirectSubmissionId.runPersistedContextInput()then callssave()afterpersistInput()succeeds.message_start/message_endevent is emitted on that path.ctx.subscribeEvent(...)inpackages/runtime/src/cloudflare/agent-coordinator.ts, so the persisted direct user input never reaches the event stream.The React package seems to expect the missing server event:
packages/react/src/agent-reducer.tshas reconciliation logic for a streamed user echo keyed bysubmissionId.apps/docs/src/content/docs/guide/react.mdsays: “The stream then reconciles that optimistic message with its durable copy.”But the runtime does not currently emit that durable copy for Cloudflare direct prompts.
Expected Behavior
For a Cloudflare direct prompt sent through
POST /agents/:name/:idorclient.agents.send():submissionIdused for the direct submission.client.agents.stream(name, id, { offset: '-1' })after a page refresh includes the user message as arole: "user"message event.A normal stream replay should be sufficient to reconstruct the conversation transcript. Local optimistic UI state should only be temporary, not the only source of user messages.
The user message event should be emitted only once per submitted input. Retry/recovery paths that find an already-persisted direct input should not append duplicate user events.
Steps to Reproduce
Create or use a basic Cloudflare Flue app with an HTTP-routed agent and the React hook or SDK stream reader.
Send a direct prompt:
Observe that assistant/model/tool events are present, but there is no
message_startormessage_endevent whosemessage.roleis"user"for the submitted prompt.If using
@flue/react, the user message may appear immediately after submit because of local optimistic state. Refresh the page and rebuild from durable stream history: the user message is gone.Additional Context
Current package/version check:
@flue/runtime@1.0.0-beta.1shows the behavior.@flue/runtime@1.0.0-beta.2is the latest published runtime as of 2026-06-19, and the same code path is still present there.main(df62fe375c5a5832b380a9588dff319c09cd6a56) also still has the same path.Implementation direction that would match the documented contract:
runPersistedDirectSubmissionInput()first appends the canonical user message andsave()succeeds, emit a durable usermessage_endevent, or bothmessage_startandmessage_endfor consistency.submissionIdso React/SDK consumers can reconcile it with the optimistic local message (submission:${submissionId}:user:0).findDirectSubmissionInput(submissionId)already finds the input.