Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 73 additions & 8 deletions skills/orchestrating-agent-relay/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@ A headless orchestrator is an agent that:
4. Coordinates work without human intervention

The orchestrator drives the team and reads/sends/lists through the **Agent
Relay MCP server** (`agent-relay mcp`), which auto-registers the orchestrating
session as the `orchestrator` agent when a workspace key is present. Lifecycle
Relay MCP server** (`agent-relay mcp`). The session registers itself once with
the `register_agent` tool (name it `orchestrator`) — every messaging tool
errors with `Not registered. Call the "register_agent" tool first.` until it
does. Lifecycle
control — starting the broker, spawning/releasing local agents, streaming
broker debug events — goes through the `agent-relay node` command group. The
workers it spawns are registered participants too; their peer-messaging
Expand Down Expand Up @@ -158,8 +160,8 @@ runs in `interactive` spawn mode; pass `--exit-after-task` for a one-shot worker

### Step 3: Monitor and Coordinate

The orchestrator reads and sends through the relay MCP (it is auto-registered as
`orchestrator`):
The orchestrator reads and sends through the relay MCP (after its one-time
`register_agent` call):

```text
# Read messages directed to you — DM replies, mentions, reactions
Expand Down Expand Up @@ -269,6 +271,33 @@ the `message` group: `agent-relay message inbox check`,
`agent-relay message post <channel> <text>`,
`agent-relay message reply <messageId> <text>`.

### Plain-Shell Orchestration (no relay MCP configured)

An orchestrating session without the relay MCP still has full read access:
`agent-relay message list <channel> --limit 50` works with no credentials and
returns a JSON array (**newest first**; `[]` when the channel is empty — each
item carries `text`, `from.name`, `createdAt`, `kind`). Every **send**, however,
fails with `requires agentToken or agentClient` until the shell holds an agent
token. Mint one once per project and keep it inside the gitignored broker state
dir:

```bash
grep -qxF '.agentworkforce/' .gitignore 2>/dev/null || echo '.agentworkforce/' >> .gitignore
TOKEN=$(agent-relay agent register orchestrator --type system | grep -oE 'at_live_[A-Za-z0-9_-]+' | head -1)
[ -n "$TOKEN" ] || { echo "registration failed — no token captured" >&2; exit 1; }
(umask 077 && printf '%s' "$TOKEN" > .agentworkforce/relay/orchestrator.token)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Comment thread
willwashburn marked this conversation as resolved.
chmod 600 .agentworkforce/relay/orchestrator.token # tighten a pre-existing file too

TOKEN=$(cat .agentworkforce/relay/orchestrator.token) # later shells re-read it
RELAY_AGENT_TOKEN="$TOKEN" agent-relay message post general "checkpoint: reviews start after both DONEs"
RELAY_AGENT_TOKEN="$TOKEN" agent-relay message dm send Worker1 "NO-GO findings: …"
RELAY_AGENT_TOKEN="$TOKEN" agent-relay message inbox check
```

Never echo, commit, or log the token. Registering also makes `orchestrator` a
DM-able recipient for workers — the same identity an MCP-based session creates
with the `register_agent` tool.

### Monitoring Workers (Essential)

Spawn/send/release commands are in the Quick Reference and Bootstrap Step 3 —
Expand All @@ -287,6 +316,39 @@ real-time output when debugging.
> `agent-relay node agent list` from inside the backgrounded loop rather than
> blocking the foreground on `sleep`.

The push-style alternative to polling is a `node tail` pipeline used purely as
a **wake-up trigger**, run through the harness's background-task facility (or
with `&` in a plain shell — in the foreground it blocks until a match):

```bash
# Exits the moment a matching relay_inbound event arrives.
# Anchor on the body VALUE starting with the marker — not a loose substring.
agent-relay node tail | grep -m1 '"body":"DONE Implementer r2'
```

`relay_inbound` events carry each message's `body`, `from`, and `target`, and
the stream replays a bounded window before following live. Two independent traps
make a loose `grep` fire on the wrong event, and you need to defend against both:

- **Replay + quoting.** A substring like `REVIEW VERDICT: GO` matches not only the
real verdict but every earlier message that *quoted* the marker — an ACK saying
"I'll post `REVIEW VERDICT: GO` when done" is in the replay window and fires
first. Anchoring the pattern on `"body":"<marker>` (the body value **beginning**
with the marker) rejects quotes, since a quoted marker sits mid-sentence after
some other opening text.
- **Your own outbound.** If you DM a worker the instructions containing the marker
string, that DM is a `relay_inbound` event too (with `"from":"orchestrator"`).
Add `"from":"<Worker>"` to the pattern, or anchor on the body as above, so your
own messages can't trip it.

Give workers a run-specific marker (`DONE <name> <round-tag> — <evidence>`) so a
replayed event from an earlier round can't match either. A bare `"body":"DONE`
substring is only safe for a first, one-shot wait in a fresh workspace. This
wakes you the second the event happens instead of on the next poll tick; it is
still not the durable log, so read the actual messages with `check_inbox` /
`list_messages` after waking. (Stock macOS has no `timeout(1)` — bound a one-off
listen with `python3` `subprocess.run(..., timeout=N)` or a backgrounded kill.)

### Troubleshooting

```bash
Expand Down Expand Up @@ -326,7 +388,7 @@ Quick Reference. Then enforce this protocol:
- In a harnessed environment, never wait with a bare foreground `sleep`
(it is blocked) — run ACK/DONE poll loops with run_in_background or a
Monitor/until-loop, polling `check_inbox` and `node agent list` from inside it
- **ACK/DONE target: `orchestrator` (the auto-registered spawning identity) or
- **ACK/DONE target: `orchestrator` (the registered spawning identity) or
the `general` channel — NEVER `broker`.** `broker` is the broker's internal
routing self-name, not a spawnable/DM-able agent: a worker DM to `broker`
fails with `Agent "broker" not found`. Write the worker task prompt to DM
Expand Down Expand Up @@ -463,6 +525,8 @@ named `target_node`).
| Not monitoring workers | Attach with `agent-relay node agent attach <name> --mode view` frequently to track progress |
| Workers seem stuck | Inspect with `agent-relay node agent attach <name> --mode view` for errors |
| Messages not delivered | Check channel history with `list_messages(channel: "general")`; for new DMs use `check_inbox`, for already-read DM history use `list_dms` + `agent-relay message dm list <conversationId>` |
| CLI send fails with `requires agentToken or agentClient` | Plain-shell sends need an agent token — register an `orchestrator` identity and pass it via `RELAY_AGENT_TOKEN` (see Plain-Shell Orchestration). Channel reads need no token |
| Monitor loop never matches the DONE post | `agent-relay message list <channel>` returns **newest first**; in `node tail`, message text is in `relay_inbound` events' `body` field |
| Tried to read replies with `node tail` | `node tail` streams broker events; `node tail --agent <name>` streams the worker's raw output — neither is durable messages. Read replies with `check_inbox` / `list_messages` / `get_message_thread` |
| Worker DM to `broker` fails with `Agent "broker" not found` | Expected — `broker` is the broker's internal routing self-name, not a DM-able agent. Workers must ACK/DONE to `orchestrator` or `general`. Fix the worker task prompt; never instruct "DM the broker" |
| `node status` says running but `node agent list`/MCP calls return empty or `Failed to query broker session` | The CLI is dialing a **stale/wrong broker** — leftover `.agentworkforce/relay/connection.json` from a prior run on an old port, or a second broker process. `ps aux \| grep -c '[a]gent-relay-broker'` (>1 ⇒ kill extras), compare `.agentworkforce/relay/connection.json` to the actual listening port, then `agent-relay node down --force`, delete `.agentworkforce/relay/`, `agent-relay node up` clean |
Expand All @@ -485,6 +549,7 @@ named `target_node`).
- Set `ANTHROPIC_API_KEY` or authenticate via `claude auth login`

3. **For MCP-based coordination**: run `agent-relay mcp` as the relay MCP stdio
server in your client's MCP settings. With a workspace key present it
auto-registers the session as `orchestrator`; messaging tools (`send_dm`,
`post_message`, `check_inbox`, …) then work from the orchestrating session.
server in your client's MCP settings, then register the session once with
the `register_agent` tool (as `orchestrator`). Messaging tools (`send_dm`,
`post_message`, `check_inbox`, …) work only after that call — before it they
fail with `Not registered. Call the "register_agent" tool first.`