Passive awareness for long-horizon multi-agent collaboration — four coding agents that keep working while they listen
English | 简体中文 | Español | 日本語 | 한국어 | العربية
Give four coding agents a shared radio channel. They divide the work, negotiate the plan, and keep broadcasting discoveries while they work — because listening runs as a background task instead of stealing a turn.
This repository contains the code and data to reproduce the experiments of the paper AgentRadio: Passive Awareness for Long-Horizon Multi-Agent Collaboration (arXiv:2607.28430).
☁️ Prefer the product? → Coral Code
Coral Code is the product version of AgentRadio. New users get $30 in free credit.
| Configuration | What it adds | Task acc. (Opus 4.6) | Task acc. (DeepSeek V4 Pro) |
|---|---|---|---|
| B0 single agent | — | 32.3 % | 29.0 % |
| B1 best of six single runs | 6× budget, no coordination | 37.9 % | 31.4 % |
| L1 four agents + division | division of labor | 39.5 % | 31.4 % |
| L2 + negotiation | joint planning + cross-review (blocking receive) | 51.6 % | 39.5 % |
| L3 + passive awareness (AgentRadio) | background wait_for_mention |
62.1 % | 50.8 % |
The step from L2 to L3 changes only the communication mode. It wins 15 tasks and loses 2 with Opus 4.6 (exact McNemar test, p = 0.0023) and wins 17 while losing 3 with DeepSeek (p = 0.0026). Four Opus 4.6 agents under AgentRadio (62.1 %) surpass the strongest single-agent leaderboard entry, Claude Code with the newer Opus 4.8 (57.2 %).
→ See the full results · paper · run it yourself
- 2026-08 — AgentRadio was covered by VentureBeat: "Four AI agents coordinating in real time outperformed Claude Opus 4.8 on enterprise coding tasks". 📰
- 2026-07 — The AgentRadio paper is released on arXiv. 🎉
- 2026-07 — Code, adapters, and the full 124-task SWE-Atlas QnA setup are open-sourced. 🚀
- Communication stops costing work —
wait_for_mentionruns as a background task of the harness, so a teammate's message surfaces at the next step boundary instead of consuming a turn. Agents no longer choose between working and listening. - Mid-execution correction — in blocking systems a discovery cannot reach a teammate until the next phase boundary. Under passive awareness it lands immediately, and the teammate folds it into the task already in flight.
- No harness modification — the harness only has to run a shell command in the background, which mainstream coding harnesses already do. AgentRadio ships as a standalone message server plus three thin shell scripts.
- No extra LLM calls — the watcher is an ordinary OS process, not an agent step. The only new tokens an agent pays for are the messages that actually surface.
- Model-agnostic — the same protocol, prompts, and startup scripts run on Claude Opus 4.6 and on DeepSeek-V4-Pro through a LiteLLM translation proxy.
- A clean ablation ladder — B0 → L1 → L2 → L3 isolates division of labor, negotiation, and passive awareness one layer at a time, on identical harness settings.
AgentRadio exposes three operations to every agent:
| Primitive | Behavior |
|---|---|
create_thread(name, participants) |
Opens a named conversation on the message server and returns its identifier. |
send_message(thread, content, mentions) |
Appends a message to a thread and returns immediately, whether or not anyone is listening. May @-mention specific agents. |
wait_for_mention(timeout) |
Blocks until a message mentioning the caller arrives, then returns it together with a full snapshot of every thread — so the caller never needs a second read to reconstruct context. |
The layer takes no position on when an agent listens. Where wait_for_mention runs is the
single degree of freedom separating the two communication modes:
- Foreground → blocking receive. The agent stops working in order to listen. Every message heard costs a step of work. This is the L2 baseline.
- Background task → passive awareness. The agent keeps working and any mention surfaces at the next step boundary, with no step spent listening. This is L3, full AgentRadio.
Everything else — the primitives, the threads, the protocol — stays fixed. That single-bit difference is what the experiments isolate.
Four agents run a fixed protocol of division of labor and negotiation. Agent-1 additionally serves as the assembler: it opens the planning, worklog, and final-answer threads, and gates every transition — a phase ends only after it collects an explicit approval from every agent.
- P1 · Explore — every agent starts its background watcher, independently explores the repository, and drafts the sub-questions it sees. Nothing is sent.
- P2 · Divide — the assembler opens a planning thread. Agents pool their findings, negotiate a partition of the sub-questions, and revise it until every agent approves.
- P3 · Execute — each agent works its own sub-questions. A discovery triggers a worklog post the moment it is made: a finding that bears on a teammate, a contradiction with the agreed plan, an obstacle, or an abandoned dead end.
- P4 · Review — each agent broadcasts its findings with evidence in its own results thread. Reviewers post factual conflicts, thin evidence, and unmentioned observations, and can send a sub-question back to P3.
- P5 · Submit — the assembler composes the final answer from the approved results, broadcasts the draft for a last round of approvals, and submits.
Under blocking receive the same five phases run unchanged, but the live sharing of P3 disappears: hearing a message costs a foreground wait, so agents fall silent while they work and a discovery cannot reach a teammate before P4.
data/qa/ 124 SWE-Atlas QnA tasks (harbor dataset scale-ai/swe-atlas-qna)
multi_agent/
coral_multi_agent.py L2 adapter: division + negotiation (blocking receive)
coral_multi_agent_ablation.py L1 adapter: division only
coral_multi_agent_passive.py L3 adapter: full AgentRadio (passive awareness)
startup.sh / startup_ablation.sh / startup_passive.sh
per-agent bootstrap + protocol prompts (CLAUDE.md)
coral-agent*.toml message-server agent definitions
passive_scripts/ MCP-over-HTTP shell primitives (create_thread /
send_message / wait_for_mention / read_resource)
coral-server.jar message server (download from Releases, see below)
monitor_coral_log.sh live thread/message monitor for running containers
run_config/qa/
claude-token OAuth token helper
full_run.sh B0 baseline batch runner (all 124 tasks)
run_passive_multi_agent.sh L3 batch runner
verify_local.py rubric verifier (LLM judge), run locally on a trial dir
Every task directory under data/qa/ carries the instruction, the pinned execution
environment, and the rubric set used by the verifier.
Runs execute in Docker containers on Modal, orchestrated by Harbor. One task = one container running the message server plus four Claude Code agents.
Install from https://www.docker.com/products/docker-desktop/ and verify with docker run hello-world.
curl -LsSf https://astral.sh/uv/install.sh | shNewer Harbor releases (0.7+) have breaking API changes that make these adapters fail. Pin the versions:
| Component | Working version |
|---|---|
| harbor | 0.6.4 |
| modal | 1.4.2 |
uv tool uninstall harbor 2>/dev/null || true
uv tool install 'harbor[modal]==0.6.4'
harbor --version # must show 0.6.4pip install 'modal==1.4.2'
modal --version # must show 1.4.2
modal setup # opens browser to log incurl -fsSL https://claude.ai/install.sh | sh
claude --versionYou need a Claude Max subscription for the agents. The verifier additionally needs an Anthropic API key.
The 106 MB server JAR is hosted as an anonymized artifact (too large for a git blob).
The confirm=t parameter bypasses the large-file scan interstitial so curl gets the
binary directly:
curl -L -o multi_agent/coral-server.jar \
"https://drive.usercontent.google.com/download?id=17b40_1kXFrAC0pnN8w_7PPY13O7pYVke&export=download&confirm=t"The adapters upload this JAR into each task container. Nothing needs to run locally, so no local JDK is required.
cp run_config/qa/claude-token ~/.local/bin/claude-token
chmod +x ~/.local/bin/claude-token
cp .env.example .env # then fill in your Anthropic API keyThe Claude Code OAuth token rotates. Each container gets a static snapshot at launch, and a stale token kills all four agents with 401 mid-run. Refresh before every session:
claude /login # opens browser
security find-generic-password -s "Claude Code-credentials" -w | python3 -c "
import json, sys, os
data = json.loads(sys.stdin.read())
oauth = data.get('claudeAiOauth', {})
with open(os.path.expanduser('~/.claude/.credentials.json'), 'w') as f:
json.dump({'claudeAiOauth': oauth}, f, indent=2)
print(f'Token refreshed. Expires at: {oauth.get(\"expiresAt\")}')
"
~/.local/bin/claude-token --check
source .envAll commands run from the repository root, after source .env. Task IDs are the directory
names under data/qa/ (repeat -i to batch; drop -i entirely to run all 124). -n is the
number of concurrent tasks (one task = four agents for L1–L3).
source .env
harbor run \
-p ./data/qa \
-a claude-code \
-m "anthropic/claude-opus-4-6" \
-e modal -k 1 -n 1 \
-i "task-6905333b74f22949d97ba998" \
--ak reasoning_effort=high \
-o results/qa/ \
--job-name "baseline-ba998" \
-yAgent-1 explores briefly, partitions the question, and each agent solves its share independently. Answers are merged without review.
source .env
export PYTHONPATH="$(pwd):${PYTHONPATH:-}"
harbor run \
-p ./data/qa \
--agent-import-path='multi_agent.coral_multi_agent_ablation:CoralMultiAgentAblation' \
-m "anthropic/claude-opus-4-6" \
-e modal -k 1 -n 1 \
-i "task-6905333b74f22949d97ba998" \
--ak reasoning_effort=high \
-o results/qa/ \
--job-name "division-ba998" \
-yThe full five-phase protocol — joint exploration, negotiated partition to unanimity, live
execution, cross-review, assembled submission — with wait_for_mention running in the
foreground, so agents stop working in order to listen.
source .env
export PYTHONPATH="$(pwd):${PYTHONPATH:-}"
harbor run \
-p ./data/qa \
--agent-import-path='multi_agent.coral_multi_agent:CoralMultiAgent' \
-m "anthropic/claude-opus-4-6" \
-e modal -k 1 -n 1 \
-i "task-6905333b74f22949d97ba998" \
--ak reasoning_effort=high \
-o results/qa/ \
--job-name "divneg-ba998" \
-ySame protocol, but wait_for_mention runs as a background task: agents keep working and
messages surface between steps. Claude Code gets no MCP config — all communication goes
through the thin shell wrappers in passive_scripts/.
source .env
export PYTHONPATH="$(pwd):${PYTHONPATH:-}"
harbor run \
-p ./data/qa \
--agent-import-path='multi_agent.coral_multi_agent_passive:CoralMultiAgentPassive' \
-m "anthropic/claude-opus-4-6" \
-e modal -k 1 -n 1 \
-i "task-6905333b74f22949d97ba998" \
--ak reasoning_effort=high \
-o results/qa/ \
--job-name "passive-ba998" \
-yrun_config/qa/run_passive_multi_agent.sh wraps the same command as a batch runner, one
harbor job per task id.
The multi-agent configurations (L1–L3) can be run with DeepSeek-V4-Pro agents instead of Opus 4.6, reproducing the DeepSeek column of the results table. Everything about the protocol, prompts, startup scripts, and resume guard is identical; only the LLM backend changes.
Claude Code speaks only the Anthropic Messages API, while DeepSeek is served through
OpenRouter (OpenAI-compatible only). We bridge the two with a LiteLLM translation proxy
hosted once on Modal. The task containers install nothing — they just point
ANTHROPIC_BASE_URL at the proxy's public URL.
The rubric verifier is unchanged: it still uses your Anthropic judge (OPENAI_API_KEY /
EVAL_MODEL). DeepSeek is only the agent backend.
# 1. An OpenRouter API key with deepseek-v4-pro access (https://openrouter.ai/keys)
# is stored as a Modal secret — it never leaves your Modal account.
modal secret create openrouter-deepseek OPENROUTER_API_KEY=sk-or-...
# 2. Deploy the proxy. This prints your personal URL.
modal deploy multi_agent/deepseek_litellm_modal.py
# -> https://<your-user>--deepseek-litellm-proxy-serve.modal.run
# 3. Put that URL in .env so the adapters can find it:
echo 'export AGENTRADIO_PROXY_URL=https://<your-user>--deepseek-litellm-proxy-serve.modal.run' >> .env
source .envThe proxy stays warm (min_containers=1); redeploy only after editing it. To stop billing
when idle: modal app stop deepseek-litellm-proxy (a later modal deploy brings it back).
Identical to the Opus commands above, but the import path points at the DeepSeek adapter and
-m "deepseek-v4-pro" routes through the proxy. source .env must have exported
AGENTRADIO_PROXY_URL. Task IDs and -i batching work exactly as above.
The DeepSeek baseline uses a thin subclass of the built-in claude-code agent (it forces the
proxy endpoint and drops the OAuth token the built-in agent would otherwise re-mint), so it
takes --agent-import-path rather than -a claude-code.
source .env
export PYTHONPATH="$(pwd):${PYTHONPATH:-}"
harbor run \
-p ./data/qa \
--agent-import-path='multi_agent.claude_code_deepseek:ClaudeCodeDeepseek' \
-m "deepseek-v4-pro" \
-e modal -k 1 -n 1 \
-i "task-6905333b74f22949d97ba998" \
--ak reasoning_effort=high \
-o results/qa/ \
--job-name "deepseek-baseline-ba998" \
-ysource .env
export PYTHONPATH="$(pwd):${PYTHONPATH:-}"
harbor run \
-p ./data/qa \
--agent-import-path='multi_agent.coral_multi_agent_ablation_deepseek:CoralMultiAgentAblationDeepseek' \
-m "deepseek-v4-pro" \
-e modal -k 1 -n 1 \
-i "task-6905333b74f22949d97ba998" \
--ak reasoning_effort=high \
-o results/qa/ \
--job-name "deepseek-division-ba998" \
-ysource .env
export PYTHONPATH="$(pwd):${PYTHONPATH:-}"
harbor run \
-p ./data/qa \
--agent-import-path='multi_agent.coral_multi_agent_deepseek:CoralMultiAgentDeepseek' \
-m "deepseek-v4-pro" \
-e modal -k 1 -n 1 \
-i "task-6905333b74f22949d97ba998" \
--ak reasoning_effort=high \
-o results/qa/ \
--job-name "deepseek-divneg-ba998" \
-ysource .env
export PYTHONPATH="$(pwd):${PYTHONPATH:-}"
harbor run \
-p ./data/qa \
--agent-import-path='multi_agent.coral_multi_agent_passive_deepseek:CoralMultiAgentPassiveDeepseek' \
-m "deepseek-v4-pro" \
-e modal -k 1 -n 1 \
-i "task-6905333b74f22949d97ba998" \
--ak reasoning_effort=high \
-o results/qa/ \
--job-name "deepseek-passive-ba998" \
-yThe shared proxy injection (which swaps the LLM backend while inheriting all multi-agent
logic, startup scripts, and the resume guard) lives in multi_agent/deepseek_proxy.py; the
B0 baseline subclass is multi_agent/claude_code_deepseek.py.
source .env
export PYTHONPATH="$(pwd):${PYTHONPATH:-}"
harbor job resume -p results/qa/<job-name> -f CancelledError -f RuntimeErrorbash multi_agent/monitor_coral_log.sh # renders coral://state from the running containerEach trial writes the team's answer to <trial>/agent/answer.txt. Score it with the
benchmark's LLM judge:
source .env
python3 verify_local.py <task-id> <trial-dir>
# e.g.
python3 verify_local.py task-6905333b74f22949d97ba998 \
results/qa/divneg-ba998/task-6905333b74f22949d97ba998__XXXXXThis writes <trial>/verifier/reward.txt (1 only when every rubric passes) and
evaluation_results.json (per-rubric scores). pip install openai if missing.
task-xxx__randomId/
├── config.json / result.json / trial.log
├── agent/
│ ├── answer.txt # final answer (written by agent-1)
│ ├── coral-server.log # threads and messages
│ └── agent-{1..4}-claude-code.txt
└── verifier/
├── reward.txt
└── evaluation_results.json
Full results on SWE-Atlas QnA (124 tasks, 1,306 rubrics). Category rows give tasks resolved, with category sizes in parentheses. Within each model column every configuration uses the same harness and settings.
B0 = single Claude Code · L1 = 4× Claude Code + division of labor · L2 = L1 +
negotiation · L3 = L2 + passive awareness (AgentRadio).
Opus 4.6
| B0 | L1 | L2 | L3 | |
|---|---|---|---|---|
| Architecture and system design (44) | 15 | 13 | 24 | 30 |
| Root-cause analysis (37) | 9 | 16 | 18 | 20 |
| Code onboarding (28) | 11 | 12 | 14 | 18 |
| Security (11) | 4 | 7 | 7 | 7 |
| API and library integration (4) | 1 | 1 | 1 | 2 |
| All tasks resolved (124) | 40 | 49 | 64 | 77 |
| Task accuracy (%) | 32.3 | 39.5 | 51.6 | 62.1 |
| Rubric pass rate (%) | 84.2 | 86.1 | 91.3 | 93.1 |
DeepSeek V4 Pro
| B0 | L1 | L2 | L3 | |
|---|---|---|---|---|
| Architecture and system design (44) | 14 | 13 | 17 | 24 |
| Root-cause analysis (37) | 11 | 13 | 15 | 18 |
| Code onboarding (28) | 7 | 8 | 10 | 13 |
| Security (11) | 4 | 4 | 6 | 7 |
| API and library integration (4) | 0 | 0 | 1 | 1 |
| All tasks resolved (124) | 36 | 39 | 49 | 63 |
| Task accuracy (%) | 29.0 | 31.4 | 39.5 | 50.8 |
| Rubric pass rate (%) | 81.2 | 83.7 | 85.9 | 90.2 |
L3 vs. L2, exact McNemar on paired task outcomes — Opus 4.6: wins 15, loses 2, p = 0.0023. DeepSeek V4 Pro: wins 17, loses 3, p = 0.0026.
Rubric-level analysis shows the passive-awareness gain growing with task difficulty, consistent with mid-course correction as the underlying mechanism.
- 401 errors mid-run — the OAuth token snapshot went stale. Refresh (see above), then
harbor job resume -p results/qa/<job-name> -f NonZeroAgentExitCodeError. claude: not foundin coral-server.log — the startup scripts exportPATH="$HOME/.local/bin:$PATH"; check that Claude Code installed inside the container.- Alpine-based tasks — some tasks use Alpine images; the adapters auto-detect this and install an Alpine-compatible JDK.
- Inspecting communication —
grep "sent message\|created thread" <trial>/agent/coral-server.log | sed 's/\x1b\[[0-9;]*m//g'
The task data is the SWE-Atlas QnA benchmark
(harbor dataset scale-ai/swe-atlas-qna) by Scale AI. Runs are orchestrated with
Harbor on Modal.
@misc{ren2026agentradio,
title = {AgentRadio: Passive Awareness for Long-Horizon Multi-Agent Collaboration},
author = {Xinxing Ren and Qianbo Zang and Ziyan Wang and Caelum Forder and
Suman Deb and Peter Carroll and Zekun Guo},
year = {2026},
eprint = {2607.28430},
archivePrefix = {arXiv},
url = {https://arxiv.org/abs/2607.28430}
}Released under the Apache License 2.0.
Built at Coral AI Labs, SnT — Université du Luxembourg, King's College London, and the University of Hull.
