You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(skills): journal-source harvest read-path to survive the task-store drain
Re-diagnose-first verification spike: harvest was already drain-survivable (the session journal is session_id-scoped under pact-sessions/, the drained task store is team_name-scoped under tasks/). Re-point Step 10's variety read to the journal (filtered by feature task_id + latest-ts, KeyError-safe via resolve_variety_total), and retire the born-dead revision_number>1 harvest read-branch that routed revision-harvest to the GC-vulnerable metadata.handoff instead of the drain-proof journal. Correct the agent-teams revision-visibility instruction to the accurate lead-only-completion model; keep the revision_number field (rejection-record / META-BLOCK trail). Adds a non-vacuous drain-survival regression suite. PATCH 4.4.38.
Completes ACs of #995 sub-1 (journal-sourced harvest survives the drain). Parent #995 stays open (sub-2 is an accepted lead-process-only teammate-frame boundary). Follow-up observation tracked in #1017.
Copy file name to clipboardExpand all lines: pact-plugin/skills/pact-agent-teams/SKILL.md
+2-2Lines changed: 2 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -194,12 +194,12 @@ If the team-lead rejects your teachback or HANDOFF, you wake on the inbound Send
194
194
3.**Revise**. For teachback rejection: rewrite `metadata.teachback_submit` per the corrections. For HANDOFF rejection: revise the deliverable (re-edit files, re-run tests, etc.) and rewrite `metadata.handoff`.
195
195
196
196
4.**Re-submit on the SAME task** (do NOT create a new task):
197
-
- Increment `metadata.revision_number`. The team-lead writes `revision_number=1` in the rejection record. On your first revision, increment to `2`. On each subsequent revision, increment again. The harvest path reads `metadata.handoff` directly when `revision_number > 1` to surface revised content; setting `revision_number=1` would route harvest to the rejected journal event and silently lose the revised content.
197
+
- Increment `metadata.revision_number`. The team-lead writes `revision_number=1` in the rejection record. On your first revision, increment to `2`. On each subsequent revision, increment again. This count is the rejection-cycle audit trail — it feeds the imPACT META-BLOCK 3-cycle signal, not harvest routing. It does NOT gate whether your revised content is preserved: the team-lead's acceptance (the single completion) emits whatever `metadata.handoff` holds at that moment, so the revised content reaches the journal regardless of the count.
198
198
- SendMessage the team-lead: `"[{sender}→team-lead] Revised teachback/HANDOFF on Task #{id}. See metadata.{teachback_submit|handoff} (revision {N})."`
> **Revision visibility**: on revision (`revision_number > 1`), the journal `agent_handoff` event from your *first* completion is preserved (one event per task lifetime). The secretary's harvest path reads `metadata.handoff`directly when `revision_number > 1`, so your revised content reaches institutional memory. The metadata write is sufficient.
202
+
> **Revision visibility**: your revised content reaches institutional memory because of *when*the journal event is emitted, not because of `revision_number`. A rejection keeps your task `in_progress` and emits nothing; the only `agent_handoff` journal event fires at the team-lead's single completion — their acceptance — and captures whatever `metadata.handoff`holds at that moment, i.e. your revised content. So the journal carries the accepted, revised HANDOFF, and harvest reads it from there (drain-proof). Your job on revision is simply to rewrite `metadata.handoff` before the lead accepts.
Copy file name to clipboardExpand all lines: pact-plugin/skills/pact-handoff-harvest/SKILL.md
+10-14Lines changed: 10 additions & 14 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -41,26 +41,22 @@ Read your processed task list from your team's section in agent memory (`~/.clau
41
41
42
42
### Step 3: Read All HANDOFFs
43
43
44
-
For each discovered task, read the HANDOFF using this revision-aware fallback:
44
+
For each discovered task, read the HANDOFF using this journal-first fallback:
45
45
46
-
1.**Revision-aware metadata read**: Read the task's `metadata` (raw JSON; `TaskGet` is metadata-blind for `handoff` content). If `metadata.revision_number` is set and `> 1`, prefer `metadata.handoff` over the journal event. The journal `agent_handoff` event captured the FIRST (rejected) submission only — `agent_handoff_emitter.py` writes one journal event per task lifetime via an O_EXCL marker. On revision, the team-lead-completion of the revised task does NOT emit a second journal event, so the revised content lives in `metadata.handoff` only.
47
-
2.**Session journal** (preferred for revision_number == 1 or unset, GC-proof): If the task was discovered via `agent_handoff` journal events and `revision_number` is unset or 1, the journal event's `handoff` field contains the full HANDOFF content inline — use it directly. This is the most reliable source for first-pass acceptance flows.
48
-
3.**`TaskGet` fallback**: If both above fail (no journal event AND no `metadata.handoff`), fall back to `TaskGet(taskId).metadata.handoff`. May fail for garbage-collected tasks.
49
-
4.**Report gap**: If all sources fail, report the gap to lead — note the task_id, agent name, and timestamp so the team-lead has context.
46
+
1.**Session journal** (preferred, GC-proof): If the task was discovered via `agent_handoff` journal events, the journal event's `handoff` field contains the full HANDOFF content inline — use it directly. The journal carries the **accepted** HANDOFF for every completed task: the team-lead's single completion (acceptance) emits whatever `metadata.handoff` holds at that moment, so on a reject→revise→accept flow the journal event holds the revised content the lead accepted. This is the most reliable source for all completed tasks, first-pass and revised alike.
47
+
2.**`TaskGet` fallback**: If there is no journal event for the task, fall back to `TaskGet(taskId).metadata.handoff` (read the raw `metadata` JSON; `TaskGet` is metadata-blind for `handoff` content). May fail for garbage-collected tasks.
48
+
3.**Report gap**: If all sources fail, report the gap to lead — note the task_id, agent name, and timestamp so the team-lead has context.
50
49
51
-
Pseudocode for the revision-aware branch:
50
+
Pseudocode for the journal-first read:
52
51
53
52
```python
54
53
for task_id in unprocessed:
55
54
journal_event =next((e for e in journal_events if e.task_id == task_id), None)
56
-
task_meta = read_task_metadata(task_id) or {} # raw JSON read; TaskGet is metadata-blind
57
-
revision_n = task_meta.get("revision_number", 1)
58
-
if revision_n >1:
59
-
# Revised HANDOFF; journal event captured only the first (rejected) submission.
# No journal event — last-resort metadata read (may be GC'd).
59
+
task_meta = read_task_metadata(task_id) or {} # raw JSON; TaskGet is metadata-blind
64
60
handoff = task_meta.get("handoff")
65
61
# ...process handoff...
66
62
```
@@ -160,7 +156,7 @@ Gaps: {any HANDOFFs that were thin or missing}",
160
156
### Step 10: Gather Calibration Data
161
157
162
158
After processing HANDOFFs, gather calibration metrics for the orchestrator's variety scoring feedback loop:
163
-
- Read the feature task metadata for `initial_variety_score` (stored during variety assessment). If `TaskGet` fails (garbage-collected), ask the team-lead for the variety score instead.
159
+
- Read `initial_variety_score` from the journal's `variety_assessed` event (GC-proof, survives the task-store drain): `python3 -c "import sys; sys.path.insert(0, '{hooks_dir}'); from shared.session_journal import read_events; import json; [print(json.dumps(e)) for e in read_events('variety_assessed')]"`. **Select the event for THIS feature** — `variety_assessed` events carry a `task_id`, and a resumed/multi-feature session holds one per feature (plus, because the platform reuses task_ids across arcs, the current feature's id can match a PRIOR arc too). So do NOT take the first event: filter to events whose `task_id` matches the feature task being harvested and take the **latest-`ts`** match — the `resolve_arc_start(events, feature_task_id)` semantics the wrap-up retrospective uses (`shared/variety_divergence.resolve_arc_start` is the canonical implementation). Then resolve the scalar total from that event's `variety` dict via the pure `resolve_variety_total(variety)` helper (`shared/teachback_schema.py`) rather than indexing `variety['total']` directly — it prefers the canonical `total` key, falls through a documented fallback chain, and returns `None` instead of raising `KeyError` if the dict is malformed or `total` is missing. If no `variety_assessed` event matches this feature (e.g., a feature dispatched without a variety emit), or `resolve_variety_total` returns `None`, ask the team-lead for the variety score instead.
164
160
- Scan `TaskList` for blocker count (tasks with "BLOCKER:" in subject). Note: `TaskList` may be incomplete in long sessions due to garbage collection — report what's available.
165
161
- Scan `TaskList` for phase rerun count (retry/redo phase tasks)
0 commit comments