| name | doey-dispatch |
| description | Send tasks to idle worker panes. Use when you need to "send a task", "assign work", "delegate to workers", "dispatch to idle panes", or "give a worker something to do". |
- Session config: !
cat $(tmux show-environment DOEY_RUNTIME 2>/dev/null | cut -d= -f2-)/session.env 2>/dev/null || true
- Window index: !
echo "${DOEY_WINDOW_INDEX:-}"|| true
- Team environment: !
cat $(tmux show-environment DOEY_RUNTIME 2>/dev/null | cut -d= -f2-)/team_${DOEY_WINDOW_INDEX:-0}.env 2>/dev/null || true
Always copy-mode -q before paste-buffer/send-keys.
Auto-scale (dynamic grid, no idle workers → add column)
RD=$(tmux show-environment DOEY_RUNTIME 2>/dev/null | cut -d= -f2-)
W="${DOEY_WINDOW_INDEX:-0}"
if [ "$(grep '^GRID=' "${RD}/session.env" 2>/dev/null | cut -d= -f2)" = "dynamic" ]; then
HAS_IDLE=false
for WIDX in $(echo "$WORKER_PANES" | tr ',' ' '); do
W_SAFE=$(echo "${SESSION_NAME}:${W}.${WIDX}" | tr ':.-' '_')
[ -f "${RD}/status/${W_SAFE}.reserved" ] && continue
_W_STATUS=$(grep '^STATUS: ' "${RD}/status/${W_SAFE}.status" 2>/dev/null | head -1 | cut -d' ' -f2-) || _W_STATUS=""
case "$_W_STATUS" in READY|FINISHED) HAS_IDLE=true; break ;; esac
done
if [ "$HAS_IDLE" = "false" ] && [ "${WORKER_COUNT:-0}" -lt "${MAX_WORKERS:-20}" ]; then
doey add 2>/dev/null
source "${RD}/session.env"; [ -f "${RD}/team_${W}.env" ] && source "${RD}/team_${W}.env"
fi
fi
Pre-flight (❯ = idle, skip reserved)
RD=$(tmux show-environment DOEY_RUNTIME 2>/dev/null | cut -d= -f2-)
W="${DOEY_WINDOW_INDEX:-0}"
PANE_SAFE=$(echo "${SESSION_NAME}:${W}.X" | tr ':.-' '_')
[ -f "${RD}/status/${PANE_SAFE}.reserved" ] && echo "Reserved — skip"
_STATUS=$(grep '^STATUS: ' "${RD}/status/${PANE_SAFE}.status" 2>/dev/null | head -1 | cut -d' ' -f2-) || _STATUS=""
echo "Status: $_STATUS"
Smart Context Check (≥30% overlap → delegate, else restart)
RD=$(tmux show-environment DOEY_RUNTIME 2>/dev/null | cut -d= -f2-)
W="${DOEY_WINDOW_INDEX:-0}"; PANE="${SESSION_NAME}:${W}.X"
PANE_SAFE=$(echo "$PANE" | tr ':.-' '_')
USE_DELEGATE=false
if [ "${FORCE_RESTART:-0}" != "1" ]; then
STATUS_OUT=$(doey status get "$PANE_SAFE" 2>/dev/null) || STATUS_OUT=""
LAST_TAGS=$(echo "$STATUS_OUT" | grep '^LAST_TASK_TAGS: ' | cut -d' ' -f2-) || LAST_TAGS=""
LAST_TYPE=$(echo "$STATUS_OUT" | grep '^LAST_TASK_TYPE: ' | cut -d' ' -f2-) || LAST_TYPE=""
LAST_FILES=$(echo "$STATUS_OUT" | grep '^LAST_FILES: ' | cut -d' ' -f2-) || LAST_FILES=""
if [ -n "$LAST_TAGS" ] || [ -n "$LAST_TYPE" ] || [ -n "$LAST_FILES" ]; then
source "${PROJECT_DIR}/shell/doey-task-helpers.sh" 2>/dev/null || true
if type task_should_restart >/dev/null 2>&1; then
if ! task_should_restart "$LAST_TAGS" "$LAST_TYPE" "$LAST_FILES" \
"${NEW_TASK_TAGS:-}" "${NEW_TASK_TYPE:-}" "${NEW_TASK_FILES:-}"; then
USE_DELEGATE=true; echo "Context overlap ≥30% — delegating"
fi
fi
fi
fi
USE_DELEGATE=true → skip kill+restart, jump to Rename.
Task Registration (before dispatch)
TASK_ID=$(doey task create --title "$TASK_TITLE" --type "${TASK_TYPE:-feature}" --description "$TASK_DESCRIPTION")
echo "Created task #${TASK_ID}"
Include Task #${TASK_ID} in the prompt header so hooks can track it.
Subtask ID (optional)
If a subtask ID is provided, set it as an env var on the target pane before sending the task. This lets the worker and stop hook track which subtask is being executed.
if [ -n "${SUBTASK_ID:-}" ]; then
doey_send_command "$PANE" "export DOEY_SUBTASK_ID=${SUBTASK_ID}"
sleep 0.3
fi
Insert this step after the worker is ready (post kill+restart or delegate check) but before pasting the task.
Dispatch Sequence
RD=$(tmux show-environment DOEY_RUNTIME 2>/dev/null | cut -d= -f2-)
W="${DOEY_WINDOW_INDEX:-0}"; PANE="${SESSION_NAME}:${W}.X"
PANE_SAFE=$(echo "$PANE" | tr ':.-' '_')
[ -f "${RD}/status/${PANE_SAFE}.reserved" ] && { echo "Reserved — skip"; exit 0; }
tmux copy-mode -q -t "$PANE" 2>/dev/null
PANE_PID=$(tmux display-message -t "$PANE" -p '#{pane_pid}')
CHILD_PID=$(pgrep -P "$PANE_PID" 2>/dev/null)
OUTPUT=$(tmux capture-pane -t "$PANE" -p 2>/dev/null)
ALREADY_READY=false
[ -n "$CHILD_PID" ] && echo "$OUTPUT" | grep -q "bypass permissions" && echo "$OUTPUT" | grep -q '❯' && ALREADY_READY=true || true
if [ "$ALREADY_READY" = "false" ] && [ "$USE_DELEGATE" != "true" ]; then
if [ -n "$CHILD_PID" ]; then
kill "$CHILD_PID" 2>/dev/null || true
for _i in 1 2 3 4 5 6 7 8 9 10 11 12; do
kill -0 "$CHILD_PID" 2>/dev/null || break
sleep 0.25
done
fi
CHILD_PID=$(pgrep -P "$PANE_PID" 2>/dev/null)
[ -n "$CHILD_PID" ] && kill -9 "$CHILD_PID" 2>/dev/null && sleep 0.5
tmux copy-mode -q -t "$PANE" 2>/dev/null || true
PANE_IDX="${PANE##*.}"
WORKER_PROMPT=$(grep -l "pane ${W}\.${PANE_IDX} " "${RD}/worker-system-prompt-"*.md 2>/dev/null | head -1)
CMD="claude --dangerously-skip-permissions --model ${DOEY_WORKER_MODEL:-opus}"
WORKER_AGENT="${DOEY_WORKER_AGENT:-}"
[ -n "$WORKER_AGENT" ] && CMD="${CMD} --agent \"${WORKER_AGENT}\""
[ -n "$WORKER_PROMPT" ] && CMD="${CMD} --append-system-prompt-file \"${WORKER_PROMPT}\""
source "$HOME/.local/bin/doey-send.sh" 2>/dev/null || true
doey_send_launch "$PANE" "$CMD"
for _i in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20; do
_OUT=$(tmux capture-pane -t "$PANE" -p -S -10 2>/dev/null) || _OUT=""
echo "$_OUT" | grep -q 'bypass permissions' && echo "$_OUT" | grep -q '❯' && break
sleep 0.5
done
tmux copy-mode -q -t "$PANE" 2>/dev/null
fi
if [ -n "${SUBTASK_ID:-}" ]; then
doey_send_command "$PANE" "export DOEY_SUBTASK_ID=${SUBTASK_ID}"
sleep 0.3
fi
tmux select-pane -t "$PANE" -T "task-name_$(date +%m%d)"
TASKFILE=$(mktemp "${RD}/task_XXXXXX.txt")
cat > "$TASKFILE" << TASK
You are a worker on Doey for project: ${PROJECT_NAME}
Project directory: ${PROJECT_DIR}
All file paths should be absolute.
Your detailed task prompt here.
TASK
TASK_CONTENT=$(cat "$TASKFILE")
doey_send_verified "$PANE" "$TASK_CONTENT" && echo "✓ Worker ${W}.X started" || echo "✗ FAILED — run unstick sequence"
rm -f "$TASKFILE"
Variants
- Batch: Parallel Bash per worker | Short (<200 chars):
send-keys (verify still required)
- File conflicts: One file per worker | Unstick:
copy-mode -q → C-c → C-u → Enter, 3s. 2 fails: kill -9, relaunch
- Force restart:
FORCE_RESTART=1
Rules
Re-check .reserved before dispatch; verify after. One task/worker/cycle. Include PROJECT_NAME, PROJECT_DIR, absolute paths.