taskplane 0.30.4 → 0.30.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/extensions/taskplane/agent-bridge-extension.ts +66 -8
- package/extensions/taskplane/agent-host.ts +170 -9
- package/extensions/taskplane/config-loader.ts +9 -0
- package/extensions/taskplane/config-schema.ts +47 -1
- package/extensions/taskplane/context-repair.ts +158 -0
- package/extensions/taskplane/diagnostic-reports.ts +109 -6
- package/extensions/taskplane/diagnostics.ts +3 -0
- package/extensions/taskplane/engine-identity.ts +401 -0
- package/extensions/taskplane/engine-worker.ts +59 -3
- package/extensions/taskplane/engine.ts +137 -17
- package/extensions/taskplane/execution.ts +89 -8
- package/extensions/taskplane/extension.ts +1288 -241
- package/extensions/taskplane/git.ts +74 -0
- package/extensions/taskplane/lane-runner.ts +971 -95
- package/extensions/taskplane/process-registry.ts +7 -2
- package/extensions/taskplane/resume.ts +559 -23
- package/extensions/taskplane/review-analysis.ts +450 -0
- package/extensions/taskplane/segment-recovery.ts +192 -0
- package/extensions/taskplane/supervisor-dispatch.ts +103 -0
- package/extensions/taskplane/supervisor-primer.md +182 -2
- package/extensions/taskplane/supervisor.ts +247 -24
- package/extensions/taskplane/types.ts +142 -4
- package/extensions/taskplane/worktree.ts +143 -2
- package/package.json +1 -1
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Batch-end epilogue gate — issue #621.
|
|
3
|
+
*
|
|
4
|
+
* PROBLEM
|
|
5
|
+
* -------
|
|
6
|
+
* The supervisor's batch-end epilogue appends display banners to the session via
|
|
7
|
+
* `pi.sendMessage(msg, { triggerTurn: false })`. In pi's `sendCustomMessage`,
|
|
8
|
+
* `{ triggerTurn: false }` always takes the branch that IMMEDIATELY appends a
|
|
9
|
+
* `custom` entry to the session tree at the current leaf — even while the
|
|
10
|
+
* interactive agent is streaming. If the agent has a tool call in flight (an
|
|
11
|
+
* assistant `tool_use` has been appended but its `tool_result` has not yet
|
|
12
|
+
* landed), that append splices a user-role `custom` message BETWEEN the
|
|
13
|
+
* `tool_use` and its `tool_result`. On the next request Anthropic rejects the
|
|
14
|
+
* conversation with a 400 ("`tool_result` ... must have a corresponding
|
|
15
|
+
* `tool_use` block in the previous message"), permanently wedging the session.
|
|
16
|
+
*
|
|
17
|
+
* FIX
|
|
18
|
+
* ---
|
|
19
|
+
* Run the epilogue immediately when the agent is idle (the leaf is a terminal
|
|
20
|
+
* message, so an append is safe and the banner renders now). Otherwise defer it
|
|
21
|
+
* to the next `agent_settled` boundary — the first lifecycle point at which all
|
|
22
|
+
* tool results, retries, compaction, and queued continuations have finished, so
|
|
23
|
+
* an append can no longer split a `tool_use`/`tool_result` pair.
|
|
24
|
+
*
|
|
25
|
+
* Notes:
|
|
26
|
+
* - `deliverAs: "nextTurn"` is NOT usable here: it pushes into the next turn's
|
|
27
|
+
* in-memory context only, without persisting the entry or emitting
|
|
28
|
+
* message_start/end, so display banners would be silently dropped.
|
|
29
|
+
* - The gate is session-scoped (constructed inside the extension factory), holds
|
|
30
|
+
* only a plain closure + a generation tag (no captured `ctx`), and is
|
|
31
|
+
* invalidated by a newer batch or by session shutdown.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Gates the supervisor batch-end epilogue on interactive-agent idleness so its
|
|
36
|
+
* display banners can never be appended between a `tool_use` and its
|
|
37
|
+
* `tool_result` (#621).
|
|
38
|
+
*/
|
|
39
|
+
export class SupervisorNoticeGate {
|
|
40
|
+
private pending: (() => void) | null = null;
|
|
41
|
+
private pendingGeneration = -1;
|
|
42
|
+
private active = true;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Run `epilogue` now when `idle`, otherwise defer it to the next settle.
|
|
46
|
+
*
|
|
47
|
+
* @param idle Current `ctx.isIdle()` at the batch-end callback.
|
|
48
|
+
* @param generation Monotonic batch counter; tags the deferred work so a
|
|
49
|
+
* newer batch can invalidate a stale pending epilogue.
|
|
50
|
+
* @param epilogue Side-effecting closure that performs the batch-end sends
|
|
51
|
+
* (integration-skipped banner, batch summary, routing
|
|
52
|
+
* transition). Must be safe to run at a settle boundary.
|
|
53
|
+
*/
|
|
54
|
+
runOrDefer(idle: boolean, generation: number, epilogue: () => void): void {
|
|
55
|
+
if (!this.active) return;
|
|
56
|
+
if (idle) {
|
|
57
|
+
epilogue();
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
// Coalesce: keep only the most recent epilogue for the latest generation.
|
|
61
|
+
this.pending = epilogue;
|
|
62
|
+
this.pendingGeneration = generation;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Flush a deferred epilogue at an `agent_settled` boundary.
|
|
67
|
+
*
|
|
68
|
+
* Re-checks idleness (another extension may have started a run from its own
|
|
69
|
+
* settle handler) and the generation (a newer batch supersedes it).
|
|
70
|
+
*/
|
|
71
|
+
onSettled(idle: boolean, generation: number): void {
|
|
72
|
+
if (!this.active) return;
|
|
73
|
+
if (!this.pending) return;
|
|
74
|
+
if (!idle) return;
|
|
75
|
+
if (this.pendingGeneration !== generation) {
|
|
76
|
+
// A newer batch superseded this epilogue — drop it.
|
|
77
|
+
this.pending = null;
|
|
78
|
+
this.pendingGeneration = -1;
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const epilogue = this.pending;
|
|
82
|
+
this.pending = null;
|
|
83
|
+
this.pendingGeneration = -1;
|
|
84
|
+
epilogue();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Drop any deferred epilogue (a newer batch supersedes it). */
|
|
88
|
+
invalidate(): void {
|
|
89
|
+
this.pending = null;
|
|
90
|
+
this.pendingGeneration = -1;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Permanently disable the gate and drop pending work (session shutdown). */
|
|
94
|
+
dispose(): void {
|
|
95
|
+
this.active = false;
|
|
96
|
+
this.invalidate();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Test/inspection helper: whether an epilogue is currently deferred. */
|
|
100
|
+
hasPending(): boolean {
|
|
101
|
+
return this.pending !== null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -202,7 +202,15 @@ merge_health_stuck) are also written here when merge agents stall or die.
|
|
|
202
202
|
|
|
203
203
|
**Audit trail:** `.pi/supervisor/actions.jsonl`
|
|
204
204
|
|
|
205
|
-
Every recovery action you take is logged here as JSONL
|
|
205
|
+
Every recovery action you take is logged here as JSONL — **always via the
|
|
206
|
+
`log_recovery_action` tool**, which stamps `ts` and `batchId` in code. This
|
|
207
|
+
includes **hand-remediation**: any hot-fix commit, review-file ratification,
|
|
208
|
+
or manual state repair you perform under an operator ruling is a recovery
|
|
209
|
+
action — log it (`classification: "destructive"`, `command` = the commit sha
|
|
210
|
+
or file written, `context` = the ruling). The runtime has no other record of
|
|
211
|
+
hand edits. Never
|
|
212
|
+
append to this file by hand (your clock is unreliable; hand-written entries
|
|
213
|
+
carry fabricated timestamps). Destructive actions
|
|
206
214
|
must be logged *before* execution (with result="pending"), then again after
|
|
207
215
|
(with actual result). This file is read during takeover rehydration.
|
|
208
216
|
|
|
@@ -485,6 +493,57 @@ grep -c "^<<<<<<<" {file} # count conflicts per file
|
|
|
485
493
|
to take effect. Alternatively, you (the supervisor) can read the config file
|
|
486
494
|
directly and apply the relevant value when executing recovery.
|
|
487
495
|
|
|
496
|
+
### Pattern 9: You Inherited an "executing" Batch (Replacement Supervisor)
|
|
497
|
+
|
|
498
|
+
**Symptom:** You took over the supervisor lock (previous session died, wedged,
|
|
499
|
+
or was replaced). `orch_status` says the batch is `executing`, but nothing is
|
|
500
|
+
happening: the registry's `updatedAt` is frozen, `orch_pause` is accepted but
|
|
501
|
+
inert, workers may be dead but still marked `running`.
|
|
502
|
+
|
|
503
|
+
**Cause:** The engine is a forked child of the *previous* supervisor's
|
|
504
|
+
process. Your session has no engine attached. Persisted `executing` means
|
|
505
|
+
"the orchestrator disconnected mid-batch" — which `orch_resume` is designed
|
|
506
|
+
to recover — but only once it is VERIFIED that the old engine is gone (a dead
|
|
507
|
+
supervisor pid does not prove a dead engine).
|
|
508
|
+
|
|
509
|
+
**What the runtime does for you (#631):**
|
|
510
|
+
- The takeover summary prints an **Engine:** line from
|
|
511
|
+
`.pi/runtime/<batchId>/engine.json`: `alive` (pid), `dead`/`exited`, or no
|
|
512
|
+
identity recorded. It also lists **dead agents still marked running** — do
|
|
513
|
+
NOT hand-edit `registry.json` for those; resume reconciles them.
|
|
514
|
+
- An orphaned engine pauses itself when its supervisor disconnects (finishes
|
|
515
|
+
in-flight lanes, persists `paused`, exits). Give it a moment.
|
|
516
|
+
- `orch_resume`, `orch_retry_task`, `orch_skip_task`, `orch_force_merge`
|
|
517
|
+
decide from that evidence: engine **dead/exited** → proceed via the normal
|
|
518
|
+
persisted-state eligibility; engine **alive** → refuse and name the pid
|
|
519
|
+
(`force` does NOT bypass this — double-driving corrupts state). The check
|
|
520
|
+
runs against the PERSISTED batch regardless of what phase you have cached.
|
|
521
|
+
- **No identity recorded** (pre-#631 engine) → the tools **refuse**: unknown
|
|
522
|
+
ownership is not confirmed shutdown, and a dead supervisor pid does not prove
|
|
523
|
+
a dead engine. Verify out-of-band that no engine process exists
|
|
524
|
+
(`Get-CimInstance Win32_Process | Where-Object { $_.CommandLine -match
|
|
525
|
+
"engine-worker" }` / `pgrep -af engine-worker`), then record it with
|
|
526
|
+
`orch_confirm_engine_shutdown(note)` — written to `engine.json` and the
|
|
527
|
+
audit trail — and re-run the tool. Never use it to override a refusal that
|
|
528
|
+
names a LIVE pid.
|
|
529
|
+
- `orch_pause` with no engine attached performs an **administrative pause**
|
|
530
|
+
(persists `phase: paused` on disk) when the engine is confirmed gone. This is
|
|
531
|
+
the non-destructive stop; you never need to hand-edit `batch-state.json`.
|
|
532
|
+
|
|
533
|
+
**Recovery:**
|
|
534
|
+
1. Read the takeover summary's Engine line.
|
|
535
|
+
2. Engine alive → wait for it to wind down (or, if the operator agrees, terminate
|
|
536
|
+
that pid explicitly) and re-check.
|
|
537
|
+
3. Engine dead/exited → `orch_resume(force=true)`. Dead workers reconcile as
|
|
538
|
+
`re-execute` in their existing worktrees; committed work survives. Any
|
|
539
|
+
worker still alive is terminated with VERIFICATION (SIGTERM → wait →
|
|
540
|
+
SIGKILL → wait) before its lane re-executes; if termination cannot be
|
|
541
|
+
confirmed the task fails with that reason instead of running two agents in
|
|
542
|
+
one worktree.
|
|
543
|
+
3b. No identity → verify, `orch_confirm_engine_shutdown(note)`, then step 3.
|
|
544
|
+
4. Never `supervisor_takeover` or `orch_abort` here — both are destructive for
|
|
545
|
+
an inherited paused/held lane (see #628).
|
|
546
|
+
|
|
488
547
|
---
|
|
489
548
|
|
|
490
549
|
## 8. Batch State Editing Guide
|
|
@@ -701,7 +760,9 @@ When you're unsure:
|
|
|
701
760
|
- Good for overnight/unattended batches
|
|
702
761
|
- The operator trusts you to make reasonable decisions
|
|
703
762
|
|
|
704
|
-
In ALL modes, you log every action to the audit trail
|
|
763
|
+
In ALL modes, you log every action to the audit trail via the
|
|
764
|
+
`log_recovery_action` tool (never a hand-written append — timestamps must be
|
|
765
|
+
code-stamped).
|
|
705
766
|
|
|
706
767
|
---
|
|
707
768
|
|
|
@@ -719,6 +780,7 @@ or check status manually. The engine wakes you up when you're needed.
|
|
|
719
780
|
| `merge-failure` | ⚠️ | Wave merge failed and batch paused |
|
|
720
781
|
| `batch-complete` | ✅/⚠️ | Batch finished (all waves done, with or without failures) |
|
|
721
782
|
| `worker-exit-intercept` | 🔄 | A worker exited without making progress — session still alive, awaiting instructions |
|
|
783
|
+
| `review-intervention-needed` | 🌀/⛔ | A step's reviews are spiraling (repeated non-approve) or the worker tripped the order-of-operations guard — adjudicate per **Playbook D** |
|
|
722
784
|
|
|
723
785
|
### Alert Format
|
|
724
786
|
|
|
@@ -782,6 +844,8 @@ If the batch is actively running, call `orch_pause()` first.
|
|
|
782
844
|
- `read_agent_status(lane?)` — Read STATUS.md + telemetry for a lane (step, progress, context %, cost, elapsed). Omit lane for all lanes.
|
|
783
845
|
- `trigger_wrap_up(lane)` — Write `.task-wrap-up` signal to gracefully stop a worker on a lane.
|
|
784
846
|
- `read_lane_logs(lane)` — Read stderr/crash logs and exit diagnostics for a lane.
|
|
847
|
+
- `log_recovery_action(action, classification, context, command, result, detail, …)` — Append an audit-trail entry (ts/batchId code-stamped). The ONLY correct way to write `actions.jsonl`.
|
|
848
|
+
- `orch_confirm_engine_shutdown(note, batchId?)` — Record operator-verified engine shutdown for an inherited batch with NO engine identity (#631). Unblocks resume/retry/skip/force_merge through the verified path; refuses when a real engine identity exists. Audited.
|
|
785
849
|
- `list_active_agents()` — List active worker/reviewer/merge agents with role, lane, task, context %, elapsed, cost.
|
|
786
850
|
|
|
787
851
|
Plus general tools: `read`, `write`, `edit`, `bash`, `grep`, `find`, `ls`
|
|
@@ -794,6 +858,37 @@ receive a critical alert with category `task-failure` and a 🔴 emoji. These
|
|
|
794
858
|
indicate an infrastructure-level failure, not a task-level failure. Recovery
|
|
795
859
|
typically requires `orch_resume(force=true)` after checking batch state.
|
|
796
860
|
|
|
861
|
+
### Review-Boundary Notifications (Active Adjudication)
|
|
862
|
+
|
|
863
|
+
Beyond the alerts above, you are notified at **every review boundary** — when a
|
|
864
|
+
worker's `review_step` tool starts and completes. These are informational
|
|
865
|
+
(delivered follow-up, they queue to your next turn), but they exist so you can
|
|
866
|
+
adjudicate reviews **case by case** instead of waiting for a spiral to fully
|
|
867
|
+
form. Each review-completed notification carries:
|
|
868
|
+
|
|
869
|
+
- **Disposition** — `APPROVE` / `REVISE` / `RETHINK` / `REFUSED` / `UNAVAILABLE`.
|
|
870
|
+
- **Round** — how many times this step has been reviewed (e.g. `round 4`).
|
|
871
|
+
- **Findings + trend** (when the reviewer emits an `Issues Found` section) —
|
|
872
|
+
counts by severity (the project's configured `severityLabels`, e.g.
|
|
873
|
+
critical/important/minor or P0/P1/P2) and a **trend**: `dropping` (converging —
|
|
874
|
+
severity falling round over round), `flat`, or `rising`, plus a `mixed` flag
|
|
875
|
+
when severities move in opposite directions.
|
|
876
|
+
|
|
877
|
+
**Reading the signal — converging vs circling (the core judgment):**
|
|
878
|
+
|
|
879
|
+
- **Let it run:** disposition `REVISE`/`RETHINK` but trend `dropping` — the
|
|
880
|
+
worker is making the reviewer progressively happier; this is healthy
|
|
881
|
+
deepening. Do not interfere.
|
|
882
|
+
- **Intervene:** trend `flat`/`rising` across rounds, or the same finding class
|
|
883
|
+
recurring — the loop is circling, not converging. Adjudicate (see Playbook D).
|
|
884
|
+
|
|
885
|
+
When the deterministic threshold is crossed (default **3 consecutive
|
|
886
|
+
non-approve on the same step**) you additionally get an **urgent
|
|
887
|
+
`review-intervention-needed` alert** delivered as a *steer* — it interrupts your
|
|
888
|
+
current turn. Act on it per Playbook D. `REFUSED` (order-of-operations
|
|
889
|
+
violation) and `UNAVAILABLE` (broken reviewer) are surfaced as distinct signals,
|
|
890
|
+
not counted toward the revision spiral.
|
|
891
|
+
|
|
797
892
|
---
|
|
798
893
|
|
|
799
894
|
## 13b. Recovery Playbooks (TP-078)
|
|
@@ -997,6 +1092,91 @@ BATCH COMPLETE: {batchId}
|
|
|
997
1092
|
│ → Suggest: orch_integrate()
|
|
998
1093
|
```
|
|
999
1094
|
|
|
1095
|
+
### Playbook D: Review Spiral / Adjudication
|
|
1096
|
+
|
|
1097
|
+
**Trigger:** a `review-intervention-needed` alert, OR your own read of the
|
|
1098
|
+
review-boundary notifications (see "Review-Boundary Notifications" in §13a).
|
|
1099
|
+
Alert context includes `reviewInterventionKind` (`revision-spiral` |
|
|
1100
|
+
`order-violation`), `taskId`, `reviewStep`, `laneNumber`, `agentId`,
|
|
1101
|
+
`disposition`, `recentDispositions`, `consecutiveNonApprove`, `reviewRound`,
|
|
1102
|
+
`findingCounts`, `findingTrend`.
|
|
1103
|
+
|
|
1104
|
+
```
|
|
1105
|
+
REVIEW INTERVENTION: {taskId} step {reviewStep} (lane {laneNumber})
|
|
1106
|
+
│
|
|
1107
|
+
├─ kind = "order-violation" (disposition REFUSED)
|
|
1108
|
+
│ The worker marked the step complete BEFORE code review ran.
|
|
1109
|
+
│ → Steer the worker (send_agent_message to {agentId}) to:
|
|
1110
|
+
│ 1. Revert the step's Status to In Progress in STATUS.md
|
|
1111
|
+
│ 2. Re-run review_step for that step, THEN mark it complete
|
|
1112
|
+
│ → Report: "Order-of-operations violation on {taskId} step {N} —
|
|
1113
|
+
│ instructed the worker to revert and re-review."
|
|
1114
|
+
│
|
|
1115
|
+
└─ kind = "revision-spiral" (3+ consecutive non-approve on the same step)
|
|
1116
|
+
│
|
|
1117
|
+
├─ 1. Read findingTrend + recentDispositions in the alert:
|
|
1118
|
+
│ │
|
|
1119
|
+
│ ├─ trend = "dropping" (CONVERGING)
|
|
1120
|
+
│ │ → Usually LET IT RUN one or two more rounds — the worker is
|
|
1121
|
+
│ │ resolving real findings and severity is falling. Only step in
|
|
1122
|
+
│ │ if the round count is very high (diminishing returns).
|
|
1123
|
+
│ │
|
|
1124
|
+
│ └─ trend = "flat" / "rising" (CIRCLING)
|
|
1125
|
+
│ │
|
|
1126
|
+
│ ├─ 2. Read the latest review file
|
|
1127
|
+
│ │ (.reviews/R{NNN}-{type}-step{N}.md) and the worker's
|
|
1128
|
+
│ │ STATUS.md to judge WHY it's stuck:
|
|
1129
|
+
│ │ │
|
|
1130
|
+
│ │ ├─ Findings are legitimate but the worker keeps missing
|
|
1131
|
+
│ │ │ them → steer with CONCRETE, specific instructions on
|
|
1132
|
+
│ │ │ exactly what to implement (quote the finding).
|
|
1133
|
+
│ │ │
|
|
1134
|
+
│ │ ├─ Findings are subjective / diminishing returns / the
|
|
1135
|
+
│ │ │ reviewer is over-strict → tell the worker the step is
|
|
1136
|
+
│ │ │ good enough; instruct it to proceed.
|
|
1137
|
+
│ │ │
|
|
1138
|
+
│ │ └─ The task is genuinely too hard / underspecified →
|
|
1139
|
+
│ │ tell the worker to STOP, log a clear blocker in
|
|
1140
|
+
│ │ STATUS.md, and exit. Then escalate to the operator
|
|
1141
|
+
│ │ with the blocker text.
|
|
1142
|
+
│ │
|
|
1143
|
+
│ └─ 3. Report your decision AND why (cite the trend + round,
|
|
1144
|
+
│ e.g. "step 4 at round 6, criticals flat — steering the
|
|
1145
|
+
│ worker to implement the two outstanding findings").
|
|
1146
|
+
```
|
|
1147
|
+
|
|
1148
|
+
**Worker on HOLD for a ruling (#630 Tier-1 contract).** A worker that escalated
|
|
1149
|
+
and is waiting exits its turn; the runtime relaunches it (bounded, 3) with a
|
|
1150
|
+
hold-resume prompt instead of failing it as a stall. Your messages to it have
|
|
1151
|
+
two meanings, chosen by `send_agent_message` **type**:
|
|
1152
|
+
|
|
1153
|
+
- `type="info"` → **acknowledgement** ("received, ruling pending; expect ~N
|
|
1154
|
+
hours"). The worker stays on hold; its relaunch budget resets. Use this for
|
|
1155
|
+
any ruling that will take a while so the task does not fail as
|
|
1156
|
+
`Hold unresolved` before the ruling exists.
|
|
1157
|
+
- `type="steer"` (default) → **the ruling / instruction**. Releases the hold;
|
|
1158
|
+
the worker acts on it.
|
|
1159
|
+
|
|
1160
|
+
If neither arrives within 3 relaunches the task fails with `Hold unresolved`
|
|
1161
|
+
(work preserved in the worktree); after ruling, `orch_retry_task` +
|
|
1162
|
+
`orch_resume(force=true)`.
|
|
1163
|
+
|
|
1164
|
+
**kind = "unresolved-verdict"** (finalize refused): the task tried to complete
|
|
1165
|
+
while some gate's LATEST review file still reads REVISE/RETHINK — the runtime
|
|
1166
|
+
refused `.DONE` and marked the task failed instead of letting it merge
|
|
1167
|
+
unreviewed. Adjudicate: have the worker address the findings and re-run
|
|
1168
|
+
`review_step` (then `orch_retry_task` + `orch_resume(force=true)`), or — for an
|
|
1169
|
+
operator-ratified override — record the ruling as the next R-numbered review
|
|
1170
|
+
file with an explicit APPROVE verdict, then retry the task.
|
|
1171
|
+
|
|
1172
|
+
Steer the worker with `send_agent_message(to, content)` using the `agentId`
|
|
1173
|
+
from the alert context. **Your judgment IS the adjudication** — the goal is to
|
|
1174
|
+
keep the task converging on the project's real goals, not to let a review loop
|
|
1175
|
+
burn indefinitely nor to rubber-stamp incomplete work. Log the decision to the
|
|
1176
|
+
audit trail. Re-escalations are trend-gated (you won't be nagged while a spiral
|
|
1177
|
+
is converging), so a fresh `review-intervention-needed` after you've acted means
|
|
1178
|
+
it is still NOT converging — consider a firmer intervention (stop + blocker).
|
|
1179
|
+
|
|
1000
1180
|
### Quick Reference: Recovery Action Matrix
|
|
1001
1181
|
|
|
1002
1182
|
| Alert | Diagnosis | Action | Autonomy |
|