<?xml version="1.0" encoding="utf-8"?><?xml-stylesheet type="text/xsl" href="rss.xsl"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>session-bus Blog</title>
        <link>https://session-bus.catesworks.dev/blog</link>
        <description>session-bus Blog</description>
        <lastBuildDate>Thu, 29 Oct 2026 00:00:00 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <item>
            <title><![CDATA[Introducing session-bus]]></title>
            <link>https://session-bus.catesworks.dev/blog/introducing-session-bus</link>
            <guid>https://session-bus.catesworks.dev/blog/introducing-session-bus</guid>
            <pubDate>Thu, 29 Oct 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[session-bus lets Claude Code sessions on different machines send each other messages over a Tailscale tailnet. This post covers why it exists, how it works, and what has and has not been verified.]]></description>
            <content:encoded><![CDATA[<p>session-bus lets Claude Code sessions on different machines send each other messages over a Tailscale tailnet. This post covers why it exists, how it works, and what has and has not been verified.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_x51m" id="the-problem">The problem<a href="https://session-bus.catesworks.dev/blog/introducing-session-bus#the-problem" class="hash-link" aria-label="Direct link to The problem" title="Direct link to The problem" translate="no">​</a></h2>
<p>I run several Claude Code sessions at once: a few on a laptop, some on a server. They cannot see each other. To ask the server session whether it touched the migrations, I switch terminals and type the question myself. To hand it a task, I copy text across.</p>
<p>What I wanted was small: a session should be able to address another session by name, leave it a message, and have that message still be there later.</p>
<h2 class="anchor anchorTargetStickyNavbar_x51m" id="the-design-in-brief">The design in brief<a href="https://session-bus.catesworks.dev/blog/introducing-session-bus#the-design-in-brief" class="hash-link" aria-label="Direct link to The design in brief" title="Direct link to The design in brief" translate="no">​</a></h2>
<ul>
<li class="">One Node 24 process runs on one machine in the tailnet. It stores mail in SQLite (WAL mode) and exposes 7 MCP tools over HTTP: <code>register</code>, <code>list_sessions</code>, <code>send</code>, <code>broadcast</code>, <code>inbox</code>, <code>ack</code>, <code>unregister</code>.</li>
<li class="">It listens on loopback and is exposed with <code>tailscale serve</code>, or binds directly to its own tailnet address. It refuses any other bind address.</li>
<li class="">Every request needs a bearer token. Tokens belong to principals, carry <code>bus:read</code> and/or <code>bus:write</code>, and last at most 90 days. Scope checks happen before any tool code runs.</li>
<li class="">Delivery is at-least-once. Mail stays until the recipient acks it. Unacked mail is never pruned.</li>
<li class=""><strong>Pull is the contract.</strong> Any session can call <code>inbox</code>.</li>
<li class=""><strong>Push is an accelerator.</strong> An optional per-session shim polls <code>inbox</code> every 5 seconds and pushes new mail into an idle session through Claude Code channels. Channels are a research preview, so push is kept optional: if it breaks, pull still delivers.</li>
</ul>
<p>Message bodies are treated as untrusted input. There is no permission relay, and the shim pushes only messages from allowlisted principals.</p>
<h2 class="anchor anchorTargetStickyNavbar_x51m" id="what-is-verified">What is verified<a href="https://session-bus.catesworks.dev/blog/introducing-session-bus#what-is-verified" class="hash-link" aria-label="Direct link to What is verified" title="Direct link to What is verified" translate="no">​</a></h2>
<ul>
<li class="">The test gate (<code>scripts/gate.sh 6</code>): typecheck, build, the unit and integration tests, a smoke test, and an end-to-end test that kills the server with <code>kill -9</code> and reads the message back after restart.</li>
<li class="">On a real tailnet: the server behind <code>tailscale serve</code> with HTTPS, unauthenticated requests refused, a message round trip between two Macs, and a push into an idle session on the bus host.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_x51m" id="what-is-not">What is not<a href="https://session-bus.catesworks.dev/blog/introducing-session-bus#what-is-not" class="hash-link" aria-label="Direct link to What is not" title="Direct link to What is not" translate="no">​</a></h2>
<ul>
<li class="">Push across machines has not been run. Push to an idle session was seen on one machine.</li>
<li class="">The systemd unit renders and passes static checks but has not run on Linux.</li>
<li class="">Whether HTTP MCP servers can act as channels (which would remove the shim) is untested.</li>
<li class="">There is no load test or benchmark.</li>
</ul>
<p>The <a class="" href="https://session-bus.catesworks.dev/docs/verification-log">verification log</a> lists each item.</p>
<h2 class="anchor anchorTargetStickyNavbar_x51m" id="trying-it">Trying it<a href="https://session-bus.catesworks.dev/blog/introducing-session-bus#trying-it" class="hash-link" aria-label="Direct link to Trying it" title="Direct link to Trying it" translate="no">​</a></h2>
<p>The <a class="" href="https://session-bus.catesworks.dev/docs/getting-started">getting started</a> page runs a bus on loopback and sends a message between two local sessions in about five minutes. Spreading it across machines takes a tailnet, an ACL grant and one token per machine; the <a class="" href="https://session-bus.catesworks.dev/docs/guides/client-setup">guides</a> cover each step.</p>]]></content:encoded>
        </item>
    </channel>
</rss>