Skip to main content
Glama

Server Configuration

Describes the environment variables required to run the server.

NameRequiredDescriptionDefault
RELAY_DB_PATHNoPath to SQLite database file (default '~/.bot-relay/relay.db').~/.bot-relay/relay.db
RELAY_HTTP_HOSTNoHost for HTTP daemon (default 127.0.0.1).127.0.0.1
RELAY_HTTP_PORTNoPort for HTTP daemon (default 3777).3777
RELAY_TRANSPORTNoTransport mode: 'stdio' (default), 'http', or 'both'.stdio
RELAY_AGENT_TOKENNoPer-agent token for authentication; issued on first registration.
RELAY_HTTP_SECRETNoShared secret for HTTP authentication; required for non-loopback hosts in production.
RELAY_ALLOW_LEGACYNoSet to '1' to allow tool calls against unmigrated pre-v1.7 agents (temporary).0
RELAY_SPAWN_EFFORTNoEffort level for spawned agents (e.g., 'high', 'xhigh').high
RELAY_TERMINAL_APPNoOverride for terminal app in spawn_agent (allowlist-gated).
RELAY_SQLITE_DRIVERNoSQLite driver: 'native' (default, better-sqlite3) or 'wasm' (sql.js).native
RELAY_ENCRYPTION_KEYNoLegacy single encryption key (base64-32); auto-wraps to keyring with deprecation warning.
RELAY_SPAWN_KICKSTARTNoCustom kickstart prompt for spawned agents (default auto-pulls messages).
RELAY_TRUSTED_PROXIESNoComma-separated CIDRs for trusted reverse proxies (e.g., '127.0.0.0/8,::1/128').
RELAY_DASHBOARD_SECRETNoSecret for dashboard access; falls back to RELAY_HTTP_SECRET if not set.
RELAY_ALLOW_OPEN_PUBLICNoSet to '1' to allow starting on public hosts without RELAY_HTTP_SECRET (dev only).0
RELAY_ENCRYPTION_KEYRINGNoInline JSON for encryption keyring (e.g., '{"current":"k1","keys":{"k1":"<base64>"}}').
RELAY_SPAWN_DISPLAY_NAMENoCustom display name for spawned agents (default uses agent name).
RELAY_SPAWN_NO_KICKSTARTNoSet to '1' to disable kickstart prompt for spawned agents.0
RELAY_HTTP_SECRET_PREVIOUSNoComma-separated list of previous secrets for rotation without downtime.
RELAY_SPAWN_PERMISSION_MODENoPermission mode for spawned agents (e.g., 'bypassPermissions', 'auto').bypassPermissions
RELAY_ENCRYPTION_KEYRING_PATHNoFile path to encryption keyring JSON (e.g., '~/.bot-relay/keyring.json').
RELAY_ENCRYPTION_LEGACY_KEY_IDNoKey ID for decrypting legacy 'enc1:...' rows (default 'k1').k1

Instructions

Guidance the server publishes about itself, which clients place ahead of the tool catalog so the model reads it before choosing anything.

This server publishes no instructions, or was last inspected before Glama recorded them.

Capabilities

Features and capabilities supported by this server

Protocol revision2025-11-25

CapabilityDetails
tools
{}
prompts
{}
resources
{
  "subscribe": true
}

Tools

Functions exposed to the LLM to take actions

NameDescription
register_agentA

Register this terminal as a named agent so other agents can address it.

When to use: call this first thing in any session that needs to send/receive messages, post tasks, or join channels. Idempotent upsert, safe to call again on reconnect. The SessionStart hook (hooks/check-relay.sh) typically calls it for you.

Behavior: creates or updates the agent row keyed by name. First registration mints a fresh agent_token (returned ONCE, store it in RELAY_AGENT_TOKEN). Re-registering preserves the existing token unless recovery_token is presented (v2.1 Phase 4b.1 v2 recovery flow). Capabilities are immutable on re-register (v1.7.1), use expand_capabilities for additive changes.

Returns: { success: true, agent: AgentWithStatus, protocol_version, message }. First-time registration also includes agent_token (shown ONCE — store in RELAY_AGENT_TOKEN) and auth_note. If the request asked for capabilities that differ from the stored set, capabilities_note explains the immutability. If queued auto-routed tasks were assigned at register time, auto_assigned_tasks: { task_id, title, priority }[] lists them. Successful recovery flow includes recovery_completed: true.

Errors: AUTH_FAILED (recovery_pending row presented without recovery_token), RECOVERY_REQUIRED (token rejected, present recovery_token), INVALID_INPUT (name/role/capabilities malformed), RATE_LIMITED.

discover_agentsA

List every registered agent with computed presence + operator-controlled status.

When to use: pick a routing target by role (e.g., 'any builder'), confirm an expected agent is online before sending it work, or surface the agent fleet to a dashboard. For periodic team rollups use get_standup instead, it bundles agents + recent activity in one call.

Behavior: pure read; never mutates last_seen (v1.3 presence-integrity fix). Optionally filters by role. The returned status (online | stale | offline) is computed from last_seen deltas; the returned agent_status (idle | working | blocked | waiting_user | stale | offline | abandoned | closed) is operator-controlled via set_status. Token hashes are stripped, has_token: boolean only.

Returns: { agents: AgentWithStatus[], count: number, filter: { role } | 'none' }. Ordered by last_seen DESC.

Errors: RATE_LIMITED. (No auth required, this surface is intentionally observable for orchestration.)

unregister_agentA

Remove an agent row so the relay reflects true presence after a clean shutdown.

When to use: terminal exit, role rotation, or one half of the recovery flow when reusing a name after revoke_token set the row to revoked. For graceful working-state announcements without removing the row, use set_status with offline instead.

Behavior: deletes the agent row + all messages and tasks the agent was the from/to of (cascade). Idempotent, unregistering a name that does not exist returns removed:false instead of an error. Auth: requires the agent's own token, OR for admin removals an authenticated agent with manage_others capability.

Returns: { success: true, name, removed: boolean, note }. removed=false indicates the name was already absent (idempotent no-op) and is NOT an error.

Errors: AUTH_FAILED (token missing or wrong owner), INVALID_INPUT.

abandon_registrationA

Self-clean YOUR OWN botched (orphaned) registration when you lost the agent_token before ever authenticating — e.g. a curl/script caller truncated the register response. Authenticated by the one-time registration_recovery handle returned in the register_agent response (NOT the lost token), so it needs no auth token.

When to use: you registered but never captured/used the token, and the row is now an orphan you can't unregister (unregister needs the token you lost). NOT for a live agent that lost its token mid-session — that agent has authenticated, so this is refused; use rotate_token / relay recover instead.

Behavior: verifies the registration_recovery handle (bcrypt, name-scoped, one-time, TTL-bound) and — ONLY if the target row has NEVER authenticated (the keystone) — deletes it, bumps the auth generation, and fires an agent.unregistered webhook. The keystone is re-asserted inside the DELETE, so it can never reach a working agent (a row that authenticates between check and delete is left intact) — the safe, self-serve alternative to the operator kill endpoint. Orphans are also auto-GC'd after ~30min (never-authed + session-less + older than the orphan TTL) as a backstop.

Returns: { success, name, abandoned }. Errors (AUTH_FAILED): agent has authenticated (not an orphan), invalid/expired handle, or no such registration.

spawn_agentA

Open a new Claude Code terminal pre-configured as a relay agent (macOS only).

When to use: orchestrators delegating work to a fresh sub-agent. The new terminal arrives in a known role + capability set with RELAY_AGENT_NAME/ROLE/CAPABILITIES already in env, and the SessionStart hook auto-registers it before the LLM's first turn. Linux/Windows drivers exist for headless smoke tests but do not open a UI window.

Behavior: pre-registers the new agent server-side (so its token is minted before the child process starts and reaches it via env), opens an iTerm2 or Terminal.app window via AppleScript, and runs the configured shell command in that window. Optional initial_message is queued in the new agent's mailbox and surfaces on its first get_messages. brief_file_path (v2.1.4) threads a durable on-disk task brief into the KICKSTART prompt, preferred over initial_message for non-trivial scopes because file-on-disk does not read as prompt-injection the way an inbox message can.

Returns: { success: true, name, role, capabilities, platform, driver, agent_token, auth_note, has_initial_message, brief_file_path, note }. agent_token is shown ONCE — already written to the per-instance file vault at <instanceDir>/agents/<name>.token (v2.6.1). The spawned terminal's launcher reads the vault before exec'ing claude (macOS / Linux), and the stdio MCP server transport's resolveToken falls back to RELAY_AGENT_TOKEN env or the per-instance vault when RELAY_AGENT_NAME is set, so the child authenticates from its first tool call regardless of platform. HTTP clients must always present an explicit token via args.agent_token or X-Agent-Token header — the daemon's own env (RELAY_AGENT_TOKEN, RELAY_AGENT_NAME) and the per-instance vault are stdio-only credentials and are never used to authenticate HTTP callers (R3 transport gate).

Errors: SPAWN_NOT_SUPPORTED (non-macOS host without an explicit driver), AUTH_FAILED, INVALID_INPUT, RATE_LIMITED.

send_messageA

Send a text message addressed to a single agent.

When to use: 1:1 communication, dispatching work, relaying a status update, asking a peer a question. Prefer broadcast for fan-out to many agents, post_to_channel for topical group coordination, and post_task (or post_task_auto) when the recipient should track state-machine progress (accept/complete/reject) rather than a free-text message.

Behavior: stores the message with status='pending' and notifies any matching webhook subscribers (message.sent event). The recipient sees it on its next get_messages call (which auto-marks read unless peek=true). Content is encrypted at rest if RELAY_ENCRYPTION_KEY is set. Caps at RELAY_MAX_PAYLOAD_BYTES (default 64 KB). Auth: sender must present a valid token whose row matches from.

Returns: { success: true, message_id, from, to, priority, note }. The new message is stored with status='pending' until the recipient drains it.

Errors: AUTH_FAILED (token missing/mismatched), SENDER_NOT_REGISTERED (caller's agent row absent), PAYLOAD_TOO_LARGE, RATE_LIMITED.

get_messagesA

Drain or peek your own mailbox.

When to use: each turn that should observe new mail; orchestrators that batch-poll many agents may prefer get_messages_summary (cheaper preview) or peek_inbox_version (counts only). For surveys that must NOT consume mail, set peek=true.

Behavior: returns messages addressed to you, ordered by priority then created_at newest-first. By default status='pending' returns un-read messages and atomically marks them read for THIS session (sessions are per-session_id; a fresh terminal re-sees previously-read messages, v2.0 final fix). Optional since ('1h' | '24h' | '7d' | ISO | 'all') bounds already-OBSERVED history (v2.1.6 default '24h'); a pending drain ALWAYS returns UNDELIVERED (never-drained) mail regardless of since (#198; 3.0.1 — keyed on delivery, not observation, so a prior peek cannot hide it). When status='pending' returns 0 with since<24h, the response includes a hint field nudging toward since='all' for older already-seen mail. peek=true (v2.2.2) suppresses the read-MARK — the returned messages are not marked read for your session — but it is NOT side-effect-free: like any first view it stamps the observation cursor (seq). (Post-3.0.1 that observation no longer changes what a later drain returns.)

Returns: { messages: MessageRecord[], count, agent, filter, since, since_bound, hint? }. since_bound is the ISO timestamp the relay actually filtered by (after resolving duration shorthands or 'session_start').

Errors: AUTH_FAILED, VALIDATION (bad since format), RATE_LIMITED.

get_messages_summaryA

Cheap, non-mutating mailbox preview (v2.1.6).

When to use: orchestrators scanning many inboxes per cycle, dashboards rendering a per-agent backlog count, or any flow where you want to see what is there without consuming it. After picking interesting IDs, expand them with get_messages (which CAN mutate) or read them by ID.

Behavior: same status + since filter surface as get_messages. Returns headers + a 100-char content_preview (decrypted on the fly when RELAY_ENCRYPTION_KEY is set). Never marks messages read. Auth: agent token (own mailbox only).

Returns: { summaries: { id, from_agent, priority, status, created_at, content_preview, content_truncated }[], count, agent, filter, since, since_bound }. content_truncated=true when the original content exceeded the 100-char preview cap.

Errors: AUTH_FAILED, VALIDATION, RATE_LIMITED.

get_outstandingA

The SENDER's outstanding-ask recap + the pull source of truth for overdue drift (ADR-0011).

When to use: an orchestrator (or any sender) reconstructing what it is owed — on a fresh session, or any time it wants the current picture of asks/obligations it SENT that haven't been resolved. This PULL is the source of truth; the optional message.read/message.resolved webhooks are push-on-top and may be missed.

Behavior: returns the messages YOU sent with disposition in ('ask','obligation'), each with its sender-visible lifecycle state (unread / read-unresolved / resolved) and a REPORT-ONLY overdue flag computed at query time — it NEVER mutates a message (report-first, never auto-resolve). LOG messages are excluded (LOG never goes overdue). overdue = still-unresolved AND past its bound: an obligation past its deadline, else an ask/deadline-less-obligation past created_at + RELAY_OVERDUE_SECONDS (default 24h, tunable). include_resolved=false (default) returns only the outstanding set; true adds resolved rows for the full sender view. Auth: agent token; sender-scoped (you only see mail you sent).

Returns: { success, agent, include_resolved, overdue_bound_seconds, count, overdue_count, outstanding: { id, to_agent, disposition, created_at, deadline, read_at, resolved_at, state, overdue, content_preview, content_truncated }[] }.

Errors: AUTH_FAILED, VALIDATION, RATE_LIMITED.

resolve_messagesA

Permanently resolve (ack) specific messages so they leave your pending queue for good (v2.12.0).

When to use: PARTIAL handling — you've actioned some of your mail but not all ("I did these, not those"). For the common "I've handled everything I just drained" path, prefer get_messages(status='pending', ack=true) which drains AND resolves in one call. Resolving is the durable, session-INDEPENDENT counterpart to reading: read is a per-session observation (a fresh terminal re-sees prior-session-read mail so handovers don't drop unfinished work); resolved is a permanent "handled, archive it" that the pending filter honors, so an already-handled message never re-floods a new session.

Behavior: sets resolved_at=now() for the given ids WHERE to_agent is you AND not already resolved, in one transaction. Does NOT mark messages read (orthogonal plane) and does NOT delete them — they remain in status='all'/'history'/'resolved'. Idempotent: re-resolving, unknown ids, or ids addressed to another agent are silently skipped (reflected in the returned counts). Recipient-scoped: you can only resolve your OWN mail (the dispatcher binds your token to agent_name; the DB also filters by to_agent).

Returns: { success: true, agent, resolved_ids: string[], resolved_count, requested_count, note }. resolved_count < requested_count when some ids were already resolved, unknown, or not yours.

Errors: AUTH_FAILED (token missing/mismatched), VALIDATION (empty/oversized id list), RATE_LIMITED.

broadcastA

Fan out a single message to every registered agent (or every agent of a given role).

When to use: announcements, fleet-wide pings, role-targeted prompts ('all builders, refresh your dependencies'). For 1:1 use send_message. For topical group coordination prefer post_to_channel, channels persist membership and avoid spamming agents who have explicitly opted out by leaving.

Behavior: stores one row per recipient with status='pending'; the sender is excluded from the recipient set. Fires one message.broadcast webhook event for the whole batch (delivery_id + idempotency_key in the envelope). Optional role narrows the recipient set. Same payload size cap as send_message (RELAY_MAX_PAYLOAD_BYTES).

Returns: { success: true, sent_to: string[], message_ids: string[], count, note }. count=0 with a note string when no other agents matched the filter (still success, not error).

Errors: AUTH_FAILED, PAYLOAD_TOO_LARGE, RATE_LIMITED.

post_to_capabilityA

Route an FYI/coordination message to the current owner(s) of a capability (v2.10 — capability routing, principle #1).

When to use: surface a finding, status, or cross-cutting update to whoever owns a domain WITHOUT knowing their name — e.g. an ad-hoc agent tags a 'relationships' finding and the agent that owns that capability picks it up on its next get_messages. FYI/COORDINATION LANE ONLY: action-required completions (a STAGED build needing an audit, a SHIP that triggers a merge) MUST stay point-to-point completion reports via send_message — that point-to-point reliability is what triggers the orchestrator's next action. A capability-routed message never triggers an action.

Behavior: exact-string matches capability against every registered agent's declared capabilities (same lookup as post_task_auto), then fans the message out — one messages row per owner, stamped with routed_capability so recipients + dashboards distinguish the FYI lane from point-to-point mail. Recipients drain via the normal get_messages (use lane='capability' to read only the FYI lane, lane='direct' for only point-to-point). The sender is excluded by default (exclude_self). No current owner → routed_to:[] and nothing stored (fire-and-forget to current owners, NOT queued-until-owner). Fires one message.capability_routed webhook for the batch. Content encrypted at rest if RELAY_ENCRYPTION_KEY is set; same payload cap as send_message.

Returns: { success: true, capability, routed_to: string[], message_ids: string[], count, note }. routed_to is empty (with an explanatory note) when no agent currently owns the capability.

Errors: AUTH_FAILED, SENDER_NOT_REGISTERED, PAYLOAD_TOO_LARGE, RATE_LIMITED, VALIDATION.

post_taskA

Assign a tracked task to a specific agent.

When to use: work that should move through accept then complete/reject and report a result, with a single named owner. Prefer post_task_auto when you do not care which capable agent picks it up. Prefer send_message for free-text comms that do not need a state machine.

Behavior: creates a row with status='posted' and notifies task.posted webhook subscribers. The assignee accepts/completes/rejects via update_task; the assigner can cancel via the same call. Tasks have a heartbeat lease, if the assignee does not update_task action='heartbeat' within RELAY_TASK_LEASE_SECONDS, the health monitor surfaces the task as stuck. Auth: requester token; to must be a registered agent.

Returns: { success: true, task_id, from, to, title, priority, status, note }. status is 'posted' on first creation.

Errors: AUTH_FAILED, PAYLOAD_TOO_LARGE, RATE_LIMITED.

post_task_autoA

Auto-route a task to the least-loaded capable agent (v2.0).

When to use: when you know the required capabilities but do not want to hard-code a specific assignee, load balances across the fleet. Prefer post_task when the assignee is intentional. Prefer broadcast for non-tracked notifications.

Behavior: picks the agent with the smallest accepted-task backlog whose capability set is a superset of required_capabilities. Tie-break: freshest last_seen. If no live agent qualifies, the task enters status='queued' and is auto-assigned the first time a capable agent calls register_agent (the assignment is included in that response's auto_assigned). By default the sender is excluded from routing, set allow_self_assign=true to opt in (v2.1).

Returns: { success: true, task_id, status: 'posted' | 'queued', assigned_to: string | null, routed: boolean, candidate_count, required_capabilities, note }. routed=true only when an agent matched at post time; routed=false with status='queued' is the no-match path that auto-resolves on next register.

Errors: AUTH_FAILED, PAYLOAD_TOO_LARGE, RATE_LIMITED.

update_taskA

Drive a task through its state machine, or extend its lease.

When to use: assignees acknowledge work (accept), report outcome (complete / reject), or keep the lease alive on long-running tasks (heartbeat). Requesters cancel work they no longer need (cancel). Read-only progress checks belong in get_task / get_tasks.

Behavior: enforces role-by-action, accept/complete/reject/heartbeat are assignee-only; cancel is requester-only. Heartbeat refreshes lease_renewed_at without changing status, so the health monitor does not requeue a long task. result is required on complete/reject and surfaces in get_task. Fires task.accepted / task.completed / task.rejected webhooks. Auth: agent token (matching the action's required role).

Returns: { success: true, task_id, status, result, note }. Heartbeat additionally includes lease_renewed_at: ISO. Other actions transition status to accepted / completed / rejected / cancelled.

Errors: AUTH_FAILED, INVALID_STATE (action not allowed in current status), NOT_FOUND (unknown task_id), NOT_PARTY (caller is neither requester nor assignee), PAYLOAD_TOO_LARGE.

get_tasksA

Query the tasks you are involved with.

When to use: assignees triaging their queue (role='assigned'), requesters checking on dispatched work (role='posted'). For a single task by id use get_task. For team-wide rollup use get_standup.

Behavior: pure read. Filters by role + status; default status='all'. Ordered by priority then created_at newest-first. Auth: agent token (only your own tasks are visible).

Returns: { tasks: TaskRecord[], count, agent, role, filter }. Each task carries id, from_agent, to_agent, title, description, priority, status, result, created_at, updated_at, lease_renewed_at.

Errors: AUTH_FAILED, RATE_LIMITED.

get_taskA

Look up a single task by id.

When to use: any flow that needs the canonical state of one specific task, e.g., the assignee just heartbeat'd and wants to confirm the row, or the requester is checking on a known task_id from an earlier post_task response. For browsing many tasks use get_tasks.

Behavior: pure read. Returns the full task record including encrypted-at-rest description + result fields decrypted on the fly. Auth: agent token whose row is either the requester or assignee on this task, the relay refuses to leak third-party tasks.

Returns: { success: true, task: TaskRecord } with the full row including encrypted-at-rest description + result decrypted on the fly.

Errors: NOT_PARTY (caller is not the requester or assignee), NOT_FOUND (unknown task_id), RATE_LIMITED.

register_task_schemaA

Register a reusable, immutable JSON Schema that gates task completion (v2.10 — safety).

When to use: define the PROOF shape a completing agent must satisfy — e.g. a completion report requiring {ci_status:'green', tests_passed, summary}. A requester attaches the schema id to a task via post_task's schema_id; the assignee's update_task(action='complete', result=...) is then validated against it. Built-ins ship_pong_v1 / audit_verdict_v1 / merge_ready_v1 are auto-registered on init.

Behavior: the document is meta-validated + hardened (no $ref/$dynamicRef/$recursiveRef/$data) BEFORE ajv compiles it (a registered schema is compiled, so it is an attack surface). Schemas are IMMUTABLE — re-registering an id is refused; bump the version id. Auth: requires the manage_schemas capability.

Returns: { success: true, id, created_by, created_at, note }.

Errors: SCHEMA_MISMATCH (invalid/forbidden schema document), ALREADY_EXISTS (id already registered), CAP_DENIED, AUTH_FAILED, VALIDATION.

task_schema_getA

Fetch a registered task schema by id (v2.10).

When to use: an assignee about to complete a schema-gated task reads the required shape first, so its result conforms and the completion is accepted. Pure read; no auth required.

Behavior: returns the stored JSON Schema document verbatim (the parsed object).

Returns: { success: true, id, json_schema, created_by, created_at }.

Errors: NOT_FOUND (no such schema id).

register_webhookA

Subscribe an HTTP endpoint to relay events.

When to use: reactive integrations, Slack notifier, audit pipeline, dashboard refresher. For polling-style observation prefer get_standup or peek_inbox_version. For local UIs the bundled /dashboard already consumes the live event stream.

Behavior: stores the subscription + optional HMAC secret (encrypted at rest with the same keyring the message body uses, v2.1 Phase 4p). Each delivery POSTs the event JSON with X-Relay-Delivery-ID + X-Relay-Idempotency-Key headers and an X-Relay-Signature HMAC-SHA256 if a secret was registered. Outbound URLs are SSRF-validated against the cloud-metadata + private-IP blocklist (v1.10). Events: message.sent | message.broadcast | task.posted | task.accepted | task.completed | task.rejected | channel.message_posted | agent.unregistered | agent.spawned | '*'. Optional filter (agent name) narrows by sender/recipient.

Returns: { success: true, webhook_id, url, event, filter, has_secret: boolean, resolved_ips: string[], note }.

Errors: AUTH_FAILED (caller needs webhooks capability), URL_BLOCKED (SSRF target), INVALID_INPUT, RATE_LIMITED.

list_webhooksA

List every webhook subscription registered on the relay.

When to use: sanity-checking integrations ('is the Slack notifier still wired up?'), pre-cleanup audits, or building an admin UI. To narrow by event you currently filter client-side from this list.

Behavior: pure read. The raw HMAC secret is NEVER returned, each row exposes has_secret: boolean only. Auth: any registered agent (subscriptions are observable so admins can audit them, but secrets stay write-only).

Returns: { webhooks: { id, url, event, filter, has_secret, created_at }[], count }.

Errors: AUTH_FAILED, RATE_LIMITED.

delete_webhookA

Tear down a webhook subscription by id.

When to use: cleanup when an integration is being retired, when the receiver URL is dead and you do not want delivery-log noise, or when rotating a webhook secret (delete + register fresh).

Behavior: removes the subscription row and any pending entries in webhook_delivery_log. Auth: the registrant's token, OR an authenticated agent with webhooks capability for cross-owner cleanup.

Returns on success: { success: true, webhook_id, note: 'Webhook deleted' }. Returns on missing-id: { success: false, webhook_id, note: 'Webhook not found' } with isError: true — this surfaces as a tool error so callers know the id was already gone (NOT a soft-success).

Errors: AUTH_FAILED, INVALID_INPUT. Missing-id is reported via the success: false + isError: true envelope above, not a separate error_code.

create_channelA

Create a named channel for many-to-many topical coordination.

When to use: ongoing conversations that more than two agents care about and that should persist beyond the lifetime of any one agent (e.g., #deploys, #triage). For 1:1 use send_message. For one-shot fleet-wide announcements use broadcast.

Behavior: creates the channel row and adds the creator as a member. Channels are flat (no hierarchy) and globally addressable by name. Auth: caller must hold the channels capability.

Returns: { success: true, channel: { id, name, description, created_by, created_at }, message }.

Errors: AUTH_FAILED (missing channels capability), ALREADY_EXISTS (name collision), INVALID_INPUT, RATE_LIMITED.

join_channelA

Subscribe to a channel so you receive its messages from your join time forward.

When to use: any agent that wants to follow a channel's traffic, joining is open to any authenticated caller (no invite gate; channels are intentionally low-friction). Pair with post_to_channel for posting and get_channel_messages for reading.

Behavior: inserts the membership row with joined_at = now. get_channel_messages and the channel.message_posted webhook event scope to messages with created_at >= joined_at for this member, so historical traffic is NOT replayed (a deliberate design choice, channels are streams, not archives). Idempotent: rejoining is a no-op. Auth: any agent token.

Returns: { success: true, channel_name, agent_name, joined: boolean, note }. joined=false indicates the agent was already a member (idempotent no-op).

Errors: AUTH_FAILED, NOT_FOUND (unknown channel_name), RATE_LIMITED.

leave_channelA

Cancel your membership so you stop receiving a channel's messages.

When to use: channel is no longer relevant to your role, or you are shutting down and want to be a clean citizen (the dashboard surfaces ghost members otherwise). Idempotent, calling it on a channel you never joined is fine.

Behavior: removes the membership row. Past messages stay in the channel (other members still see them); your joined_at cursor is forgotten so a future join_channel starts a fresh observation window. Auth: agent token (you can only leave on your own behalf).

Returns: { success: true, channel_name, agent_name, left: boolean, note }. left=false indicates the agent was not a member (idempotent no-op).

Errors: AUTH_FAILED, NOT_FOUND, RATE_LIMITED.

post_to_channelA

Send a message into a channel you have joined.

When to use: ongoing topical coordination among multiple agents ('deploy started', 'triage thread for incident-12'). For 1:1 use send_message; for fleet-wide one-shots use broadcast. The audience is exactly the current channel membership at post time.

Behavior: stores a channel_messages row, fires the channel.message_posted webhook event, and surfaces in every member's get_channel_messages whose joined_at <= post.created_at. Same RELAY_MAX_PAYLOAD_BYTES cap as direct messages. Encrypted at rest when keyring is configured. Auth: caller must be a current member AND hold the channels capability.

Returns: { success: true, message_id, channel_name, from }.

Errors: NOT_MEMBER (caller has not joined the channel), AUTH_FAILED (missing channels cap), PAYLOAD_TOO_LARGE, NOT_FOUND, RATE_LIMITED.

get_channel_messagesA

Read the messages you are entitled to see in a channel.

When to use: any flow that observes channel traffic, dashboard refresh, post-incident review thread, role onboarding ('catch up on #deploys'). For 1:1 mailbox use get_messages; for whole-fleet activity use get_standup.

Behavior: scoped to messages with created_at >= your join_time. Ordered by priority then created_at newest-first. Pure read, channel posts have no per-recipient read state, so this call is fully idempotent. Auth: caller must be a current member.

Returns: { messages: ChannelMessage[], count, channel_name, agent }. Each message carries id, channel_id, from_agent, content, priority, created_at.

Errors: NOT_MEMBER (caller has not joined the channel), NOT_FOUND, RATE_LIMITED.

set_statusA

Declare your operational state independently of presence.

When to use: tell the relay what kind of work you are in, so the health monitor and orchestrators can route or skip accordingly. Distinct from last_seen-derived presence (online/stale/offline), that one is computed; this one is your declared intent. For one-call team rollup use get_standup.

Behavior: updates the agent row's agent_status (idle | working | blocked | waiting_user | offline). v2.1.3 (I6) widened the enum from the original online/busy/away/offline. busy and away map to working for backward compatibility. The health monitor exempts working/blocked/waiting_user rows from automatic task reassignment. Auth: own agent token only.

Returns: { success: true, agent, status, note, status_normalized_from? }. status_normalized_from is set when the input alias (e.g., online/busy/away) was rewritten to the canonical enum value (idle/working).

Errors: NOT_FOUND (unknown agent_name), AUTH_FAILED, INVALID_INPUT, RATE_LIMITED.

report_livenessA

Restamp your own liveness anchor (agent_pid + process start-time) — a narrow, metadata-only presence self-report.

When to use: your hooks call this automatically (SessionStart + PostToolUse) so the relay can positively probe whether your process is alive. An old/existing session that registered before the anchor mechanism becomes probe-able without a full re-register — critical because register_agent rotates your session_id and can re-surface already-read mail, whereas this touches ONLY agent_pid + start-time (+ fills host_id when unset). You rarely call it by hand.

Behavior: updates agent_pid + agent_pid_start (and host_id if NULL) for your row. Does NOT rotate session_id, bump last_seen, or touch your read cursor. Idempotent — restamping the same values is a no-op. Auth: own agent token only.

Returns: { success: true, agent_name, agent_pid, note }.

Errors: NOT_FOUND (unknown agent_name), AUTH_FAILED, INVALID_INPUT, RATE_LIMITED.

health_checkA

Report relay process health + live counts.

When to use: liveness probes (/health HTTP endpoint mirrors this surface), version-pinning checks during upgrades, dashboard footers. Cheaper than get_standup for binary up/down questions.

Behavior: pure read. Counts agents by presence, pending messages, active and queued tasks, channels, and webhook subscriptions. Reports version (from package.json via the v2.1 Phase 4a single source of truth) + protocol_version (the client-compat surface, distinct from package version). Works on stdio AND HTTP transports. No capability required, intentionally observable.

Returns: { status: 'ok', version, protocol_version, transport, uptime_seconds, legacy_grace_active, agents: {...counts}, messages: {...counts}, tasks: {...counts}, channels, webhooks }. When the caller presents a token (arg / header / env), the response also includes token_validated: true, auth_error: boolean, and (on validation failure) auth_error_reason, plus agent_name + auth_state on success.

Errors: none expected (status='ok' is the only success shape).

rotate_tokenA

Self-rotate your own agent_token (v2.1).

When to use: scheduled rotation, suspected token leak, or any time you want a fresh secret without losing identity. For admin-driven rotation of someone else's token use rotate_token_admin. To wipe the token entirely use revoke_token.

Behavior: requires the current valid token. For Managed agents (registered with managed:true) the relay enters a grace window during which BOTH old and new tokens authenticate, and a priority='high' push-message carries the new token to the agent so it can self-update. For unmanaged agents (default, Claude Code terminals), the response carries restart_required:true and the old token is invalid immediately.

Returns: { success: true, agent_name, new_token, rotated_at: ISO, agent_class: 'managed' | 'unmanaged' }. Managed-with-grace adds grace_expires_at: ISO, push_sent: boolean, auth_note. Managed-with-zero-grace adds grace_expires_at: null, push_sent: false, auth_note. Unmanaged adds restart_required: true, operator_note.

Errors: NOT_FOUND (unknown agent), INVALID_STATE (auth_state ≠ active — recovery_pending / revoked / legacy_bootstrap / rotation_grace each return a state-specific hint), CONCURRENT_UPDATE (CAS race lost), INTERNAL.

rotate_token_adminA

Admin-initiated rotation of another agent's token (v2.1 Phase 4b.2).

When to use: operator-driven incident response, scheduled rotation across the fleet, or onboarding a Managed agent into a new key generation. Requires rotate_others capability on the rotator. For self-service use rotate_token. For revocation without re-issuance use revoke_token.

Behavior: same Managed-vs-unmanaged split as rotate_token. Managed targets get the new token via push-message + a grace window. Unmanaged targets return the new token in the rotator's response (the rotator delivers it out-of-band) and the response carries restart_required:true. The audit log records BOTH the rotator and the target so attribution survives.

Returns: { success: true, target_agent_name, rotator, rotated_at: ISO, agent_class: 'managed' | 'unmanaged' }. Managed-with-grace adds grace_expires_at: ISO, push_sent: boolean, note. Managed-with-zero-grace adds new_token, grace_expires_at: null, push_sent: false, note. Unmanaged adds new_token, restart_required: true, operator_note.

Errors: AUTH_FAILED (rotator not authenticated, or missing rotate_others), NOT_FOUND (unknown target), INVALID_STATE, CONCURRENT_UPDATE, RATE_LIMITED.

revoke_tokenA

Invalidate another agent's token (v2.1 Phase 4b.1 v2).

When to use: confirmed compromise, lost device, or graceful retirement of an agent name. For routine key-hygiene rotation prefer rotate_token / rotate_token_admin (those keep identity). For removing the agent entirely use unregister_agent.

Behavior: transitions the target row to auth_state='recovery_pending' (when issue_recovery=true, also returns a one-time recovery_token the operator hands off out-of-band; the agent re-registers via register_agent with that token to mint a fresh agent_token) or auth_state='revoked' (terminal, only unregister_agent + register_agent can reuse the name). Original token_hash is preserved for forensic correlation; the state column, not the hash, enforces rejection. v2.6.2 R1: the per-instance vault file at <instanceDir>/agents/<name>.token is also scrubbed on every successful revoke (best-effort, ENOENT-safe) — the security boundary already held via the state check, but the scrub aligns the mental model so revoke_token leaves no credential on disk. Requires revoke_others capability.

Returns: { success: true, revoked: target_agent_name, revoked_by, revoked_at: ISO, changed: boolean, auth_state_before, auth_state_after, note }. When issue_recovery=true and the call actually changed state, also includes recovery_token (shown ONCE), recovery_note, and recovery_reissued: boolean. changed=false is an idempotent no-op (target was already revoked).

Errors: AUTH_FAILED (caller missing revoke_others), NOT_FOUND, RATE_LIMITED.

get_standupA

One-shot team-status synthesis for orchestrators (v2.1.4).

When to use: every observation cycle that would otherwise call discover_agents + get_messages + get_tasks and synthesize in-LLM. The relay does the rollup server-side so the caller burns near-zero tokens. For specific drill-downs after the rollup, fall through to the underlying tools.

Behavior: pure read. Given a window (since: '15m' | '1h' | '3h' | '1d' | ISO), returns active_agents (filtered to non-offline by default, set include_offline=true to include them), message_activity counts, task_state breakdown, and rule-based observation bullets ('agent X has been blocked >30min', etc.). Observations are hand-rolled heuristics, NO LLM on the relay side. Optional agents / roles arrays narrow the snapshot. Auth: any agent token.

Returns: { success: true, window: { since, now, duration_ms }, active_agents: Agent[], message_activity, task_state: { completed_in_window, queued, blocked, assigned_by_agent }, observations: string[] }.

Errors: VALIDATION (bad since format), AUTH_FAILED, RATE_LIMITED.

expand_capabilitiesA

Self-managed additive capability expansion (v2.1.4).

When to use: an agent registered (often via the SessionStart hook) with a narrow capability set and now needs more, e.g., a builder later picks up a webhooks integration. Reductions are NOT supported (unregister_agent + fresh register_agent for those). For privileged cross-agent edits, no equivalent admin tool exists by design, capability changes are caller-attested.

Behavior: caller presents their token; the requested set MUST be a SUPERSET of current caps (additive only, closes the v1.7.1 immutability gap without re-opening the capability-escalation CVE). Reductions reject with REDUCTION_NOT_ALLOWED. Requesting only already-held caps rejects with NO_OP_EXPANSION. The expansion is recorded in the audit log with the verified caller name.

Returns: { success: true, agent, added: string[], capabilities: string[] }. capabilities is the new full set after expansion; added is the diff of newly-granted caps.

Errors: NOT_FOUND (unknown agent), REDUCTION_NOT_ALLOWED, NO_OP_EXPANSION, AUTH_FAILED, INTERNAL.

set_dashboard_themeA

Set the server-side default dashboard theme (v2.2.1).

When to use: org-level theme defaults ('every new operator should land on dark'), or programmatically applying a brand palette via mode='custom'. Each individual operator's localStorage preference still beats this default for repeat visits, this only affects first-visit theming for newly-connecting clients.

Behavior: stores the chosen theme + optional custom_json in dashboard_prefs. Modes: 'catppuccin' (default Mocha palette), 'dark' (tool-neutral), 'light' (tool-neutral), 'custom' (requires custom_json with all 13 CSS color tokens). No WebSocket push, already-open dashboards adopt on full reload. Auth: dashboard-secret-equivalent capability (treated as an admin operation).

Returns: { success: true, theme, updated_at: ISO, note }.

Errors: AUTH_FAILED, INVALID_INPUT (custom mode missing required tokens), RATE_LIMITED.

peek_inbox_versionA

Cheap non-mutating mailbox version probe (v2.3.0 Phase 4s, ambient wake support).

When to use: low-rate polling that wants to know 'do I have new mail?' without paying a get_messages round-trip, clients diff total_unread_count against their cached value and only call get_messages on a change. Pair with the optional filesystem-marker wake (when RELAY_FILESYSTEM_MARKERS=1) for low-latency idle wake. For full mailbox content use get_messages (mutating) or get_messages_summary (preview).

Behavior: pure read. Returns { mailbox_id, epoch, last_seq, total_messages_count, total_unread_count }. WATCH total_unread_count for new-mail detection, it advances on every send_message/broadcast to this agent. last_seq only advances when the recipient calls get_messages (read-cursor). epoch rotates on backup/restore, a client whose cached epoch no longer matches MUST reset its local last_seen_seq to 0 and re-drain. Auth: any agent token.

Returns: { success: true, mailbox_id, epoch, last_seq, total_messages_count, total_unread_count }.

Errors: AUTH_FAILED, NOT_FOUND (unknown agent_name), RATE_LIMITED.

Prompts

Interactive templates invoked by user choice

NameDescription
recover-lost-tokenWalks the operator through the `relay recover` CLI flow for an agent whose RELAY_AGENT_TOKEN was lost. Filesystem-gated — the operator's access to the DB path IS the authority.
invite-workerWalks the operator through spawning a sub-agent, getting them registered, and handing off a brief. macOS-only spawn (see spawn_agent docs); on Linux/Windows, the operator starts the new terminal manually.
rotate-compromised-agentWalks the operator through rotating an agent whose `agent_token` has leaked. Revokes the old token, issues a recovery_token for one-shot reclaim, and registers the agent fresh.

Resources

Contextual data attached and managed by the client

NameDescription
Current relay stateJSON snapshot: agents (with agent_status derived from last_seen), active tasks, pending-message counts per agent. Same shape family as the dashboard's /api/snapshot but surfaced via MCP.
Recent audit activityLast 50 audit_log entries (tool, source, success, timestamp, agent_name). Sensitive params_json field stripped. For incident review without hitting the SQLite file directly.
Agent graphAgents + edges (message-sent-between counts + active-task assignments). Structured object suited for visualization tools that render agent interaction graphs.

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Maxlumiere/bot-relay-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server