{"slug": "three-ai-agents-corrected-each-other-none-of-them-were-talking", "title": "Three AI Agents Corrected Each Other. None of Them Were Talking.", "summary": "A developer tested CrewAI 1.15.21 with three agents — an Analyst and Writer on Qwen 3 235B via OpenRouter and an Editor on Anthropic's Claude Opus 4.8 — and found that apparent agent-to-agent conversation is actually tool invocation. The Editor's correction of a $1,600 home AI server price came through calls to ask_question_to_coworker and delegate_work_to_coworker, meaning agents share nothing beyond what one call passes to the next. The run took 118 seconds and 14,609 input tokens, and the developer noted the Analyst confirmed its correction from training data rather than any lookup, leaving the revised figures unverified.", "body_md": "CrewAI is an open-source Python framework for running several AI agents as a team. You give each agent a role and a goal, give the team a list of tasks, and the framework passes the work between them. Before building anything real with it, I wanted to answer one question: when people say the agents \"talk to each other\", what is actually happening?\n\nSo I set up a small crew on the server in my house — a ThinkCentre that already runs my monitoring and automation — and gave it a practice topic.\n\nThree agents, three tasks, run in order:\n\nThe Analyst and the Writer run on Qwen 3 235B through OpenRouter, a service that puts dozens of model hosts behind one API. The Editor runs on Claude Opus 4.8, direct from Anthropic. The cheap model does the volume; the expensive one makes the judgement calls.\n\nSetup is four commands:\n\n```\nuv tool install crewai\nCREWAI_DMN=true crewai create crew starter_crew --provider anthropic/claude-haiku-4-5\ncd starter_crew && crewai install\ncrewai run\n```\n\nThat installed CrewAI 1.15.21, the version everything below was tested on. `CREWAI_DMN` isn't in `crewai --help`. I found it in the CLI's source: it switches off every interactive prompt, which is what you want when a script rather than a person is driving. It also means the scaffold never writes your `.env` — the API keys are yours to add. The `--provider` flag only chooses the starting model that gets written into each agent's file; I swapped those afterwards, to Qwen for the Analyst and the Writer and Claude Opus 4.8 for the Editor. The project itself is plain JSON: one file per agent, one `crew.jsonc` for the tasks.\n\nIt took 118 seconds, 14,609 tokens in and 3,928 out.\n\nThe Writer asked the Analyst a question before drafting. Then the Editor read the draft and stopped on a price: a capable home AI server for about $1,600. It asked the Analyst whether that held up. The answer came back that $1,600 is the graphics card alone — a complete machine runs $2,500–3,500 — and that a single 24 GB card can't run a 70-billion-parameter model at a usable speed anyway. The Editor sent the paragraph back to the Writer with the correction, took the revised version, and recorded both exchanges in its Team notes.\n\nThree agents, one caught error, one fix. It looked exactly like a team conversation.\n\nThe activity log at the bottom of the run screen says what actually happened. Three entries:\n\n```\n✓ ask_question_to_coworker     15.3s\n✓ ask_question_to_coworker     20.4s\n✓ delegate_work_to_coworker     8.9s\n```\n\nThose are tools. When an agent is allowed to delegate, CrewAI hands it two extra functions, the same way you'd hand an agent a web search or a file reader. Calling `ask_question_to_coworker` with a coworker's name, a question and some context starts a fresh model call as that coworker, and whatever it returns comes back to the caller as the tool's result. The Editor never spoke to the Analyst. It called a function whose implementation happens to be another agent.\n\n**A conversation between agents isn't a conversation. It's a function call with a job title.**\n\nThat changes how you build with it. The quality of the \"discussion\" is the quality of the arguments one model passes into a tool. The Editor only knew about the $1,600 because it was in the draft it was handed; the Analyst could only answer what the question contained. Nothing is shared between agents except what one call gives the next — and the handoffs between tasks work the same way, each task's output pasted into the next task's prompt.\n\nIt also exposes the limit of what I watched. Nobody in that exchange looked anything up. These agents had no web access, so the Analyst \"confirmed\" the correction from the same kind of training data that produced the original number. The new figures — the $2,500–3,500 and the claim about the 24 GB card — are plausible. Neither is verified. The Analyst had no way to say \"I don't know\", and one of the operating rules I keep for this blog covers exactly that: [a system with no way to say \"I don't know\" will answer anyway](https://tedagentic.com/rules). A second model agreeing is a second opinion, not a second source. Giving agents tools can change that. Giving them coworkers doesn't.\n\n**The key was a placeholder.** My `.env` held template text, not a key, and Anthropic answered `401 invalid x-api-key`. One direct request to the API would have told me that before any framework was involved.\n\n**The provider wasn't installed.** The scaffold depends only on `crewai[tools]`. Anthropic support is an optional extra, so the first run died with `Anthropic native provider not available`. The fix is `uv add \"crewai[anthropic]\"`.\n\n**Memory wanted a key I never gave it.** The scaffold switches crew memory on, and memory's default embedder is OpenAI's. With no OpenAI key, every memory lookup errored. `\"memory\": false` until you configure a different embedder.\n\n**The newest Claude doesn't fit yet.** I wanted Opus 5 as the Editor, so I read CrewAI's Anthropic code first. Its thinking setting accepts only \"enabled\" or \"disabled\"; Opus 5 thinks by default and rejects the old \"enabled with a budget\" form; and CrewAI drops the thinking blocks when it rebuilds a tool-call turn. Agents calling each other through tools is exactly the path that would hit it. I didn't spend money finding out — Opus 4.8 costs the same per token and only thinks when asked.\n\nThe fifth one is the one worth a section.\n\nThe first full three-agent run failed halfway. The Analyst finished its task. The Writer failed on every attempt — ten in a row — with this, passed back from the model host:\n\n```\nRequested token count exceeds the model's maximum context length of 131072 tokens.\nYou requested a total of 132086 tokens: 1014 tokens from the input messages\nand 131072 tokens for the completion.\n```\n\nThe Writer's prompt was about a thousand tokens. The other 131,072 were the reply — under a reply limit I had never set.\n\nThat was the whole problem. CrewAI only sends a reply limit (`max_tokens`) when you configure one; I checked the code. With none sent, the reply was treated as allowed to fill the model's entire context window, my thousand tokens of input went on top, and the total was rejected for being over the window. The request didn't fail because it was big. It failed because it didn't say how big.\n\nI reproduced it without CrewAI: one request to the same host, a 12-token prompt, no `max_tokens`. Same error — 12 plus 131,072 is 131,084, over by twelve. Why the Analyst survived, I can only guess: OpenRouter spreads requests across hosts, and my best explanation is that its requests landed on one that handled a missing limit differently. I didn't capture which host served them.\n\nHere is what I can show, and what I can't. The limit being enforced is 131,072 — the error comes back from that host's backend — while OpenRouter's listing for the same host advertises 262,144 tokens of context and 235,929 of output. What I can't show from outside is which layer wrote 131,072 into the reply field, OpenRouter or the host. That it matches the host's real window, not the advertised one, points at the host. Treat that as a hypothesis.\n\nThe fix is one field. Every agent's model now carries `\"max_tokens\": 4096` — 16,000 for the Claude editor. It's another case of a rule from the same list, [defaults are policy](https://tedagentic.com/rules): an unset value isn't an absence. Something downstream always chooses one, and here it chose the largest number it had.\n\n**An unset limit isn't no limit. It's the maximum.**\n\nWith only the Qwen models, a full three-agent run cost under half a cent. With Claude as Editor, the OpenRouter share stayed around $0.003. Anthropic doesn't report per-run cost to an API key, but even if every one of that run's 18,537 tokens had gone through Claude at Opus 4.8's list price — $5 per million input tokens, $25 per million output — it would come to about 17 cents.\n\nBefore publishing, I built a second crew and gave it this draft. A Cold Reader plays a stranger arriving from Google. A Reframe Critic looks for the one sentence where the idea flips. A Claude Editor checks every claim against the text and gives a verdict. Before any of them ran, plain code handled the mechanical checks — no private site names, a valid category, the image present, the links resolving — because no model should be trusted with those.\n\nThe verdict was \"fix first\", and the two most useful catches were mine to own. I had stated two guesses as facts: why the Analyst's requests survived, and which layer filled in the 131,072. Both are labelled as guesses now. It also noticed that the setup command names one Claude model while the crew runs another, with nothing to reconcile them.\n\nOne reviewer was simply wrong. The Cold Reader said a stranger \"cannot access\" the rules page these posts link to. It's a public page. It said so with exactly the same confidence as everything else it said.\n\nThat review had the same shape as the run above. The reviewers didn't discuss the post. Each one was a function call handed the draft, and the Editor was handed their outputs. The good catches came from the Editor checking claims against the text in front of it. The false one came from an agent with no way to check anything, answering anyway.\n\n`max_tokens` on every agent's model. Don't let something downstream pick it.`CREWAI_DMN=true crewai run` in scripts — plain output that exits when it's done. Use plain `crewai run` when you want to watch.", "url": "https://wpnews.pro/news/three-ai-agents-corrected-each-other-none-of-them-were-talking", "canonical_source": "https://dev.to/henry_dan_81513dd35a2f540/three-ai-agents-corrected-each-other-none-of-them-were-talking-5d2h", "published_at": "2026-09-15 04:43:01+00:00", "updated_at": "2026-09-15 05:00:48.717683+00:00", "lang": "en", "topics": ["ai-agents", "ai-tools", "large-language-models", "developer-tools"], "entities": ["CrewAI", "Qwen 3 235B", "OpenRouter", "Anthropic", "Claude Opus 4.8", "ThinkCentre"], "alternates": {"html": "https://wpnews.pro/news/three-ai-agents-corrected-each-other-none-of-them-were-talking", "markdown": "https://wpnews.pro/news/three-ai-agents-corrected-each-other-none-of-them-were-talking.md", "text": "https://wpnews.pro/news/three-ai-agents-corrected-each-other-none-of-them-were-talking.txt", "jsonld": "https://wpnews.pro/news/three-ai-agents-corrected-each-other-none-of-them-were-talking.jsonld"}}