Skip to content

feat: add Codex relay skill for sub-agent communication#595

Merged
khaliqgant merged 18 commits intomainfrom
feature/codex-relay-skill
Mar 20, 2026
Merged

feat: add Codex relay skill for sub-agent communication#595
khaliqgant merged 18 commits intomainfrom
feature/codex-relay-skill

Conversation

@khaliqgant
Copy link
Member

@khaliqgant khaliqgant commented Mar 19, 2026

Summary

  • Adds a Codex-native skill (plugins/codex-relay-skill/) that enables peer-to-peer sub-agent communication via Relaycast MCP
  • Codex sub-agents are parent-orchestrated with no native sibling messaging — Agent Relay fills this gap
  • Follows the same patterns as existing Claude and Gemini plugins but adapted for Codex's skill/agent/hooks system

What's included

File Purpose
SKILL.md Codex skill manifest with relay protocol (ACK/STATUS/BLOCKED/DONE)
hooks/hooks.json Lifecycle hook definitions (SessionStart, Stop, UserPromptSubmit)
hooks/session-start.sh Auto-creates workspace, registers agent, persists state
hooks/stop-inbox.sh Blocks exit while unread relay messages exist
hooks/prompt-inbox.sh Rate-limited inbox polling, injects messages as context
agents/openai.yaml MCP dependency declaration for Relaycast
codex-config/config.toml Template MCP server config for .codex/config.toml
codex-config/relay-worker.toml Worker sub-agent definition for .codex/agents/
README.md Installation and usage docs

Hook mapping (Claude → Codex)

Claude Hook Codex Equivalent Notes
PostToolUse UserPromptSubmit AfterToolUse not yet in hooks.json engine
Stop Stop Returns decision: "block"
SessionStart SessionStart Auto-connect + state persistence
SubagentStart (via SKILL.md) No Codex equivalent
PreCompact (none) Codex has no compaction

Test plan

  • Install skill to .agents/skills/agent-relay/ and verify Codex discovers it
  • Verify session-start.sh creates workspace and persists state to ~/.relay/
  • Verify stop-inbox.sh blocks exit when unread messages exist
  • Verify prompt-inbox.sh injects inbox messages before prompts
  • Test relay-worker sub-agent spawning and ACK/DONE signaling
  • Verify MCP config template works in .codex/config.toml

🤖 Generated with Claude Code


Open with Devin

khaliqgant and others added 2 commits March 19, 2026 23:08
Add a Codex-native skill that enables peer-to-peer sub-agent
communication via Relaycast MCP, filling the gap in Codex's
parent-orchestrated sub-agent model.

Includes:
- SKILL.md with relay protocol (ACK/STATUS/BLOCKED/DONE)
- Lifecycle hooks: SessionStart, Stop, UserPromptSubmit
- relay-worker.toml custom agent definition
- MCP server config template for .codex/config.toml
- agents/openai.yaml MCP dependency declaration

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
devin-ai-integration[bot]

This comment was marked as resolved.

Include features.codex_hooks = true and hooks.json installation
in the setup instructions. Without these, the lifecycle hooks
(auto-connect, inbox polling, stop guard) don't fire.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
devin-ai-integration[bot]

This comment was marked as resolved.

khaliqgant and others added 4 commits March 20, 2026 06:42
Installation is now a single cp command. The skill self-installs
on first activation via scripts/setup.sh — no manual config editing
needed. Manual setup moved to a collapsible details section.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

View 9 additional findings in Devin Review.

Open in Devin Review

Comment on lines +129 to +152
if (!in_block && $0 ~ /^[[:space:]]*mcp_servers[.]relaycast[.]command[[:space:]]*=/) {
block_seen = 1
dotted_seen = 1
command_seen = 1
command_line = $0
sub(/^[[:space:]]*mcp_servers[.]relaycast[.]command[[:space:]]*=/, "command =", command_line)
next
}
if (!in_block && $0 ~ /^[[:space:]]*mcp_servers[.]relaycast[.]args[[:space:]]*=/) {
block_seen = 1
dotted_seen = 1
args_seen = 1
args_line = $0
sub(/^[[:space:]]*mcp_servers[.]relaycast[.]args[[:space:]]*=/, "args =", args_line)
next
}
if (!in_block && $0 ~ /^[[:space:]]*mcp_servers[.]relaycast[.]env[[:space:]]*=/) {
block_seen = 1
dotted_seen = 1
env_seen = 1
env_line = $0
sub(/^[[:space:]]*mcp_servers[.]relaycast[.]env[[:space:]]*=/, "env =", env_line)
next
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 AWK config migration silently drops dotted key values when a section block also exists

ensure_relaycast_mcp_block in setup.sh shares command_seen/args_seen/env_seen flags across both dotted-key processing (lines 129-152) and section-block processing (lines 168-183). When a config file has both a dotted key like mcp_servers.relaycast.command = "custom" and a [mcp_servers.relaycast] section, the dotted key line is consumed via next (not printed) and its _seen flag is set. Later, when the section block is processed, write_missing_keys() skips that key because _seen is already true. The net result is the dotted key's value is silently dropped from the output — e.g. the command line disappears entirely, producing an invalid MCP server config without a command key.

Example input that triggers the bug
mcp_servers.relaycast.command = "custom-cmd"

[mcp_servers.relaycast]
args = ["-y", "@relaycast/mcp"]
env = { RELAY_API_KEY = "" }

Output is missing command:

[mcp_servers.relaycast]
args = ["-y", "@relaycast/mcp"]
env = { RELAY_API_KEY = "" }
Prompt for agents
In plugins/codex-relay-skill/scripts/setup.sh, the ensure_relaycast_mcp_block AWK script (lines 102-210) uses shared _seen flags for both dotted-key and section-block processing. When dotted keys are consumed (lines 129-152, via next), their _seen flags prevent write_missing_keys() from emitting those values later.

Fix: When a [mcp_servers.relaycast] section header is encountered and dotted_seen is true (line 158), reset the _seen flags back to 0 and re-set command_line/args_line/env_line from the stored dotted values so that write_missing_keys() writes any keys not already present in the section block. Alternatively, when in_block is entered and command_seen was set by dotted processing, print command_line immediately and only then set command_seen for the section scope.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

View 11 additional findings in Devin Review.

Open in Devin Review

Comment on lines +135 to +136
write_file "$KEY_FILE" "$workspace_key"
write_file "$TOKEN_FILE" "$token"
Copy link
Contributor

@devin-ai-integration devin-ai-integration bot Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Token/state file path mismatch: session-start.sh saves to re-namespaced dir but prompt-inbox.sh and stop-inbox.sh look in the original dir

When RELAY_AGENT_NAME is not set (the default per the README), session-start.sh re-namespaces RELAY_DIR to $HOME/.relay/agents/<derived-name> at line 205 after calling derive_agent_name(), and persists the token and state there (line 235 → persist_state). However, prompt-inbox.sh and stop-inbox.sh both compute RELAY_DIR solely from RELAY_AGENT_NAME at startup (prompt-inbox.sh:11-16, stop-inbox.sh:11-16). When RELAY_AGENT_NAME is empty, they set RELAY_DIR=$HOME/.relay and look for the token at $HOME/.relay/token — but the token was actually saved at $HOME/.relay/agents/<derived-name>/token. Since read_token() finds no file and no RELAY_TOKEN env var, both hooks silently exit with {}, effectively disabling inbox polling and the stop guard in the default configuration.

Concrete path trace for default case (RELAY_AGENT_NAME unset)
  1. session-start.sh line 14-15: RELAY_DIR = $HOME/.relay (empty namespace)
  2. session-start.sh line 202: agent_name = codex-user-host-123456 (derived)
  3. session-start.sh line 205: RELAY_DIR = $HOME/.relay/agents/codex-user-host-123456 (re-namespaced)
  4. session-start.sh line 145: token written to $HOME/.relay/agents/codex-user-host-123456/token
  5. prompt-inbox.sh line 14-15: RELAY_DIR = $HOME/.relay (empty namespace, no re-namespace step)
  6. prompt-inbox.sh line 17: TOKEN_FILE = $HOME/.relay/token → file not found → hook silently no-ops
  7. Same for stop-inbox.sh
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

khaliqgant and others added 4 commits March 20, 2026 10:13
Add step in SKILL.md startup protocol to show the user the
agentrelay.dev/observer URL so they can follow agent
conversations live.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The startup protocol now explicitly tells Codex to check for
a workspace, create one if missing, and register — using actual
MCP tool names. Previously relied on hooks that may not fire
on first session.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Address four issues from real-world testing:
1. Explicit workspace bootstrap — never assume one exists
2. Distinguish Relaycast agents (agent.add) from Codex sub-agents
   (spawn_agent) — use the right tool for the job
3. Add deterministic ACK fallback sequence (30s timeout)
4. Tighten handoff templates with exact tool call sequences

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@khaliqgant khaliqgant force-pushed the feature/codex-relay-skill branch from c485aab to 8e83993 Compare March 20, 2026 09:50
devin-ai-integration[bot]

This comment was marked as resolved.

khaliqgant and others added 5 commits March 20, 2026 13:56
- Add chmod 600 after writing credential files (workspace-key, token,
  session state) in session-start.sh to prevent world-readable secrets
- Fix AWK config migration in setup.sh that silently dropped dotted-key
  values when both dotted keys and a [mcp_servers.relaycast] section
  block existed in the same TOML file
- Add ensure_top_level_approval_policy() to write approval_policy at
  the TOML top level instead of accidentally scoping it under [features]

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…r agent

hooks.json referenced plugins/codex-relay-skill/hooks/ but the README
instructs users to install to .agents/skills/agent-relay/. Updated all
paths to match the documented install location.

All scripts now namespace state files under ~/.relay/agents/<name>/ using
RELAY_AGENT_NAME env var (or the derived agent name in session-start.sh)
so concurrent agents no longer overwrite each other's token, session, and
poll state.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…intent)

The file was removed on this branch as part of moving plugins to the
skills repo. Main had a tweak to the same file (#594). Keeping it
deleted per this branch's intent.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ctive mode

`opencode --prompt` launches the interactive TUI, which renders escape
codes to stdout and hangs when there's no TTY. The correct headless
command is `opencode run <message>`.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@khaliqgant khaliqgant merged commit 205539b into main Mar 20, 2026
1 check passed
@khaliqgant khaliqgant deleted the feature/codex-relay-skill branch March 20, 2026 13:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant