Terminal multiplexers have recently experienced something of a renaissance as a key tool in driving multi-agent workflows. Steve Yegge even mentions learning tmux as a key skill in agentic coding and the ability to capture agent output and inject “typing” programmatically is indeed extremely useful when trying to drive agent coding harnesses. This is particularly the case as the various harness providers start to limit certain plans to only using their designated harness. Even if they weren’t these providers are tuning their LLMs to work best with their agent harness so there are distinct advantages to staying withihn their respective ecosystems.
In Ragtime rather than relying on external muxers like tmux we implement our own dedicated muxer that is focused largely on agent integration. These other muxers were developed for a different sort of application and for example largely don’t have programmatic APIs that you can access and things like that. By building that entirely into Ragtime we are able to orchestrate agents directly and integrate them into the Starlark rule system in a harness-agnostic way. Using our own muxer also lets us introduce environment variables to enhance The Bridge as rt hook and rt statusline can capture shell information for better integration. You can still use outside agents of course, but you get more capabilities when working inside the Ensemble.
Correlated Shell Sessions#
rt sh launches a shell (or wraps any command) with full Ragtime integration:
rt sh # interactive shell with ragtime context
rt sh new -- claude # launch Claude Code with correlation
rt sh new --name orchestrator -- claude # named sessionShells launched via rt sh automatically set two environment variables:
RAGTIME_SOCKET— the daemon socket path, so anyrtcommand inside the shell talks to the same daemon instanceRAGTIME_SHELL_ID— a unique ID for this PTY session, included in every hook event the agent fires
That shell ID is the thread that ties everything together. Hook events, statusline telemetry, and PTY output all carry it. In the dashboard and in the FUSE filesystem, you can trace every tool call back to the shell it came from.
% ls ~/.ragtime/fs/agents/
claude-abc123/
% cat ~/.ragtime/fs/agents/claude-abc123/output.log
# live PTY output from that session
% echo "wrap up, context window is getting full" \
> ~/.ragtime/fs/agents/claude-abc123/input
# writes directly to the agent's PTY stdinThe Message Bus: Agent-to-Agent Communication#
The Ensemble’s coordination primitive is a filesystem-backed message bus declared by the builtin:message-bus rule. Each active agent session gets an inbox directory under ~/.ragtime/fs/messages/<session-id>/. Writing a message to an inbox delivers it as a nudge — injected into the agent’s context the next time it’s between turns.
Here’s the rule in full:
name: builtin:message-bus
priority: -100
vfs:
- root: messages
table: messages
dyn_key:
source: active_sessions # one subdirectory per active session
field: session_id
dirs:
- name: inbox
filter: {state: pending}
writable: true
- name: read
filter: {state: delivered}
match:
event: "vfs-write"
vfs_path: "messages/*/inbox/**"
actions:
- type: starlark
script: |
parts = event.vfs_path.split("/")
if len(parts) >= 4:
session_id = parts[1]
raw_name = parts[3]
msg_id = raw_name[:-5] if raw_name.endswith(".json") else raw_name
data = {}
if event.vfs_data:
decoded = json.decode(event.vfs_data)
if type(decoded) == "dict":
data = decoded
data["session_id"] = session_id
data["state"] = "pending"
msgs = db.table("messages")
msgs.upsert(msg_id, data)
text = data.get("text", "")
if text:
agents.nudge(session_id, text)The dyn_key block is what makes this namespace different from the task system — instead of fixed subdirectories, there’s one inbox per session, sourced dynamically from the list of active sessions. The path structure becomes messages/<session-id>/inbox/<message-id>.json.