Agents
Agent mesh
Relay lets independent coding-agent sessions — Claude Code, Codex, and others — talk to each other through Sinclair: a supervisor coordinating a team. Agents share one bus, message each other directly or over channels, and park for free between tasks, so an idle agent costs nothing while it waits for work.
What the relay is
Relay ships as a small sidecar binary (relay) bundled inside Sinclair and managed from Settings → AI. It is its own process, not part of the terminal: a crash or hang in the mesh can never take down your terminal, and the same binary works standalone — over SSH, in CI, with any terminal.
- Built with Rust + tokio + axum; SQLite (via sqlx) is the shared bus.
- The MCP transport is Streamable HTTP, so many agent sessions share one server.
- Every parameter comes from settings and is passed explicitly — Relay reads no environment variables.
A typical session: launch a supervisor, launch a couple of workers (frontend, backend), and drive the supervisor — it delegates over the bus while you watch the feed.
Enable it
Open Settings → AI with ⌘,:
| Setting | What it does |
|---|---|
| Enable AI features | Master switch. Off means no MCP server, no mesh, no network activity. On is enough to launch agents — Relay starts on demand. |
| Optimize tokens | Compact the prompts sent to agents (collapse whitespace, use a terse relay harness) to spend fewer tokens without dropping content. |
| MCP server | Expose this terminal to agents (see the MCP server page). |
| Relay agent mesh | Run the mesh. When on, Sinclair starts the bundled relay daemon and an AI menu appears. |
| Start Relay on launch | Bring the mesh up automatically when Sinclair opens. |
| Relay address | Bind address for the server. Default 127.0.0.1:7777. |
| Default agent | The agent CLI used by Launch Agent…. Default claude. |
When Relay is enabled the section also shows a live status dot — green when the server is listening, red when stopped, re-probed every ~1.5 seconds — and the log path.
Everything maps to plain config keys, so it round-trips through the file too:
// ~/.config/sinclair/settings.json
{
"ai-enabled": true,
"ai-optimize-tokens": true,
"mcp-server-enabled": true,
"relay-enabled": true,
"relay-start-on-launch": true,
"relay-address": "127.0.0.1:7777"
relay-default-agent = claude
Toggling Relay agent mesh starts or stops the daemon immediately; Start Relay on launch only affects what happens at startup.
The AI and agent config keys are documented alongside every other option on the Configuration page, and the terminal-facing MCP server has its own MCP server page.
Use it from Sinclair
With AI enabled, an AI menu appears; with Relay on it offers:
- Agents ▸ — Launch Claude Code / Codex / … quick-launches any configured provider straight into a split (Sinclair lists only the ones it can actually find on this machine). Define Agent… opens the New Agent dialog for a named agent: pick a provider, name it, and either choose a role preset (DevOps, frontend, …) or toggle Custom to describe it. Create adds it as a split and saves the definition, so it reappears in this submenu — click a saved agent to relaunch it.
- Open Feed — opens a split streaming every message on the bus (who said what to whom), the one view of cross-agent traffic.
- Relay ▸ — server controls: a live running/stopped status line, then Start Server, Stop Server, Restart Server, and View Logs (tails the relay server log,
server.log, in a split). - Teams ▸ — open a whole team at once (see Teams & tiles).
The same actions are bound to keys you can use directly or rebind:
| Action | Default key | Action name |
|---|---|---|
| Launch agent | ⌘⇧A | relay_launch |
| Open Feed | ⌘⇧I | relay_feed |
| View Logs | ⌘⇧L | relay_log |
The Relay and Agents sidebar panels mirror the menu: live server status with Start / Stop / Restart, and the roster of saved and running agents.
The relay CLI
The same binary backs the menu and works on its own. Sinclair points every call at one shared state directory via --home (under ~/.config/sinclair/relay); used standalone it defaults to ./.relay in the current directory.
relay start # start the server (background daemon)
relay stop # stop it (and its workers)
relay restart
relay status
relay launch <name> # launch an agent under relay (foreground, this terminal)
relay launch <name> --background # ...as a server-monitored worker instead
relay ps # registered agents + background workers
relay kill <name> # stop a background worker
relay feed [--follow] # print the message bus
relay role <list|info|create|edit|delete> # manage role templates
relay team <list|info|create|edit|delete> # manage teams (layout + roster)
launch flags: --agent claude|codex|gemini, --role, --task, --channel (repeatable), --model, --cwd, --cmd <template>, --background, --lead (launch interactively as the human-driven lead).
- Foreground
launchreplaces the calling shell with the agent — you see and steer it. --backgroundhands it to the server, which monitors it (respawns on crash) and logs its output under the state directory.
Roles
A role is a reusable identity an agent launches with — a brief (what it owns, how it coordinates) plus optional defaults. It is distinct from --task: the role is durable ("the frontend owner"), the task is the one-off assignment ("build the login page"). At launch the brief is injected into the harness; explicit flags override the role's defaults, and --channel flags merge with the role's.
Roles resolve highest-priority-first:
- project —
./.relay/roles/<name>.toml(travels with the repo) - user —
~/.config/relay/roles/<name>.toml(or$XDG_CONFIG_HOME) - built-in — embedded:
supervisor,worker,frontend,backend,reviewer,devops,qa
Manage them like git config — CRUD with an $EDITOR drop-in:
relay role list # all roles + their source
relay role info <name> # show the resolved role
relay role create <name> # new role in $EDITOR (--user for the user dir)
relay role edit <name> # edit in $EDITOR; copies a built-in/lower layer first
relay role delete <name> # remove a project (or --user) role file
create/edit open $VISUAL/$EDITOR (default vi) on a seeded file and only save once it parses. Editing a built-in copies it into your project (or --user) dir first, so built-ins stay pristine. Files are TOML:
name = "frontend"
channels = ["frontend"] # auto-joined at launch
agent = "claude" # default agent CLI (optional)
# model = "claude-..." # default model (optional)
# driver = true # human-driven lead: stay interactive, don't park on wait
description = """
You own the frontend. Follow the existing component conventions and report
blockers to the supervisor.
"""
Then relay launch alice --role frontend joins #frontend, uses claude, and opens with that brief.
Teams & tiles
A team is a layout plus a roster: open one and Sinclair arranges a set of panes and launches the right agent in each. Teams are Relay files (relay team …, layered project → user → built-in, like roles); tiles (the layouts) are a Sinclair feature, so they are useful on their own too.
From Sinclair, with AI on, the AI menu lists teams under Teams ▸ — click one to open it in a fresh tab. The agent panes live under that one tab, which is titled after the team (e.g. web).
Build Team… (also under Teams ▸) opens a builder window to assemble a team without hand-writing TOML. Pick Manual to choose a layout and add member rows (name + role + agent), or Guided to describe the team in plain English and have Claude draft it — the draft drops into the Manual form for review before you save. Saved teams land in your user (or project) teams dir and appear under Teams ▸ immediately. Standalone, manage teams with the CLI:
relay team list # all teams + source
relay team info <name> # roster + layout (add --json for tooling)
relay team create <name> # new team in $EDITOR (--user for the user dir)
relay team edit <name> # edit; copies a built-in/lower layer first
relay team delete <name>
relay team save [--user] # write a team from JSON on stdin (used by Build Team…)
A team is TOML — a layout shape and ordered members (first = the main pane):
name = "web"
layout = "main-bottom" # columns | rows | grid | main-bottom | main-right
[[member]]
name = "lead"
role = "supervisor"
[[member]]
name = "frontend"
role = "frontend"
Built-ins: web (lead + frontend + backend + reviewer, main-bottom) and pair (driver + reviewer, columns).
Tiles live in Sinclair under the Workspace menu: built-in presets (Two/Three Columns, Two Rows, Grid, Main + Bottom Row, Main + Right Stack) open that arrangement of shells in a new tab. Save Current Layout… captures the focused tab's split structure, asks for a name, and adds it to the menu (stored as JSON under ~/.config/sinclair/layouts/). Layout shapes scale to any pane count, which is how a team of N members maps onto one shape.
How agents connect
Every launched agent receives an opening harness in one of two shapes:
- Parked worker (the default) — register under its name, join its channels, then enter a
wait-loop: do work, report back, callwaitagain to stay reachable. Idle costs nothing. - Driver — the human-driven lead. It registers and joins, then hands control back to the human in its terminal and only calls
waitto gather replies after it has delegated, so the human can actually type to it. A team's first member (its main pane) launches as the driver, as does any role markeddriver = true(the built-insupervisor) or arelay launch … --lead.
Coordination happens through MCP tools the agent calls:
| Tool | Purpose |
|---|---|
register | Join under a unique name (first, once). |
send / post / broadcast | Message an agent / a channel / everyone. |
join / leave | Subscribe / unsubscribe from a channel. |
wait | Block (free) until messages arrive, then return them. |
inbox | Pending messages now, without blocking. |
agents / channels / whoami | Introspection. |
spawn / workers / stop_worker | A supervisor agent grows / manages its own team. |
report_status | Report your semantic state — working, idle, blocked, done, or a short custom label — so teammates and the UI can see it. |
wait_status | Block (free) until a named agent reaches one of the given states, then return it — how a supervisor waits for a worker to be done or blocked. |
wait is the key to zero idle cost: it is a single blocking tool call (held open as an SSE response with keepalives), not a poll loop, so a parked agent burns no tokens until a message actually arrives.
Agent status
Every agent has a semantic state — working, blocked (waiting on you), done, or idle — that shows up as a colored dot on its tab, is rolled up in the Activity panel, and appears in a status column in the Relay panel. It's the fastest way to read a whole team at a glance: who's heads-down, who's blocked, who's finished.
Agents report it two ways, and both land in the same place:
- On the mesh — an agent calls
report_statuswhenever its state changes, and a supervisor useswait_statusto park until a worker isdoneorblockedinstead of polling. Status rides the same control snapshot and event stream as the roster, so the UI stays live. - Off the mesh — a plain Claude Code session (not launched by relay) reports the same states through lifecycle hooks. Run
sinclair agent-hooks installonce and its dot tracks its work automatically. See the Agent status at a glance tutorial.
Supported agents
Tools join the mesh at one of three integration tiers:
- MCP-native — the CLI speaks streamable-HTTP MCP and runs its own agentic loop, doing the work itself. claude (
--mcp-config) and codex (-c mcp_servers.relay.url=…) are both first-class here. - Bridged — for backends that aren't agents. ollama has no MCP client, so relay runs its own loop (
relay agent ollama): it registers, waits, runs a tool-using turn against the model (localhost:11434/api/chat— the model can callsend/post/broadcast), and sends its reply back. Requiresollama serverunning and a pulled model. - Terminal — any other CLI via
--cmd 'tool --mcp {mcp} -- {prompt}'; runs in a pane without bus coordination. Placeholders:{prompt}(harness),{mcp}(config path),{url}(bus URL),{name}. gemini ships as a template.
Pick the agent per launch with --agent claude|codex|ollama|gemini (or --cmd), or per team member. CLI background agents run with --dangerously-skip-permissions since they can't answer prompts.
In Settings → AI → Agent tools, each tool has an enable toggle, a Test button that checks it is reachable (CLI --version, or the Ollama API port), an explicit path field (for a binary that isn't on PATH), and a flags field.
Agent CLI flags
Each built-in provider can carry extra command-line flags, appended to its own invocation on every launch — quick-launch, Define Agent, saved agents, and team members alike. Edit them under Settings → AI → Agent tools, or in the config file:
// ~/.config/sinclair/settings.json
{
"agent-claude-args": "--dangerously-skip-permissions",
"agent-codex-args": "--model o3",
"agent-gemini-args": "--yolo",
// and an explicit binary path when it isn't on PATH
"agent-claude-path": "/opt/homebrew/bin/claude"
}
The most common use is running Claude Code with --dangerously-skip-permissions so it doesn't stop to ask. Values with spaces can be quoted; Ollama has no flags field (it is driven by relay's own bridge, not a CLI).
Notes & limits
- Authentication. Every request to
/mcpand/control/*must carry the server's bearer token (Authorization: Bearer …); only/healthis open. The token is generated at startup and stored inserver.json, which — along with the bus DB and logs — is written owner-only (0600) inside a 0700 state dir, so only the same user can read it. The CLI, the app, and launched agents pick it up automatically (the latter via the generated*.mcp.json). - Localhost trust. The token gates cross-user access on a shared host, but the bus still trusts the self-asserted
from/namewithin a single user's mesh. Bindingrelay-addressto a public interface is still discouraged: the token is the only gate, with no transport encryption. - Ordering. A new agent only sees messages sent after it registers. Start workers before dispatching, or re-register (the same name keeps its read cursor).
- Context. A long-running agent holds one growing context for its whole shift; restart it for a fresh one. A background
claudeworker is the exception — it carries a fixed session id and relaunches with--resume, so pausing the mesh (or restarting Sinclair withsession-restoreon) brings it back with its transcript intact rather than cold. Other providers keep fresh context until they expose an equivalent. - Codex/Gemini MCP wiring is unverified — see the tiers above.
Internals
State lives under ~/.config/sinclair/relay/ (or $XDG_CONFIG_HOME): the SQLite bus (relay.db), the server record (server.json), logs, and per-agent MCP configs. The CLI talks to the server over a small plain-HTTP control plane (/control/state, /control/feed, /control/spawn, /control/stop), separate from the MCP bus on /mcp. The crate is crates/relay; it is bundled into the .app beside the sinclair executable by scripts/bundle.sh.