Last reviewed: 2026-08-29

Direct answer

Do not send every tool call in a model-generated batch straight into one concurrent executor. Put a scheduler between response parsing and tool execution. It should validate every call, classify its effects from trusted policy, identify calls that touch the same resource, and choose parallel or serial execution before anything can change state.

Run only verified, independent read operations concurrently. Serialize calls that write files, update shared state, publish artifacts, deploy changes, or otherwise require ordering. Hold destructive or unknown calls for approval. If one serial call fails, do not silently drop the calls that were skipped: create a terminal error or not-executed result for every original call ID.

The CometAPI Chat Completions documentation says a tool-using response contains a message.tool_calls array and that each returned tool result must use the matching tool_call_id. It also warns that request parameters and response fields can vary between providers. The Anthropic parallel tool-use guide makes the execution decision explicit: independent reads are generally suitable for parallel execution, while side effects, shared state, and ordering requirements favor sequential execution.

That yields a practical rule: treat a batch as a proposal, not an execution plan. Your application—not the model—owns effect classification, conflict detection, approval, ordering, and complete result reporting.

Who this is for

This guide is for developers and platform operators running tool-using coding agents through CometAPI, especially when one model turn can request several repository, CI, issue-tracker, database, or publishing operations.

It is most useful when your agent can switch among model families, call MCP tools, or operate against shared workspaces. It assumes you already have a tool registry and an executor. The missing layer is a policy-aware scheduler that can distinguish harmless concurrency from an unsafe race.

The same design also helps teams that currently force all tools to run serially. A trusted classifier lets independent reads run together without giving state-changing calls the same freedom.

Key takeaways

  • A multi-call response does not prove that all calls are independent.
  • Tool names alone are weak classifiers; include effect class, validated resource scope, ordering constraints, and approval policy.
  • Parallelize only verified read-only calls whose resource scopes do not conflict.
  • Keep mutations serial by default, even when they are idempotent. Idempotency limits repeat effects; it does not remove shared-state races.
  • Preserve every original call ID and emit a terminal result for executed, failed, denied, and skipped calls.
  • Treat MCP annotations as hints unless they came from a trusted server and agree with local policy.
  • Keep request retry policy separate from tool scheduling. Use dedicated idempotency controls for retried calls to prevent a transport retry from replaying a completed mutation.

Sources checked

  • CometAPI Chat Completions documentation documents the multi-provider compatibility interface, tool_calls, matching tool_call_id values, and provider-specific variation.
  • OpenAI function-calling guide explains that a model may request multiple functions in one turn and that parallel_tool_calls can constrain a response to zero or one tool call.
  • Anthropic parallel tool-use guide describes concurrent, sequential, and mixed execution, including complete results for skipped calls.
  • MCP Tools specification defines tool behavior annotations and requires clients to treat annotations as untrusted unless they come from trusted servers.

Contract details to verify

Verify the contract for each model and endpoint in staging. CometAPI routes Chat Completions across model providers, but its documentation cautions that provider support can differ. It also directs some model families to the Responses endpoint. This article uses the Chat Completions message.tool_calls and tool_call_id contract; do not copy those field paths into a different endpoint without checking that endpoint’s response schema.

1. Verify how many calls the model can return. The OpenAI guide documents parallel_tool_calls: false as a way to limit a turn to zero or one function call. Anthropic’s native interface places disable_parallel_tool_use inside tool_choice. Those controls are not interchangeable. Before relying on either through a compatibility layer, run a capability fixture against the exact model and endpoint. A local serial scheduler is still necessary because limiting model output and safely executing returned work are separate concerns.

Use tool-call contract tests across models to record the observed call count, call-ID field, argument shape, finish reason, and result-message shape for every enabled model.

2. Make local policy authoritative. The MCP specification provides readOnlyHint, destructiveHint, and idempotentHint, but calls them hints and warns against trusting annotations from an untrusted server. Resolve tool identity to a trusted registry entry before using those fields. Reject unknown tools, duplicate call IDs, malformed arguments, or a claimed read operation that conflicts with the registry.

A conservative policy matrix looks like this:

Effect classRequired evidenceDefault execution
Verified read-onlyTrusted registry says no environment change; validated scopes are disjointParallel within a bounded worker pool
Idempotent mutationTrusted registry says repeats add no further effectSerial unless a stronger transaction policy proves independence
Destructive or shared-state mutationWrites, deletion, publishing, deployment, or overlapping scopeSerial; require approval where policy says so
Unknown or untrustedNo trusted classification, invalid arguments, or conflicting metadataDo not execute automatically

3. Detect conflicts using resource scope. Two different tool names can still write the same file, branch, deployment, record, or artifact. Conversely, two calls to the same read tool may be independent when they target disjoint resources. Derive a normalized resource scope from validated arguments, then build conflict groups. Do not put raw argument values into general logs.

If a call’s scope cannot be determined, classify it as unknown rather than assuming independence. Preserve the model’s returned order inside a conflict group as a deterministic local convention, but do not pretend that order proves a dependency. If a safe order is ambiguous, stop the group and request approval or another model turn.

4. Use a concrete happy-path workflow. Suppose one turn requests two read operations on separate repository files plus one write to a separate report artifact. The operator workflow is:

  1. Capture the complete tool-call batch before execution.
  2. Validate tool names, unique call IDs, schemas, and allowed resource scopes.
  3. Resolve each tool against the trusted registry and calculate conflicts.
  4. Run the two disjoint reads concurrently under a bounded concurrency limit.
  5. Wait for the read group to reach terminal states.
  6. Run the report write once in the serial mutation lane.
  7. Assemble one result for every original call ID in stable batch order.
  8. Send the results back using the endpoint’s required tool-result envelope.

The scheduler can be summarized without binding it to one SDK:

validate batch -> classify calls -> build conflict groups
parallel(verified independent reads)
serial(side effects and shared-resource groups)
emit(one terminal result for every original call ID)

The happy path ends only when every call is represented by a success, error, denial, or not-executed result. A successful first call does not justify omitting the rest of the batch.

5. Define the error path before production. Consider a serial group containing update_manifest followed by publish_artifact, where local policy says publishing depends on a valid manifest update. If the update fails validation, the scheduler must not run the publish call. It should record the first call as an error and the second as not executed because its dependency failed.

A normalized internal result can look like this; translate it into the selected endpoint’s required wire format afterward:

[
  {
    "tool_call_id": "call_1",
    "status": "error",
    "error_class": "validation_failed"
  },
  {
    "tool_call_id": "call_2",
    "status": "not_executed",
    "error_class": "dependency_failed"
  }
]

Return both outcomes. Anthropic’s guide specifically says that when an earlier sequential call fails, a skipped call should still receive an error result explaining that it was not executed. CometAPI’s Chat Completions contract requires tool results to match their original call IDs. Together, those rules prevent the model from waiting for a result that your executor silently discarded.

6. Log decisions without logging payloads. Operators need enough information to reconstruct why a call ran, waited, failed, or was skipped. Keep these fields where available:

  • timestamp, run ID, turn ID, and batch ID;
  • model identifier and endpoint family;
  • tool call ID, tool name, and batch position;
  • trusted effect class and classification source;
  • sanitized resource-scope category, not the raw target;
  • chosen schedule mode and conflict-group ID;
  • approval decision, terminal status, duration, and error class;
  • a redacted argument digest only when your retention policy permits it.

A sanitized event might be:

{
  "timestamp": "2026-08-29T00:00:00Z",
  "run_id": "run_42",
  "turn_id": "turn_7",
  "batch_id": "batch_3",
  "tool_call_id": "call_2",
  "tool_name": "publish_artifact",
  "effect_class": "destructive",
  "classification_source": "trusted_registry",
  "resource_scope": "artifact_release",
  "schedule_mode": "serial",
  "batch_position": 2,
  "decision": "skip",
  "status": "not_executed",
  "duration_ms": 0,
  "error_class": "dependency_failed",
  "arguments_digest": "[REDACTED]"
}

Do not place raw prompts, tool arguments, file contents, command output, request headers, or full tool results in a general scheduling log. Store sensitive operational evidence only in its approved system, with separate access and retention controls.

Failure modes

Blind concurrent execution. Passing every returned call to an unfiltered concurrent runner can make two writes race, publish an artifact before validation completes, or let a deletion overlap a read. Concurrency should be an explicit scheduler decision, not a property inherited from the response shape.

Trusting a tool’s self-description. A remote server can label a state-changing operation as read-only. MCP annotations are useful inputs only after the server is trusted and the annotation agrees with the local registry and validated arguments.

Classifying by tool name alone. A generic shell, database, or file tool may read in one call and mutate in another. The scheduler needs the validated operation and resource scope, not just a friendly name.

Assuming one provider’s switch works everywhere. OpenAI and Anthropic expose different controls for limiting parallel calls, while CometAPI warns that fields vary by provider. Test the exact model and endpoint, and retain a local serial fallback.

Dropping skipped calls. If a batch has three calls and the second fails, returning only two results leaves an incomplete conversation state. Create an explicit terminal record for the skipped third call and preserve its original ID.

Retrying the entire batch after an ambiguous timeout. Reads may be safe to repeat, but a completed mutation can be duplicated when its response was lost. Reconcile the previous execution state before retrying and apply idempotency controls separately from concurrency policy.

Leaking payloads through observability. Raw arguments can contain source code, issue text, paths, or environment-derived data. Log the scheduling decision and sanitized scope rather than the payload.

Executing an unknown call. A new model or changed tool registry may produce an unrecognized name, duplicate ID, or invalid argument shape. Fail closed and return a structured error; do not guess which local function was intended.

FAQ

Should I disable parallel tool calls completely?

Not necessarily. Disabling them can simplify an early implementation, and the OpenAI guide documents a control that limits a turn to at most one function call. However, independent reads can benefit from bounded concurrency. A robust design supports both: restrict model-side batching where the endpoint supports it, then enforce local scheduling for every returned batch.

Are MCP annotations enough to decide what runs in parallel?

No. They are hints. Use them only after authenticating the server through your normal trust process and reconciling them with a local policy entry. Unknown, conflicting, or untrusted classifications belong in the blocked lane.

What if two calls target the same file or deployment?

Put them in the same conflict group and serialize them. If both mutate state and the correct order is not established by local workflow policy, do not infer intent from proximity alone. Return a not-executed result or require human approval. For sensitive tools, add human approval gates for coding agents .

Must tool results be returned in the original order?

The call ID is the authoritative correlation field in the CometAPI Chat Completions contract. Keeping results in stable original order is still a useful local convention for review and debugging. Provider-native formatting may impose additional grouping rules, so verify the selected endpoint.

What should happen after one serial call fails?

Stop dependent work, mark unaffected independent work according to policy, and create a terminal result for every call. Never represent an unexecuted mutation as a success. Do not automatically replay a failed or ambiguous mutation without reconciliation.

Is an idempotent write safe to run beside another write?

Not by default. Idempotency describes the effect of repeating the same operation; it does not prove that two different operations commute or target disjoint state. Keep mutations serial unless a tested transaction or conflict policy proves otherwise.

Reader next step

Inventory every tool your coding agent can call. For each one, record a trusted effect class, resource-scope extractor, approval rule, timeout, and retry policy. Then add a dry-run scheduler that prints the proposed parallel and serial groups without executing them.

Test at least two fixtures against every enabled model: a happy path with independent reads plus one isolated mutation, and an error path where the first mutation fails and later work is explicitly skipped. Confirm that every original call ID receives a terminal result and that the scheduling log contains no raw payloads.

Once that policy and test harness are ready, Start with CometAPI and verify the exact endpoint contract before enabling state-changing tools.