Cognition: don't build multi-agents
The day before Anthropic published its multi-agent research system (C11), Walden Yan at Cognition, the company behind the Devin coding agent, published the opposite advice: don't build multi-agents. Read together, the two posts don't contradict each other. They describe different kinds of work. This case explains Cognition's two principles, the Flappy Bird example that made them famous, and how to tell which kind of work you have.
- Sam A day before Anthropic's post, someone argued the opposite?
- Priya Walden Yan at Cognition, the makers of the Devin coding agent, wrote "Don't build multi-agents". It's an argument from experience, not a measured study, and it turns out not to contradict Anthropic at all.
- Sam What are the principles?
- Priya Two. Share context, and share full agent traces, not just individual messages. And actions carry implicit decisions, and conflicting decisions carry bad results. His recommendation is a single-threaded, linear agent, where every action sees every earlier decision. For very long tasks, a separate model can compress the history into key details, events and decisions, though he notes that's hard to get right.
- Sam What's the Flappy Bird example?
- Priya Ask for a Flappy Bird clone and split it: one subagent builds the background, one builds the bird. Neither sees what the main conversation established. The background comes out looking like Super Mario Bros., and the bird doesn't move like Flappy Bird's. The main agent is left reconciling parts that don't fit.
- Sam And the demo?
- Priya Two subagents given only the task text fall back on their own defaults, and four decisions conflict with what the user meant. Share the full trace of decisions, or use one agent, and the parts fit.
- Priya Your turn. Which is safest to split: two halves of a blog post, or summaries of five different vendors' documents?
- Priya The summaries. They share no decisions. Half a blog post each gives you two voices.
- Sam So who's right, Anthropic or Cognition?
- Priya Both, for different work. Research has independent parts, so splitting pays. Coding one connected thing has coupled parts, so one agent wins. The rule: split only work whose parts don't share decisions, pass the decisions along when you do, use subagents for reading rather than writing, and compress rather than split long tasks.
1. The situation
Splitting a task across agents is tempting: each subagent gets a smaller job and a fresh context window, and they can run at once. Frameworks made it easy to set up. Cognition, building a coding agent that works on long, tightly connected tasks, found that these setups were fragile.
2. The two principles
| Principle | What it means |
|---|---|
| "Share context, and share full agent traces, not just individual messages" | A subagent given only its sub-task misses everything the main conversation established: the user's preferences, earlier decisions, what was tried. |
| "Actions carry implicit decisions, and conflicting decisions carry bad results" | Every piece of work embeds choices nobody stated. Two agents working apart make different ones, and the parts don't fit. |
The recommendation: a single-threaded, linear agent, where every action sees every earlier decision. For tasks too long for one context window, a separate model can "compress a history of actions & conversation into key details, events, and decisions"; Yan notes this is hard to get right.
3. How the split goes wrong
4. The split in code
The task needs decisions about art style, gravity and the flap. Two subagents given only the task text fall back on their own reasonable-looking defaults, and four decisions conflict with what the user meant. Sharing the full trace of earlier decisions fixes it; so does one agent with one context. The program was run in all three languages; each prints the output below it.
# Decisions made earlier in the main conversation: what "a Flappy Bird clone" meant to this user.
TRACE = {"art style": "8-bit pixel art", "gravity": "strong fall", "flap": "instant jump"}
# What each subagent assumes when nobody told it (its own reasonable-looking defaults).
DEFAULTS = {
"background": {"art style": "Super Mario-style hills"},
"bird": {"art style": "smooth vector art", "gravity": "gentle glide", "flap": "gradual lift"},
}
NEEDS = {"background": ["art style"], "bird": ["art style", "gravity", "flap"]}
def subagent(part, shared):
"""Builds one part. Every decision comes from what it was given, or else from its own defaults."""
return {d: shared.get(d, DEFAULTS[part][d]) for d in NEEDS[part]}
def merge(parts):
"""The final step: compare every decision with what the user meant."""
clashes = []
for part, decisions in parts.items():
for d, value in decisions.items():
if value != TRACE[d]:
clashes.append(f"{part} {d} = {value}")
return clashes
setups = [
("two subagents, task text only", lambda part: subagent(part, {})),
("two subagents, full trace shared", lambda part: subagent(part, TRACE)),
("one agent, one context", lambda part: {d: TRACE[d] for d in NEEDS[part]}),
]
for name, build in setups:
clashes = merge({part: build(part) for part in ("background", "bird")})
print(f"{name}: {'fits together' if not clashes else str(len(clashes)) + ' conflicting decisions'}")
for c in clashes:
print(" ", c)
type Decisions = Record<string, string>;
// Decisions made earlier in the main conversation: what "a Flappy Bird clone" meant to this user.
const TRACE: Decisions = { "art style": "8-bit pixel art", gravity: "strong fall", flap: "instant jump" };
// What each subagent assumes when nobody told it (its own reasonable-looking defaults).
const DEFAULTS: Record<string, Decisions> = {
background: { "art style": "Super Mario-style hills" },
bird: { "art style": "smooth vector art", gravity: "gentle glide", flap: "gradual lift" },
};
const NEEDS: Record<string, string[]> = { background: ["art style"], bird: ["art style", "gravity", "flap"] };
// Builds one part. Every decision comes from what it was given, or else from its own defaults.
function subagent(part: string, shared: Decisions): Decisions {
return Object.fromEntries(NEEDS[part].map((d) => [d, shared[d] ?? DEFAULTS[part][d]]));
}
// The final step: compare every decision with what the user meant.
function merge(parts: Record<string, Decisions>): string[] {
const clashes: string[] = [];
for (const [part, decisions] of Object.entries(parts)) {
for (const [d, value] of Object.entries(decisions)) if (value !== TRACE[d]) clashes.push(`${part} ${d} = ${value}`);
}
return clashes;
}
const setups: [string, (part: string) => Decisions][] = [
["two subagents, task text only", (part) => subagent(part, {})],
["two subagents, full trace shared", (part) => subagent(part, TRACE)],
["one agent, one context", (part) => Object.fromEntries(NEEDS[part].map((d) => [d, TRACE[d]]))],
];
for (const [name, build] of setups) {
const clashes = merge({ background: build("background"), bird: build("bird") });
console.log(`${name}: ${clashes.length ? clashes.length + " conflicting decisions" : "fits together"}`);
for (const c of clashes) console.log(" ", c);
}import java.util.*;
import java.util.function.Function;
public class Main {
// Decisions made earlier in the main conversation: what "a Flappy Bird clone" meant to this user.
static final Map<String, String> TRACE = Map.of("art style", "8-bit pixel art", "gravity", "strong fall", "flap", "instant jump");
// What each subagent assumes when nobody told it (its own reasonable-looking defaults).
static final Map<String, Map<String, String>> DEFAULTS = Map.of(
"background", Map.of("art style", "Super Mario-style hills"),
"bird", Map.of("art style", "smooth vector art", "gravity", "gentle glide", "flap", "gradual lift"));
static final Map<String, List<String>> NEEDS = Map.of("background", List.of("art style"), "bird", List.of("art style", "gravity", "flap"));
// Builds one part. Every decision comes from what it was given, or else from its own defaults.
static Map<String, String> subagent(String part, Map<String, String> shared) {
Map<String, String> out = new LinkedHashMap<>();
for (String d : NEEDS.get(part)) out.put(d, shared.getOrDefault(d, DEFAULTS.get(part).get(d)));
return out;
}
// The final step: compare every decision with what the user meant.
static List<String> merge(Map<String, Map<String, String>> parts) {
List<String> clashes = new ArrayList<>();
parts.forEach((part, decisions) -> decisions.forEach((d, value) -> { if (!value.equals(TRACE.get(d))) clashes.add(part + " " + d + " = " + value); }));
return clashes;
}
public static void main(String[] args) {
Map<String, Function<String, Map<String, String>>> setups = new LinkedHashMap<>();
setups.put("two subagents, task text only", part -> subagent(part, Map.of()));
setups.put("two subagents, full trace shared", part -> subagent(part, TRACE));
setups.put("one agent, one context", part -> { Map<String, String> m = new LinkedHashMap<>(); for (String d : NEEDS.get(part)) m.put(d, TRACE.get(d)); return m; });
setups.forEach((name, build) -> {
Map<String, Map<String, String>> parts = new LinkedHashMap<>();
parts.put("background", build.apply("background"));
parts.put("bird", build.apply("bird"));
List<String> clashes = merge(parts);
System.out.println(name + ": " + (clashes.isEmpty() ? "fits together" : clashes.size() + " conflicting decisions"));
for (String c : clashes) System.out.println(" " + c);
});
}
}two subagents, task text only: 4 conflicting decisions
background art style = Super Mario-style hills
bird art style = smooth vector art
bird gravity = gentle glide
bird flap = gradual lift
two subagents, full trace shared: fits together
one agent, one context: fits together
5. Try it: spot the hidden decision
Each line is a piece of work split between two agents that don't share context. Is it safe to split, or will they make conflicting decisions?
6. Two posts, one rule
| Anthropic (C11) | Cognition (C12) | |
|---|---|---|
| Work | Broad research: many independent leads | Coding: one connected artefact |
| Parts | Independent: one lead's finding doesn't change another's | Coupled: the bird must match the background |
| Architecture | Lead with parallel subagents | One agent, compression for long runs |
| Cost of being wrong | A duplicated search | Parts that don't fit and must be rebuilt |
The rule that reconciles them: split only work whose parts don't share decisions, and when you do split, pass the decisions along. Read-only exploration (searching, reviewing, summarising) splits well; writing one connected thing usually doesn't.
7. Lessons
- Default to one agent for coupled work: a feature, a refactor, a document with one voice (Module 07a).
- If you split, share the trace or a written spec of the decisions, not just the sub-task (Module 08a).
- Use subagents for reading, not writing: search, review and summarise in isolation, then let one agent write (Module 05a).
- For long tasks, compress rather than split: compaction and notes keep one thread of decisions.
8. Your turn
- List hidden decisions (10 min). For a feature you're building, write five decisions nobody stated (naming, error handling, styling, data shapes).
- Try a split (15 min). Give two agents halves of a small coupled feature with only the task text. Count conflicts. Repeat with the decisions written down.
- Write the rule (5 min). One sentence for your team on what may be split across agents.
- "Why not split this across agents?" Because the parts share decisions. Cognition's point is that actions carry implicit decisions; agents that don't share context make conflicting ones. I'd use one agent with compaction, and use subagents only for independent reading tasks.
9. Check yourself
What are Cognition's two principles?
In the Flappy Bird example, why didn't the parts fit?
Which task is safest to split across subagents?
For a task too long for one context window, what does Cognition suggest?
10. Sources and further reading
| Resource | Why |
|---|---|
| Don't build multi-agents (Walden Yan, Cognition, June 2025) | Every quote on this page. |
| C11: Anthropic's multi-agent research system | The case where splitting pays. |
| Modules 07a and 05a | Architectures, and compression for long tasks. |