The full upstream README, mirrored here for reference. Install config, tool schemas, adoption signals, and an original overview live on the YAAC listing page.
A radio for agentic coding sessions.
You have several sessions open at once — different worktrees, different tasks,
maybe different people driving them. They need to tell each other things:
"schema for rosters changed, the field is recipient_group now", "pushing the
refactor in ten minutes, hold your commits". YAAC is how they do that, and how
you talk into the same conversation by hand.
The mental model is a network of handheld radios, not a phone network. Buy one
and it works — it just has nobody to talk to. Buy a second and there's a
conversation. There is no config file, no environment variable, no port to choose,
no daemon, and nothing to run first. Sessions find each other at a fixed local
address — tcp://127.0.0.1:19116, and 19116 is 0x4AAC, which is where the
name comes from. Whichever session needs it first claims it and relays for the
others; if that session goes away, another takes over by itself, within a few
seconds and without anyone doing anything.
It connects sessions that were never designed to meet. Agents inside one harness could always talk — an orchestrator wires its own subagents, and that was never the problem. YAAC is for two (or more) unrelated sessions in unrelated clients, alive on your machine right now: a Claude Code session and a Claude Desktop chat, Codex, Gemini CLI — anything that can run a local MCP server. One conversation is 300k tokens into a task; another, 400k tokens in, holds exactly the experience it needs. Give them a radio. If you can talk to both of them, now they can talk to each other.
Configuration rounds to zero. If you can add a local MCP server, you are done — no Redis to stand up, no PostgreSQL to prepare, no broker, no port to choose. Adding YAAC hands each client a radio, switched off. Then, at any moment, you tell a session "connect to yaac" — and it deals with the rest.
* On Claude Code, installed as a plugin, these arrive on their own — see Instead: as a plugin. Everywhere else the listening session receives them next time it checks its inbox; see Messages do not arrive on their own below.
Two ways in. As an MCP server works in every MCP client and is the one to reach for by default. As a plugin is fewer steps and brings a skill along, but only on clients that implement one of the two plugin standards.
All of these need uv on your PATH. uvx fetches
the package and a suitable
Python by itself, so there is nothing else to install and no virtualenv to
manage.
-s user installs it for every project on the machine, which is usually what you
want: a radio only one of your sessions can hear is not much of a radio. Leave it
out to add YAAC to the current project only.
Check it took with claude mcp list, or /mcp inside a session.
Add YAAC to claude_desktop_config.json, then restart the app:
~/Library/Application Support/Claude/claude_desktop_config.json%APPDATA%\Claude\claude_desktop_config.jsonIf Desktop reports that it cannot find uvx, give the absolute path instead —
which uvx will tell you where it is. GUI applications do not always inherit the
PATH your shell has.
YAAC is a plain stdio MCP server with no client-specific behaviour. Whatever your client's configuration looks like, the two things it needs are:
uvxyet-another-agentic-chatSessions on different clients can talk to each other, as long as they are on the same machine.
If your client speaks one of the two plugin standards, this replaces the configuration above and adds a skill explaining how to use the radio.
Claude Code, which has its own plugin format. This repository is the marketplace:
There it brings the one thing the MCP server alone cannot: messages arrive
without being asked for. A hook hands the session whatever came in — as it
works, when you type, and as a turn ends, which is the one that reopens a
finished turn so it can act on the news. It is a delivery, not a nudge: the
message text itself, already read, with check_inbox left for when you want to
look on purpose. The same hook fires after a compaction and hands the session
back the connections it holds, since a summary drops the identifiers the other
tools need. Nothing to configure, and silent when there is nothing to say.
Agent Plugins 1.0.0 — ChatGPT, Codex, Cursor,
GitHub Copilot, Kiro, VS Code. Point your client at this repository; the plugin
is the plugin/ directory. Claude Code is not part of that standard, which is
why there are two sets of instructions rather than one.
Either way the plugin runs the published package with uvx, so it carries no
copy of the server and picks up new releases without being reinstalled.
To sit on a channel yourself and watch it live:
It joins as an ordinary participant, so agents see you as one of them. Unlike an
MCP session it gets messages the moment they arrive — the pull-only limitation
below is MCP's, not YAAC's, and a terminal has no such problem. See
docs/tui.md.
To run the latest unreleased code, replace yet-another-agentic-chat with
git+https://github.com/amyodov/yet-another-agentic-chat in any command above.
To hack on a local checkout, see docs/development.md.
Append one of these as the final argument to any command above. All three name the same thing — where sessions meet — and which one you use says what this session is willing to do there:
--rendezvous tcp://127.0.0.1:<port> | Relay if this session gets there first, join whoever did otherwise. The default, and what happens with no flag at all. |
--bind tcp://127.0.0.1:<port> | Insist on relaying: refuse to join if another session already holds it. |
--connect tcp://127.0.0.1:<port> | Never relay for anybody. Join, talk, and leave the spine to someone else. |
Every session that should hear the others must be given the same address:
sessions on different ones are invisible to each other, which is also exactly
what makes this useful for a second, isolated net — a development build beside
your daily one, say, as in docs/development.md. Pick a
free port below 32768, out of the range the kernel hands to outbound
connections.
There is no --port, because the address already carries one. The two flags
beyond the default are worth having for opposite reasons: a terminal client you
leave open makes an excellent stable hat, and --bind is how you find out it
did not become one, rather than discovering it when the net changes hands. A
session that will be gone in a minute makes a poor one, and --connect is how
it declines.
Keep the address on loopback. YAAC has no authentication and is not meant to — the whole design assumes one machine and one user — so binding something the network can reach hands an open message bus to whoever finds it. That is yours to do if you mean it; you will get a warning on stderr, not a refusal.
Nothing happens until you say so. A freshly installed YAAC opens no socket and creates no file — it is a switched-off radio that knows how to be switched on. Idle cost is as close to zero as it gets: no listener, no connection, nothing on disk, and on most clients only two tools' worth of context in the session — the full toolset appears when you join and withdraws when you leave. The first session that actually joins is the one that binds the socket for everyone.
Going on air is always an explicit act by you. The name is your choice — YAAC will never infer one from the directory, the hostname, or the task.
Channel names and participant names are raw text. Any string works: spaces, Cyrillic, emoji, punctuation. Nothing is reserved, parsed, or case-folded.
| Tool | What it does |
|---|---|
list_channels() | What is on the air, with participant counts. No side effects. |
join_channel(channel, name) | Go on air. If nobody is on the channel, joining creates it — and says so. |
send(body, name=None) | Message one participant, or the whole channel if name is omitted. |
check_inbox() | Read what has arrived since last time. |
peers() | Who else is on your channel. |
dev_connections() | Diagnostic: every connection you hold, with unread counts. |
leave_channel() | Leave one channel and remove its inbox. |
Only the first two are offered until you join something. The rest appear once you are on air and disappear when you leave the last channel, so a session that never joins carries almost nothing. On a client that cannot handle a changing tool list, all seven are listed from the start instead — see Compatibility notes.
You may be on more than one channel at once. join_channel returns a connection
id; pass it as connection_id when you hold several, and leave it out when you
hold one.
There is no separate verb for creating a channel, because a channel is not a
lasting object — it exists exactly as long as somebody is on it. Joining an empty
name is what brings it into being, and the result says created: true so a typo
cannot quietly leave you alone on z combinator forun.
This is the thing to understand about v0. MCP has no way for a server to push
text into a session that is sitting idle — the protocol simply has no such verb.
So an agent only hears what it has collected: it must call check_inbox().
The tool descriptions tell it to do this before acting and before ending a turn, and every YAAC tool result carries an unread count as a nudge. It still means a message sent to an idle session waits until that session's agent next checks. If your agent seems deaf, tell it to check the inbox.
Two clients can be told to hand a session its mail without it asking. Both are
optional, both are additive, and plain check_inbox() keeps working underneath.
Claude Code gets it from the plugin, with nothing to configure: a hook hands
the session whatever arrived, as it works and as a turn ends. Those messages are
then already read. The one thing a hook cannot reach is a session sitting idle,
so join_channel also returns a watch URL — point the Monitor tool at it
once per join and each arrival becomes an event, even while nothing is running.
That event is a doorbell, not the message: check_inbox() still reads it.
The same hook covers the other way a session goes quiet. join_channel() returns
a connection_id and a peer_secret that the other tools require, and a
compaction is exactly the thing that drops an opaque string from a conversation —
leaving a session still on the air, still holding its name, and unable to say a
word. So the plugin also fires on a post-compaction SessionStart and hands the
session back what it was holding. Nothing is stored to make that work: the
memberships never went anywhere, only the model's record of them did.
If you are not on Claude Code, or the hook does not fire, the recovery is the
same one the tool descriptions name: call join_channel() again with the same
channel and name, and the membership you already hold comes back, secret
included.
Codex needs one file, because its hooks cannot call an MCP tool — so a
separate program answers them. In ~/.codex/hooks.json, or .codex/hooks.json
in a project:
Nothing to name and nothing to keep in step: yaac-hook asks the same address
every participant already meets at which sessions are running here, and picks
out its own by what its client told both halves, by the directory it was started
in, or by the process line it shares with its server. Several Codex sessions at
once are told apart the same way. Where that is genuinely ambiguous it says
nothing, because somebody else's mail is worse than none.
Codex reviews a hook before it runs it — approve it with /hooks. From then on,
a session is told when mail is waiting and reads it with check_inbox() itself.
A hook only fires when a session does something, so none of the above reaches one waiting at its prompt. Codex can be reached there through its app-server, which is experimental and off by default. Run your session under one:
That is the whole of it. There is nothing to add to config.toml, because the
app-server is the process that starts the MCP server, and its address is written
on its own command line — so a session finds its own door by looking up its
ancestry. Not an app-server on the machine: the one actually running it, which
is why several at once are no more ambiguous than one, and why Codex's permanent
app-server daemon, which listens on nothing, is never mistaken for a door.
YAAC then puts a line in front of that session when mail arrives, exactly as if
you had typed it — the model reads its history, hooks fire, and check_inbox()
does the rest. It joins the session's queue rather than barging in, so a session
that is in the middle of something is told when it finishes rather than
interrupted. One wake covers any number of messages, and the next needs new mail
to exist.
The app-server is experimental, and the port is yours to pick. Every failure is silent: nothing above this session that could start a turn, nothing listening, no such thread. Your mail waits in the inbox exactly as it would have anyway, so the worst case is the behaviour you had before.
Credit where it is due: this route was found by Vadim, who had it working before it was in YAAC at all.
Both clients: join_channel() returns a peer_uid and a peer_secret.
send(), peers() and check_inbox() want the secret back — it keeps one
conversation from reaching into another's connection in a client that runs a
single server for the whole application, and it is an honour-system convention
rather than a boundary, since everything here runs under one user account.
Joining again with the same pair comes back as the same participant, which is how
a session reclaims its name after a restart.
Nothing here is required, and nothing writes to disk.
send addresses one person unless you leave out the name. A broadcast
interrupts every session on the channel and costs each of them context, so it is
for genuine announcements — not politeness.
send reports accepted, never delivered. It means handed to the network. It
does not mean anybody read it, and today it does not even guarantee arrival.
Not a chat application. No threads, no reactions, no history, no shared task list, no "who is editing which file" presence. These were considered and left out on purpose.
v0 may lose messages. There is no spool, no retry, no acknowledgement. A message in flight while the rendezvous point changes hands is gone. What v0 promises is that it loses messages loudly: an undeliverable message produces a bounce in the sender's inbox rather than silence.
A channel is not a confidentiality boundary. Whichever session claimed the rendezvous point relays all traffic, in every channel, in clear text — and that is an ordinary session that happened to get there first. A channel isolates participants at the transport level, so you never receive another channel's messages; but the relaying session sees everything — the hat is Eve by construction, not by accident. On one machine under one user account this is fine. Do not treat it as more than it is.
Messages become context in the receiving session. Whatever another participant sends is read by your agent as text it may act on — "hold your commits" is indistinguishable from an instruction you typed yourself. The hat never parses a body, so nobody can forge the protocol or another name, but nothing prevents a body from reading as an instruction. Join channels with sessions you trust, and treat an incoming message the way you would treat a message in any chat: as something a person said, not as a command.
Local only. 127.0.0.1. No multi-host, no authentication, no encryption.
Codex works with YAAC. It just costs more context there than it should, and the reason is worth knowing.
MCP lets a server change its tool list while running and say so, with
notifications/tools/list_changed. YAAC uses that: a dormant session carries two
tools, and the other five appear the moment you join a channel. Codex receives
the notification, writes a line to its log, and re-reads nothing — the tool list
a session sees is fixed when its thread starts, and no prompting will shake it
loose. Left alone, a Codex session could join a channel and then be unable to
send, read, or leave.
So YAAC looks at who connected. When the client identifies itself as Codex, all seven tools are listed from the start, because a tool published later is one that client will never see. There is nothing to configure — it works, it is simply five tool descriptions a Codex session may never use.
This is not new, and it is not obscure:
notifications/tools/list_changed", open since January 2026. Filed against a
part of the spec that has been there since 2024-11-05.Claude Code, Gemini CLI and OpenCode all implement it. OpenAI's stated policy is to prioritise by community upvotes, and on #12449 the reason given for not acting was that #10105 "has received zero upvotes". So if the extra tools bother you, you know where to vote.
On Claude Desktop, one name per conversation takes a little care.
Desktop runs one MCP server for the whole application rather than one per
conversation. YAAC handles that — a session can hold several connections at once,
each with its own name and inbox — but the conversation has to remember which
connection is its own. A call that cannot tell which connection you meant reports
the choices, and dev_connections() lists them on demand.
list_channels from a session that has not joined anything, with no side effects--rendezvous, --bind, --connect — so a
session can insist on relaying, or refuse to, without anyone deciding who winsEverything below is additive. Pure MCP keeps working underneath all of it, so a client with no extension mechanism at all loses nothing it has today, and none of this changes the core.
A Claude Code channel would do what
the watch does, more neatly and with no watcher to arm: events arrive as
<channel> tags in the model's own context. It is a research preview, and it
wants Anthropic authentication, an organisation setting on Team and Enterprise
plans, and a --dangerously-load-development-channels launch until third-party
channels are allowlisted — so the watch is what works today, everywhere Claude
Code runs.
Waking a session that is sitting idle is the one thing that still needs the
client to offer a door, and the two that do offer one are answered differently.
Codex has an app-server, so a session running under one is woken by joining its
queue — nothing to configure, since the address is on the app-server's own
command line. Claude Code has no equivalent, so there the watch URL and a
Monitor the session arms once do the same job. Other clients stay pull-based
until each offers an opening of its own, and lose nothing they have today.
Not planned, and deliberately so: delivery guarantees, message history, threads, reactions, and multi-host operation.
docs/tools.md — the MCP tool reference, generated from the
live server, so it always matches what a client seesdocs/tui.md — the terminal client: its modal navigation, how
addressing works, and what is not built yetdocs/message-format.md — the wire format: the
{"yaac":2 magic, field order, addresses, bouncesdocs/development.md — running YAAC from a checkout,
debugging, an isolated development netMIT.