Describe the Bug
When a provider response ends as incomplete while it contains a tool call, Pi maps the response to stopReason: "length" and intentionally creates a failed tool result instead of executing potentially truncated arguments.
Flue persists the resulting events as:
assistant_message_completed with stopReason: "length"
tool_outcome containing Pi's synthetic truncation error
tool_results_committed
The third record violates Flue's own reducer contract, which only permits committed tool results when the corresponding assistant message has stopReason: "toolUse":
Committed tool results require a completed tool-use assistant.
The submission consequently fails with the less-specific public error:
OperationFailedError [FlueError]: dispatch(...) failed:
A canonical conversation record violates the conversation stream contract.
Recovery is then deferred because the submission has already settled as failed.
This was observed with:
@flue/runtime@2.0.1
@earendil-works/pi-agent-core@0.83.0
- Azure OpenAI Responses
- Cloudflare target
- Vite
8.2.0
Pi's behavior appears intentional: for a "length" response containing tool calls, agent-loop.ts calls failToolCallsFromTruncatedMessage() and includes the synthetic results in turn_end.
In Flue, the session event bridge records any assistant tool call as the canonical tool request, persists the synthetic outcome, and emits tool_results_committed whenever turn_end.toolResults is nonempty. The reducer then rejects that batch because the assistant stopped with "length" rather than "toolUse".
Expected Behavior
Flue should support Pi's truncated-tool-call result without violating the canonical conversation contract.
For example, it could persist the synthetic failed result as a valid continuation of a "length" assistant message and allow the next model turn to retry the tool call with complete arguments.
Alternatively, Flue could terminalize the response through a defined error path before writing incompatible canonical records.
In either case:
- the conversation stream should remain reducible;
- recovery should not encounter a framework-generated invariant violation; and
- the surfaced error should preserve the specific invariant reason if processing must fail.
Steps to Reproduce
-
Create a basic Flue agent using an OpenAI Responses-compatible model and register at least one tool.
-
Cause the provider to return response.incomplete while streaming that tool's arguments. This can be reproduced deterministically with a mocked Responses endpoint or a sufficiently small output limit combined with a prompt requiring a long tool argument.
-
Dispatch a message to the agent and await settlement.
-
Observe Pi produce a failed tool result similar to:
Tool call "<tool>" was not executed: the response hit the output
token limit, so its arguments may be truncated. Re-issue the tool
call with complete arguments.
-
Observe Flue fail the submission with:
A canonical conversation record violates the conversation stream contract.
-
Inspect the canonical records. The assistant is completed with
stopReason: "length", followed by a tool_outcome; attempting to append
tool_results_committed triggers:
Committed tool results require a completed tool-use assistant.
Describe the Bug
When a provider response ends as
incompletewhile it contains a tool call, Pi maps the response tostopReason: "length"and intentionally creates a failed tool result instead of executing potentially truncated arguments.Flue persists the resulting events as:
assistant_message_completedwithstopReason: "length"tool_outcomecontaining Pi's synthetic truncation errortool_results_committedThe third record violates Flue's own reducer contract, which only permits committed tool results when the corresponding assistant message has
stopReason: "toolUse":The submission consequently fails with the less-specific public error:
Recovery is then deferred because the submission has already settled as failed.
This was observed with:
@flue/runtime@2.0.1@earendil-works/pi-agent-core@0.83.08.2.0Pi's behavior appears intentional: for a
"length"response containing tool calls,agent-loop.tscallsfailToolCallsFromTruncatedMessage()and includes the synthetic results inturn_end.In Flue, the session event bridge records any assistant tool call as the canonical tool request, persists the synthetic outcome, and emits
tool_results_committedwheneverturn_end.toolResultsis nonempty. The reducer then rejects that batch because the assistant stopped with"length"rather than"toolUse".Expected Behavior
Flue should support Pi's truncated-tool-call result without violating the canonical conversation contract.
For example, it could persist the synthetic failed result as a valid continuation of a
"length"assistant message and allow the next model turn to retry the tool call with complete arguments.Alternatively, Flue could terminalize the response through a defined error path before writing incompatible canonical records.
In either case:
Steps to Reproduce
Create a basic Flue agent using an OpenAI Responses-compatible model and register at least one tool.
Cause the provider to return
response.incompletewhile streaming that tool's arguments. This can be reproduced deterministically with a mocked Responses endpoint or a sufficiently small output limit combined with a prompt requiring a long tool argument.Dispatch a message to the agent and await settlement.
Observe Pi produce a failed tool result similar to:
Observe Flue fail the submission with:
Inspect the canonical records. The assistant is completed with
stopReason: "length", followed by atool_outcome; attempting to appendtool_results_committedtriggers: