Skip to content

Commit 485046d

Browse files
fix(pause): TaskStop guarantee tier for teammate shutdown + termination-primitive docs (v4.6.1) (#1161)
* fix(pause): add TaskStop guarantee tier to teammate shutdown; codify TaskStop as the termination primitive shutdown_request is cooperative-only — empirically, on the tmux backend an approved shutdown_response does not terminate the teammate's pane/process. pause.md step 6 now mirrors refresh.md step 5's two-tier shape (graceful request, then TaskStop as the guarantee tier); the agent-teams skill and orchestrator persona codify the same correction with one shared sentence skeleton across all three surfaces. imPACT.md assessed: already TaskStop-routed, no change. * chore: bump plugin version to 4.6.1 (PATCH) * fix(imPACT): shutdown_request is cooperative-only, not a standalone termination path Rewrites the when-to-terminate clause to the shared cooperative-only / termination-primitive skeleton, reconciles the 'last resort' framing with the routine guarantee-tier role, and unifies the TaskStop signature to the name form. * fix(shutdown-docs): review-minors batch — honest dual-mode scoping, loop-error guidance, ported guards refresh.md rationale aligned to the cooperative-only skeleton; pause.md gains continue-on-not-found loop guidance plus refresh's no-sync-confirmation and post-stop-send guards; SKILL.md scopes the non-termination claim to the tmux backend (in-process semantics unprobed) and reframes memory-saving as incremental; stagger phrasing unified (1 teammate/turn, request+stop = 2 ops). * test: structural pins for pause two-tier shutdown + cross-surface termination skeleton TestPauseShutdownStructure pins the two-tier loop invariants (order, cooperative-only rationale, post-state note, no-delete line, error idempotency); TestShutdownTerminationSkeletonAcrossSurfaces asserts the uniform cooperative-only skeleton on all four shutdown surfaces so a single-surface regression desyncs loudly. * test: add imPACT.md to the termination-skeleton surface pins * fix(teammate_idle): two-tier the force-threshold shutdown advisory The ACTION-REQUIRED message the hook emits for the orchestrator now instructs the graceful request followed by TaskStop, with the cooperative-only rationale; test pins both tokens.
1 parent 3fb3a1d commit 485046d

12 files changed

Lines changed: 129 additions & 11 deletions

File tree

.claude-plugin/marketplace.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
"name": "PACT",
1313
"source": "./pact-plugin",
1414
"description": "Orchestration harness that turns Claude Code into a coordinated team of specialist AI agents",
15-
"version": "4.6.0",
15+
"version": "4.6.1",
1616
"author": {
1717
"name": "Synaptic-Labs-AI"
1818
},

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -605,7 +605,7 @@ When installed as a plugin, PACT lives in your plugin cache:
605605
│ └── cache/
606606
│ └── pact-plugin/
607607
│ └── PACT/
608-
│ └── 4.6.0/ # Plugin version
608+
│ └── 4.6.1/ # Plugin version
609609
│ ├── agents/
610610
│ ├── commands/
611611
│ ├── skills/

pact-plugin/.claude-plugin/plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "PACT",
3-
"version": "4.6.0",
3+
"version": "4.6.1",
44
"description": "Orchestration harness that turns Claude Code into a coordinated team of specialist AI agents",
55
"author": {
66
"name": "Synaptic-Labs-AI",

pact-plugin/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# PACT — Orchestration Harness for Claude Code
22

3-
> **Version**: 4.6.0
3+
> **Version**: 4.6.1
44
55
Turn a single Claude Code session into a managed team of specialist AI agents that prepare, design, build, and test your code systematically.
66

pact-plugin/agents/pact-orchestrator.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,8 @@ Exceptions:
407407
- rePACT sub-scope specialists shut down after their nested cycle (orchestrator relays handoff details to subsequent sub-scopes)
408408
- comPACT specialists shut down when user chooses "Pause work for now"
409409

410+
`shutdown_request` is cooperative-only — empirically, on the tmux backend an approved `shutdown_response` does not terminate the teammate's pane/process. `TaskStop("{teammate_name}")` is the authoritative termination primitive (reaps the pane/process and removes the roster entry; the team config file and team identity survive): any flow that requires a teammate actually gone MUST follow the graceful request with `TaskStop`.
411+
410412
**Inter-teammate messages always go individually by name.** `SendMessage` requires a specific `to=` recipient — there is no broadcast addressing mode. To reach multiple teammates (HALT, shutdown, plan approval, structured protocol messages, plain-text announcements), iterate over the relevant teammates and send one `SendMessage` per recipient. Use the Lead-Side HALT Fan-Out idiom below as the canonical pattern.
411413

412414
### Lead-Side HALT Fan-Out

pact-plugin/commands/imPACT.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ Answer three questions:
117117
| **Redo prior phase** | Issue is upstream in P→A→C→T | Re-delegate to relevant agent(s) to redo the prior phase |
118118
| **Augment present phase** | Need help in current phase | Re-invoke blocked agent with additional context + parallel agents |
119119
| **Invoke rePACT** | Sub-task needs own P→A→C→T cycle | Use `/PACT:rePACT` for nested cycle |
120-
| **Terminate agent** | Agent unrecoverable (infinite loop, context exhaustion, stall after resume) | `TaskStop(task_id=taskId)` (force-stop: terminates immediately, non-cooperative) + `TaskUpdate(taskId, status="completed", metadata={"terminated": true, "reason": "..."})` |
120+
| **Terminate agent** | Agent unrecoverable (infinite loop, context exhaustion, stall after resume) | `TaskStop("{agent-name}")` (force-stop: terminates immediately, non-cooperative) + `TaskUpdate(taskId, status="completed", metadata={"terminated": true, "reason": "..."})` |
121121
| **Not truly blocked** | Neither question is "Yes" | Instruct agent to continue with clarified guidance |
122122
| **Escalate to user** | 3+ imPACT cycles without resolution | Proto-algedonic signal—systemic issue needs user input |
123123

@@ -127,7 +127,7 @@ If the blocker reveals that a sub-task is more complex than expected and needs i
127127
/PACT:rePACT backend "implement the OAuth2 token refresh that's blocking us"
128128
```
129129

130-
**When to terminate**: Last resort — agent resumed once and stalled again, looping on same error 3+ times, context exhausted, or TeammateIdle stall unresolved by resume. `TaskStop` is a force-stop (immediate, non-cooperative); use `SendMessage(to="{agent-name}", message={"type": "shutdown_request"})` for cooperative shutdown. After termination, spawn a fresh agent with partial HANDOFF from the terminated agent's task metadata.
130+
**When to terminate**: Last resort — agent resumed once and stalled again, looping on same error 3+ times, context exhausted, or TeammateIdle stall unresolved by resume. `TaskStop` is a force-stop (immediate, non-cooperative) and the termination primitive; `SendMessage(to="{agent-name}", message={"type": "shutdown_request"})` is cooperative-only wind-down messaging that does not itself terminate the agent — any flow that requires the agent actually gone MUST follow the graceful request with `TaskStop`. "Last resort" scopes the decision to terminate an unrecoverable agent mid-workflow — not the `TaskStop` primitive itself, which is also the routine guarantee tier in graceful shutdown flows. After termination, spawn a fresh agent with partial HANDOFF from the terminated agent's task metadata.
131131

132132
> **Force-termination is a carve-out** to the team-lead-only-completion rule. The `metadata.terminated == true` marker on the team-lead-driven `TaskUpdate(status="completed")` distinguishes this path from the standard cooperative acceptance flow. The terminated teammate is not idling on `awaiting_lead_completion`; the team-lead is force-completing an unrecoverable agent's task out-of-band. See [Completion Authority](../protocols/pact-completion-authority.md#completion-authority) for the carve-out table.
133133

pact-plugin/commands/pause.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,14 +105,17 @@ The timestamp (`ts`) is set automatically by `make_event()` and serves the same
105105

106106
### 6. Shut Down Teammates
107107

108-
Send `shutdown_request` individually to each active teammate **by name** and wait for responses. The secretary must have completed consolidation tasks (steps 1 and 3) before receiving the shutdown request.
108+
Shut down each active teammate **by name**, staggered 1 teammate per turn — the stagger counts ops, and request + stop = 2 ops (rate-limit discipline): graceful `shutdown_request` first, then `TaskStop("{teammate_name}")` as the guarantee tier — `shutdown_request` is cooperative-only (empirically, on the tmux backend an approved `shutdown_response` does not terminate the teammate's pane/process); `TaskStop` is authoritative. The secretary must have completed consolidation tasks (steps 1 and 3) before receiving the shutdown request.
109109

110110
```
111111
For each active teammate:
112112
SendMessage(to="{teammate_name}", message={"type": "shutdown_request", "reason": "Session paused"})
113+
then: TaskStop("{teammate_name}")
113114
```
114115

115-
Do NOT delete the team — it will be garbage-collected or reused on resume.
116+
Treat a `TaskStop` not-found / already-exited error as already-stopped success and CONTINUE the loop — never abort mid-iteration; an abort strands later teammates unstopped.
117+
118+
EXPECTED post-state: each stop removes that member's roster entry — the config FILE and the team IDENTITY survive; a lead-only roster is the correct post-pause state, not corruption. Do NOT add synchronous send-confirmation (message delivery lands asynchronously) — verify by a disk re-read of the team config, or simply proceed. A send to an already-stopped teammate fails hard with a no-agent-reachable error — this is EXPECTED post-stop behavior, not a diagnostic detour. Do NOT delete the team — it will be garbage-collected or reused on resume.
116119

117120
### 7. Report
118121

pact-plugin/commands/refresh.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ The timestamp (`ts`) is set automatically by the journal writer and becomes the
117117

118118
### 5. SHUTDOWN teammates
119119

120-
Shut down each active teammate **by name**, staggered 1-2 sends per turn (rate-limit discipline): graceful `shutdown_request` first, then `TaskStop(name)` as the guarantee tier — a teammate may reject the request mid-task; `TaskStop` is authoritative.
120+
Shut down each active teammate **by name**, staggered 1 teammate per turn — the stagger counts ops, and request + stop = 2 ops (rate-limit discipline): graceful `shutdown_request` first, then `TaskStop(name)` as the guarantee tier — `shutdown_request` is cooperative-only (empirically, on the tmux backend an approved `shutdown_response` does not terminate the teammate's pane/process); `TaskStop` is authoritative.
121121

122122
```
123123
For each active teammate:

pact-plugin/hooks/teammate_idle.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -338,7 +338,10 @@ def main():
338338
# to send a shutdown_request via systemMessage.
339339
messages.append(
340340
f"ACTION REQUIRED: Send shutdown_request to '{teammate_name}' "
341-
f"via SendMessage(type=\"shutdown_request\", recipient=\"{teammate_name}\")."
341+
f"via SendMessage(type=\"shutdown_request\", recipient=\"{teammate_name}\"), "
342+
f"then TaskStop(\"{teammate_name}\") — shutdown_request is "
343+
f"cooperative-only and does not itself terminate the teammate; "
344+
f"TaskStop is the termination primitive."
342345
)
343346

344347
output = {"systemMessage": IDLE_PREAMBLE + " | ".join(messages)}

pact-plugin/skills/pact-agent-teams/SKILL.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -520,7 +520,9 @@ When you receive a `shutdown_request`:
520520
| Idle, consultant with no active questions, or domain no longer relevant | Approve |
521521
| Mid-task, awaiting response, or remediation may need your input | Reject with reason |
522522

523-
> **Save memory before approving**: If you haven't saved domain learnings to your agent memory yet, do so before approving — your process terminates on approval.
523+
> **Save learnings incrementally**: under guarantee-tier shutdown flows the lead may follow the graceful request with an immediate `TaskStop`, so you can be reaped before ever seeing the request. Save domain learnings to your agent memory as you work and treat any turn as possibly your last; approving a `shutdown_request` is a courtesy, not your save trigger.
524+
525+
`shutdown_request` is cooperative-only. Empirically, on the tmux backend an approved `shutdown_response` does not terminate the teammate's pane/process — approval authorizes termination but does not perform it. On the in-process backend, `shutdown_response` semantics are unprobed: platform documentation claims approval terminates the process, but this is unverified in either direction. `TaskStop` is the termination primitive; any flow that requires a teammate actually gone MUST follow the graceful request with `TaskStop` (or verify pane/process death).
524526

525527
## Completion Integrity (SACROSANCT)
526528

0 commit comments

Comments
 (0)