Skip to content

Truncated tool calls produce an invalid canonical tool_results_committed record #548

Description

@schwartzmj

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:

  1. assistant_message_completed with stopReason: "length"
  2. tool_outcome containing Pi's synthetic truncation error
  3. 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

  1. Create a basic Flue agent using an OpenAI Responses-compatible model and register at least one tool.

  2. 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.

  3. Dispatch a message to the agent and await settlement.

  4. 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.
    
  5. Observe Flue fail the submission with:

    A canonical conversation record violates the conversation stream contract.
    
  6. 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.
    

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions