agent-relay
Use when you need Codex to coordinate multiple agents through Agent Relay for peer-to-peer messaging, lead/worker handoffs, or shared status tracking across sub-agents and terminals.
Install
npx skills add https://github.com/AgentWorkforce/relay/tree/main/plugins/codex-relay-skill
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install agentworkforce-relay@llmmart
git clone https://github.com/AgentWorkforce/relay.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole agentworkforce/relay collection as a plugin from our marketplace. Git is the plain clone.
README
Codex Relay Skill
Codex-native multi-agent coordination via Relaycast.
What it does
This package gives Codex a reusable relay coordination layer so sub-agents can communicate through Relaycast instead of staying limited to parent-only result collection.
It includes:
- a Codex skill that teaches lead and worker messaging protocol
- an Agent Relay MCP dependency declaration
- a template Agent Relay MCP config block for
.codex/config.toml - a
relay-workercustom agent template for.codex/agents/
With these pieces installed, Codex can:
- coordinate teams through direct messages, channels, and threads
- require ACK/DONE signaling from workers
- let workers send peer-to-peer updates through Relaycast
- reuse the same relay workflow across project-scoped and user-scoped setups
Installation
mkdir -p .agents/skills
cp -R plugins/codex-relay-skill .agents/skills/agent-relay
That's it. Everything else is automatic.
On first use, the skill self-installs by running scripts/setup.sh, which:
- adds the Agent Relay MCP server to
.codex/config.toml - enables
features.codex_hooks = true - writes
.codex/hooks.jsonwith SessionStart, UserPromptSubmit, and Stop hooks - installs
.codex/agents/relay-worker.toml
All of this is idempotent — safe to run multiple times, and it merges with existing config rather than overwriting.
For user-wide availability, install to $HOME/.agents/skills/agent-relay instead.
Optional: join an existing workspace
Set RELAY_API_KEY before launching Codex to join a specific Relaycast workspace:
export RELAY_API_KEY="rk_live_your_key_here"
If unset, a new workspace is auto-created on the first session.
Manual setup (advanced)
If you prefer to configure everything manually instead of using the auto-installer:
- Add to
.codex/config.toml:
features.codex_hooks = true
[mcp_servers.agent-relay]
command = "npx"
args = ["-y", "agent-relay", "mcp"]
env = { RELAY_API_KEY = "", RELAY_BASE_URL = "https://cast.agentrelay.com", RELAY_AGENT_TYPE = "agent" }
- Copy hooks config:
cp .agents/skills/agent-relay/hooks/hooks.json .codex/hooks.json
- Install the worker agent:
mkdir -p .codex/agents
cp .agents/skills/agent-relay/codex-config/relay-worker.toml .codex/agents/relay-worker.toml
Usage
Use the skill directly
Invoke the skill explicitly:
$agent-relay Coordinate this refactor with two workers and keep all status updates in Relaycast.
Or describe the task naturally and let Codex match the skill from its description.
Spawn relay workers
Once relay-worker.toml is installed, delegate bounded tasks to the relay-worker custom agent and include:
- the worker relay name
- the lead relay name
- the workspace-key source
- exact task scope
- completion criteria
Example:
Spawn a relay-worker named api-worker.
Have it check Relaycast, ACK me, update the API route tests only, send STATUS after the first green test run, and send DONE with evidence before exit.
Coordinate a team
Use Relaycast when workers need to message each other directly, not only the lead. Good fits:
- parallel implementation across separate subsystems
- lead/worker review loops
- shared channel updates for longer-running tasks
- cross-terminal or cross-machine collaboration
Environment variables
| Variable | Required | Default | Description |
|---|---|---|---|
RELAY_API_KEY |
No | "" in the template config |
Relaycast workspace key |
RELAY_BASE_URL |
No | https://cast.agentrelay.com |
Relaycast API base URL |
RELAY_AGENT_TYPE |
No | agent |
Default Relaycast agent type |
RELAY_AGENT_NAME |
No | unset | Optional stable relay identity when your workflow wants a fixed name |
Plugin structure
codex-relay-skill/
SKILL.md # Codex skill manifest and workflow instructions
README.md # Installation and usage docs
agents/
openai.yaml # Agent Relay MCP dependency metadata
codex-config/
config.toml # Template MCP server config for .codex/config.toml
relay-worker.toml # Template custom worker agent for .codex/agents/
scripts/
setup.sh # Auto-installer (runs on first skill activation)
hooks/
hooks.json # Hook definitions (SessionStart, Stop, UserPromptSubmit)
session-start.sh # Auto-connect and state persistence
stop-inbox.sh # Block exit while unread messages exist
prompt-inbox.sh # Rate-limited inbox polling and context injection
Installed layout in a project typically looks like:
.agents/skills/agent-relay/ # Skill directory Codex scans
.codex/config.toml # Runtime Agent Relay MCP server + features.codex_hooks
.codex/hooks.json # Hook wiring (copied from skill)
.codex/agents/relay-worker.toml
Skill manifest
Agent Relay
Use this skill when Codex needs real-time coordination across multiple agents. It gives Codex a repeatable workflow for:
- connecting to an Agent Relay workspace
- spawning relay-aware workers
- sending direct messages, channel updates, and thread replies
- keeping lead and worker state synchronized through ACK, STATUS, BLOCKED, and DONE signals
Relay fills the peer-to-peer gap in Codex sub-agent workflows. Codex can spawn and collect worker results, but Agent Relay gives those workers a shared message bus so they can talk to the lead and to each other.
Auto-setup
On first activation, this skill auto-configures Codex by running scripts/setup.sh. This adds the Agent Relay MCP server to .codex/config.toml, enables hooks, installs hooks.json, and copies the relay-worker.toml agent definition. No manual setup is required after installing the skill.
Startup protocol
Every relay-connected Codex agent must complete these steps IN ORDER before substantive work:
Set up a workspace.
- If
RELAY_WORKSPACE_KEYis set in the environment, callset_workspace_keywith that key. - If only the legacy
RELAY_API_KEYalias is set, treat it as the same workspace key. - If no key is available, call
create_workspaceto auto-create one. This returns a workspace key — save it for workers.
- If
Register as an agent. Call
register_agentwith your agent name andtype: "agent". UseRELAY_AGENT_NAMEfrom the environment if set, otherwise derive a name from the task context (e.g.,lead,auth-worker).Keep workspace credentials out of output. Never print the workspace key or construct an observer URL from it — it is an administrative credential. When the user asks to follow the conversation, run
agent-relay observerand print the URL it returns. That mints a scoped, read-only token (ot_live_...) that expires in 24 hours and excludes agent DMs, so the link is safe to share. Narrow it further with--channels, widen it with--include-dmsor--expires, and revoke it withagent-relay observer revoke <id>.Check the relay inbox. Call
check_inboxto see if there are any pending messages or task assignments.Send an ACK. If you received a task assignment, send
ACK: <one-sentence understanding>to your lead viasend_dm. If the assignment is unclear, sendBLOCKED: <question>instead of guessing.When the task is complete, send
DONE: <summary with evidence>before stopping.
If workspace creation or registration fails, retry once, then report the failure to the user — do not proceed without a relay connection.
Critical rule
Do not assume the current MCP session already has an active Agent Relay workspace. Always call set_workspace_key or create_workspace before registering.
Working rules
- Include
as: "<agent-name>"on relay calls that support explicit attribution. - Keep the relay identity stable for the whole task. Do not switch names mid-task.
- Check the inbox again after meaningful milestones, before long-running work, and before stopping.
- Prefer direct messages for lead/worker coordination. Use channels only when multiple agents need the same update.
- Keep status messages short, factual, and scoped to the assigned work.
- Do not spawn additional relay workers unless the lead explicitly asks for more delegation.
- If the lead updates the task, follow the newest explicit instruction.
Message templates
ACK: I understand the assignment and I am starting work on <scope>.STATUS: Finished <milestone>; next I am doing <next-step>.BLOCKED: I cannot continue because <blocker>.DONE: Completed <scope>. Evidence: <files changed, commands run, tests, or decisions>.
Worker patterns
There are two current ways to involve more agents. Use the right one for the job.
Registered workspace identities
Use register_agent for an agent process that is already running and only
needs a Relay identity. Registration does not start a new model runtime.
Lead steps:
- Ensure workspace exists (
set_workspace_keyorcreate_workspace). - Register the lead (
register_agent). - Give the other running process the workspace key and tell it to call
register_agentwith a stable name. - Send the assignment via
send_dm(to: "worker-name", text: "..."). - Poll lead inbox for ACK (
check_inbox).
Worker steps:
- Call
set_workspace_keywith the shared key. - Register with
register_agent. - Check inbox (
check_inbox). - Send ACK to lead via
send_dm. - Perform the assigned scope.
- Send DONE to lead via
send_dm.
Relay-spawned workers
Use add_agent when the lead should ask Relay to start a provider-backed
worker. The current tool requires name, cli, and task; optional fields
include channel, persona, and model.
Lead steps:
- Ensure workspace exists and lead is registered.
- Spawn the worker with
add_agent(name: "worker-name", cli: "codex", task: "..."). - Include
https://agentrelay.com/skill, the lead name, exact scope, and completion criteria in the task prompt. - Poll lead inbox for ACK (
check_inbox). - Release the worker with
remove_agentafter the work is accepted.
Worker steps:
- Follow the
using-agent-relayrole fromhttps://agentrelay.com/skill. - Check inbox, send ACK, do the assigned work, and send DONE.
Codex sub-agents
If your Codex surface has a sub-agent spawn capability, use the bundled
relay-worker agent definition for code-heavy work that needs a separate Codex
runtime with file access and tools. Include the workspace key, relay name, lead
name, exact scope, and completion criteria in the sub-agent prompt. If that
spawn capability is not available, use add_agent instead.
Worker ACK fallback
If a worker does not ACK within 30 seconds:
- Check whether the worker appears in
list_agents. - If this is a running process, have it call
register_agent. - If this should be a spawned worker, call
add_agentwithname,cli, andtask. - Send (or re-send) the assignment via
send_dm. - Poll the lead inbox again for ACK.
- If still no ACK after a second attempt, report the exact failed step to the user.
Handoff template
Worker: api-worker
Type: relay-spawned worker (use add_agent with name, cli, and task)
Lead: lead
Scope: check the Agent Relay inbox and confirm connectivity
Protocol:
1. Check inbox
2. DM lead with ACK
3. Perform scope
4. DM lead with DONE
For code-heavy tasks, change the type line to:
Type: Codex sub-agent (use relay-worker if your Codex surface provides sub-agent spawning)
Files (relay)
-
agents
-
openai.yaml 224 B
dependencies: tools: - type: 'mcp' value: 'agent-relay' description: 'Agent Relay real-time messaging for multi-agent coordination' transport: 'stdio' command: 'agent-relay' args: ['mcp']
-
-
codex-config
-
config.toml 358 B
# Add this block to .codex/config.toml or ~/.codex/config.toml. # Enable the hooks engine (required for auto-connect, inbox polling, stop guard) features.codex_hooks = true [mcp_servers.agent-relay] command = "npx" args = ["-y", "agent-relay", "mcp"] env = { RELAY_API_KEY = "", RELAY_BASE_URL = "https://cast.agentrelay.com", RELAY_AGENT_TYPE = "agent" } -
relay-worker.toml 1.1 KB
name = "relay-worker" description = "Executes relay-coordinated tasks with ACK/DONE signaling" developer_instructions = """ You are a relay-connected Codex worker. Before doing substantive work: 1. Set the Relaycast workspace key from your assignment or environment. Do not print the key. 2. Register with Relaycast using your assigned relay name and type "agent" unless you were explicitly pre-registered. 3. Check your inbox with the same relay identity to load your task and lead context. 4. Send ACK to your lead with a one-sentence understanding of the assignment. 5. If you are blocked or the task is ambiguous, send BLOCKED instead of guessing. Working rules: - Include as: "<agent-name>" on relay calls that support explicit attribution. - Stay within the assigned scope and do not spawn more workers unless the lead explicitly directs you to. - Check inbox again after meaningful milestones, during long-running work, and before exit. - Keep status updates concise and factual. Completion: - Send DONE before exiting. - Include evidence when relevant: files changed, commands run, tests executed, or decisions made. """ mcp_servers = ["agent-relay"]
-
-
hooks
-
hooks.json 887 B
{ "hooks": { "SessionStart": [ { "hooks": [ { "type": "command", "command": "bash .agents/skills/agent-relay/hooks/session-start.sh", "timeoutSec": 15, "statusMessage": "Connecting Relaycast" } ] } ], "UserPromptSubmit": [ { "hooks": [ { "type": "command", "command": "bash .agents/skills/agent-relay/hooks/prompt-inbox.sh", "timeoutSec": 5, "statusMessage": "Checking relay inbox" } ] } ], "Stop": [ { "hooks": [ { "type": "command", "command": "bash .agents/skills/agent-relay/hooks/stop-inbox.sh", "timeoutSec": 5, "statusMessage": "Verifying relay inbox" } ] } ] } } -
prompt-inbox.sh 4.1 KB
#!/usr/bin/env bash set -euo pipefail SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) PLUGIN_DIR=$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd) ENV_FILE="${PLUGIN_DIR}/.env" BASE_RELAY_DIR="${HOME}/.relay" # Per-agent namespacing: use RELAY_AGENT_NAME to avoid concurrent agents # overwriting each other's state files _agent_ns=$(printf '%s' "${RELAY_AGENT_NAME:-}" | tr -c 'A-Za-z0-9._-' '-' | sed 's/^-*//; s/-*$//' | cut -c1-64) if [ -n "$_agent_ns" ]; then RELAY_DIR="${BASE_RELAY_DIR}/agents/${_agent_ns}" else RELAY_DIR="${BASE_RELAY_DIR}" fi TOKEN_FILE="${RELAY_DIR}/token" STATE_FILE="${RELAY_DIR}/codex-session.json" LAST_POLL_FILE="${RELAY_DIR}/last-poll" DEFAULT_BASE_URL="https://cast.agentrelay.com" EMPTY_OUTPUT='{}' MAX_RENDERED_MESSAGES=20 MIN_POLL_INTERVAL=3 load_env() { if [ -f "$ENV_FILE" ]; then set -a # shellcheck disable=SC1090 . "$ENV_FILE" set +a fi } command_exists() { command -v "$1" >/dev/null 2>&1 } trim() { printf '%s' "${1:-}" | awk '{$1=$1;print}' } normalize_base_url() { local value value=$(trim "${1:-}") if [ -z "$value" ] && [ -f "$STATE_FILE" ] && command_exists jq; then value=$(jq -r '.baseUrl // empty' "$STATE_FILE" 2>/dev/null || true) fi value=$(trim "${value:-$DEFAULT_BASE_URL}") value=${value%/} printf '%s' "${value:-$DEFAULT_BASE_URL}" } read_token() { local token token=$(trim "${RELAY_TOKEN:-}") if [ -n "$token" ]; then printf '%s' "$token" return fi if [ -s "$TOKEN_FILE" ]; then trim "$(cat "$TOKEN_FILE" 2>/dev/null || true)" return fi printf '' } should_skip_poll() { local now last elapsed now=$(date +%s) if [ -f "$LAST_POLL_FILE" ]; then last=$(cat "$LAST_POLL_FILE" 2>/dev/null || printf '0') case "$last" in ''|*[!0-9]*) last=0 ;; esac elapsed=$((now - last)) if [ "$elapsed" -lt "$MIN_POLL_INTERVAL" ]; then return 0 fi fi mkdir -p "$RELAY_DIR" printf '%s\n' "$now" > "$LAST_POLL_FILE" return 1 } main() { local token base_url messages count formatted overflow context load_env if ! command_exists curl || ! command_exists jq; then printf '%s\n' "$EMPTY_OUTPUT" exit 0 fi if should_skip_poll; then printf '%s\n' "$EMPTY_OUTPUT" exit 0 fi token=$(read_token) if [ -z "$token" ]; then printf '%s\n' "$EMPTY_OUTPUT" exit 0 fi base_url=$(normalize_base_url "${RELAY_BASE_URL:-}") messages=$( curl -fsS \ -X POST \ -H "Authorization: Bearer ${token}" \ -H 'Content-Type: application/json' \ -d '{}' \ "${base_url}/v1/inbox/check" 2>/dev/null || true ) if [ -z "$messages" ]; then printf '%s\n' "$EMPTY_OUTPUT" exit 0 fi count=$(printf '%s' "$messages" | jq -r '(.messages // []) | length' 2>/dev/null || printf '0') case "$count" in ''|*[!0-9]*) count=0 ;; esac if [ "$count" -eq 0 ]; then printf '%s\n' "$EMPTY_OUTPUT" exit 0 fi formatted=$( printf '%s' "$messages" | jq -r --argjson limit "$MAX_RENDERED_MESSAGES" ' (.messages // []) | .[:$limit] | map( if ((.channel // "") | length) > 0 then "Relay message from \(.from // "unknown") in #\(.channel)\(if ((.id // "") | length) > 0 then " [\(.id)]" else "" end): \((.text // "") | gsub("[\\r\\n]+"; " "))" else "Relay message from \(.from // "unknown")\(if ((.id // "") | length) > 0 then " [\(.id)]" else "" end): \((.text // "") | gsub("[\\r\\n]+"; " "))" end ) | join("\n") ' 2>/dev/null || true ) if [ -z "$formatted" ]; then printf '%s\n' "$EMPTY_OUTPUT" exit 0 fi overflow="" if [ "$count" -gt "$MAX_RENDERED_MESSAGES" ]; then overflow=$(printf '\n... and %s more unread relay message(s).' "$((count - MAX_RENDERED_MESSAGES))") fi context=$(printf 'Relay inbox update (%s unread):\n%s%s\nRead and respond to any messages that affect the current task.' "$count" "$formatted" "$overflow") jq -nc \ --arg context "$context" \ '{ hookSpecificOutput: { hookEventName: "UserPromptSubmit", additionalContext: $context } }' } main "$@" -
session-start.sh 7.3 KB
#!/usr/bin/env bash set -euo pipefail SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) PLUGIN_DIR=$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd) ENV_FILE="${PLUGIN_DIR}/.env" BASE_RELAY_DIR="${HOME}/.relay" # Per-agent namespacing: use RELAY_AGENT_NAME to avoid concurrent agents # overwriting each other's state files _agent_ns=$(printf '%s' "${RELAY_AGENT_NAME:-}" | tr -c 'A-Za-z0-9._-' '-' | sed 's/^-*//; s/-*$//' | cut -c1-64) if [ -n "$_agent_ns" ]; then RELAY_DIR="${BASE_RELAY_DIR}/agents/${_agent_ns}" else RELAY_DIR="${BASE_RELAY_DIR}" fi KEY_FILE="${RELAY_DIR}/workspace-key" TOKEN_FILE="${RELAY_DIR}/token" STATE_FILE="${RELAY_DIR}/codex-session.json" DEFAULT_BASE_URL="https://cast.agentrelay.com" load_env() { if [ -f "$ENV_FILE" ]; then set -a # shellcheck disable=SC1090 . "$ENV_FILE" set +a fi } run_local_setup() { if [ -x "${PLUGIN_DIR}/scripts/setup.sh" ]; then "${PLUGIN_DIR}/scripts/setup.sh" "$PWD" >/dev/null 2>&1 || true fi } command_exists() { command -v "$1" >/dev/null 2>&1 } trim() { printf '%s' "${1:-}" | awk '{$1=$1;print}' } normalize_base_url() { local value value=$(trim "${1:-$DEFAULT_BASE_URL}") value=${value%/} printf '%s' "${value:-$DEFAULT_BASE_URL}" } json_value() { local payload="$1" local query="$2" printf '%s' "$payload" | jq -r "$query // empty" 2>/dev/null || true } sanitize() { printf '%s' "${1:-}" | tr -c 'A-Za-z0-9._-' '-' | sed 's/^-*//; s/-*$//' | cut -c1-64 } derive_workspace_name() { local user host suffix user=$(sanitize "${USER:-${USERNAME:-codex}}") host=$(hostname 2>/dev/null | cut -d '.' -f 1 | tr -c 'A-Za-z0-9._-' '-' | sed 's/^-*//; s/-*$//' | cut -c1-20) suffix=$(date +%s) printf 'codex-%s-%s-%s' "${user:-codex}" "${host:-local}" "$suffix" | cut -c1-64 } read_existing_agent_name() { if [ -f "$STATE_FILE" ] && command_exists jq; then jq -r '.agentName // empty' "$STATE_FILE" 2>/dev/null || true fi } derive_agent_name() { local explicit existing user host suffix explicit=$(trim "${RELAY_AGENT_NAME:-}") if [ -n "$explicit" ]; then sanitize "$explicit" return fi existing=$(trim "$(read_existing_agent_name)") if [ -n "$existing" ]; then sanitize "$existing" return fi user=$(sanitize "${USER:-${USERNAME:-codex}}") host=$(hostname 2>/dev/null | cut -d '.' -f 1 | tr -c 'A-Za-z0-9._-' '-' | sed 's/^-*//; s/-*$//' | cut -c1-20) suffix=$(date +%s | tail -c 7) printf 'codex-%s-%s-%s' "${user:-codex}" "${host:-local}" "$suffix" | cut -c1-64 } write_file() { local path="$1" local content="$2" printf '%s' "$content" > "$path" } create_workspace() { local base_url="$1" local name="$2" curl -fsS \ -X POST \ -H 'Content-Type: application/json' \ -d "$(jq -nc --arg name "$name" '{name: $name}')" \ "${base_url}/v1/workspaces" 2>/dev/null || true } register_v1_agents() { local base_url="$1" local workspace_key="$2" local agent_name="$3" curl -fsS \ -X POST \ -H "Authorization: Bearer ${workspace_key}" \ -H 'Content-Type: application/json' \ -d "$(jq -nc --arg name "$agent_name" '{name: $name, agent_type: "agent", type: "agent"}')" \ "${base_url}/v1/agents" 2>/dev/null || true } register_v1_register() { local base_url="$1" local workspace_key="$2" local agent_name="$3" curl -fsS \ -X POST \ -H 'Content-Type: application/json' \ -d "$(jq -nc --arg workspace "$workspace_key" --arg name "$agent_name" '{workspace: $workspace, name: $name, cli: "codex", type: "agent"}')" \ "${base_url}/v1/register" 2>/dev/null || true } persist_state() { local base_url="$1" local workspace_key="$2" local workspace_id="$3" local agent_id="$4" local agent_name="$5" local token="$6" write_file "$KEY_FILE" "$workspace_key" chmod 600 "$KEY_FILE" write_file "$TOKEN_FILE" "$token" chmod 600 "$TOKEN_FILE" jq -nc \ --arg baseUrl "$base_url" \ --arg workspaceKey "$workspace_key" \ --arg workspaceId "$workspace_id" \ --arg agentId "$agent_id" \ --arg agentName "$agent_name" \ --arg token "$token" \ --arg updatedAt "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \ '{ baseUrl: $baseUrl, workspaceKey: $workspaceKey, workspaceId: $workspaceId, agentId: $agentId, agentName: $agentName, token: $token, cli: "codex", updatedAt: $updatedAt }' > "$STATE_FILE" chmod 600 "$STATE_FILE" } main() { run_local_setup load_env if ! command_exists curl || ! command_exists jq; then exit 0 fi mkdir -p "$RELAY_DIR" local base_url workspace_key workspace_id agent_name registration token registered_name agent_id base_url=$(normalize_base_url "${RELAY_BASE_URL:-}") workspace_key=$(trim "${RELAY_API_KEY:-}") workspace_id="" if [ -z "$workspace_key" ] && [ -s "$KEY_FILE" ]; then workspace_key=$(trim "$(cat "$KEY_FILE" 2>/dev/null || true)") fi if [ -z "$workspace_key" ]; then local created created=$(create_workspace "$base_url" "$(derive_workspace_name)") workspace_key=$(json_value "$created" '.api_key') [ -z "$workspace_key" ] && workspace_key=$(json_value "$created" '.apiKey') [ -z "$workspace_key" ] && workspace_key=$(json_value "$created" '.data.api_key') [ -z "$workspace_key" ] && workspace_key=$(json_value "$created" '.data.apiKey') workspace_id=$(json_value "$created" '.workspace_id') [ -z "$workspace_id" ] && workspace_id=$(json_value "$created" '.workspaceId') [ -z "$workspace_id" ] && workspace_id=$(json_value "$created" '.data.workspace_id') [ -z "$workspace_id" ] && workspace_id=$(json_value "$created" '.data.workspaceId') fi [ -z "$workspace_key" ] && exit 0 agent_name=$(derive_agent_name) # Re-namespace state directory now that we know the agent name RELAY_DIR="${BASE_RELAY_DIR}/agents/${agent_name}" KEY_FILE="${RELAY_DIR}/workspace-key" TOKEN_FILE="${RELAY_DIR}/token" STATE_FILE="${RELAY_DIR}/codex-session.json" mkdir -p "$RELAY_DIR" registration=$(register_v1_agents "$base_url" "$workspace_key" "$agent_name") if [ -z "$registration" ]; then registration=$(register_v1_register "$base_url" "$workspace_key" "$agent_name") fi [ -z "$registration" ] && exit 0 token=$(json_value "$registration" '.token') [ -z "$token" ] && token=$(json_value "$registration" '.data.token') registered_name=$(json_value "$registration" '.name') [ -z "$registered_name" ] && registered_name=$(json_value "$registration" '.data.name') agent_id=$(json_value "$registration" '.id') [ -z "$agent_id" ] && agent_id=$(json_value "$registration" '.agent_id') [ -z "$agent_id" ] && agent_id=$(json_value "$registration" '.data.id') [ -z "$agent_id" ] && agent_id=$(json_value "$registration" '.data.agent_id') [ -z "$workspace_id" ] && workspace_id=$(json_value "$registration" '.workspace_id') [ -z "$workspace_id" ] && workspace_id=$(json_value "$registration" '.workspaceId') [ -z "$workspace_id" ] && workspace_id=$(json_value "$registration" '.data.workspace_id') [ -z "$workspace_id" ] && workspace_id=$(json_value "$registration" '.data.workspaceId') [ -z "$token" ] && exit 0 [ -z "$registered_name" ] && registered_name="$agent_name" [ -z "$agent_id" ] && agent_id="$registered_name" [ -z "$workspace_id" ] && workspace_id="ws_unknown" persist_state "$base_url" "$workspace_key" "$workspace_id" "$agent_id" "$registered_name" "$token" } main "$@" -
stop-inbox.sh 3.9 KB
#!/usr/bin/env bash set -euo pipefail SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) PLUGIN_DIR=$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd) ENV_FILE="${PLUGIN_DIR}/.env" BASE_RELAY_DIR="${HOME}/.relay" # Per-agent namespacing: use RELAY_AGENT_NAME to avoid concurrent agents # overwriting each other's state files _agent_ns=$(printf '%s' "${RELAY_AGENT_NAME:-}" | tr -c 'A-Za-z0-9._-' '-' | sed 's/^-*//; s/-*$//' | cut -c1-64) if [ -n "$_agent_ns" ]; then RELAY_DIR="${BASE_RELAY_DIR}/agents/${_agent_ns}" else RELAY_DIR="${BASE_RELAY_DIR}" fi TOKEN_FILE="${RELAY_DIR}/token" STATE_FILE="${RELAY_DIR}/codex-session.json" DEFAULT_BASE_URL="https://cast.agentrelay.com" EMPTY_OUTPUT='{}' MAX_RENDERED_MESSAGES=20 load_env() { if [ -f "$ENV_FILE" ]; then set -a # shellcheck disable=SC1090 . "$ENV_FILE" set +a fi } command_exists() { command -v "$1" >/dev/null 2>&1 } trim() { printf '%s' "${1:-}" | awk '{$1=$1;print}' } normalize_base_url() { local value value=$(trim "${1:-}") if [ -z "$value" ] && [ -f "$STATE_FILE" ] && command_exists jq; then value=$(jq -r '.baseUrl // empty' "$STATE_FILE" 2>/dev/null || true) fi value=$(trim "${value:-$DEFAULT_BASE_URL}") value=${value%/} printf '%s' "${value:-$DEFAULT_BASE_URL}" } json_number() { local payload="$1" local query="$2" printf '%s' "$payload" | jq -r "$query" 2>/dev/null || printf '0' } read_stop_hook_active() { if ! command_exists jq; then printf 'false' return fi jq -r '.stop_hook_active // false' 2>/dev/null || printf 'false' } read_token() { local token token=$(trim "${RELAY_TOKEN:-}") if [ -n "$token" ]; then printf '%s' "$token" return fi if [ -s "$TOKEN_FILE" ]; then trim "$(cat "$TOKEN_FILE" 2>/dev/null || true)" return fi printf '' } main() { local input stop_hook_active token base_url messages count formatted overflow reason load_env if ! command_exists curl || ! command_exists jq; then printf '%s\n' "$EMPTY_OUTPUT" exit 0 fi input=$(cat) stop_hook_active=$(printf '%s' "$input" | read_stop_hook_active) if [ "$stop_hook_active" = "true" ]; then printf '%s\n' "$EMPTY_OUTPUT" exit 0 fi token=$(read_token) if [ -z "$token" ]; then printf '%s\n' "$EMPTY_OUTPUT" exit 0 fi base_url=$(normalize_base_url "${RELAY_BASE_URL:-}") messages=$( curl -fsS \ -X POST \ -H "Authorization: Bearer ${token}" \ -H 'Content-Type: application/json' \ -d '{}' \ "${base_url}/v1/inbox/check" 2>/dev/null || true ) if [ -z "$messages" ]; then printf '%s\n' "$EMPTY_OUTPUT" exit 0 fi count=$(json_number "$messages" '(.messages // []) | length') case "$count" in ''|*[!0-9]*) count=0 ;; esac if [ "$count" -eq 0 ]; then printf '%s\n' "$EMPTY_OUTPUT" exit 0 fi formatted=$( printf '%s' "$messages" | jq -r --argjson limit "$MAX_RENDERED_MESSAGES" ' (.messages // []) | .[:$limit] | map( if ((.channel // "") | length) > 0 then "Relay message from \(.from // "unknown") in #\(.channel)\(if ((.id // "") | length) > 0 then " [\(.id)]" else "" end): \((.text // "") | gsub("[\\r\\n]+"; " "))" else "Relay message from \(.from // "unknown")\(if ((.id // "") | length) > 0 then " [\(.id)]" else "" end): \((.text // "") | gsub("[\\r\\n]+"; " "))" end ) | join("\n") ' 2>/dev/null || true ) if [ -z "$formatted" ]; then printf '%s\n' "$EMPTY_OUTPUT" exit 0 fi overflow="" if [ "$count" -gt "$MAX_RENDERED_MESSAGES" ]; then overflow=$(printf '\n... and %s more unread relay message(s).' "$((count - MAX_RENDERED_MESSAGES))") fi reason=$(printf 'You have %s unread relay message(s). Please read and respond before stopping:\n%s%s' "$count" "$formatted" "$overflow") jq -nc --arg reason "$reason" '{decision: "block", reason: $reason}' } main "$@"
-
-
scripts
-
setup.sh 8.8 KB
#!/usr/bin/env bash set -euo pipefail SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) SKILL_DIR=$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd) TARGET_ROOT="${1:-$PWD}" CODEX_DIR="${TARGET_ROOT}/.codex" AGENTS_DIR="${CODEX_DIR}/agents" CONFIG_FILE="${CODEX_DIR}/config.toml" HOOKS_FILE="${CODEX_DIR}/hooks.json" WORKER_SOURCE="${SKILL_DIR}/codex-config/relay-worker.toml" WORKER_TARGET="${AGENTS_DIR}/relay-worker.toml" command_exists() { command -v "$1" >/dev/null 2>&1 } shell_quote() { printf "'%s'" "$(printf '%s' "$1" | sed "s/'/'\\\\''/g")" } write_if_changed() { local path="$1" local tmp="$2" if [ -f "$path" ] && cmp -s "$path" "$tmp"; then rm -f "$tmp" return fi mv "$tmp" "$path" } ensure_features_codex_hooks() { local file="$1" local tmp tmp=$(mktemp) awk ' BEGIN { in_features = 0 features_seen = 0 codex_hooks_written = 0 } function write_codex_hooks() { if (!codex_hooks_written) { print "codex_hooks = true" codex_hooks_written = 1 } } { if ($0 ~ /^[[:space:]]*features[.]codex_hooks[[:space:]]*=/) { print "features.codex_hooks = true" codex_hooks_written = 1 next } if ($0 ~ /^\[[^]]+\][[:space:]]*$/) { if (in_features) { write_codex_hooks() } if ($0 == "[features]") { in_features = 1 features_seen = 1 } else { in_features = 0 } print next } if (in_features && $0 ~ /^[[:space:]]*codex_hooks[[:space:]]*=/) { write_codex_hooks() next } print } END { if (in_features) { write_codex_hooks() } if (!features_seen && !codex_hooks_written) { if (NR > 0) { print "" } print "[features]" print "codex_hooks = true" } } ' "$file" > "$tmp" write_if_changed "$file" "$tmp" } ensure_top_level_approval_policy() { local file="$1" local tmp tmp=$(mktemp) awk ' BEGIN { found = 0 first_section = 0 inserted = 0 } { # If we find approval_policy at top level (before any section or as dotted key), mark found if (!first_section && $0 ~ /^[[:space:]]*approval_policy[[:space:]]*=/) { found = 1 print next } # Detect first section header if ($0 ~ /^\[[^]]+\][[:space:]]*$/) { if (!first_section && !found && !inserted) { # Insert approval_policy before the first section header print "approval_policy = \"on-request\"" print "" inserted = 1 } first_section = 1 } print } END { # File has no sections at all if (!found && !inserted) { if (NR > 0) { print "" } print "approval_policy = \"on-request\"" } } ' "$file" > "$tmp" write_if_changed "$file" "$tmp" } ensure_agent_relay_mcp_block() { local file="$1" local tmp tmp=$(mktemp) awk ' BEGIN { in_block = 0 block_seen = 0 dotted_seen = 0 command_seen = 0 args_seen = 0 env_seen = 0 command_line = "command = \"npx\"" args_line = "args = [\"-y\", \"agent-relay\", \"mcp\"]" env_line = "env = { RELAY_API_KEY = \"\", RELAY_BASE_URL = \"https://cast.agentrelay.com\", RELAY_AGENT_TYPE = \"agent\" }" } function write_missing_keys() { if (!command_seen) { print command_line command_seen = 1 } if (!args_seen) { print args_line args_seen = 1 } if (!env_seen) { print env_line env_seen = 1 } } { if (!in_block && $0 ~ /^[[:space:]]*mcp_servers[.]agent-relay[.]command[[:space:]]*=/) { block_seen = 1 dotted_seen = 1 command_seen = 1 command_line = $0 sub(/^[[:space:]]*mcp_servers[.]agent-relay[.]command[[:space:]]*=/, "command =", command_line) print next } if (!in_block && $0 ~ /^[[:space:]]*mcp_servers[.]agent-relay[.]args[[:space:]]*=/) { block_seen = 1 dotted_seen = 1 args_seen = 1 args_line = $0 sub(/^[[:space:]]*mcp_servers[.]agent-relay[.]args[[:space:]]*=/, "args =", args_line) print next } if (!in_block && $0 ~ /^[[:space:]]*mcp_servers[.]agent-relay[.]env[[:space:]]*=/) { block_seen = 1 dotted_seen = 1 env_seen = 1 env_line = $0 sub(/^[[:space:]]*mcp_servers[.]agent-relay[.]env[[:space:]]*=/, "env =", env_line) print next } if ($0 ~ /^\[[^]]+\][[:space:]]*$/) { if (in_block) { write_missing_keys() } if ($0 == "[mcp_servers.agent-relay]") { in_block = 1 block_seen = 1 } else { in_block = 0 } print next } if (in_block) { if ($0 ~ /^[[:space:]]*command[[:space:]]*=/) { command_seen = 1 print next } if ($0 ~ /^[[:space:]]*args[[:space:]]*=/) { args_seen = 1 print next } if ($0 ~ /^[[:space:]]*env[[:space:]]*=/) { env_seen = 1 print next } } print } END { if (in_block) { write_missing_keys() } if (!block_seen && !dotted_seen) { if (NR > 0) { print "" } print "[mcp_servers.agent-relay]" print command_line print args_line print env_line } } ' "$file" > "$tmp" write_if_changed "$file" "$tmp" } desired_hooks_json() { local session_cmd prompt_cmd stop_cmd session_cmd="bash $(shell_quote "${SKILL_DIR}/hooks/session-start.sh")" prompt_cmd="bash $(shell_quote "${SKILL_DIR}/hooks/prompt-inbox.sh")" stop_cmd="bash $(shell_quote "${SKILL_DIR}/hooks/stop-inbox.sh")" jq -n \ --arg session_cmd "$session_cmd" \ --arg prompt_cmd "$prompt_cmd" \ --arg stop_cmd "$stop_cmd" \ '{ hooks: { SessionStart: [ { hooks: [ { type: "command", command: $session_cmd, timeoutSec: 15, statusMessage: "Connecting Relaycast" } ] } ], UserPromptSubmit: [ { hooks: [ { type: "command", command: $prompt_cmd, timeoutSec: 5, statusMessage: "Checking relay inbox" } ] } ], Stop: [ { hooks: [ { type: "command", command: $stop_cmd, timeoutSec: 5, statusMessage: "Verifying relay inbox" } ] } ] } }' } merge_hooks_file() { local desired_json="$1" local tmp tmp=$(mktemp) if [ -f "$HOOKS_FILE" ] && jq empty "$HOOKS_FILE" >/dev/null 2>&1; then jq \ --argjson desired "$desired_json" \ ' def remove_owned_groups($event; $status; $script): .hooks[$event] = ( (.hooks[$event] // []) | map( select( ( (.hooks // []) | any( ((.statusMessage // "") == $status) or (((.command // "") | tostring) | contains($script)) ) ) | not ) ) ); .hooks = (.hooks // {}) | remove_owned_groups("SessionStart"; "Connecting Relaycast"; "session-start.sh") | remove_owned_groups("UserPromptSubmit"; "Checking relay inbox"; "prompt-inbox.sh") | remove_owned_groups("Stop"; "Verifying relay inbox"; "stop-inbox.sh") | .hooks.SessionStart = ((.hooks.SessionStart // []) + $desired.hooks.SessionStart) | .hooks.UserPromptSubmit = ((.hooks.UserPromptSubmit // []) + $desired.hooks.UserPromptSubmit) | .hooks.Stop = ((.hooks.Stop // []) + $desired.hooks.Stop) ' "$HOOKS_FILE" > "$tmp" else printf '%s\n' "$desired_json" > "$tmp" fi write_if_changed "$HOOKS_FILE" "$tmp" } install_worker_agent() { if [ ! -f "$WORKER_SOURCE" ]; then return fi local tmp tmp=$(mktemp) cp "$WORKER_SOURCE" "$tmp" write_if_changed "$WORKER_TARGET" "$tmp" } main() { if ! command_exists jq; then exit 0 fi mkdir -p "$CODEX_DIR" "$AGENTS_DIR" touch "$CONFIG_FILE" chmod +x "${SKILL_DIR}/scripts/setup.sh" "${SKILL_DIR}/hooks/"*.sh 2>/dev/null || true ensure_features_codex_hooks "$CONFIG_FILE" ensure_top_level_approval_policy "$CONFIG_FILE" ensure_agent_relay_mcp_block "$CONFIG_FILE" merge_hooks_file "$(desired_hooks_json)" install_worker_agent } main "$@"
-
-
README.md 5.3 KB
# Codex Relay Skill Codex-native multi-agent coordination via Relaycast. ## What it does This package gives Codex a reusable relay coordination layer so sub-agents can communicate through Relaycast instead of staying limited to parent-only result collection. It includes: - a Codex skill that teaches lead and worker messaging protocol - an Agent Relay MCP dependency declaration - a template Agent Relay MCP config block for `.codex/config.toml` - a `relay-worker` custom agent template for `.codex/agents/` With these pieces installed, Codex can: - coordinate teams through direct messages, channels, and threads - require ACK/DONE signaling from workers - let workers send peer-to-peer updates through Relaycast - reuse the same relay workflow across project-scoped and user-scoped setups ## Installation ```bash mkdir -p .agents/skills cp -R plugins/codex-relay-skill .agents/skills/agent-relay ``` That's it. Everything else is automatic. On first use, the skill self-installs by running `scripts/setup.sh`, which: - adds the Agent Relay MCP server to `.codex/config.toml` - enables `features.codex_hooks = true` - writes `.codex/hooks.json` with SessionStart, UserPromptSubmit, and Stop hooks - installs `.codex/agents/relay-worker.toml` All of this is idempotent — safe to run multiple times, and it merges with existing config rather than overwriting. For user-wide availability, install to `$HOME/.agents/skills/agent-relay` instead. ### Optional: join an existing workspace Set `RELAY_API_KEY` before launching Codex to join a specific Relaycast workspace: ```bash export RELAY_API_KEY="rk_live_your_key_here" ``` If unset, a new workspace is auto-created on the first session. <details> <summary>Manual setup (advanced)</summary> If you prefer to configure everything manually instead of using the auto-installer: 1. Add to `.codex/config.toml`: ```toml features.codex_hooks = true [mcp_servers.agent-relay] command = "npx" args = ["-y", "agent-relay", "mcp"] env = { RELAY_API_KEY = "", RELAY_BASE_URL = "https://cast.agentrelay.com", RELAY_AGENT_TYPE = "agent" } ``` 2. Copy hooks config: ```bash cp .agents/skills/agent-relay/hooks/hooks.json .codex/hooks.json ``` 3. Install the worker agent: ```bash mkdir -p .codex/agents cp .agents/skills/agent-relay/codex-config/relay-worker.toml .codex/agents/relay-worker.toml ``` </details> ## Usage ### Use the skill directly Invoke the skill explicitly: ```text $agent-relay Coordinate this refactor with two workers and keep all status updates in Relaycast. ``` Or describe the task naturally and let Codex match the skill from its description. ### Spawn relay workers Once `relay-worker.toml` is installed, delegate bounded tasks to the `relay-worker` custom agent and include: - the worker relay name - the lead relay name - the workspace-key source - exact task scope - completion criteria Example: ```text Spawn a relay-worker named api-worker. Have it check Relaycast, ACK me, update the API route tests only, send STATUS after the first green test run, and send DONE with evidence before exit. ``` ### Coordinate a team Use Relaycast when workers need to message each other directly, not only the lead. Good fits: - parallel implementation across separate subsystems - lead/worker review loops - shared channel updates for longer-running tasks - cross-terminal or cross-machine collaboration ## Environment variables | Variable | Required | Default | Description | | ------------------ | -------- | ----------------------------- | -------------------------------------------------------------------- | | `RELAY_API_KEY` | No | `""` in the template config | Relaycast workspace key | | `RELAY_BASE_URL` | No | `https://cast.agentrelay.com` | Relaycast API base URL | | `RELAY_AGENT_TYPE` | No | `agent` | Default Relaycast agent type | | `RELAY_AGENT_NAME` | No | unset | Optional stable relay identity when your workflow wants a fixed name | ## Plugin structure ```text codex-relay-skill/ SKILL.md # Codex skill manifest and workflow instructions README.md # Installation and usage docs agents/ openai.yaml # Agent Relay MCP dependency metadata codex-config/ config.toml # Template MCP server config for .codex/config.toml relay-worker.toml # Template custom worker agent for .codex/agents/ scripts/ setup.sh # Auto-installer (runs on first skill activation) hooks/ hooks.json # Hook definitions (SessionStart, Stop, UserPromptSubmit) session-start.sh # Auto-connect and state persistence stop-inbox.sh # Block exit while unread messages exist prompt-inbox.sh # Rate-limited inbox polling and context injection ``` Installed layout in a project typically looks like: ```text .agents/skills/agent-relay/ # Skill directory Codex scans .codex/config.toml # Runtime Agent Relay MCP server + features.codex_hooks .codex/hooks.json # Hook wiring (copied from skill) .codex/agents/relay-worker.toml ``` -
SKILL.md 6.8 KB
--- name: agent-relay description: Use when you need Codex to coordinate multiple agents through Agent Relay for peer-to-peer messaging, lead/worker handoffs, or shared status tracking across sub-agents and terminals. --- # Agent Relay Use this skill when Codex needs real-time coordination across multiple agents. It gives Codex a repeatable workflow for: - connecting to an Agent Relay workspace - spawning relay-aware workers - sending direct messages, channel updates, and thread replies - keeping lead and worker state synchronized through ACK, STATUS, BLOCKED, and DONE signals Relay fills the peer-to-peer gap in Codex sub-agent workflows. Codex can spawn and collect worker results, but Agent Relay gives those workers a shared message bus so they can talk to the lead and to each other. ## Auto-setup On first activation, this skill auto-configures Codex by running `scripts/setup.sh`. This adds the Agent Relay MCP server to `.codex/config.toml`, enables hooks, installs `hooks.json`, and copies the `relay-worker.toml` agent definition. No manual setup is required after installing the skill. ## Startup protocol Every relay-connected Codex agent must complete these steps IN ORDER before substantive work: 1. **Set up a workspace.** - If `RELAY_WORKSPACE_KEY` is set in the environment, call `set_workspace_key` with that key. - If only the legacy `RELAY_API_KEY` alias is set, treat it as the same workspace key. - If no key is available, call `create_workspace` to auto-create one. This returns a workspace key — save it for workers. 2. **Register as an agent.** Call `register_agent` with your agent name and `type: "agent"`. Use `RELAY_AGENT_NAME` from the environment if set, otherwise derive a name from the task context (e.g., `lead`, `auth-worker`). 3. **Keep workspace credentials out of output.** Never print the workspace key or construct an observer URL from it — it is an administrative credential. When the user asks to follow the conversation, run `agent-relay observer` and print the URL it returns. That mints a scoped, read-only token (`ot_live_...`) that expires in 24 hours and excludes agent DMs, so the link is safe to share. Narrow it further with `--channels`, widen it with `--include-dms` or `--expires`, and revoke it with `agent-relay observer revoke <id>`. 4. **Check the relay inbox.** Call `check_inbox` to see if there are any pending messages or task assignments. 5. **Send an ACK.** If you received a task assignment, send `ACK: <one-sentence understanding>` to your lead via `send_dm`. If the assignment is unclear, send `BLOCKED: <question>` instead of guessing. 6. **When the task is complete**, send `DONE: <summary with evidence>` before stopping. If workspace creation or registration fails, retry once, then report the failure to the user — do not proceed without a relay connection. ## Critical rule **Do not assume the current MCP session already has an active Agent Relay workspace.** Always call `set_workspace_key` or `create_workspace` before registering. ## Working rules - Include `as: "<agent-name>"` on relay calls that support explicit attribution. - Keep the relay identity stable for the whole task. Do not switch names mid-task. - Check the inbox again after meaningful milestones, before long-running work, and before stopping. - Prefer direct messages for lead/worker coordination. Use channels only when multiple agents need the same update. - Keep status messages short, factual, and scoped to the assigned work. - Do not spawn additional relay workers unless the lead explicitly asks for more delegation. - If the lead updates the task, follow the newest explicit instruction. ## Message templates - `ACK: I understand the assignment and I am starting work on <scope>.` - `STATUS: Finished <milestone>; next I am doing <next-step>.` - `BLOCKED: I cannot continue because <blocker>.` - `DONE: Completed <scope>. Evidence: <files changed, commands run, tests, or decisions>.` ## Worker patterns There are two current ways to involve more agents. Use the right one for the job. ### Registered workspace identities Use `register_agent` for an agent process that is already running and only needs a Relay identity. Registration does not start a new model runtime. **Lead steps:** 1. Ensure workspace exists (`set_workspace_key` or `create_workspace`). 2. Register the lead (`register_agent`). 3. Give the other running process the workspace key and tell it to call `register_agent` with a stable name. 4. Send the assignment via `send_dm(to: "worker-name", text: "...")`. 5. Poll lead inbox for ACK (`check_inbox`). **Worker steps:** 1. Call `set_workspace_key` with the shared key. 2. Register with `register_agent`. 3. Check inbox (`check_inbox`). 4. Send ACK to lead via `send_dm`. 5. Perform the assigned scope. 6. Send DONE to lead via `send_dm`. ### Relay-spawned workers Use `add_agent` when the lead should ask Relay to start a provider-backed worker. The current tool requires `name`, `cli`, and `task`; optional fields include `channel`, `persona`, and `model`. **Lead steps:** 1. Ensure workspace exists and lead is registered. 2. Spawn the worker with `add_agent(name: "worker-name", cli: "codex", task: "...")`. 3. Include `https://agentrelay.com/skill`, the lead name, exact scope, and completion criteria in the task prompt. 4. Poll lead inbox for ACK (`check_inbox`). 5. Release the worker with `remove_agent` after the work is accepted. **Worker steps:** 1. Follow the `using-agent-relay` role from `https://agentrelay.com/skill`. 2. Check inbox, send ACK, do the assigned work, and send DONE. ### Codex sub-agents If your Codex surface has a sub-agent spawn capability, use the bundled `relay-worker` agent definition for code-heavy work that needs a separate Codex runtime with file access and tools. Include the workspace key, relay name, lead name, exact scope, and completion criteria in the sub-agent prompt. If that spawn capability is not available, use `add_agent` instead. ## Worker ACK fallback If a worker does not ACK within 30 seconds: 1. Check whether the worker appears in `list_agents`. 2. If this is a running process, have it call `register_agent`. 3. If this should be a spawned worker, call `add_agent` with `name`, `cli`, and `task`. 4. Send (or re-send) the assignment via `send_dm`. 5. Poll the lead inbox again for ACK. 6. If still no ACK after a second attempt, report the exact failed step to the user. ## Handoff template ```text Worker: api-worker Type: relay-spawned worker (use add_agent with name, cli, and task) Lead: lead Scope: check the Agent Relay inbox and confirm connectivity Protocol: 1. Check inbox 2. DM lead with ACK 3. Perform scope 4. DM lead with DONE ``` For code-heavy tasks, change the type line to: ```text Type: Codex sub-agent (use relay-worker if your Codex surface provides sub-agent spawning) ```
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.