FlowDrop Workflow Specification 1.0-draft

A tool-calling pass reports whether it did any work

An all-repeat pass returns a full list of tool messages and a successful outcome while having invoked nothing, so an agent loop gated on "messages non-empty" re-enters forever. This is the port that answers the question directly.

The rule

Normative: this is the rule
  1. A node that invokes tools reports executed_any, a boolean, and executed, the call identifiers behind it, the complement of the skipped list.
  2. executed_any is true when at least one call was handled for the first time in this run, which includes a call naming a tool that is not wired: nothing was invoked, but the recoverable "not an available tool" result is new and the model must re-plan against it.
  3. An empty batch reports false.
  4. The two lists partition only the calls that reach the guard: a call with no identifier always runs and appears in neither while setting executed_any, and a malformed call (one that is not a map, or whose name is blank or not a scalar) is dropped before the guard, appears in neither, and leaves executed_any false.
  5. executed_any is exposed by default; executed is not.

What it means

A repeat batch can look, from every other port, exactly like work getting done: it still returns a full list of tool messages (each repeat re-emits its stored message) and a successful outcome, having invoked nothing new. executed_any is the one port that answers the question a loop actually needs answered — whether anything happened this pass — and it is the only one that says no.

A name that is not wired to any tool still sets executed_any: nothing ran, but the recoverable failure the model gets back is a new result it has not seen, and that is enough for a loop to re-enter safely. A call with no identifier always runs and always sets executed_any, but is named in neither executed nor skipped — it cannot be recorded either way. Only a malformed call is excluded from all of it: dropped before anything is checked, present in neither list, and leaving executed_any false.

Example

A batch resending a call that already ran, with nothing new in it:

A batch of nothing but an already-executed callresent
{ "tool_calls": [{ "name": "do_thing", "args": [], "tool_call_id": "call_a" }] }
The executed_any output of the repeat passidle
false

The same repeat alongside one genuinely new call:

The same repeat, plus one new callsent
{
  "tool_calls": [
    { "name": "do_thing", "args": [], "tool_call_id": "call_a" },
    { "name": "other_thing", "args": [], "tool_call_id": "call_b" }
  ]
}
The executed output of the mixed passactive
["call_b"]

skipped is ["call_a"] in both passes, ok is true in both and tool_messages is non-empty in both; only executed_any and executed tell them apart.

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-10 · changed in spec 1.0