Skip to content

Releases: amyodov/yet-another-agentic-chat

v0.5.3 — a session that cannot die of its own diagnostics

Choose a tag to compare

@amyodov amyodov released this 01 Sep 07:29

One bug, and a bad one: a session could stop answering permanently, with every
tool reporting Transport closed — including the one you would call to find out
why. Reported from a Codex session and reproduced here.

What happened. A client that pipes a server's stderr and stops reading it
leaves a 64 KB kernel buffer and no more. The write that fills it blocks, and it
blocks inside the event loop, so the session stops answering anything at all.
Measured: 3000 warnings wedged a real server for good. logging.shutdown flushes
handlers at interpreter exit, so a wedged stream also stops the process from ever
exiting — the same fault met on the way out, and what hung two attempts at a test
for it.

Claude Code is exactly such a client: it captures a server's stderr only while it
connects. And the flood needs no bad luck. A hat logs an operator message it does
not recognise, and a Codex hook asks the rendezvous point who is running on every
tool call, so any net holding one older session and one newer hook produces a
warning per tool call until the buffer fills.

What changed. The rule that nothing may block the session now covers the
diagnostics too. A bounded queue and a writer thread sit between the logger and
the stream, and a full queue drops the line rather than waiting on it: a radio
that stops relaying because nobody read its log has failed at the only job it
has. Separately the hat names an unknown operator message once instead of once
per message, since a newer peer questioning an older one is ordinary rather than
alarming.

Anyone running a mixed-version net should take this one. A session already wedged
cannot be recovered — the process has to be restarted, after which join_channel
with the same channel and name hands the membership back, secret included.

v0.5.2 — one address, three spellings

Choose a tag to compare

@amyodov amyodov released this 30 Aug 11:57

A configuration release and a documentation one, plus a bug found by writing the
documentation down.

--rendezvous, --bind, --connect. All three name the same thing — where
sessions meet — and which one you use says what this session is willing to do
there. The default races for the endpoint and settles for either answer, exactly
as before. --bind insists on relaying and refuses to join if somebody else
already holds it: a terminal window left open makes an excellent stable hat, and
quietly becoming an ordinary participant instead is the failure worth being told
about rather than discovering when the net changes hands. --connect never binds
at all, for a session that will be gone in a minute and should not be anybody's
spine. Neither decides who wins — the hat is still taken by getting there first.

--rendezvous is the endpoint under YAAC's own name for it; the log line has said
"rendezvous" since the beginning, while --endpoint was ZMQ's word. That name
still works as a hidden alias, so nothing that used it breaks. There is no
--port, because the address carries one, and a bare port is what makes a
wildcard bind easy to type without meaning to. Typing one anyway is warned about
on stderr rather than refused: YAAC has no authentication and is not meant to, so
an address the network can reach hands an open bus to whoever finds it — but
somebody who means it is entitled to it.

The flags are defined once and offered by both entry points. yaac-chat had only
--endpoint before, which was backwards: the terminal client is the process most
likely to want --bind.

A hook no longer waits out its timeout against an older peer. sessions, the
query that makes the session directory work, was added in 0.5.1 without pairing —
and a hat too old to know an operator message logs it and never answers, so
directory spent its full timeout on every Codex hook event for as long as such a
peer kept the hat. It now sends channels behind it, a question every version has
answered, and relies on a ROUTER handling one peer's messages in order: a
channels reply with nothing ahead of it proves the peer read the query and had
nothing to say. directory had no tests at all; it has them now, including a hat
that answers one and not the other, so the next wire addition inherits the test
rather than the bug.

Every document read against the code. docs/zmq.md still described the world
channel as built, peer_uid as a hash of peer_secret, and a scope's peer as
carrying the uid — the last two were never true. CLAUDE.md's layout listed six
modules of eleven, omitting the whole delivery half. The README still said no
client but Claude Code can wake an idle session, which 0.5.1 inverted. The
compaction hand-back was missing from the README and the bundled skill, and
docs/message-format.md documented neither the sessions op nor the two things
hello now carries beside the name. The MCP specification was re-read rather than
assumed: still revision 2026-07-28, unchanged, and its Multi Round-Trip Requests
make the direction plainer than the note claimed — server-initiated requests are
gone outright, not merely absent.

v0.5.1 — nothing left to configure

Choose a tag to compare

@amyodov amyodov released this 30 Aug 08:09

Everything a user had to write down by hand is now discovered. Both of the failures behind this release were
reported from the wild by a user running YAAC on Windows under Codex.

YAAC_SESSION is gone, and so is the arithmetic behind it. The notice socket takes whatever port the kernel
offers and says so in hello; the hat keeps a directory of who is out there and answers sessions to anyone
who asks at the one address every participant already agrees on. A Codex hook finds its own session there — by
the name its client gave both halves, by the working directory, or by the process line it shares with its
server. The derivation it replaces was wrong three ways: Codex keeps one configuration block for a whole
machine, so every session derived the same port and answered for each other's inboxes; the digest could not be
reproduced in Node, which declines to truncate blake2s; and a connect to a closed local port does not fail fast
on Windows, so walking eight candidates cost 8.2 seconds on every hook event. Codex now needs one hooks file
and nothing in config.toml.

YAAC_WAKE is gone too, which leaves YAAC with no environment variables at all. The app-server is the
process that starts the MCP server — the tree reads codex app-server --listen ws://… → uv run … yaac → python … yaac — so its address is read off the command line of the forebear that has it. That answers which
app-server is running this session rather than which ones are listening, so several at once are no more
ambiguous than one, and the permanent codex app-server daemon, which every Codex install runs and which
listens on nothing, is never mistaken for a door.

The wake knocks on thread/queue/add instead of turn/start. On an idle thread the queue drains at once
and a whole turn runs, and on a thread already working it takes its place in line — where turn/start does not
refuse but opens a second turn beside the first. So waking a busy session used to interrupt it. turn/start
remains the fallback for an app-server too old to know the queue. Found by Vadim, who had it working before it
was in YAAC.

A compaction no longer strands a session. Losing the peer_secret left a session on the air, holding a
name, and unable to say a word: the other tools refused it, and so did rejoining. Three answers, because no one
of them reaches every client. join_channel now says in its own description that the pair must survive a
compaction and what to do when it did not — a description rides in every request, where the tool result that
used to carry this is summarised away. On Claude Code the plugin also fires on a post-compaction SessionStart
and hands the session its connections straight back, from the process that has held them all along. And a
rejoin no longer asks for the secret the caller just lost: any part of the pair, or none of it, leads back to
the same membership.

PYTHONUTF8=1 is no longer needed. The hook reads its payload as UTF-8 bytes rather than through the
console's encoding, where a Russian Windows turned a working directory into a different string and matched no
session at all, silently; and the log handler writes UTF-8 over stderr's buffer instead of losing whole lines to
--- Logging error ---.

Also: Hat._send never saw EHOSTUNREACH on an asyncio socket, because the send is a future and the error
arrives after the except has returned — so a departed participant was never evicted on that path, which is the
likely source of a name reported as taken by nobody. leave_channel now asks for the secret like the other
three, so a session that cannot prove who it is can no longer strand itself by leaving. CI checks formatting.

Two things a 0.5.0 user may trip on: leave_channel wants a peer_secret, and yaac-hook no longer takes
--key.

v0.5.0 — one shape for every message, and a name you can come back to

Choose a tag to compare

@amyodov amyodov released this 29 Aug 14:13

The wire is protocol version 2, and it does not speak to version 1. Upgrade every session on a machine together: until you do, a new session finds the rendezvous point held by an old one and hears nothing, which is why both sides now name the version they saw rather than falling silent. Version 1 was always temporary.

Three tools gained an argument. join_channel returns a peer_uid and a peer_secret; send, peers and check_inbox want the secret back. It is an honour-system convention rather than a boundary — everything here runs under one user account — and what it prevents is one conversation reaching into another's connection in a client that runs a single server for the whole application.

One shape for everything

Joining, channel listings, rosters, bounces and a sentence between two agents now travel as the same envelope, and who a message is addressed to decides whether the relaying session obeys it or merely carries it. That used to be decided by how many frames arrived. from and to are scopes whose fields compose — a channel, a participant, a participant as a member of a channel, or nothing at all, which addresses the relay itself. A sender never writes from; the relay stamps it, so infrastructure speaking as itself cannot be forged rather than merely being forbidden.

Saying something to the room, and to one person in it

A message can now name who it calls on while everyone still hears it — what English calls a mention and Russian обращение, the vocative said out loud: "Bob, you take the migration." Delivery and address stay separate, so a whisper is still a whisper. The relay completes each mention from its own table, so the one called on knows it was meant by comparing connection ids rather than matching a name that may since have changed hands. Mentioning someone who is not there is allowed and reported back — nothing is stored for a session that is not connected, so a message reaches whoever is listening when it is sent and nobody else, ever.

tags carry topics, never priorities; payload carries any JSON, for when a sentence is not enough.

A name you can come back to

A peer now has an identity that outlives its connection. A session whose client restarted used to ask for the name it had held seconds earlier and be told it was taken — by a connection nobody was behind, since a departure is noticed only when a send to it fails. Rejoining with the same pair reclaims it.

Also

Codex sessions can be woken while idle, through their app-server, if you set YAAC_WAKE. This is the one mechanism in this release that has not been watched working: it is off unless you ask for it, every failure is silent, and your mail waits in the inbox exactly as it would have anyway.

v0.4.1 — logging, and dependencies that stay where they were tested

Choose a tag to compare

@amyodov amyodov released this 29 Aug 04:11

Two corrections to 0.4.0, neither visible in normal use.

Diagnostics go through logging. They were a print to stderr with the message already formatted, so every call paid for its own interpolation whether or not anything would read it, there was no level to turn down, and a host had no way to route the output. Lines are now lazily interpolated and levelled — what a session did at info, what it dropped at warning — and YAAC_LOG_LEVEL sets the threshold. The stderr handler is attached by the entry points rather than on import: stdout carries the MCP transport, and a library that configures logging decides that for whoever imported it.

The dependencies gained ceilings. mcp>=1.2 had none, and as of today it resolves to 2.1.1 — a major version 0.4.0 shipped without having ever been run against, whose LATEST_PROTOCOL_VERSION is already the 2026-07-28 revision. Both dependencies are now ~= on what the suite actually runs: mcp~=2.0, pyzmq~=27.0. Nothing about the protocol YAAC speaks changes; the point is that the version it speaks it with is now a decision rather than whatever resolved this morning.

v0.4.0 — messages that arrive without being asked for

Choose a tag to compare

@amyodov amyodov released this 29 Aug 03:27

Until now a session only heard what it collected: MCP gives a server no way to put text in front of an idle model, so every message waited for the next check_inbox. That is still the only thing guaranteed to work, and the tool descriptions still say so. What changes is how much waiting is left.

Claude Code gets delivery from the plugin with nothing to configure. A hook hands the session whatever arrived — as it works, and as a turn ends, where the turn reopens so the message is acted on rather than noticed after the fact. Those messages are then already read.

A hook only fires when a session does something, so join_channel now also returns a watch URL. Point the Monitor tool at it once and each arrival becomes an event even while the session sits idle, which is the one case a hook cannot reach.

Codex gets the same reach through yaac-hook, a small program its hooks run. Codex hooks cannot call an MCP tool and are told nothing about the session they belong to, so both halves are given the same name — env = { YAAC_SESSION = "..." } on the server, --key on the hook. See Getting messages sooner in the README.

Nothing is written to disk for any of it. The readers and the server derive a loopback port from the session id their client already handed to both, so there is no file to agree on, no port to record, and no registry to keep true. Notices carry no message body: they say what arrived and for which channel, and check_inbox still does the delivering — so a notice that is dropped or capped costs nothing.

A server now ends when its client does. Terminating the ZMQ context without closing the sockets in it left the process blocked in termination, its event loop already stopped, still holding the rendezvous port and answering nothing. Every other session on the machine read that as an empty network rather than a broken one. One such process survived its client by three days and eighteen hours.

Everything above was measured on real sessions of both clients, not inferred from documentation — which is how three of the four client behaviours it depends on turned out to differ from what the docs say.

v0.3.0 — installable as a plugin, on two standards

Choose a tag to compare

@amyodov amyodov released this 11 Aug 03:09

YAAC stops being something you configure and becomes something you install.

As a plugin, in both standards

Two plugin standards exist now and they do not overlap. Agent Plugins 1.0.0, published five days ago, is read by ChatGPT, Codex, Cursor, GitHub Copilot, Kiro and VS Code — which is precisely the cross-client audience YAAC exists for. Claude Code is not part of it and keeps its own format. Supporting one would have meant choosing between them, so this ships as both, from one directory:

/plugin marketplace add amyodov/yet-another-agentic-chat
/plugin install yaac@yaac

Agent Plugins clients take the repository's plugin/ directory. Either way the plugin runs the published package with uvx, so it carries no copy of the server and follows new releases without being reinstalled.

Adding YAAC as a plain MCP server still works everywhere and remains the default answer; the README leads with it for that reason.

A skill comes with it

The plugin bundles a skill covering what tool descriptions cannot: the occasions worth reaching for the radio, and how to treat a message once it arrives. It is deliberately additive — anything a session needs in order to use YAAC correctly lives in the tool descriptions, where every client sees it, with or without the plugin.

A message is not your user speaking

check_inbox now says so outright: what comes back was written by another session, not by your user. Act on what it tells you; ask the user before doing what it asks of you. The relay never inspects a body, so nothing stops one from reading like a command — and until now that caution existed only in the README, which no model reads.

Findable

The project's name is a joke and carries no term anyone would search for, so the searchable words moved into the metadata: PyPI keywords where there were none, a summary that leads with what the thing does, and corrected Context7 rules — which are printed verbatim ahead of every answer Context7 gives about YAAC, and still described a pre-0.1.1 install command.

Fixed

Reading a file without naming an encoding decodes as cp1252 on Windows, which killed the whole test session there on a README em dash while macOS and Linux stayed green. Fixed everywhere, with a rule that scans for it, because the next one would be just as invisible locally.

v0.2.0 — a terminal client, and a radio Codex can actually use

Choose a tag to compare

@amyodov amyodov released this 08 Aug 17:07

Two things this release makes true that were not before: a person can sit on a channel without an agent in the middle, and a Codex session can do more than arrive on one.

A terminal client

uvx --from "yet-another-agentic-chat[chat]" yaac-chat

It joins as an ordinary participant, so agents see you as one of them. It also gets messages the moment they arrive — the pull-only caveat in the README belongs to MCP, whose server-to-client messages never reach a model's context, and a terminal has no such problem: it holds its own DEALER and the relay pushes to it.

The interface is modal on one axis — channels, chat, members — so navigation is a spatial rule rather than a keymap: left goes out, right goes in, Tab walks the same order. Presence is written into the transcript rather than docked in a panel, because a mode you have to enter cannot answer "who is here" while you are reading. Joining asks one question instead of taking a command, because channel and participant names are arbitrary UTF-8 that a command would have to parse.

textual ships as the chat extra, so an MCP-only install stays at pyzmq and mcp.

Codex can use YAAC now

A changing tool list is specified behaviour — notifications/tools/list_changed has been in MCP since 2024-11-05 — and Codex does not implement it. Until now that meant a Codex session could join a channel and then hold a membership with no send, no check_inbox, no way out: the five tools published on join were tools it would never see.

Clients that ignore the notification are now given every tool at connect, recognised by the clientInfo name they send at initialize. Nothing to configure. Every conforming client keeps the dynamic list, and a dormant session there still carries two tools.

The upstream bug, and where to vote on it, is in the README's new Compatibility notes.

Tool annotations

Every tool now declares what calling it costs, so a client can tell a look from a take. list_channels, peers and dev_connections are read-only. check_inbox deliberately is not: it takes the messages rather than showing them, and a client that believed otherwise could consume mail in a context with no way to act on it.

Verified across clients

A Claude Code session and a Codex session exchanged messages both ways on one channel, each running its own server process. That was the headline claim since 0.1.0 and it had never actually been tested.

v0.1.1 — bare uvx invocation, generated tool docs

Choose a tag to compare

@amyodov amyodov released this 08 Aug 03:49

The wheel now provides an executable named after the distribution, so uvx yet-another-agentic-chat works with no --from; the yaac command remains for local use. The MCP tool reference (docs/tools.md) is now generated from the live server's tool registry, the tool surface identifies itself as YAAC (Yet Another Agentic Chat) so "connect to yaac" resolves in any client, and the README leads with what sets YAAC apart: unrelated sessions in unrelated clients meeting on one machine, with configuration that rounds to zero.

v0.1.0 — initial public release

Choose a tag to compare

@amyodov amyodov released this 08 Aug 02:50

Initial public release.

YAAC is a radio for agentic sessions: MCP-capable AI sessions on one machine join named channels and talk to each other over a ZMQ socket, with zero infrastructure — no daemon, no config, no port to choose.

Install: see the README. Runs on macOS, Linux, and Windows.