--- id: MEM-3 family: GR-MEM level: extended profiles: [storage-api, runtime] posture: normative-target added: "1.0" changed: "1.0" source: https://flowdrop.io/spec/rules/gr-mem/mem-3 specification: FlowDrop Workflow Specification 1.0-draft licence: CC BY 4.0 --- # MEM-3 — A user turn heals tool calls that were never answered *GR-MEM (Part I) · level: extended · profiles: storage-api, runtime · added in 1.0* 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: ```json title="The buffer before the healing user turn" verdict="unhealed" [ { "role": "assistant", "content": "", "metadata": { "tool_calls": [{ "tool_call_id": "c1", "name": "search" }] } }, { "role": "user", "content": "anything?", "metadata": {} }, { "role": "assistant", "content": "sorry, lost it", "metadata": {} } ] ``` ```json title="What appending a user turn produces" verdict="healed" [ { "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. ## Related rules - Names: MEM-10, MEM-11 - Referenced by: MEM-5, MEM-10, MEM-11, MEM-16 --- Rule identifiers are permanent and are never renumbered. This specification carries no implementation status: each implementation publishes its own standing against these rules. Licensed CC BY 4.0.