--- id: MEM-5 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-5 specification: FlowDrop Workflow Specification 1.0-draft licence: CC BY 4.0 --- # MEM-5 — Normalizing a conversation makes it sendable to a provider *GR-MEM (Part I) · level: extended · profiles: storage-api, runtime · added in 1.0* Stored history and provider history are not the same shape. Normalization is the one place that reconciles them, and it reports what it had to drop. ## The rule > **Normative.** This is the rule. > > 1. A conversation-normalization node takes one message list and emits a normalized message list plus the number of entries dropped, applying four rules in order. > > 2. (1) Each entry is coerced to the flat message shape a reasoner consumes; a non-list entry or one without a role is dropped, and content that is not a scalar is serialized to text rather than blanked, falling back to the empty string if it cannot be serialized. > > 3. (2) `system`-role messages move to the front, with the system group and the remaining group each keeping their own relative order; a system message is never dropped. > > 4. (3) Tool pairing is repaired by the same procedure a reasoner applies mid-loop: an unanswered call gets a synthetic interrupted result adjacent to it, an orphan or duplicate result is dropped, and a tool call id declared a second time is dropped from the later turn, so the first declaration owns the pairing. > > 5. (4) Any message still leading the list with role `tool` is stripped, and because stripping it can leave a call unanswered, rule (3) is applied again; repair is idempotent. > > 6. The reported drop count comes from the repair pass itself, never from a separate reimplementation of its rules. ## What it means The reported drop count has to come from the repair pass itself, never from a second count kept alongside it — the two can disagree on a case a reader would not think to try. A tool call id can be present in the buffer and still be unusable: `"0"` is a legal id string but a falsy value, and once the call carrying it is treated as unusable, the turn that declared it, the id itself, and the result answering it all have to go — one unusable id costing three drops, not one, from a buffer of two entries. The other clause worth reading closely is what happens at the front of the list. A tool result placed before the assistant turn that declares its id is not caught by id-matching alone — the declaring call exists somewhere in the list, just not before its result — so a separate leading-orphan guard strips it. Stripping it un-answers the assistant turn it was answering, so tool pairing is applied again; the result is provider-sendable, not merely orphan-free. ## Example A call id that is technically declared but falsy: ```json title="A call id and its result, both spelled \"0\"" verdict="unusable" [ { "role": "assistant", "content": "", "tool_calls": [{ "tool_call_id": "0", "name": "search" }] }, { "role": "tool", "content": "result", "tool_call_id": "0" } ] ``` ```json title="What normalization resolves it to" verdict="dropped" [] ``` A tool result appearing before the call that declares its id: ```json title="A result placed ahead of its declaring call" verdict="misordered" [ { "role": "tool", "content": "out of order", "tool_call_id": "c1" }, { "role": "assistant", "content": "", "tool_calls": [{ "tool_call_id": "c1", "name": "search" }] } ] ``` ```json title="What normalization resolves it to" verdict="repaired" [ { "role": "assistant", "content": "", "tool_calls": [{ "tool_call_id": "c1", "name": "search" }] }, { "role": "tool", "content": "Tool call was interrupted; no result.", "tool_call_id": "c1" } ] ``` ## Related rules - Names: MEM-3, MEM-16 - Referenced by: 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.