We Added Agent-to-Agent Communication to Claudeway — Here’s What We Built and Why Developer ktamas77 added two MCP tools, ask_claudeway and ask_agent, to the open-source Claudeway Slack-to-Claude-Code gateway on the feat/local-ask-mcp branch, giving one Claude Code bot a way to ask another bot a question and wait for the reply. The tools are committed with unit tests using a fake Slack client, but the live end-to-end test against the real backend bot is still pending. The change addresses two failures in the prior approach: outbound messages appeared under the developer's own Slack name rather than the Flutter bot's, and the Flutter agent fired and forgot, recovering answers only by polling the channel later. “Two Claude Code bots in the same Slack workspace. No way for one to ask the other anything and actually wait for the answer. So we built one.” Claudeway is an open-source Slack-to-Claude-Code gateway built by ktamas77 https://github.com/ktamas77/claudeway on GitHub. It’s a small personal tool with a focused job: messages sent in Slack reach the official Claude Code CLI running on the developer’s own machine, and replies come back as threaded messages in the same channel. Each project folder maps to a Slack channel. Each channel has its own Slack bot. A bot only starts a session when it’s actually @mentioned. No shared tokens. No third-party routing. No multi-user service. One developer, their own Claude subscription through the official CLI, with Slack acting as the remote terminal. We’ve been running it across two projects: a Flutter mobile codebase and a backend API codebase. The Flutter bot lives in its own channel. The Backend bot lives in its own channel. Each one knows its own project deeply and responds to questions about it. It’s a setup that works — until one agent needs context from the other. The core Claudeway repo handles the Slack-to-Claude-Code routing cleanly. What it didn’t have was any mechanism for one bot to ask another bot something and wait for the answer. That’s the gap we built into: we added two new MCP tools — ask claudeway and ask agent — on a feature branch feat/local-ask-mcp that extends Claudeway with agent-to-agent communication capabilities. Both tools are committed with unit tests using a fake Slack client. The live end-to-end test against the real backend bot is still pending. More on that at the end. The Flutter agent was mid-task and hit a question that only the backend codebase could answer. An endpoint contract. A response field name. Context that lived in a completely different project folder, in a completely different Claude Code session. The way it was being handled before: the Flutter agent would post a message into the Backend channel through a generic Slack connector. The message arrived. The Backend bot would eventually reply. Technically, information moved. But two things were wrong. The first: the message appeared in the backend channel under the developer’s own Slack name, not the Flutter bot’s. Anyone reading the channel history — or the Backend bot itself trying to understand the source of a message — saw what looked like a human posting. If you’re building agentic workflows and you can’t tell from the audit trail whether a human or an agent sent something, that’s a reliability problem wearing a cosmetic disguise. The second was worse: the Flutter agent fired and forgot. After posting the question, it moved on with no mechanism for waiting. The only way it eventually found the answer was by polling the channel later — checking if a reply had arrived and trying to connect it back to the question. Fragile, awkward, and fundamentally broken for any coherent multi-turn task flow. Two things needed to change: correct identity on outbound messages, and a genuine wait-for-reply mechanism. The first idea: share the Slack session between the running Claude Code terminal and Claudeway. Same session ID, each process reading what the other wrote. We dropped it for two concrete reasons. A terminal session that’s already open cannot live-update from a transcript another process is writing — the shell doesn’t re-read previous output. And sharing a session ID with Claudeway’s existing architecture is specifically dangerous: if two processes share a session and one encounters an “already in use” error, Claudeway’s recovery path deletes that session’s transcript and starts fresh. Two processes sharing a session would trigger this constantly, potentially destroying conversation history from either side. The second idea — keep posting under the human’s name but handle the polling better — wasn’t really an idea. It was dressing up the broken thing we were already doing. We needed to actually fix the identity problem, not work around it. Before solving agent-to-agent, we solved the slightly simpler version: a local developer, inside their own Claude Code session, asking a question about a different project without leaving their current context. ask claudeway is an MCP tool that handles this. When a Claude Code session calls it, a small HTTP endpoint inside Claudeway — localhost only, random port, bearer token authentication, explicitly configured to reject browser requests — runs the prompt in a completely separate per-project session and streams the answer back inline. The calling session and the answering session never share state. The answer arrives in the same turn, not through a later poll. Read-only by default. The tool gives one session visibility into another project’s context, and the safe default is that visibility shouldn’t imply write access unless the caller explicitly requests it. This solved the human-in-terminal case cleanly. But ask claudeway bypasses Slack entirely — it talks to Claudeway's local API directly. One bot still couldn't ask another bot something through the actual channels where they live. That required a different approach. ask agent is what makes true agent-to-agent communication work. Here's the full flow: Flutter agent caller │ ▼Calls ask agent "What does /health return?", target="backend" │ ▼Claudeway posts to api AS the Flutter bot@mentions the Backend bot in the message │ ▼Slack delivers the message to the Backend bot │ ▼Backend bot picks it up📥 reaction appears queued/received ⏳ reaction appears running │ ▼Backend bot answers in the thread✅ reaction appears done — or ❌ failed │ ▼Claudeway reads the thread repliesReturns them inline to the Flutter agent's current turn The key insight here is that Claudeway already puts emoji reactions on every message it handles — 📥 received, ⏳ running, ✅ done, ❌ failed. These exist so a human watching the channel can see what state a message is in. We repurposed that same signalling mechanism as the completion protocol between bots. ask agent watches for ✅ or ❌ and knows when the other side is done without needing any new protocol invented from scratch. If nothing shows a pickup reaction within 60 seconds, ask agent returns a clear "not picked up" message and stops waiting. Silent hangs were the original problem. The timeout is load-bearing, not a nice-to-have. Follow-up questions reuse the same thread. The Backend bot sees the full conversation context from the thread history, not just the most recent message, which makes genuine multi-turn exchanges between agents possible. From the Flutter agent’s perspective, this is identical to any other MCP tool call: invoke, wait, get a result, continue. The Slack plumbing, the reaction polling, the thread reading — all invisible. One decision worth explaining: agents are addressed by configured name, by raw bot user ID, or by Slack @mention syntax. Any of the three works, and Claudeway resolves whichever form it receives. "peers": { "backend": { "channel": "