FlowDrop Workflow Specification 1.0-draft

A user turn heals tool calls that were never answered

A crashed or interrupted loop leaves an assistant turn declaring a call with no result. Most providers reject that history outright, so the buffer closes the pair before the conversation moves on.

The rule

Normative: this is the rule
  1. Before a user-role message is appended, every tool call declared in the buffer that has no matching tool result anywhere in the buffer is answered by a synthetic tool-role message stating that the call was interrupted and returned no result.
  2. The synthetic message is marked as healed, carries the id it answers, and is inserted directly after the turn that declared the call, not at the end of the buffer, because a result must be adjacent to its call.
  3. Healing runs only at this boundary: appending a tool or assistant message never triggers it.

What it means

A crashed or interrupted loop can leave a buffer with an assistant turn that declared a call and no result answering it. Healing does not wait for the buffer to end before closing that pair: the synthetic result is inserted directly after the turn that declared the call, wherever that is, because a result has to sit next to its call for the history to be sendable at all. A conversation that carried on past the crash — more turns appended after the dangling call, before anyone appends a user message — still gets its synthetic result inserted mid-buffer, not tacked onto the end.

The other clause worth noting: healing runs only at a user-turn boundary. Appending a tool or assistant message never triggers it, even when the buffer already holds an unanswered call.

Example

A call goes unanswered, and two more turns are appended before anyone appends a user message:

The buffer before the healing user turnunhealed
[
  { "role": "assistant", "content": "", "metadata": { "tool_calls": [{ "tool_call_id": "c1", "name": "search" }] } },
  { "role": "user", "content": "anything?", "metadata": {} },
  { "role": "assistant", "content": "sorry, lost it", "metadata": {} }
]
What appending a user turn produceshealed
[
  { "role": "assistant", "content": "", "metadata": { "tool_calls": [{ "tool_call_id": "c1", "name": "search" }] } },
  { "role": "tool", "content": "Tool call was interrupted; no result.", "metadata": { "tool_call_id": "c1", "healed": true } },
  { "role": "user", "content": "anything?", "metadata": {} },
  { "role": "assistant", "content": "sorry, lost it", "metadata": {} },
  { "role": "user", "content": "try again", "metadata": {} }
]

The synthetic result lands at index 1, next to the call it answers, not at the end where the new user turn was appended. Appending a tool message against the same dangling call, instead of a user message, leaves the call unanswered — healing never runs.

Rule identifiers are permanent and are never renumbered. Each implementation publishes its own standing against these rules; this specification does not.spec 1.0-draft · MEM-3 · changed in spec 1.0