Introduce a setup-only skill that discovers implementer CLIs and writes
delegate-fleet.v1 lane maps (implementer + dials) to global or project
config after approval, without dispatching work or touching agent instruction files.
Co-authored-by: Cursor <cursoragent@cursor.com>
The codex, opencode, grok, and kimi relays probed their implementer's version
before the watchdog was armed, so a CLI that never answered --version wedged
the relay with no result.json for the caller to poll — the exact failure the
relay contract exists to prevent. Their `catch { return null }` also collapsed
"binary missing" and "binary broken" into *_unavailable/127, sending callers to
reinstall something already on disk.
Port the shape cursor, qoder, vibe, and pi already use: cap the probe at
min(--timeout, 10s) with killSignal SIGKILL, and return { version, error } so a
missing binary stays *_unavailable/127, a hung probe reports timeout/124, and a
non-zero exit reports failed with the probe's exit code and stderrTail. Prepare
the run directory before probing so a failed preflight still has somewhere to
publish result.json.
The suite now drives all four through the preflight matrix. Both fake CLIs were
generalized to recognize every probe form in the repo and to take hang/fail from
the mode name, replacing the per-CLI special cases.
codex, opencode, grok, kimi, and qoder validated --timeout only against
parseDuration returning null, so "0s" survived as 0 and "600h" as a delay past
Node's 2^31-1 ms timer ceiling. Both make setTimeout fire on the next tick: the
relay felled the implementer half a second in and wrote status "timeout",
reporting a 25-day budget as already spent. A watchdog that reports a limit as
exhausted when it never ran is the one failure mode it must not have.
Bound parseDuration itself, the way claude, cursor, and vibe already do, so
every call site inherits the check rather than each guarding separately, and
drop qoder's now-redundant zero check.
Drive all ten relays through the unhonourable durations from one shared smoke
matrix, with a positive control at the ceiling so the bound cannot over-reject.
The per-relay timeout loops it subsumes (agy's and cursor's --timeout lists,
qoder's zero case, pi's zero and out-of-range cases) fold into it; agy's
--print-timeout row stays, since its 60s grace gives it a lower ceiling.
Co-authored-by: Cursor <cursoragent@cursor.com>
The pull request template told contributors that relay-smoke covered shape and
registration. It did not: the suite walked a hard-coded SKILLS array and never
opened a SKILL.md, a references directory, or skills.sh.json — so a skill could
ship complete and still sit outside every scenario, which is exactly how a
relay ends up unexercised.
The new block reads skills/ from disk rather than from SKILLS, because the one
thing a hard-coded list cannot catch is its own omission. Per skill it asserts
SKILL.md, one relay script, exactly the four references, an entry in
skills.sh.json, and membership in the matrix; both registries are also checked
for entries with no directory behind them.
Verified by mutation: a new skill directory outside the matrix, a skill short
one reference, and a skill dropped from skills.sh.json each fail the suite.
The claim form's ../../ links resolved to /amElnagdy/issues when GitHub renders
the form at /issues/new, so the duplicate-work check the form demands could not
be performed. Absolute URLs are unambiguous wherever a template is rendered.
(The ../blob/master link was already correct at that depth; the suggestion to
deepen it would have broken it.)
AGENTS.md banned version pins outright while the Verification status list has
always carried them on purpose — the pinned version is what makes "verified"
checkable. Written down as an exception so it stops being re-litigated.
Also: name the package gate in CONTRIBUTING and point at the full pre-publish
list; separate the relay's own resume flags from Kimi's in the ledger; and stop
saying the codex relay needs nothing but the codex binary, since it needs Node
and git like every sibling.
Two people built the same skill independently, twice, and a third had theirs
superseded — a month of outside work spent for nothing. The repo had no
written definition of a delegate skill and no way to say "I started this", so
the README was the only implicit spec.
CONTRIBUTING.md carries the four invariants and the merge checklist derived
from what the shipped skills actually contain. The claim form's label list is
the status board, so nothing here needs hand-maintaining; the pull request
template asks only for the two things CI cannot check — the claim, and what
actually ran.
Adding an implementer touched five README spots — a table row, a prose
subsection, an example line, a Requirements entry, and a verification line —
so every merge made the shape worse. It is two now: a row, and a verification
line once a run backs it.
The per-skill prose block restated the table above it and the SKILL.md below
it, and had already accumulated a placeholder for a skill that will never be
built. It goes, along with the hardcoded skill count and the per-CLI install
list that duplicated each skill's own prerequisites. The two caveats worth a
landing page survive as table footnotes: the CLIs with no enforceable
read-only mode, and grok's best-effort one.
The Codex-plugin comparison moves into codex-delegate's SKILL.md — one
implementer of ten should not hold a section on the front page. What counts
as a delegate skill takes its place and states the four invariants.
Table cells were re-derived from each relay's own flag surface rather than
copied forward: agy has no read-only mode, and qoder's is --permission-mode
plan.
When cursor-agent is launched from a Git Bash (MSYS) console with hooks
configured (its own hooks.json or imported Claude Code PreToolUse hooks),
it selects bash.exe as its persistent shell but generates hook wrappers
in PowerShell syntax, so every hook errors and Cursor blocks all command
execution fail-closed - edits land, gates never run. Reproduced and
isolated on cursor-agent 2026.07.23 (env, PATH, and process ancestry
ruled out; the same dispatch works from a PowerShell console, and works
from Git Bash once the hook entries are removed).
Document the failure signature and the workaround - dispatch from
PowerShell/cmd - in dispatch-and-poll.md and flag it in SKILL.md.
Same brief -> dispatch -> review -> land loop for cursor-agent:
- relay.mjs feeds the brief on stdin (no process-list exposure, no argv
cap), captures the stream-json events, and writes the standard
delegate-relay.result.v1 contract with sessionId, resolvedModel,
permissionMode, touchedFiles, and finalMessage.
- Fresh runs are write-capable with --force; --read-only switches to
Cursor's plan mode backed by a porcelain tripwire that reports
readOnlyViolation. --trust is always passed so headless runs never
stall on the workspace-trust prompt.
- Resume via --resume-last (--continue) or --session <id> (--resume=<id>).
- Windows launches serialize a pre-joined, quoted command string through
the shell so the cursor-agent.cmd shim resolves without Node's DEP0190
warning; POSIX spawns the binary directly in its own process group.
- Smoke harness drives the cursor relay's parse, timeout (whole-tree
kill), and abort paths alongside the other seven.
Verified end-to-end on Windows against cursor-agent 2026.07.23: write
run under --force, plan-mode read-only run with a clean tripwire,
--session resume applying a delta brief, and usage errors exiting 2
with no result file.
The agy relay derives its watchdog from --print-timeout plus a fixed 60s grace,
which assumes agy always honors its own print timeout. It does not always: I have
watched a conversation hang server-side well past it, and the grace window cannot
cover an unbounded hang. The other relays expose --timeout for exactly this, so
this closes the gap rather than inventing a mechanism.
--timeout <dur> overrides the derived value; without it nothing changes. The
value is validated up front because parseDuration returns null for a malformed
one and setTimeout(fn, null) fires on the next tick - an unvalidated flag would
turn a typo into an instant, silent "timeout". Zero is rejected for the same
reason. The timeout error message names whichever limit actually fired.
Adds four smoke assertions for the rejected forms.
claude-delegate (writeJsonAtomic) and qoder-delegate already write result.json to
a pid-suffixed temporary and rename it into place. codex, opencode, agy, grok and
kimi still write it directly, so an orchestrator polling for the file can read a
truncated, unparseable JSON object — the documented "a run is done when
result.json exists with a status" contract makes that race the normal way to
consume the file, not an edge case.
Applies the same idiom to the five, and adds a smoke assertion per relay that no
.tmp artifact survives a finished run.
--resume-last picks the most recent Codex session globally, not per repo or per
relay run: any other codex invocation between the two legs of a delegation
becomes "last", and the delta brief lands in the wrong thread. The relay already
records threadId in result.json, but had no way to consume it.
--session <id> resumes that exact thread. It is mutually exclusive with
--resume-last, an empty id is rejected (it would otherwise fall through to a
fresh run while still being reported as a resume), and it takes the same
argv path, so -s is still withheld from exec resume.
Adds five smoke assertions covering argv shape, the withheld -s, the recorded
field, and both rejections.