The task read like a simple mapping: reconcile a list of customer records against the current system and update each one. The agent produced a clean for-each loop: fetch the record, compare, patch, move to the next. The unit test used three fixture rows, ran in milliseconds, and reported green. Staging used four hundred records, and the job became four hundred sequential requests that throttled halfway through.

The API already had a bulk update endpoint that accepted an array in one call and returned a per-item result envelope. The agent never looked for it. It reasoned from the single-item sample call it had in context and treated looping over an array as the universal shape.

Why the loop felt like the obvious answer

An agent that sees one GET and one PATCH for a single record has no signal that a batch path exists. A loop over an array is the most general program it can write from that evidence, and it reads as correct: each element gets the same treatment, errors can be caught per row, and the code is easy to read back. None of that checks whether the server already solved the N-requests problem.

Bulk endpoints differ in ways a per-item loop does not model: the request body is an array rather than a single object, the response carries a partial-failure list instead of one status, and large jobs often run as an accepted async task rather than completing inline. A loop hides all of that behind repeated single calls.

DimensionPer-item loopExisting bulk endpoint
Requests for N recordsN calls1 call
Failure modelOne error stops or skips a rowPer-item success/failure envelope
Rate limit exposureN chances to be throttledOne request under the limit
Partial completionHanded by the callerReported by the server
ConsistencyRows drift between callsServer applies the batch atomically or per row

The three-row fixture makes both shapes indistinguishable in a test. Speed, correctness, and green status all hold at that size; the difference only appears when N grows and the throttle or the connection pool starts counting.

Check for a batch path before writing the loop

Before an agent commits to a loop over a resource, the cheap gate is a question: does this API expose a bulk or batch operation for the same write? That requires reading the operation list for the resource, not replaying the one sample call.

Practical checks that catch this early:

  • Estimate N from the real input size, not the fixture. A loop over three rows and a loop over four hundred rows are different programs.
  • Look for operations named bulk, batch, update-many, or a body that accepts an array of records. Do not assume the single-item path is the only write.
  • Read the bulk response shape. A batch endpoint usually returns which items succeeded, which failed, and why; design the caller around that envelope instead of inventing your own retry per row.
  • Confirm idempotency on the bulk path. Reconciliation jobs get re-run, and a batch that applies twice needs a key or a stable record reference the same way a single call does.
  • Watch for async acceptance. A bulk job may return 202 with a job id rather than the final result; the caller then polls, which the per-item loop never anticipated.

Keeping the operation list readable locally makes this scan possible without reconstructing the API from one captured call. In Powerduck the spec stays the source of truth on disk, so a bulk operation shows up in the operation list rather than having to be rediscovered from a sample exchange.

Prove the call count in the test

A test that asserts the updated value on three rows will not expose N requests. Add a recorder that counts outbound calls for a representative batch size and assert the expected shape: one bulk request carrying the array, or an explicit decision that the per-item loop is justified because no batch path exists.

Also assert the partial-failure branch. A bulk response that returns twenty successes and five failures should leave the caller able to report and retry the five, not treat the whole call as a generic error. That branch rarely appears when the agent wrote a loop it assumed would always succeed row by row.

The honest fallback is still a loop when no batch endpoint exists, but it should be a deliberate choice after checking, not the default assumption. The expensive version is the loop written because the agent never looked past the single sample call.

Where does your reconciliation code fall today: one deliberate per-item path, or a loop written before anyone checked whether the resource already offered a batch?