Skip to content

Commit ee1397d

Browse files
committed
Add --min-round flag to wait-for-state for Evaluator catchup
Bug: Evaluator used --round N+1 (exact equality) to watch for the next Generator round. Under the never-stop Generator mode (set by Feedback-06 workflows), the Generator can race past N+1 into N+2 or higher between the Evaluator's wake events — the round-field check sees the current state after wake, which is already past N+1, so the watcher keeps waiting forever even though plenty of rounds are ready to evaluate. Fix: add --min-round N flag with >= semantic. Evaluator now uses --min-round N+1, which fires as soon as round >= N+1 AND the other conditions match. Generator still uses --round N (exact) because a Generator's post-round watcher genuinely wants "the Evaluator verdict for MY round", not any later one. The two flags are mutually exclusive. Also adds catchup guidance in the Evaluator skill for when many rounds are missed at once (cumulative evaluation vs. per-round sweep), bumps plugin version to 1.0.8.
1 parent 26fbe6e commit ee1397d

3 files changed

Lines changed: 45 additions & 6 deletions

File tree

.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": "tandemkit",
3-
"version": "1.0.7",
3+
"version": "1.0.8",
44
"description": "Describe your goal, approve the spec, then step away — Claude and Codex loop together until it's right.",
55
"author": {
66
"name": "Cihat Gündüz",

scripts/wait-for-state.sh

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,16 @@ unset _TC _NV
1010
# TandemKit — wait-for-state
1111
# Blocks until a State.json field matches an expected value (or any change occurs).
1212
#
13-
# Usage: wait-for-state.sh <mission-folder> [<field> <value1> [value2 ...]] [--quiet] [--round N]
13+
# Usage: wait-for-state.sh <mission-folder> [<field> <value1> [value2 ...]] [--quiet] [--round N] [--min-round N]
1414
# mission-folder: absolute path to the TandemKit/NNN-MissionName/ folder
1515
# field: JSON field to check (e.g., evaluatorStatus, generatorStatus)
1616
# value1..N: acceptable values — script exits when field matches any of them
1717
# --quiet: suppress all output except final READY line (for Codex)
1818
# --round N: also require State.json "round" field to equal N exactly
19+
# --min-round N: also require State.json "round" field to be >= N (catches up if the
20+
# other agent raced ahead past N between wake events). Use this from the
21+
# Evaluator side when the Generator runs autonomously (Feedback-06 mode).
22+
# Mutually exclusive with --round.
1923
#
2024
# If no field/values given: blocks until State.json content changes (any field).
2125
#
@@ -25,19 +29,26 @@ unset _TC _NV
2529
MISSION_DIR="$1"
2630
shift
2731

28-
# Check for --quiet and --round flags (can appear anywhere in remaining args)
32+
# Check for --quiet, --round, --min-round flags (can appear anywhere in remaining args)
2933
QUIET=false
3034
REQUIRED_ROUND=""
35+
MIN_ROUND=""
3136
_ARGS=()
3237
while [[ $# -gt 0 ]]; do
3338
case "$1" in
3439
--quiet) QUIET=true; shift ;;
3540
--round) REQUIRED_ROUND="$2"; shift 2 ;;
41+
--min-round) MIN_ROUND="$2"; shift 2 ;;
3642
*) _ARGS+=("$1"); shift ;;
3743
esac
3844
done
3945
set -- "${_ARGS[@]+"${_ARGS[@]}"}"
4046

47+
if [[ -n "$REQUIRED_ROUND" && -n "$MIN_ROUND" ]]; then
48+
echo "ERROR: --round and --min-round are mutually exclusive" >&2
49+
exit 1
50+
fi
51+
4152
STATE_FILE="$MISSION_DIR/State.json"
4253

4354
if [[ ! -d "$MISSION_DIR" ]]; then
@@ -108,6 +119,20 @@ check_condition() {
108119
fi
109120
fi
110121

122+
# Check min-round constraint (>=). Useful when the other agent may race past N
123+
# between our wake events — fires as soon as round has reached N or later.
124+
if [[ -n "$MIN_ROUND" ]]; then
125+
local actual_round
126+
actual_round=$(read_round)
127+
# Numeric comparison; guard against non-integer output by defaulting to 0.
128+
if ! [[ "$actual_round" =~ ^[0-9]+$ ]]; then
129+
actual_round=0
130+
fi
131+
if (( actual_round < MIN_ROUND )); then
132+
return 1
133+
fi
134+
fi
135+
111136
local current
112137
current=$(read_field)
113138

@@ -143,7 +168,10 @@ INITIAL_HASH=$(file_hash)
143168
# Not ready — enter watch loop
144169
if [[ "$QUIET" != true ]]; then
145170
if [[ -n "$FIELD" ]]; then
146-
echo "WAITING: $FIELD not yet ${VALUES[*]:-non-null}. Watching $STATE_FILE..."
171+
_round_note=""
172+
[[ -n "$REQUIRED_ROUND" ]] && _round_note=" (round == $REQUIRED_ROUND)"
173+
[[ -n "$MIN_ROUND" ]] && _round_note=" (round >= $MIN_ROUND)"
174+
echo "WAITING: $FIELD not yet ${VALUES[*]:-non-null}$_round_note. Watching $STATE_FILE..."
147175
else
148176
echo "WAITING: Watching $STATE_FILE for any change..."
149177
fi

skills/evaluator/SKILL.md

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -253,11 +253,13 @@ The user invokes this skill with `/tandemkit:evaluator NNN-MissionName`. First r
253253
254254
After writing your verdict, IMMEDIATELY start TWO background watchers:
255255
256-
1. **Next round watcher:**
256+
1. **Next round watcher (use `--min-round`, NOT `--round`):**
257257
```bash
258-
bash "$HOME/.claude/plugins/cache/FlineDev/tandemkit/latest/scripts/wait-for-state.sh" "$(pwd)/TandemKit/NNN-MissionName" generatorStatus ready-for-eval --round N+1
258+
bash "$HOME/.claude/plugins/cache/FlineDev/tandemkit/latest/scripts/wait-for-state.sh" "$(pwd)/TandemKit/NNN-MissionName" generatorStatus ready-for-eval --min-round N+1
259259
```
260260
261+
Why `--min-round` and not `--round`: if the Generator is running autonomously (never-stop mode), it may advance past round N+1 while you are still writing your verdict. `--round N+1` is exact-match and would miss a Generator that already raced to round N+2 or N+3 — the condition window on round=N+1 may only have existed for milliseconds between the Generator's writes. `--min-round N+1` fires as soon as the Generator has reached at least N+1 AND is in `ready-for-eval`, which is the semantic you actually want: "wake me up when there is at least one new round to evaluate".
262+
261263
2. **Completion watcher:**
262264
```bash
263265
bash "$HOME/.claude/plugins/cache/FlineDev/tandemkit/latest/scripts/wait-for-state.sh" "$(pwd)/TandemKit/NNN-MissionName" phase complete
@@ -269,6 +271,15 @@ Run both with `run_in_background: true`. When either returns:
269271
270272
If a watch times out (10 minutes), re-read State.json and restart the watchers. NEVER go idle.
271273
274+
### Catchup case — Evaluator has fallen behind
275+
276+
If the watcher fires and State.json reports `round` is **more than one ahead** of your last-evaluated round (e.g. you last evaluated round 4 and the Generator is now at round 11), you are in a catchup situation. The Generator's per-round artifacts (`Generator/Round-NN.md`, `Generator/ChangedFiles-NN.txt`) are persistent, so no evaluation work has been lost — but you need to decide between two strategies:
277+
278+
- **Per-round sweep (preferred when the number of missed rounds is small, say ≤ 3):** evaluate each missed round individually in order (Round-NN-Discussion/Claude-01.md → Round-NN.md), bump State.json `round` appropriately after each. Accurate but slow.
279+
- **Cumulative catchup evaluation (preferred when many rounds have been missed):** write ONE evaluation at the current round targeting the cumulative delta since your last PASS, covering all intermediate milestones and feedbacks in one report. Note the catchup mode explicitly (e.g. set a `catchupEvaluation` field in State.json) so Planner/user can see why rounds 5..N-1 don't have individual Evaluator files. Faster; loses per-round granularity.
280+
281+
Never silently skip rounds — either evaluate them or record that you did a cumulative pass. The verdict in State.json reflects the catchup round's verdict.
282+
272283
════════════════════════════════════════
273284
→ Verdict delivered — Watching for next round
274285
════════════════════════════════════════

0 commit comments

Comments
 (0)