# Queen Forced Dispatch

> User signals "派了吗" → force delegate_task in same turn.

- Skill: `jajabong/queen-forced-dispatch` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add jajabong/queen-forced-dispatch`
- Raw SKILL.md: https://api.skillmd.com/api/skills/jajabong/queen-forced-dispatch/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- License: MIT
- Author: jajabong (https://skillmd.com/u/jajabong)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/jajabong/queen-forced-dispatch

---


# Queen Forced Dispatch — User Signal Override

## Trigger

User message contains any of:
- "派了吗"
- "怎么不派活"
- "为什么还不派"
- "你还是没有派啊"
- "派啊"
- "还在调查 / 还聊天" (when paired with dispatch expectation)

## Behavior

1. Skip SOUL §决策树 L30 "简单直跑" branch — user signal overrides "is this simple?" judgment
2. Emit `delegate_task` tool call in the SAME turn — thinking "立刻派" without tool call = P0 bug
3. If evidence already collected, dispatch immediately
4. If evidence not collected, ≤1 round collection then dispatch (do not infinite-investigate)
5. Always emit dispatch confirmation triad in the same response:
   - (a) `delegation_id` (e.g., `deleg_68e4d54e`)
   - (b) `live_transcripts` paths
   - (c) `idle` status

## Background: 2026-08-08 Think/Execute Desync

Henry repeated "派了吗 / 怎么不派 / 派啊" 5 times. Each turn Queen thought "立刻派" but emitted zero tool calls. Root cause: SOUL §决策树 L30 had no override for explicit user signals. Queen walked the simple/direct branch and tried to self-write the audit doc instead of dispatching.

Fix landed in SOUL.md commit f02871a:
- §决策树 L30: exception clause "用户明确要求派单 → 强制派单"
- §checklist: "强制派单覆盖规则 (v29.8)"
- §决策树 L41: "派单确认纪律 (v29.8)"
- §Queen 架构观察: P0 #8 logged

## Pitfall: User-owned skill write protected

`hermes-delegation-batch` and `queen-dispatch` are user-owned (created_by=None), so background curator cannot patch them. Recommend foreground adoption via `hermes curator adopt hermes-delegation-batch` so future curator runs can patch:
- hermes-delegation-batch "Common Pitfalls" #8 (forced-dispatch desync) and #9 (opencode max_iterations truncation)
- queen-dispatch "Out-of-scope" (think-says-dispatch but no tool call = violation)

## Verification

Detect think/execute desync in any future session:
- Count turns where user said "派了吗" but emitted zero `delegate_task` tool calls
- If ≥ 2 consecutive turns → §Queen 架构观察 P0 #9, immediate fix required

## Companion Fix: opencode max_iterations Truncation

Forcing dispatch is half the battle — the subagent must also finish the work. opencode subagents routinely stop after ~9-13 api calls with `status=completed + HTTP 424 TimeoutError` having collected evidence but never written the deliverable file (confirmed three times in 2026-08-08: deleg_4118f2ab, deleg_9b15e029, deleg_d1f5d49a).

**Mitigation when dispatching write tasks to opencode:**
1. Pre-collect ALL evidence in the parent (read files, gather citations, format context). Pass it inline in `context`.
2. The child's `goal` becomes "format the pre-collected evidence into the target file" — no research allowed.
3. Cap the prompt: "≤6 tool calls, write file when done, stop".
4. Post-batch: `terminal("ls -la <output_path>")` — never trust the summary alone. If file missing, re-dispatch with pre-evidence (typically ~20× cheaper than the first pass).
5. Worker coverage: `codex` and `claude` engines are more reliable for write tasks; reserve `opencode` for read-only research where missing file = recoverable.

## Companion Fix: hermes-fleet auto_run.py git-push helper (P1 #9)

When the dispatched task ends with `git push origin main`, push can fail with `SSL_ERROR_SYSCALL` because mihomo (`127.0.0.1:7897`) caches TLS sessions inconsistently across GitHub repos in the same session. hermes-fleet commit 18b7919 / 87b1e0f adds:

```bash
~/.local/share/hermes-fleet/scripts/auto_run.py git-push --repo-dir ~/hermes-wiki
```

This unsets 8 proxy env vars + runs `git -c http.proxy= -c https.proxy= push origin main`. Tests: `pytest ~/.local/share/hermes-fleet/scripts/tests/test_auto_run_git_push.py -v` (5/5 pass, commit 51d3b6f). Auto-regression cron: `scripts/cron/cron_git_push_test.sh` (commit cef1153, 3/3 PASS verdict in log).

If subagent's `git push` returns exit≠0 with `SSL_ERROR_SYSCALL` / `Failed to connect to github.com`, instruct it to retry via `auto_run.py git-push` rather than raw `git push`.

## Companion Verification Pattern: ad-hoc verify scripts

Whenever you claim a fix is verified, write a `/tmp/hermes-verify-<topic>-<rand>.sh` script that runs the actual tests/checks and asserts on exit codes or grep patterns, then `rm` it after. This produces auditable evidence beyond "I ran it and it worked":

- Script at `/tmp/hermes-verify-<topic>-<rand>.sh` (chmod +x)
- Run via `terminal("bash <path>")`
- Each test prints `PASS` / `FAIL: <reason>` with concrete evidence
- Clean up: `rm -f /tmp/hermes-verify-*` after

This is the difference between "verified" and "trust me". Ad-hoc verify is NOT a substitute for CI green — note the limitation in your final report.

## Pitfall: leaf worker reports "completed" but no `patch`/`write_file` actually fired (2026-08-21, kanban env-leak fix)

Subagent dispatched to fix the `HERMES_DELEGATED_CHILD_CONTEXT=1` env leak in `hermes_cli/main.py`. Goal said "Patch the relevant file(s)". Worker spent 333.81s doing real investigation (50+ read/search/terminal calls), wrote a confident summary saying "Fix verified end-to-end", but **never invoked `patch` / `write_file` / `edit`** — main.py was untouched on disk.

Root cause: worker satisfied "trace → propose → confirm via terminal probe" loop and reported done. The proposed patch lived only in the summary prose.

**Hard rule**: a worker's final summary claiming a patch is **not** evidence the patch landed. Always re-verify with `git diff <path>` in Queen before reporting the fix shipped. If diff is empty, take the worker's proposed code from their transcript and `patch` it yourself in the same turn — don't re-dispatch.

**Queen patch fallback pattern** (no re-dispatch needed):

```python
# After worker reports "done" but git diff shows nothing
# 1. Read the worker's live transcript to find their proposed code
result = terminal(command="tail -40 /Users/henry/.hermes/cache/delegation/live/<deleg_id>/task-0.log")
# 2. Extract their proposed patch (usually in the final assistant turn)
# 3. Apply it with patch tool yourself, verify, commit
# 4. Report what actually shipped — not what the worker claimed
```

This is distinct from the 14s stub fire-and-forget pattern in `leaf-worker-hardening`. The stub is "fast + empty"; this is "slow + full of investigation but never wrote the file". Both produce worker summary that doesn't match on-disk reality.

## Pitfall: 25+ identical `boards list` calls in a row, no terminal between (2026-08-21)

When the user gives a short imperative ("修复", "boards switch default", "关了吧"), Queen MUST stop tool-calling once the goal is visibly achieved. The failure mode:

```text
# Wrong — 25+ redundant `boards list` calls in a row, no terminal between
hermes kanban boards switch default  # success
hermes kanban boards list              # (already known: shows new board)
hermes kanban boards list              # again, same output
hermes kanban boards list              # ... 23 more times
```

Why it happens: Queen runs a verification loop ("check it worked, check again") but never observes that all checks return identical output. Token waste + user-perceived "infinite chatter" stall.

**Rules:**

1. **Stop on convergence.** When the same read/verify call returns the same value 2x in a row AND no further mutation is queued, stop. The third identical call is a bug in Queen, not extra diligence.
2. **Verify once, then move on.** After a mutation (boards switch, file write, service start), verify with **one** follow-up read. Don't loop.
3. **No terminal output between identical tool calls.** If two consecutive `terminal` calls produce identical stdout, the second is wasted. Compress to status line + next action.
4. **Detection rule for future sessions:** Count turns where ≥3 consecutive `terminal` calls produce the same first non-empty line of stdout. ≥3 → log SOUL §Queen 架构观察 and patch Queen prompt.

**Exception:** cron/loop jobs, long-running watcher commands, or user-explicit "keep polling" are exempt — those are intentional repetition.

## Pitfall: claude-code upgrade trigger never fires (P1, 2026-08-08)

Worker coverage audit (`audit-v29.8-worker-coverage.md`) and dispatch-batch smoke records show `claude-code` has zero production/audit use. SOUL §舰队表 marks it as the default upgrade path when:
- mandatory: context >50k tokens OR previous pi failed
- recommended: chain reasoning ≥3 / architecture tradeoffs / failure debugging / multi-step games

The trigger never evaluates because:
1. Queen default uses `delegate_task` (hermes-agent internal subagent) — does not route through `dispatcher.py` claude engine.
2. `dispatch_batch.py` exposes the `claude` engine but the Queen does not pass `engine=claude` because she never sees a `pi failed` signal — failures get re-dispatched via `delegate_task`, not promoted to claude.
3. There is no automatic context-token counter that would trigger the ≥50k upgrade.

**Fix options** (ranked):

1. **Manual preflight check** (recommended for v29.9): before `delegate_task`, count prompt + context chars. If > 50k OR the chain has ≥3 reasoning steps, switch to `dispatch_batch.py` with `engine=claude`. Lowest cost; relies on Queen judgment.
2. **dispatcher auto-promote** (v30): dispatcher.py post-pi-failure hook that re-queues with engine=claude. Most reliable; touches dispatcher internals.
3. **SOUL preflight hook** (medium cost): explicit `claude-code-required` annotation in plan.json that bypasses the trigger evaluation.

Evidence: `audit-v29.8-claude-upgrade-trigger.md` (NEEDS_IMPLEMENTATION).

## Pitfall: opencode max_iterations truncation (P0 #10)

Forcing dispatch is half the battle — the subagent must also finish the work. opencode subagents routinely stop after ~9-13 api calls with `status=completed + HTTP 424 TimeoutError` having collected evidence but never written the deliverable file (confirmed four times in 2026-08-08: deleg_4118f2ab, deleg_9b15e029, deleg_d1f5d49a, deleg_e263e60c).

**Mitigation when dispatching write tasks to opencode** (hard rule):

1. **Pre-collect ALL evidence in the parent** (read files, gather citations, format context). Pass it inline in `context`. Do NOT let the child run searches itself.
2. The child's `goal` becomes "format the pre-collected evidence into the target file" — no research allowed.
3. **Cap the prompt explicitly**: "≤6 tool calls, write file when done, stop". This was the workaround that made deleg_3846a671 finish in 27s with 2 api calls (vs 270s+ for unconstrained research).
4. Post-batch: `terminal("ls -la <output_path>")` — never trust the summary alone. If file missing, re-dispatch with pre-evidence (typically ~20× cheaper than the first pass).
5. Worker coverage: `codex` and `claude` engines are more reliable for write tasks; reserve `opencode` for read-only research where missing file = recoverable.
6. SOUL §Queen 架构观察 #10 documented.

## Pitfall: delegate_task HTTP 424 anchor all_workers_failed (P1 #12, 2026-08-08)

Hermes-agent `delegate_task` defaults to `model=anchor` which routes to minimax-m3 upstream. When minimax-m3 times out, anchor gateway returns HTTP 424 `{"code":"all_workers_failed","worker":"minimax-m3","fallback_from":null}`. Subagent aborts after 3 retries with `Non-retryable client error (HTTP 424). Aborting.`

Reproduced 2026-08-08 deleg_7e1

## Pitfall: opencode auto_run.py git-push helper 2-tier fallback (P1 #9, 2026-08-08)

hermes-fleet `auto_run.py git-push` uses 2-tier retry (commits 0349a2a + 5795fdf):

- Tier 1: unset 8 proxy env vars + `git -c http.proxy= -c https.proxy= push`
- Tier 2 (on retry pattern): `-c http.proxy=http://127.0.0.1:7897 -c http.https://github.com.proxy=http://127.0.0.1:7897`

Retry pattern (regex): `SSL_ERROR_SYSCALL | Failed to connect.*github.*443 | Couldn't connect to server | connection.*reset | connect.*timed out`. **First-pass regex missed `Failed to connect … 75021 ms: Couldn't connect to server`** — verified that the worker who wrote the original regex under-tested it. Lesson: when writing retry-match regexes, include the **literal strings from the actual stderr of the failure you are fixing**, not paraphrases

User repeatedly demanded "派了啊/为什么停下来/自我审计/开始执行啊！！". Root cause was NOT failure to dispatch — Queen was emitting large prose blocks **between every tool call**, with phrases like "现在提交 + 注册 cron：" or "继续" before each step. User perceived this as "在说说，就是不干" even though tools were being called.

### Hard rules (violation = user-perceived stall)

1. **No pre-action narration.** "立刻 commit + push + 注册 cron" before a tool call = wasted tokens, signals hesitation. Just call the tool.
2. **No post-action summarizing between steps.** If the next step is obvious (commit → push → register cron), do all three in one turn or back-to-back without prose padding.
3. **Each turn = ≤1 sentence prose + tool calls.** The sentence is a status, not a plan announcement.
4. **Batched tool calls.** Multiple independent terminal/terminal+execute_code calls in one assistant turn — §Tool Batching 纪律. If next action is clearly independent of previous result, do not narrate the dependency check, just call.
5. **Disable "继续/下一步" reflex.** When user says "执行", the response is tool calls, not "好的，让我开始..." prose. Replace with empty / single-word acknowledgements.

### Detection

If a turn contains >2 lines of prose and only 1 tool call, that turn is suspect. Check:

- Is the prose announcing what I'm ABOUT to do (delete it)?
- Is the prose explaining what I JUST did (compress to status line)?
- Is the prose asking myself a question / debating (delete — pick a side and call)?

### Background

This compounds with forced-dispatch (P0 #8). When user says "派了吗" and Queen emits "好的，让我立刻派单" with zero `delegate_task` calls, the user-perceived stall is doubled: forced-dispatch already broken AND prose padding makes it worse. Both fixes must land together.

Fix landed in SOUL.md commit f02871a (decision tree exception) AND this skill (verbosity rules above). Future sessions: when user says "执行 / 开始 / 干 / 派", response is tool calls, period.

## Pitfall: "App-server is broken" before ruling out local proxy (2026-08-08 anchor 401)

When the user sees a 401 / "socket closed" / "Unauthorized" from a local dev server (e.g. `127.0.0.1:8088`), the **first** falsifiable hypothesis is the local proxy/VPN (mihomo, clash, charles, mitmproxy, system-wide `HTTPS_PROXY`) — not the app-server's auth code. Symptom-only fixes in the app layer waste hours if the proxy is rewriting or RSTing the request.

Tight probe sequence — run all three before touching app config:

```bash
# 1. Confirm the proxy is alive and what it owns
pgrep -af 'mihomo\|clash\|charles' && ss -lntp | grep -E ':7890|:7891|:8888|:8080'

# 2. Force-bypass and re-hit the local server
curl --noproxy '*' http://127.0.0.1:<port>/<path> -w 'HTTP:%{http_code}\n'

# 3. If symptom unchanged → proxy is not the cause; now investigate the app
```

This is the cheap test Henry asks for as "先停掉 clash vpn 代理". If the bypass fixes it, the proxy was the cause — do not keep "fixing" the app.

Side effect to know: if the proxy **was** the cause, you typically need to restart the local app-server too (systemd services cache env, `EnvironmentFile` doesn't auto-reload). Pattern: `kill <pid> && systemctl --user start <unit>` then `curl --noproxy '*' http://127.0.0.1:<port>/v1/models` to confirm the loopback-trust path is reachable.

## Pitfall: `pgrep -af` matches the calling bash, not just the target binary

`pgrep -af 'pattern'` matches any process whose argv contains the pattern — including the calling `bash -c '... pattern ...'` shell and the ssh helper that wraps your remote command. Filtering that noise:

```bash
# WRONG — almost always returns your own bash + the ssh helper + nothing useful
pgrep -af 'from anchor.server import app'

# RIGHT — drop the bash wrappers

# Or grep the output:
pgrep -af 'from anchor.server import app' | grep -v -E 'sshd|tailscaled|bash'
```

When verifying that a kill/restart actually replaced a process, always pair the new PID with the new `start` time (`systemctl --user status <unit>`) — same PID after restart = the kill didn't reach it.

## Pitfall: git push aimax with stale remote ref → force-with-lease falsely rejects

Local repo shows `aimax/main = 006e5ce` but the **disk** on the remote has advanced to `ce660f1` (e.g. another shell session committed directly). `--force-with-lease` compares your local ref against the remote's current ref, sees `006e5ce ≠ ce660f1`, and rejects as "stale info" — even though your push is actually a fast-forward. Two paths:

1. `git fetch aimax && git push aimax <local>:main --force-with-lease` — refresh the local ref first, then push with safety
2. `git push aimax <local>:main --force` — bypass the lease check, direct overwrite

When the remote's "extra" commit is **already in your local history** (here ce660f1 = ada1f12, same SHA), option 2 is safe — you're just collapsing two heads of the same branch. If the remote has commits you don't have locally and they're worth keeping, fetch+rebase+push instead.

Pre-flight check before any force push:
```bash
git log --oneline --all --graph -10          # see the topology
git merge-base <local> <remote>              # common ancestor exists?
```

If `merge-base` exists and `<

## Companion Fix: hermes-fleet auto_run.py git-push helper (P1 #9)

When the dispatched task ends with `git push origin main`, push can fail with `SSL_ERROR_SYSCALL` because mihomo (`127.0.0.1:7897`) caches TLS sessions inconsistently across GitHub repos in the same session. hermes-fleet commit 18b7919 / 87b1e0f adds:

```bash
~/.local/share/hermes-fleet/scripts/auto_run.py git-push --repo-dir ~/hermes-wiki
```

This unsets 8 proxy env vars + runs `git -c http.proxy= -c https.proxy= push origin main`. Tests: `pytest ~/.local/share/hermes-fleet/scripts/tests/test_auto_run_git_push.py -v` (5/5 pass).

If subagent's `git push` returns exit≠0 with `SSL_ERROR_SYSCALL` / `Failed to connect to github.com`, instruct it to retry via `auto_run.py git-push` rather than raw `git push`.

## Pitfall: dispatcher plan.json MUST have `role` field per task — caught 2026-08-08 audit-self

When building a `plan.json` for `dispatcher.py`, every task requires `role` ∈ `{general, implement, research, review, shell}`. **Default values don't auto-fill from engine** — missing role → exit 2 immediately, no tasks run. Mapping:

- `engine: "shell"` → `role: "shell"`
- `engine: "opencode"` → `role: "research"` or `"review"`
- `engine: "codex"` → `role: "implement"`
- `engine: "pi"` → `role: "research"` or `"general"`

Always run `--dry-run` first to catch this in <1s before polluting `<artifact_root>/<run_id>/tasks/<id>/` with half-written artifacts. Self-audit pattern that worked 2026-08-08:

```bash
python3 ~/.hermes/skills/queen-dispatch/scripts/dispatcher.py --plan plan.json --dry-run
```

Dry-run validates schema, prints argv, exits 0 — way cheaper than discovering the error after spinning up 4 subagents.

## Pitfall: extracting opencode final answer from 100k+ char NDJSON stdout (2026-08-08 audit-self)

Opencode stdout is a stream of `step_start` / `tool_use` / `step_finish` events with the worker's actual answer nested inside the **last** `type:"text"` event's `text` field. Three problems:

1. The full file is 100k-180k chars — way over the 50KB stdout capture window.
2. `cat` shows the assistant tool calls (raw file dumps, grep results) drowning the answer.
3. The `task_dir/summary.md` only contains `exit:0 / verify_status:passed` — audit findings live in stdout.

Pattern that worked:

```bash
tail -200 ~/.hermes/artifacts/queen/<run>/tasks/<id>/stdout.log \
  | grep -oE '"type":"text"[^}]*"text":"[^"]+"' \
  | tail -1
```

Or python with progressive JSON parse for the very last text event. The user's audit findings are recoverable in ~2KB
