SARAVANA KUMAR · TECHNICAL KNOWLEDGE · Intermediate
Parallel tool results attach to the wrong request
Correlate each result with its tool-call identifier instead of completion order.
Illustrative learning scenario; not a claimed client engagement. A fictional assistant requests two documentation lookups in parallel. The second finishes first; a collector that pushes payloads as they arrive labels it as the first request. The final explanation can attribute the wrong evidence even though both lookups succeeded.
Symptoms
Correlate each result with its tool-call identifier instead of completion order.
Diagnosis
- Log synthetic call identifiers, status and latency separately from payload contents. Compare request order with completion order.
- Check whether the collector joins by array position after independently appending completed results.
- Test reversed completion and a single failure. Preserve one terminal outcome for each requested call identifier.
Runnable minimal example
Node.js standalone JavaScript using mock timers. Save as lab.js and run node lab.js; no model, network or persistent storage.
const calls = [
{id: 'call-a', value: 'Oracle guide', delay: 20},
{id: 'call-b', value: 'Snowflake guide', delay: 0}
];
function lookup(call) {
return new Promise(resolve => setTimeout(
() => resolve(call.value), call.delay));
}
async function main() {
const results = await Promise.all(calls.map(async call => ({
tool_call_id: call.id,
status: 'ok',
content: await lookup(call)
})));
for (const result of results) {
console.log(`${result.tool_call_id}: ${result.content}`);
}
}
main().catch(() => { console.error('lab failed'); process.exitCode = 1; });Expected output — verified locally
call-a: Oracle guide
call-b: Snowflake guideFix and boundaries
Capture the trusted call identifier in each task before awaiting it, then return an envelope containing that identifier and outcome. Promise.all retains input order, but the explicit identifier still supports later transport and audit. For independent fallible tools, catch failures within each task or use Promise.allSettled and map each settlement back to its originating call. Never silently omit a failed call.
Why it works
Each async closure retains its own call object. The second lookup can finish first without changing either envelope. Promise.all waits for all successful tasks and returns their results in input order. It fails fast on rejection and does not cancel the other tasks, so a production orchestrator needs explicit timeout, cancellation and failure handling. Use the actual tool-call identifier required by your provider protocol, not the tool name when it can repeat.
Prevention
Test reverse completion, repeated tool names with distinct identifiers, timeout and partial failure. Reject duplicate or unknown result identifiers. Recheck authorization for each call; correct result correlation does not grant permission to run a tool.
Practice exercise
Make call-b reject. First observe Promise.all rejecting; then return a status:error envelope for that identifier while retaining call-a success. Ensure the user-facing response marks the missing evidence rather than fabricating it.