feat(spawn): default to the running agent; herdr-first management, no nohup

Six agents (claude, codex, pi, cursor, opencode, copilot). claude keeps
its native --bg/agents management. Everything else spawns interactively
inside herdr when its server is up (list/read/send/attach); otherwise a
transient systemd --user unit gives status/logs/stop. Research confirmed
none of the five non-claude CLIs has a native background job manager.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matt Pocock
2026-07-23 10:47:09 +01:00
co-authored by Claude Opus 4.8
parent fc73c04a34
commit 085d886fca
2 changed files with 73 additions and 24 deletions
+4 -4
View File
@@ -1,19 +1,19 @@
---
name: spawn
description: Hand the current conversation off to a fresh background agent — claude or codex — that picks up the work immediately.
argument-hint: "[claude|codex] What will the next session be used for?"
description: Hand the current conversation off to a fresh background agent that picks up the work immediately.
argument-hint: "[agent] What will the next session be used for?"
disable-model-invocation: true
---
Write a handoff summary of the current conversation so a fresh agent can continue the work, then spawn the agent with it via the bundled [spawn.sh](spawn.sh) — run it, never read it:
```bash
bash <this skill's base directory>/spawn.sh <claude|codex> "<descriptive session name>" - <<'PROMPT'
bash <this skill's base directory>/spawn.sh <agent> "<descriptive session name>" - <<'PROMPT'
<handoff summary>
PROMPT
```
Spawn `claude` unless the user named `codex`. The agent starts in the current working directory; the script prints how the user monitors the session — relay that.
`<agent>` is one of `claude | codex | pi | cursor | opencode | copilot`. Spawn the agent you are running as, unless the user named another. The agent starts in the current working directory; the script prints how the user monitors and manages the session — relay that.
The handoff summary:
+68 -19
View File
@@ -1,11 +1,17 @@
#!/usr/bin/env bash
# spawn.sh <claude|codex> <name> [prompt|-]
# spawn.sh <agent> <name> [prompt|-]
# Launches a background agent session seeded with the prompt.
# Prompt is read from stdin when the third argument is omitted or '-'.
#
# claude has native managed background jobs (claude --bg / claude agents).
# Every other agent is spawned interactively inside herdr when its server is
# running (list/read/send/attach management); otherwise as a supervised
# one-shot under the user's systemd session (status/logs/stop management).
set -euo pipefail
usage() {
echo "usage: spawn.sh <claude|codex> <name> [prompt|-] (prompt read from stdin when omitted or '-')" >&2
echo "usage: spawn.sh <claude|codex|pi|cursor|opencode|copilot> <name> [prompt|-]" >&2
echo " prompt is read from stdin when omitted or '-'" >&2
exit 2
}
@@ -19,24 +25,67 @@ if [ "$prompt" = "-" ]; then
fi
[ -n "$prompt" ] || { echo "spawn.sh: empty prompt" >&2; exit 2; }
case "$agent" in
claude)
if [ "$agent" = "claude" ]; then
claude --bg --name "$name" "$prompt"
echo "Spawned claude session '$name'. Manage it with: claude agents"
;;
codex)
exit 0
fi
# Interactive invocation, seeded with the prompt — used inside herdr panes.
case "$agent" in
codex) interactive=(codex "$prompt") ;;
pi) interactive=(pi "$prompt") ;;
cursor) interactive=(cursor-agent "$prompt") ;;
opencode) interactive=(opencode --prompt "$prompt") ;;
copilot) interactive=(copilot -i "$prompt") ;;
*) usage ;;
esac
if herdr agent list >/dev/null 2>&1; then
herdr agent start "$name" --cwd "$PWD" --no-focus -- "${interactive[@]}"
echo "Spawned $agent session '$name' in herdr."
echo " list: herdr agent list"
echo " read: herdr agent read '$name'"
echo " steer: herdr agent send '$name' '<text>'"
echo " attach: herdr agent attach '$name'"
exit 0
fi
command -v systemd-run >/dev/null 2>&1 || {
echo "spawn.sh: no herdr server running and systemd-run unavailable — launch herdr and retry" >&2
exit 1
}
# Non-interactive one-shot — flags keep the run from blocking on approvals.
case "$agent" in
codex) oneshot=(codex exec --sandbox workspace-write --skip-git-repo-check "$prompt") ;;
pi) oneshot=(pi -p "$prompt") ;;
cursor) oneshot=(cursor-agent -p --force --output-format text "$prompt") ;;
opencode) oneshot=(opencode run --title "$name" "$prompt") ;;
copilot) oneshot=(copilot -p "$prompt" --allow-all-tools) ;;
esac
# The systemd user manager doesn't inherit this shell's PATH; resolve the
# binary now and pass PATH through for any children it spawns.
bin="$(command -v "${oneshot[0]}")" || {
echo "spawn.sh: ${oneshot[0]} is not installed" >&2
exit 1
}
oneshot[0]="$bin"
slug="$(printf '%s' "$name" | tr '[:upper:] ' '[:lower:]-' | tr -cd 'a-z0-9-')"
dir="${TMPDIR:-/tmp}/codex-spawns"
mkdir -p "$dir"
base="$dir/${slug:-session}-$(date +%Y%m%d-%H%M%S)"
nohup codex exec --sandbox workspace-write --cd "$PWD" \
-o "$base.last.md" - <<<"$prompt" >"$base.log" 2>&1 &
echo "Spawned codex session '$name' (pid $!)."
echo " log: $base.log"
echo " final answer: $base.last.md"
echo " follow with: tail -f $base.log"
;;
*)
usage
;;
unit="spawn-${slug:-session}-$(date +%H%M%S)"
systemd-run --user --unit "$unit" --working-directory="$PWD" \
--setenv=PATH="$PATH" --setenv=HOME="$HOME" "${oneshot[@]}" >/dev/null 2>&1
echo "Spawned $agent session '$name' as user unit $unit (no herdr server running)."
echo " status: systemctl --user status $unit"
echo " logs: journalctl --user -u $unit -f"
echo " stop: systemctl --user stop $unit"
case "$agent" in
codex) echo " resume: codex exec resume <session id from logs>" ;;
pi) echo " resume: pi -c (from this directory)" ;;
cursor) echo " resume: cursor-agent resume" ;;
opencode) echo " resume: opencode run -c (or: opencode session list)" ;;
copilot) echo " resume: copilot --resume" ;;
esac