FlowDrop Workflow Specification 1.0-draft

A tool call executes at most once per run

A tool call has side effects. One node can be reached twice in a run (by a fan-in, or by a re-ask after a human approved a gated call), and the same batch of calls arrives with it.

The rule

Normative: this is the rule
  1. A run records each tool call it has executed, keyed by the call identifier, and does not invoke a call whose identifier it has already recorded.
  2. The repeat is reported on a skipped output, and the stored result of its first execution (the tool-role message and whether it succeeded) is re-emitted among the run's tool messages and results and counted in the batch's outcome.
  3. Re-emitting rather than dropping is required: a batch that paused midway never delivered the already-executed call's message downstream, so dropping the repeat would leave a declared call identifier unanswered and invite the model to retry it under a fresh, unguardable one.
  4. A call is recorded only once it has returned a result, success or a recoverable error, and never before it is invoked, so a call that interrupts the node for human approval is re-asked and executed once approved, rather than looking already-executed and being silently dropped.
  5. A call that carries no identifier cannot be guarded and always runs.

What it means

A call is recorded as executed only once it has returned — succeeded, or failed in a way the run can recover from — never at the moment it is invoked. A call that pauses the node to ask for approval never reaches that point, so re-asking it after approval invokes it for real rather than finding it already recorded and being dropped.

The repeat itself is never simply discarded, either. Its first result is re-emitted among the batch's tool messages, under the same identifier, because a batch that paused partway through never delivered that message downstream the first time — dropping the repeat would leave a declared call unanswered and push the model to retry it under a fresh identifier nobody can guard.

A call with no identifier cannot be checked against anything already recorded, so it always runs, on every pass.

Example

One call already executed, and the same batch sent again:

One declared call, sent a second timeskipped
{ "tool_calls": [{ "name": "do_thing", "args": [], "tool_call_id": "call_a" }] }
The skipped output of the repeat runrecorded
["call_a"]

The tool ran once across both runs; the repeat's tool message is the stored first result, under call_a, and the run's outcome is the stored success.

A batch with one call that already ran and one that only paused for approval, resent once approval is granted:

A batch resent after the gated call is approvedresumed
{
  "tool_calls": [
    { "name": "do_thing", "args": [], "tool_call_id": "call_a" },
    { "name": "gated_thing", "args": [], "tool_call_id": "call_gate" }
  ]
}
The executed output of the resumed runmixed
["call_gate"]

call_a was recorded on the first pass and is skipped again; call_gate only paused the node last time, so it was never recorded and runs now.

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 · RT-TOOL-9 · changed in spec 1.0