First-class bounded batch fan-out for heterogeneous subagents
What variant of Codex are you using?
Codex App primarily. The orchestration primitive would ideally be shared with CLI/tool-backed Codex surfaces.
What feature would you like to see?
## Summary
Codex now has most of the pieces needed for heterogeneous multi-agent work: subagents, per-agent model configuration, async orchestration, and configurable concurrency.
The missing primitive is bounded batch fan-out.
A coordinator should be able to submit a large set of independent tasks in one operation, have Codex schedule them across cheaper/faster worker agents with explicit limits, then collect bounded results for synthesis.
The motivating shape is:
``text``
strong coordinator
|
decompose / schedule
|
+-------------+-------------+
| | |
fast worker fast worker fast worker
| | |
+-------------+-------------+
|
coordinator
reduce
For example: a Sol coordinator analyzing a large multi-repository system could dispatch 80 independent repository audits to Spark workers, run at most 32 concurrently, then synthesize their findings.
This should not require the coordinator to make 80 individual spawn_agent calls and manually manage 80 wait_agent lifecycles.
## Problem
spawn_agent is a good primitive for small, adaptive teams where the parent decides what to delegate one task at a time.
It is a poor primitive for workloads where the complete work set is already known:
- inspect every package in a monorepo;
- review 100 independent files/modules;
- run the same migration audit across many services;
- investigate one question independently against many repositories;
- generate bounded implementation/review tasks from an already-computed plan.
In these cases, repeated parent-side spawn/wait decisions add orchestration turns and context churn without adding useful reasoning.
Simply increasing agents.max_threads does not solve that. Batch size and concurrency are different things.
I may want to submit 200 tasks while allowing only 16 or 32 workers to execute concurrently.
## Proposed shape
The exact API is not important, but conceptually something like:
```text
batch = spawn_batch(
worker_profile = "fast_worker",
tasks = [...],
concurrency = 32,
max_tokens_per_worker = 20000,
total_budget = ...,
max_result_size = ...,
recursive_spawn = false,
)
results = wait_batch(batch)
```
tasks could contain independent prompts plus optional scoped context/path information.
spawn_batch should create one schedulable batch, not require the model to emit N individual spawn calls.
## Important semantics
### Batch size != concurrency
A batch may contain 200 tasks while the scheduler runs only 16 concurrently.
Excess work should queue rather than requiring the parent model to manage free slots.
### Heterogeneous workers
The worker model/profile should be selectable independently from the coordinator.
A common configuration would be:
``text``
coordinator: expensive/high-reasoning model
workers: fast/cheap model
reviewer: optional stronger model
This is where models such as Spark are particularly useful: not necessarily as the coordinator, but as high-throughput bounded workers.
### Explicit budgets
Large fan-out needs guardrails, not just a higher thread limit.
Useful controls would include:
- maximum batch size;
- maximum active workers;
- model/profile allowlist;
- per-worker token or credit budget;
- aggregate batch budget;
- timeout;
- sandbox/tool permissions;
- maximum result size;
- maximum subagent depth;
- queue vs fail behavior when capacity is exhausted.
A parent should be able to say “up to 32 Spark workers, 20k tokens each, read-only, depth 1” rather than implicitly creating an uncontrolled swarm.
### Bounded result aggregation
The coordinator usually does not need the complete transcript of every worker.
By default, batch completion should return something closer to:
``text``
task id
status
final worker result
usage
optional artifact references
rather than injecting every worker's full interaction history into the parent context.
This matters once fan-out becomes large.
### Partial failure
wait_batch should preserve successful results when some tasks fail, time out, or are cancelled.
It should be possible to retry selected task IDs without rerunning the whole batch.
### Cancellation and observability
A batch should have an ID and expose:
``text``
queued
running
succeeded
failed
cancelled
token/credit usage
The user should be able to cancel the batch or individual tasks.
## Why make this a first-class primitive?
A sufficiently capable coordinator can approximate this today with repeated spawn_agent calls.
But that pushes deterministic scheduler work back into model reasoning:
``text``
find free slot
spawn worker
remember worker id
wait
inspect completion
spawn next
repeat
None of those steps requires a frontier reasoning model.
A runtime-level batch primitive could handle queueing, limits, cancellation and result collection deterministically while leaving the coordinator responsible for the parts that actually require reasoning:
``text``
decomposition
task specification
dependency identification
final synthesis
This also makes very large fan-out safer. The runtime can enforce hard budgets even if the coordinator attempts to over-spawn.
## Concrete example
Consider an architecture audit over 80 repositories.
The coordinator first determines that each repository can be inspected independently.
Desired execution:
``text``
Sol:
build 80 bounded audit tasks
|
v
spawn_batch:
worker = Spark
tasks = 80
concurrency = 24
read-only
depth = 1
|
v
scheduler:
24 running
56 queued
|
v
80 bounded reports
|
v
Sol:
cross-repo synthesis
contradictions
architecture decision
The expensive coordinator is used where its reasoning matters. The fast workers provide breadth.
## Acceptance criteria
- A coordinator can submit N independent subagent tasks in one tool/runtime operation.
- Batch size may exceed active concurrency.
- Queued work starts automatically as worker capacity becomes available.
- Worker model/profile may differ from the coordinator.
- Per-worker and aggregate budgets are enforceable by the runtime.
- Worker recursion/depth can be disabled or bounded.
- Batch status, usage, cancellation and partial failures are observable.
- Results can be collected without importing full worker transcripts into parent context by default.
- Individual failed tasks can be retried without rerunning successful tasks.
- Existing
spawn_agentremains available for adaptive one-off delegation.
This is intended to complement, not replace, normal subagents and agent teams.
Additional information
## Related requests
I searched the existing Codex issues before filing this.
- https://github.com/openai/codex/issues/14039 — per-subagent model/provider/profile selection. This covers which model a worker uses; it does not provide batch dispatch/scheduling.
- https://github.com/openai/codex/issues/11701 — subagent configuration and orchestration, including strong planner + Spark/execution workers. This establishes the heterogeneous-agent use case; it does not provide a first-class large fan-out primitive.
- https://github.com/openai/codex/issues/16183 — higher/configurable subagent spawning limits and queueing. Raising the ceiling helps, but still leaves N individual spawn/wait operations to the coordinator.
- https://github.com/openai/codex/issues/33437 — project-scoped agent/model concurrency quotas. This would compose very well with batch fan-out: the policy defines what may run, while the batch primitive defines how a known work set is submitted and collected.
- https://github.com/openai/codex/issues/12047 — teams, hierarchical orchestration and async agent messaging. Teams are useful for persistent/adaptive collaboration; this request is specifically for data-parallel bounded work with a known task set.
Two current reports also show why explicit runtime scheduling and budgets matter:
- https://github.com/openai/codex/issues/35177 — uncontrolled Sol swarms can consume very large amounts of usage.
- https://github.com/openai/codex/issues/35050 — batching independent tool work substantially reduced orchestration/model cycles in measured workloads.
I am not asking for an unlimited subagent count.
I am asking for a bounded map/fan-out primitive where the runtime, rather than the coordinator model, handles queueing, concurrency, budgets and result collection.