| name | worker-relay |
| description | Run Codex CLI, Claude Code, or another supported external agent as a supervised asynchronous worker with strict OpenClaw routing, progress notifications, result delivery, session resume, optional provider fallback, and multi-turn supervisor ownership. Always use whenever the user asks to call, run, invoke, use, ask, or delegate to Codex or Claude, even for a small task; also use for substantial background work, automations, worker-session continuation, and chat notification on completion. Never substitute a native OpenClaw subagent or raw CLI. Direct interactive CLI is allowed only when the user explicitly asks to bypass Worker Relay and run it directly. |
Worker Relay
Worker Relay is OpenClaw's external-agent execution harness. It runs Claude Code or Codex CLI as an asynchronous worker while the current OpenClaw agent remains the supervisor and delivery owner. The canonical Python entrypoint is {baseDir}/run-task.py, invoked as python3 {baseDir}/run-task.py; it does not need an executable bit. Do not create another runner or copy it into an automation. The historical name claude-code-task may remain in upgraded deployments only as a compatibility alias, never as a second implementation.
Use these terms consistently: OpenClaw is the supervisor, Claude Code/Codex are external workers, and Worker Relay is the harness connecting dispatch, progress, delivery, wake, and continuation. A native OpenClaw subagent and a raw interactive CLI invocation are different execution surfaces.
Trigger precedence
Any explicit request to call, run, invoke, use, ask, or delegate to Claude Code or Codex triggers Worker Relay, even when the underlying task is small or could be answered inline. Do not replace that request with a native OpenClaw subagent or raw CLI. Direct CLI is allowed only when the user explicitly asks to bypass Worker Relay and run the provider interactively.
Once triggered, do not solve the delegated task inline as a substitute or supplement. The requested provider owns the task result and OpenClaw supervises it. If the user explicitly limits a check to --validate-only, stop after routing validation and do not state or infer the answer to the unlaunched worker task.
Provider and fallback selection are literal. A request for Codex means Codex only; a request for Claude means Claude only. Do not add --fallback-engine because fallback exists, because an automation uses it, or because the user asks what fallback was selected. Fallback is none unless the user or owning automation explicitly requests cross-provider continuity.
Non-negotiable operating rules
These rules apply on the first read, including during a live E2E:
- Launch only
python3 {baseDir}/run-task.py --detach. The runner owns the transient user service needed to survive OpenClaw tool exit. Do not probe it with test -x: it is a Python entrypoint and may intentionally lack an executable bit. Do not handwrite nohup, systemd-run, tmux, raw claude/codex, or another wrapper unless the user is explicitly testing that alternate surface.
- Never edit the canonical shared-skill checkout during an E2E. Capture evidence, finish or stop the test, change the repository through its normal review/test/deploy workflow, then rerun from a clean deployed commit.
- Run provider/mode probes sequentially. Claude Code and Codex share local login, rollout, and registry state; parallel probes can invalidate the diagnosis.
- One
401, transport error, no rollout found, timeout, or delayed wake is an observation, not a durable feature gap. Wait for the previous process to exit, run a standard control, retry the same case sequentially, and compare persisted state before changing current-state documentation.
- Resume only after the fresh runner process has fully exited and its provider id is present in the registry. A resume failure still maps to exit
42 for callers, but the E2E diagnosis requires the control/retry sequence above.
- Correlate asynchronous messages by engine, output path, run id, and wake id. A late wake from an older run must not trigger a new diagnosis or an extra continuation.
- For a full live test, follow
references/testing-protocol.md exactly. The test owns its ordered phases; do not improvise additional probes or launch phases in parallel.
- One detached worker phase occupies one supervisor turn. After
DETACHED_LAUNCH_ACCEPTED, make no more work/tool calls in that turn: no while, sleep, wait, systemctl, log polling, registry inspection, or next launch. End the turn and continue only when the routed completion wake starts the next turn. If the runtime exposes sessions_yield, it is the sole optional exception and must immediately terminate the turn; do nothing after it. Polling after detach is an E2E failure because it masks the wake and tool-exit lifecycle being tested.
- A completion-wake turn is the next working supervisor turn, not a status-only intermission. After verifying the completed phase, if the ordered workflow still has work and no genuine blocker, launch exactly the next detached phase in that same wake turn and release it again. Never end a wake turn with only a promise such as "I will read the skill and launch the next phase"; no future trigger exists, so that is a stalled workflow and a failed E2E attempt.
- Detached services use
systemd-run --collect. After collection, systemctl show may return default-looking Result=success and ExecMainStatus=0 for a nonexistent unit; if LoadState=not-found, those fields are not exit evidence. Determine terminal status from the correlated runner wake/output and, when needed, the unit journal line Main process exited ... status=N. For invalid resume, RESUME_FAILED plus correlated exit=42 or journal status=42 passes; never override it with post-collection defaults.
Mandatory preflight
Before every launch:
- Preserve the exact source OpenClaw session key. Never synthesize
agent:main; use the actual agent id and live session.
- Choose the real project directory. The runner does not create a Git repository inside it.
- Omission of
--engine means Codex only, using the saved ChatGPT subscription login. Select Claude explicitly with --engine claude; never infer or add a fallback.
- Omission of
--mode means Terra for Codex and Sonnet for Claude. Use --mode sol only with Codex or --mode fable only with Claude; never infer either premium mode.
- Add
--fallback-engine claude only when the caller explicitly requires continuity across provider unavailability. Fallback is only for provider unavailability before useful work; it is not a generic retry.
- Decide whether this is a fresh workstream or a continuation. Use
--resume only with a session/thread id created by the same engine, never a runner run_id or wake_id. Resume and provider fallback are intentionally mutually exclusive.
- Write a task with a concrete Definition of Done and an appropriate self-check.
- For routed runs, execute
--validate-only first with the same engine, mode, and fallback flags. A routing failure is a launch blocker.
python3 {baseDir}/run-task.py \
--task "routing probe" \
--engine codex \
--project /absolute/project/path \
--session "$OPENCLAW_SESSION_KEY" \
--validate-only
WhatsApp validation requires the exact session to exist in OpenClaw's gateway or local session registry. Telegram topics additionally require a resolvable target and session UUID. Never bypass validation by manually inventing a destination.
Launch
Put substantial prompts in a file to avoid shell quoting and truncation problems:
PROMPT_FILE=/tmp/cc-prompt-$(date +%s).txt
python3 {baseDir}/run-task.py \
--detach \
--detach-log /tmp/cc-launch.log \
--task "$(cat "$PROMPT_FILE")" \
--engine codex \
--project /absolute/project/path \
--session "$OPENCLAW_SESSION_KEY" \
--timeout 7200 \
--session-label "short workstream label"
Do not claim the task launched unless the launcher returns DETACHED_LAUNCH_ACCEPTED with a unit and log path. That is the last work result permitted in the current turn. Do not append shell &, inspect the service, poll the log, or keep any tool open with wait; --detach owns durability. End the current turn immediately, either normally or with one terminal sessions_yield call when that runtime requires it. The next turn begins from the routed completion wake: inspect terminal evidence and, when the multi-phase goal remains active, launch exactly the next phase before ending that wake turn. A text-only promise to continue later does not preserve execution.
The default examples above intentionally have no fallback. Only when the user or owning automation explicitly requests cross-provider availability may the same validated and detached commands add --fallback-engine claude (or the opposite provider).
Resume
python3 {baseDir}/run-task.py \
--detach \
--detach-log /tmp/cc-resume.log \
--task "Focused next step" \
--engine codex \
--project /absolute/project/path \
--session "$OPENCLAW_SESSION_KEY" \
--resume "$CODEX_THREAD_ID"
Resume only when prior worker context is useful and the work is sequential. Never pass a Claude session id to Codex or a Codex thread id to Claude. Start fresh for unrelated work, intentional parallelism, or a missing/expired session. Exit 42 means the resume did not start; relaunch fresh when preserving that exact old session was not a user requirement.
Execution modes
- No engine/mode/model/Fast/fallback flags: Codex on explicit
gpt-5.6-terra, with no fallback.
--engine claude without a mode/model/Fast flag: Claude on the forward-compatible sonnet alias, with no fallback.
--mode sol: explicit Codex-only premium mode, pinned to gpt-5.6-sol.
--engine claude --mode fable: explicit Claude-only premium mode, pinned to the forward-compatible fable alias.
--fallback-engine NAME: one sequential retry only when the primary executable is absent or the provider fails before useful work with recognized auth, rate-limit, network, or service-unavailable evidence. The failed primary does not emit a false final wake; the fallback owns final delivery.
--model NAME: expert provider-specific override on the selected subscription path.
--fast: the selected provider's native Fast mode. For Codex this sets features.fast_mode=true and service_tier="fast"; use only when the user explicitly requests Fast or urgent paid speed.
--mode, --model, and --fast are mutually exclusive.
- Provider fallback cannot be combined with
--resume, --mode, or provider-specific --model. It never triggers after a tool call, a completed result, an ordinary task failure, or a timeout, avoiding duplicate side effects.
python3 {baseDir}/run-task.py --mode sol --task "..." --project /absolute/path
python3 {baseDir}/run-task.py --engine claude --mode fable --task "..." --project /absolute/path
The runner strips ANTHROPIC_API_KEY for Claude and OPENAI_API_KEY, CODEX_API_KEY, CODEX_ACCESS_TOKEN, and inherited CODEX_HOME for Codex. This is a billing/auth invariant: workers use the user's saved Claude or ~/.codex ChatGPT subscription identity, not API credentials or an OpenClaw agent-local Codex home.
Delivery ownership
The external coding agent is a worker, not the acceptance owner. The supervising agent must:
- Preserve the original user goal across wake turns and compaction.
- Ask the selected worker for the real result, not merely progress or an implementation proposal.
- Include self-verification in the worker prompt when applicable.
- Inspect the result and independently run the smallest meaningful verification.
- Launch a focused follow-up when the original goal remains incomplete and no genuine blocker exists.
- Report completion only after the original goal, not merely the latest substep, is satisfied.
The runner sends launch/progress/result notifications and wakes the original supervising session. It must never wake a different agent merely because that key looks syntactically plausible.
Feature workflow
This skill is the canonical feature-delivery workflow; do not fork its rules into another skill.
Classify before launch:
| Size | Typical scope | Design action |
|---|
| S | One file, known pattern, low blast radius | Implement directly |
| M | Several files, integration or interface change | One pragmatic design in DESIGN.md |
| L | New subsystem, broad or risky change | Compare 2-3 real options in DESIGN.md |
User instructions override the gate: design-only stops after design; "just build it" skips design; explicit full-design requests use the L flow.
For M/L work, have the selected worker read local instructions and relevant code first, document interfaces, migration/rollback, risks, and testable DoD, then self-review the design. Continue autonomously through implementation and verification unless user input is genuinely required.
Use the detailed references only when the task needs them:
references/feature-size-gate-guide.md
references/feature-design-template.md
references/feature-architect-prompts.md
references/feature-autonomous-iteration-model.md
Prompt contract
A strong worker task contains:
- original intent and exact deliverable;
- target repository and relevant paths;
- local instructions/bootstrap files to read;
- constraints and non-goals;
- Definition of Done;
- verification commands or evidence expected;
- requirement to report changed files, checks run, unresolved gaps, and blockers.
Do not over-specify implementation when repository conventions should decide it. For resumed sessions, keep the next-step prompt short because prior context is already present.
Result semantics
The process exit code is meaningful:
| Code | Meaning |
|---|
0 | Worker succeeded and routed delivery/wake was confirmed when requested |
2 | CLI or routing validation error; worker did not start |
42 | Requested provider session/thread could not be resumed |
70 | Runner crashed |
75 | Worker succeeded, but final routed delivery/wake was not confirmed |
124 | Timeout |
| other | Selected worker child exit code is preserved |
The short --detach launcher returning 0 means only that systemd accepted the durable service. The worker's terminal result remains the service log, output file, registry status, and routed completion/wake; never report task success from detach acceptance alone.
Outputs retain unique /tmp/cc-<timestamp>-<run-id>.txt names unless --output is supplied. Provider sessions are tracked atomically with an engine field in the legacy-compatible ~/.openclaw/claude_sessions.json; old entries without engine mean Claude. Corrupt registries are preserved with a .corrupt-<timestamp> suffix before recovery.
On WhatsApp, success requires direct result delivery plus gateway acceptance of an asynchronous sessions_send wake (timeoutSeconds: 0). The wrapper does not block on the large target session's full reasoning turn. On Telegram, the runner wakes the exact session, extracts the supervisor response, and delivers it deterministically. HTTP 200 alone is not success; the nested gateway/tool payload must also report a non-error status.
Testing language
When the user says "run the tests" for a target project, run that project's tests. Do not reinterpret an ordinary project request as a runner E2E test.
When modifying or explicitly testing this skill itself, use both layers:
python3 -m unittest discover -s {baseDir}/tests -v
python3 -m py_compile {baseDir}/run-task.py {baseDir}/session_registry.py
Then follow the ordered live gates in references/testing-protocol.md. Do not replace its launch path or infer a persistent regression from a single live failure.
Operational references
Load these in order when needed:
CURRENT-BEHAVIOR.md for the current implementation contract.
INCIDENT-INDEX.md when diagnosing a failure or changing routing/lifecycle code.
TECHNICAL-INSIGHTS.md for durable engineering lessons.
WAKE-TROUBLESHOOTING.md for symptom-driven commands.
- Deployment-local
history/ when present and detailed incident evidence is required.
Update CURRENT-BEHAVIOR.md whenever behavior changes. For a production failure, add one compact row to INCIDENT-INDEX.md; deployments that retain private operational history may put detailed evidence in history/incidents/. Keep CHANGELOG.md concise and release-oriented.