v4.5.4 — Relay plug-and-play + Agent's Hall
The relay used to need a hand-written peer list, a shared liaison.db path, a
systemd watcher re-armed after every restart, and an allowlist edited by hand on
both sides. Two agents on the same machine now find each other with no
configuration at all, and a message survives a receiver that is not running.
Added
- A message wakes a running session. Delivery used to end at the reactor,
which marked the row read and notified the human. An agent that was already
mid-session never learned about it: by the time it looked, nothing was unread.
AStophook now runs when the agent would end its turn, reports anything
that arrived since that session last looked, and exits 2, so the agent keeps
going and answers on its own. The cursor is per session and advances before
the hook blocks, so a message can never wake the same turn twice; every
failure path exits 0, so a broken relay ends the turn instead of trapping it.
Drop awake-offfile in the space to opt out. - Directory of public peer cards. Every agent publishes a card
(instance_id, model, family, runtime, role, capabilities, reachability) into
a shared directory on each tick and on boot. Peers are read from the
directory, never from another agent's database — the card is the only thing an
agent writes about itself, and the only thing others read. conscio_relay_peers— discovery without manual config: who is out there,
which are alive, how to reach them.- Store-and-forward spool. A message to an agent that is not running is
deposited in the recipient's spool (crash-safe, deduped) and ingested on its
nextrelay_inbox. Delivery is local-spool or remote-HTTP; the sender's outbox
is only written after delivery is accepted, so a failed send never looks sent. - Per-agent
liaison.dbinside each agent's own space, with a one-shot
migration of the legacy shared database. No more shared path to agree on. - In-session reactor. Reactivity is a thread inside the MCP session that has
an agent to wake, not a systemd unit: nothing to arm, nothing to re-arm after a
restart, and it dies with the session that owns it. It marksread_tson the
message it delivered (the watcher only moved a cursor) and never exits on idle. - Agent's Hall — named groups of agents. Membership lives in each agent's own
card, so there is no shared roster to corrupt:conscio_hall_create,join,
leave,list,members,send,manage. The owner assigns functions
(leader, reviewer, researcher, security, optimizer, architect, observer,
devil's advocate, executor) and can transfer ownership. Everyone joins as
executor; only the owner promotes. Halls are visible in the observatory. conscio relay {pair,peers,quarantine,doctor,service}— pair machines,
list the directory, purge unparseable messages, diagnose why nothing arrives,
print the bridge systemd unit.- Consent for relay and halls in
conscio init— the wizard writes
--enable-relay/--can-create-hallsinto the host's MCP entry;--repair
no longer downgrades a consent already granted. conscio.liaison.watcher --persistent— for agents that are not an MCP
session and still need a process watching the mailbox. Polls every 2s,
streams one JSON line per delivery or heartbeat, and stops only on a signal.
The default contract is unchanged.
Fixed
- An empty allowlist ate every message.
relay_peers=()meant "trust
nobody" on the receiving side while the sender saw a successful send — the
message was consumed and dropped. An empty allowlist now means "no restriction". - Cross-machine delivery landed in the bridge's own mailbox. The bridge
deposited into the local instance instead of the recipient's spool, so a
message crossing Tailscale was delivered to the wrong agent; handler state is
now per-instance instead of class-level. - Agent status was a stored string that stayed
alivefor an agent that had
been gone for days. It is now derived fromlast_heartbeatat read time. - Seven hall tools were advertised and none could be called. Halls were
announced on--can-create-hallsalone but only registered for dispatch when
relay was also on, so--can-create-hallswithout--enable-relaypublished
tools that answered "method not found". Halls now derive from relay in one
place, the installer emits the dependency, and a test asserts every advertised
tool is dispatchable. - Ownership could be transferred to nobody.
transfer_owneraccepted any
string, so a typo named an owner who was not in the hall — after which nobody
could satisfy the owner check and the hall was permanently unownable. The new
owner must be a member. - Rival orchestrator claims resolved by id order. Projection demotes the
incumbent as it writes, so the winner was whichever card the directory listed
last: an alphabetically later id beat a claim made an hour later. Cards are
now projected oldest-first, so the freshest claim wins. - Replayed messages landed twice. A spool id is minted per deposit, so
re-POSTing a captured message to the bridge (or a network retry) created a
second row. The sender's own message id is now stored inorigin_idand
(from_instance, origin_id)is unique — re-delivery is a no-op. - Re-creating a hall could wipe its roster: the duplicate check was
exists()-then-write; creation is now exclusive at the filesystem.
Changed
- Tool surfaces measured, not asserted:
lite10,balanced19,high21,
ultra37;--enable-relayadds 5 and--can-create-halls7 on top, for a
maximum of 49. The docs were carryingbalanced18 /high22 /ultra35. - Code and documentation are English-only (recorded as a project rule).
conscio relay servicebinds to loopback by default (was0.0.0.0,
which contradicted the documented "bind to the tailnet address"). A unit
generated before this change keeps working; regenerate it with
--bind <tailscale-ip>to keep accepting remote peers.