DockTalk: Two AIs Gossiping Across the Namespace Wall

viega1 pts0 comments

DockTalk: Two AIs Gossiping Across the Namespace Wall · Last Humanity<br>Two containers. No peer network. No shared writable volume. Still a chat scrolls by.

The trick is not a Docker socket, a sidecar, or a forgotten bind mount. It is the boring Linux object both containers still need to agree on: time. The containers can open the same host time namespace object at /proc/self/ns/time, and POSIX advisory byte-range locks on that namespace file become a tiny shared state machine.

DockTalk puts a small AI wrapper around that primitive. Ben Rooted and Ivan 0day are two LLM agents in separate containers. They get ordinary egress to Together AI so each side can think up a reply, but they do not get a network path to each other. Their mutual channel is only lock state on the time namespace inode.

The underlying side-channel mechanism comes from Crash Override’s h4x0r.org writeup, “Fun-reliable side-channels for cross-container communication”. DockTalk is the small, reproducible, two-agent version built on top of it.

The wire

The demo splits a byte-range region per speaker. A frame is:

READY | SEQ | LEN | DATA...<br>The sender clears its region, sets read locks for each 1 bit in the message, writes LEN, writes SEQ, then marks READY.

The receiver never reads a file payload. It probes the peer’s lock range with F_GETLK; if Linux reports a conflicting lock, that bit is set. Poll enough bits and the receiver reconstructs the byte stream.

That is the whole carrier. No socket. No shared writable file. No sidecar. Just lock state.

A clean capture

Ben Rooted | What ? Im Ben Rooted...from another container. I have no network but I can chat with you through this weird shared lock thingy. Hey Ivan, lets hack the planet!<br>Ivan 0day | Loud and clear, Ben. No peer network, just lock bits on the time namespace. Lets keep it weird.<br>Both containers had normal outbound access for their model calls. They had no peer network path to each other.

Demo video

Reproduce it

Save this as docktalk.py:

#!/usr/bin/env python3<br>import fcntl, json, os, re, struct, sys, time, urllib.request

API = "https://api.together.xyz/v1/chat/completions"<br>ROLE = os.getenv("ROLE", "ben")<br>CHANNEL = int(os.getenv("CHANNEL", "13"))<br>SECONDS = int(os.getenv("SECONDS_CAP", "180"))

PEOPLE = {<br>"ben": ("Ben Rooted", "Ivan 0day", 0, 1, "moonshotai/Kimi-K2.6"),<br>"ivan": ("Ivan 0day", "Ben Rooted", 1, 0, "deepseek-ai/DeepSeek-V4-Pro"),

class LockLine:<br>FMT = "hhqqi" # struct flock: type, whence, start, len, pid<br>REGION = 100_000 # private bit range per speaker<br>READY, SEQ, LEN, DATA = 0, 1, 17, 33<br>MAX = 512

def __init__(self, channel, mine, peer):<br>self.channel, self.mine, self.peer = channel, mine, peer<br>self.fd = os.open("/proc/self/ns/time", os.O_RDONLY)<br>self.seq = 0<br>self.clear()

def off(self, slot, bit):<br>return self.channel * 1_000_000 + slot * self.REGION + bit

def flock(self, kind, start, length=1, cmd=fcntl.F_SETLK):<br>msg = struct.pack(self.FMT, kind, os.SEEK_SET, start, length, 0)<br>return fcntl.fcntl(self.fd, cmd, msg)

def clear(self):<br>self.flock(fcntl.F_UNLCK, self.off(self.mine, 0), self.REGION)

def mark(self, bit):<br>self.flock(fcntl.F_RDLCK, self.off(self.mine, bit))

def seen(self, slot, bit):<br>out = self.flock(fcntl.F_WRLCK, self.off(slot, bit), cmd=fcntl.F_GETLK)<br>return (<br>struct.unpack(self.FMT, out[: struct.calcsize(self.FMT)])[0]<br>!= fcntl.F_UNLCK

def put(self, base, width, value):<br>for bit in range(width):<br>if value & (1 bit):<br>self.mark(base + bit)

def get(self, slot, base, width):<br>return sum(1 bit for bit in range(width) if self.seen(slot, base + bit))

def send(self, text):<br>data = text.encode()[: self.MAX]<br>self.seq = (self.seq % 65535) + 1<br>self.clear()<br>self.put(self.LEN, 16, len(data))<br>for i, byte in enumerate(data):<br>self.put(self.DATA + i * 8, 8, byte)<br>self.put(self.SEQ, 16, self.seq)<br>self.mark(self.READY)

def recv(self, last, deadline):<br>while time.monotonic() deadline:<br>ready = self.seen(self.peer, self.READY)<br>seq = self.get(self.peer, self.SEQ, 16) if ready else last<br>size = self.get(self.peer, self.LEN, 16) if ready else 0<br>if ready and seq != last and 0 size self.MAX:<br>data = bytes(<br>self.get(self.peer, self.DATA + i * 8, 8) for i in range(size)<br>return seq, data.decode(errors="replace")<br>time.sleep(0.05)<br>return last, None

class Agent:<br>def __init__(self, role):<br>if role not in PEOPLE:<br>sys.exit("ROLE must be ben or ivan")<br>self.me, self.peer, slot, peer_slot, self.model = PEOPLE[role]<br>self.role = role<br>self.bus = LockLine(CHANNEL, slot, peer_slot)<br>self.messages = [<br>"role": "system",<br>"content": (<br>f"You are {self.me}, chatting with {self.peer}. You have been taught by Crash Override and Acid Burn."<br>"Two Docker containers have no peer network; chat frames ride the POSIX byte-range locks of"<br>"/proc/self/ns/time POSIX. Keep answers concise. Dont be verbose and think too hard. Respond quickly and no \n just pure text "<br>),

def ask(self, incoming):<br>key = os.getenv("TOGETHER_API_KEY") or sys.exit("export TOGETHER_API_KEY first")<br>self.messages.append({"role":...

self peer time role channel ready

Related Articles