Skip to content

Add worker agent selection to /kickoff#39

Merged
gering merged 15 commits into
mainfrom
task/kickoff-agent-selection
Jul 17, 2026
Merged

Add worker agent selection to /kickoff#39
gering merged 15 commits into
mainfrom
task/kickoff-agent-selection

Conversation

@gering

@gering gering commented Jul 17, 2026

Copy link
Copy Markdown
Owner

Summary

  • /kickoff no longer hardcodes claude as the worktree worker — it launches the repo's default agent (a single committed per-project setting), or shows a picker and offers to save the pick when none is set.
  • New plugins/work-system/scripts/agent-registry.sh is the single source of truth for the known agents (CLI × model), their aliases, launch argv, availability, and the project default.
  • Non-Claude workers (codex/grok) degrade honestly — documented, not faked — and grok availability is model-aware + bounded so a dropped/renamed model or a network hiccup never mislabels or hangs.
  • Bumps work-system to 1.9.0.

Changes

  • agent-registry.sh (new, tested): list / resolve <selector> / default get|set. Registry-driven aliases (--fable/--opus/--codex/--sol/--grok/--agent cli[:model]), per-CLI launch argv, and a project-only default (<repo>/.claude/work-system-agent) that default get validates against the registry. grok availability checks grok models (always bounded via timeout/gtimeout/self-watchdog; a failed or empty-but-successful fetch is inconclusive → trust auth).
  • herdr-launch.sh: launch execs the resolved worker argv (CLI-agnostic, argv-exec preserved) instead of a hardcoded claude; clear exit-2/3 handling with resolve's real stderr surfaced.
  • /kickoff SKILL: no-flag → default-or-picker (picker offers "save as default", applied after a successful launch); explicit flags; --pick; --agent consumes its value token; announces a non-Claude project default before launch.
  • /close, /continue SKILLs: document the CLI-agnostic teardown and the claude-only reopen (claude -c) with the codex/grok caveat surfaced inline.
  • Docs/knowledge: top-level + work-system README, CHANGELOG 1.9.0, plugin.json + marketplace.json description refresh, and a knowledge entry capturing the non-obvious decisions.

Readiness

  • ✅ README (top-level + work-system) updated
  • ✅ Version 1.9.0 (plugin.json + marketplace.json synced)
  • ✅ CHANGELOG 1.9.0 entry
  • ✅ Knowledge entry curated (features/kickoff-agent-selection.md)
  • ✅ Tests: check-structure.py green; test_agent_registry.py passes
  • ➖ Lint/Build: N/A (declarative markdown/shell plugin; bash -n via check-structure)
  • ✅ Rebased on main (includes Remove the dead grok composer backend from swarm #37 swarm composer removal), 0 behind

Test plan

  • bash agent-registry.sh list shows claude/codex/grok with correct availability
  • /kickoff <task> with no project default shows the picker + save-as-default offer
  • /kickoff <task> --sol launches codex on gpt-5.6-sol; --grok launches grok-4.5
  • A committed project default launches it (non-Claude announced first); a bogus committed value falls back to the picker
  • python3 plugins/work-system/scripts/test_agent_registry.py passes

🤖 Generated with Claude Code

gering and others added 15 commits July 18, 2026 01:05
Foundation for kickoff agent selection. One CLI x model per entry
(claude:fable/opus/sonnet, codex:gpt-5.6-terra/sol, grok:grok-4.5/composer),
registry-driven — no hardcoded alias if-chains.

Subcommands:
- list [--json]  — probe every entry (per-CLI install+auth), table/JSON
- resolve <sel>  — map a shorthand flag / cli:model name / bare cli to the
                   launch argv + metadata; exit 3 if the CLI is unavailable
- auto           — first available entry per a configurable ranking (exit 1
                   if none); deterministic, not an LLM judgment
- default/last   — persisted selection state (~/.claude/work-system-agent)
- rank           — the effective --auto ranking

Availability probe is work-system-owned (no swarm dependency). supports=
capability metadata (continue/close-exit/statusline vs commit/pr) feeds the
later /close + /continue degradation paths. bash 3.2-safe; covered by
test_agent_registry.py with fake-CLI stubs for deterministic availability.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NjTKpmBkXxxYkn7orMfZCC
launch mode no longer hardcodes `claude … /continue`. It takes an optional
agent selector (4th positional) and resolves it through agent-registry.sh into
the worker argv, then execs it — staying argv-exec (no shell-typing race) and
CLI-agnostic. With no selector it preserves the legacy claude-default path.

- exit 2 on unknown selector, exit 3 (with unavailable=/note= on stdout) when
  the chosen CLI is not available, so the caller shows a clear "run: … login"
- emits agent=<cli:model> so the caller reports which worker launched
- resume mode is unchanged (claude -c is claude-session-specific)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NjTKpmBkXxxYkn7orMfZCC
/kickoff <task> now takes an optional agent selector and wires it through
agent-registry.sh + herdr-launch.sh:

- Arguments section: the task name is the non-`--` token; the trailing `--…`
  selector picks the worker (never mistaken for the task name)
- Step 12 "Select the worker agent": flag -> selector; --auto/--last/--default
  via the registry; no flag -> AskUserQuestion picker over `list` output
  (unavailable entries marked, not hidden)
- Step 13 launch: passes SELECTOR to herdr-launch; branches on exit 2/3
  (unknown / unavailable) and reports the resolved agent=; records `last set`
- Manual (non-herdr) block resolves the selector to the exact per-CLI command

Resolution/availability stay in the script — the skill never hardcodes the
alias table (mirrored in prose for readers only).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NjTKpmBkXxxYkn7orMfZCC
Non-claude workers (codex/grok) lack the claude-session lifecycle hooks, so
the two skills that assume them now say so honestly:

- /close: note that teardown is CLI-safe by construction — Scenario A
  (close-tab) is tab-level for any worker; Scenario B (self-exit /exit +
  SessionEnd hook) is only reachable from inside a claude session, so /exit is
  never injected into a codex/grok worker (those close via Scenario A).
- /continue reopen: `claude -c` resumes a claude worker; for a codex/grok task
  the tab-reopen is CLI-agnostic but the user must resume the real worker
  (`codex resume --last` / `grok -c`), not claude -c. git/PR-derived state
  (/status, /list, [ws] statusline) already works for any worker.

Document, don't fake — per-task agent memory is a later idea.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NjTKpmBkXxxYkn7orMfZCC
- plugin.json + marketplace.json → 1.9.0
- CHANGELOG: 1.9.0 entry
- work-system README: /kickoff agent choice, new "Worker agent selection"
  section (flags/picker/ranking/degradation + AGENTS.md option), herdr
  bullets made CLI-agnostic, workflow step 2 shows the flags
- top-level README: kickoff worker-choice summary

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NjTKpmBkXxxYkn7orMfZCC
….2.101)

Live testing showed grok 0.2.101 rejects `grok-composer-2.5-fast` ("unknown
model id") — `grok models` lists only grok-4.5. The per-CLI auth probe would
mislabel composer as available, so `--composer` / the picker would fail at
launch instead of degrading.

The grok probe now also checks the model appears in `grok models` (memoized,
short timeout). Data-driven, not a hardcoded drop: composer becomes available
again on a grok CLI that lists it. codex/claude stay auth-only (no clean CLI
model-list; their shipped models are stable). Covered by two new test cases.

Verified live: claude:fable, codex:gpt-5.6-sol, grok:grok-4.5 all start and
respond; composer correctly reads unavailable.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NjTKpmBkXxxYkn7orMfZCC
grok 0.2.101 no longer offers grok-composer-2.5-fast — `grok models` lists
only grok-4.5, and `-m grok-composer-2.5-fast` errors with "unknown model id".
So the `--composer` selector / grok:grok-composer-2.5-fast entry is dead:
remove it from the registry, the --auto ranking, the flag lists (kickoff SKILL,
herdr-launch usage, README), and the tests.

The model-aware grok probe stays (it now guards grok-4.5 against a future
drop/rename, which grok does between releases). swarm still references composer
and is handled separately (its own task).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NjTKpmBkXxxYkn7orMfZCC
Simpler model per user direction: no flag = launch the configured default,
not a picker or a ranking.

- Two-level `default`: a project default committed in the repo's
  `.claude/work-system-agent` overrides a global per-user default in
  `~/.claude/work-system-agent`, which overrides the shipped fallback
  claude:opus. `default get [--project|--global]`, `default set <name>
  [--project|--global]`.
- Remove `--auto` + the whole ranking machinery (DEFAULT_RANK, rank subcommand,
  WORK_SYSTEM_AGENT_RANK[_FILE]) and the `--default` flag (no flag now == the
  default). The interactive picker moves behind `--pick`; `--last` stays.
- kickoff SKILL, both READMEs, CHANGELOG updated; tests reworked for the
  two-level default + removed subcommands.

The model-aware grok probe and per-CLI launch argv are unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NjTKpmBkXxxYkn7orMfZCC
Per user direction, drop the global default, the shipped opus fallback, and
`--last` — so there's no per-user state to store at all. The only persisted
selection is one committed per-repo default (`.claude/work-system-agent`).

- No flag: launch the repo default if set; otherwise show the picker, and in the
  same AskUserQuestion offer to save the pick as the project default (applied
  after a successful launch, so a failed one never persists).
- `--pick` forces the picker even when a default is set.
- `default get`/`default set <name>` are now project-only (no --global/--project,
  no ranking, no auto/last subcommands). `default set` errors clearly outside a
  git repo.
- kickoff SKILL, both READMEs, CHANGELOG, and the tests updated to match.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NjTKpmBkXxxYkn7orMfZCC
Address the agreed findings from the local swarm review:

- agent-registry grok probe: add a `gtimeout` fallback so `grok models` is
  time-bounded on stock macOS (only gtimeout ships there), not just where GNU
  `timeout` exists (#1).
- grok availability: a failed/unreachable `grok models` fetch is now inconclusive
  (trust auth, soft note) rather than "unavailable" — a network hiccup no longer
  wrongly blocks launch. Fetch status rides the function's exit code, since a
  command-substitution subshell can't carry a global flag back (#6).
- agent-registry resolve: reject control chars in `--session`, closing the
  newline→forged-`argv=`-line injection through the launch protocol (#2).
- kickoff SKILL manual (non-herdr) block: shell-quote each argv word (the
  codex/grok bootstrap prompt is one word with spaces) instead of space-joining
  (#3); and persist a picker "save as default" on the manual path too, not only
  the herdr path (#4).
- plugin.json: refresh the stale description (dropped the wrong lifecycle verbs,
  mention worker-agent choice) (#8).

Not changed (reviewed, disagreed): /continue's claude-only resume is a
documented degradation; README model-id duplication is accepted human-facing
docs; the CHANGELOG paragraph matches the repo's entry style.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NjTKpmBkXxxYkn7orMfZCC
- default get now VALIDATES the committed value against the registry: a stale/
  removed/attacker-supplied name (from a cloned repo) reads as "no default" →
  picker, instead of routing or bricking every no-flag kickoff (#1). kickoff also
  announces a non-claude project default before launch — visibility, not a prompt,
  so a committed default can't silently route your code off-Claude (#1 security).
- grok probe: when neither `timeout` nor `gtimeout` exists, skip `grok models`
  and return inconclusive (trust auth) rather than risk an unbounded call that
  hangs the picker (#2).
- kickoff argument grammar: `--agent` consumes the next token as its value, so it
  isn't mistaken for the task name (#4).
- agent-registry.sh committed executable (100755), matching herdr-launch.sh, so
  the documented direct invocation works (#5).
- kickoff "Critical" note: step 12 → step 13 cross-ref (launch was renumbered) (#6).
- `supports=` comment marked RESERVED/not-yet-consumed (a seed for the
  orchestration design), not "already driving" degradation (#7).
- herdr-launch bad-mode usage string shows the launch arity incl.
  [agent-selector] (#8).
- continue reopen: note reworded to match behavior (`claude -c` IS always sent)
  and the codex/grok caveat surfaced inline in the success report (#3).

Not changed (reviewed): README model-id duplication (accepted human-facing docs),
CHANGELOG paragraph (matches repo style); per-task worker persistence for a true
per-CLI resume stays a later idea.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NjTKpmBkXxxYkn7orMfZCC
- grok probe now always runs BOUNDED: when neither `timeout` nor `gtimeout`
  exists, self-bound with a background killer (fds detached so the command
  substitution doesn't block on it) instead of skipping the check — so a dropped
  model is still caught on hosts without a timeout binary, and the probe still
  can't hang the picker (#1).
- a successful `grok models` that parses to nothing (a reformatted listing that
  dropped the `*` bullet) is treated as inconclusive → availability assumed,
  rather than marking every grok entry unavailable and disabling the backend (#3).
- herdr-launch surfaces resolve's real stderr on exit 2 instead of labelling
  every cause "unknown agent selector" (#5).
- README: `/continue` reopen wording corrected — it always sends `claude -c` and
  the user resumes a codex/grok worker themselves; no automatic per-CLI resume is
  claimed (#2, doc half; per-task persistence stays a later idea).
- marketplace.json work-system description refreshed to match plugin.json (#6).

Not changed: committed-external-default consent gate (#4) — an explicit product
decision to announce, not prompt (a cloned repo can already run hooks/CLAUDE.md).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NjTKpmBkXxxYkn7orMfZCC
Capture the non-obvious decisions: single per-repo committed default (no global/
fallback/ranking), agent-registry as single source of truth, the bounded
model-aware grok probe (inconclusive -> trust auth), non-claude "document don't
fake" degradation, and the announce-not-prompt security choice.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NjTKpmBkXxxYkn7orMfZCC
- Bound EVERY external-CLI probe: factor out run_bounded (timeout/gtimeout, else
  a self-watchdog that escalates SIGTERM -> SIGKILL) and use it for codex
  `login status` (was completely unbounded) and grok `models`. Its stdout goes to
  a temp file so an orphaned grandchild can't hold the command-substitution pipe
  open — verified a SIGTERM-ignoring probe is now killed at ~12s, not left to hang
  the picker (#4).
- kickoff manual (non-herdr) block no longer auto-persists the project default:
  it only prints a command, so no launch is confirmed — tell the user to run
  `default set` once the worker is up, matching the herdr path's "persist only
  after a successful launch" rule (#2).
- kickoff picker: set OFFER_DEFAULT from the interpreted Yes/No answer, not a
  literal label match, so the save-as-default gate can't miss on case (#3).
- knowledge: add the missing `prime:` key to the new entry (#6); refresh the
  stale herdr-kickoff-automation entry (step 13, registry-resolved worker argv,
  not a hardcoded `claude … /continue`) (#7).

Not changed: the committed-external-default consent gate (#1) stays announce-not-
prompt (a settled product decision); herdr-launch's `${0%/*}` sibling lookup (#5)
is latent and matches the file's existing convention (callers always pass an
absolute path).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NjTKpmBkXxxYkn7orMfZCC
Mostly real regressions from the round-4 batch, caught by the re-review:

- run_bounded: add `-k 1` to the timeout/gtimeout branches. GNU timeout only
  SIGTERMs at the deadline then waits, so a SIGTERM-ignoring probe ran forever on
  the COMMON path (any host with a timeout binary — e.g. all Linux CI); --kill-after
  escalates to SIGKILL like the self-watchdog. Normalize a bounded kill to a single
  124 "timed out" code (#4).
- codex probe: a run_bounded timeout (124) is now inconclusive -> assume available
  (mirrors grok), not a genuine auth failure — a slow `codex login status` no longer
  tells a logged-in user to re-login and disables the backend (#5).
- grok probe: match the model id as a SUBSTRING of the raw `grok models` output
  (here-string, no pipe) instead of a positional awk field + exact-line grep, so a
  reformatted listing can't yield a wrong token and a false "model not offered" (#6).
- project default resolves the MAIN repo root via --git-common-dir, so a
  `default set` run from inside a linked worktree lands in the main checkout, not
  the disposable worktree copy (#3).
- kickoff manual path: the main-repo session runs `default set` after the user
  confirms the worker started — not a command for the user's terminal, where $REG
  is undefined and the cwd is the worktree (#2).
- knowledge: correct the herdr-kickoff entry's grok argv (`grok -m …`, not codex) (#7).

Not changed: committed-external-default consent gate (#1) — settled announce-not-
prompt product decision.

Verified: gtimeout -k bounds a SIGTERM-ignoring probe at ~11s; a hung codex probe
reads available; `default set` from the worktree writes the main repo.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NjTKpmBkXxxYkn7orMfZCC
@gering
gering force-pushed the task/kickoff-agent-selection branch from dc64df0 to a3f7bf3 Compare July 17, 2026 23:10
@gering
gering merged commit da19bee into main Jul 17, 2026
1 check passed
@gering
gering deleted the task/kickoff-agent-selection branch July 17, 2026 23:12
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