Prompting an autonomous coding subagent to “always open a pull request, address review comments, and never merge your own branch” works until context pressure rises. Eventually an agent forgets, stops early with uncommitted edits sitting in a worktree, or skips a requested change after a compaction boundary.
Both fable-sonnet-orchestrator-kit and claude-upgrades solve this by moving the workflow gate out of the LLM prompt and into deterministic code: a PowerShell Stop hook (executor-stop-gate.ps1) inspects GitHub state via gh every time an executor tries to finish.
Asymmetric Roles: Orchestrator vs. Executors
Rather than giving every agent identical prompts, the operating model splits responsibility asymmetrically:
- Orchestrator (main session): No custom agent file—configured automatically at boot by a
SessionStarthook injecting the orchestration skill. Breaks requirements into single-PR-sized GitHub issues, dispatches one executor perreadyissue, and acts as the sole reviewer and merger. - Executors (
sonnet-executor,issue-triage): Spawned subagents with explicit frontmatter pinning (model: claude-sonnet-4-6,effort: max) and a dedicated Stop hook bound to their lifecycle.
Each executor works in an isolated git worktree (../repo-<issue#>-<slug>) on branch feat/<issue#>-<slug>, ships one focused PR with Closes #N, and never runs git merge on main.
How the Deterministic Stop Gate Works
Same-account GitHub disallows formal “Request changes” reviews on your own pull requests. To preserve a hard review loop without needing separate bot accounts, review feedback is round-tripped through a tagged PR comment ([ORCH-REVIEW] CHANGES-REQUESTED):
orchestrator session
| fan out: 1 sonnet-executor per ready GitHub issue
v
sonnet-executor (worktree feat/<issue#>-<slug>)
| 1. implement + targeted tests
| 2. open PR with "Closes #<issue#>"
| 3. attempts to Stop
v
executor-stop-gate.ps1 (Stop hook, pure code)
|
+-- no open PR & no "BLOCKED:" -> BLOCK stop, keep working
+-- open PR (or BLOCKED) -> allow stop; orchestrator reviews
v
orchestrator reviews PR (sole reviewer)
|
+-- approves -> merge into main -> Closes #N
+-- requests changes -> "[ORCH-REVIEW] CHANGES-REQUESTED"
|
v
resume executor -> Stop hook injects review -> push fix -> loop
Because the gate executes as an external hook (exit 2 + stderr payload on block), the executor cannot rationalize its way past a missing PR or unaddressed review notes. At the same time, two safety valves keep the harness from wedging:
- Explicit blocker escape hatch: Emitting
BLOCKED: <reason>lets an agent surface genuine missing prerequisites cleanly. - Fail-open & loop protection: Transient
ghnetwork errors fail open rather than trapping a session, and Claude Code’sstop_hook_activeguard prevents infinite self-blocking loops.
Package Structure and Model Cost Control
In claude-upgrades, the whole setup packs cleanly into four drop-in directories guarded by a CI workflow that checks hook syntax, JSON structure, and frontmatter integrity:
skills/
fable-orchestrator/ main-agent operating model: issues-as-todo-list, fan-out
sonnet-executor/ one issue -> one worktree -> one PR; never merges
issue-triage/ context-efficient GitHub issue labeling & triage
handoff/ rolling handoff.md curation across compactions
agents/
sonnet-executor.md pinned Sonnet 4.6 + effort max + Stop hook
hooks/
executor-stop-gate.ps1 pure-code PR & review round-trip gate
settings/
settings.json SessionStart auto-injection
One subtle operational pitfall surfaced during production runs: some setups make subagents inherit the parent session’s model rather than honoring model: in agent frontmatter. When orchestrating from Opus while dispatching many concurrent worktrees, always verify executors actually land on Sonnet 4.6—passing the target model explicitly if your runtime overrides frontmatter pins.