Skip to content

Commit 67a412c

Browse files
committed
Remove round filter from wait-for-state entirely
Follow-up to 1.0.8's --min-round patch. Investigation with jeehut@fline.dev (DojoKame author) concluded that the round filter adds no safety at all — both status-field resets already prevent any stale match: - Generator signalling ready-for-eval writes evaluatorStatus=pending. So a later evaluatorStatus==done can only be THIS round's verdict. - Evaluator's verdict write does not touch generatorStatus (it stays at ready-for-eval). When the Generator starts its next round it writes generatorStatus=working first, then ready-for-eval for the new round. So the next generatorStatus==ready-for-eval after an Evaluator verdict is always a NEW round's signal. With the resets, field-value watchers are race-free without a round gate. Keeping a round gate on top only surfaces exact-match or stale-read bugs of the kind 1.0.8 was trying to work around. Drops `--round` and `--min-round` flags. Using either now prints an error pointing at the new semantics. Skill docs updated for both Generator (post-round) and Evaluator (next-round + completion) watchers — all three invocations simplify to the same pattern `wait-for-state.sh MISSION FIELD VALUE`. Bumps version to 1.0.9.
1 parent ee1397d commit 67a412c

4 files changed

Lines changed: 29 additions & 59 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.8",
3+
"version": "1.0.9",
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: 15 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -10,45 +10,43 @@ 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] [--min-round N]
13+
# Usage: wait-for-state.sh <mission-folder> [<field> <value1> [value2 ...]] [--quiet]
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)
18-
# --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.
2318
#
2419
# If no field/values given: blocks until State.json content changes (any field).
2520
#
21+
# NOTE: There is no round filter. Both agents reset the other side's status field
22+
# at the start of each round (Generator writes evaluatorStatus=pending when
23+
# signalling ready-for-eval; Evaluator's verdict write keeps generatorStatus=
24+
# ready-for-eval but the next round-start flips generatorStatus=working first),
25+
# so a field-value match can never fire on a stale value from a previous round.
26+
# Dropping the round filter removes an exact-match race condition that used to
27+
# hang watchers when the counterparty raced ahead past the expected round number.
28+
#
2629
# Exit 0 = condition met. Output includes current state summary.
2730
# Exit 1 = error (missing files, bad args).
2831

2932
MISSION_DIR="$1"
3033
shift
3134

32-
# Check for --quiet, --round, --min-round flags (can appear anywhere in remaining args)
35+
# Check for --quiet flag (can appear anywhere in remaining args).
3336
QUIET=false
34-
REQUIRED_ROUND=""
35-
MIN_ROUND=""
3637
_ARGS=()
3738
while [[ $# -gt 0 ]]; do
3839
case "$1" in
3940
--quiet) QUIET=true; shift ;;
40-
--round) REQUIRED_ROUND="$2"; shift 2 ;;
41-
--min-round) MIN_ROUND="$2"; shift 2 ;;
41+
--round|--min-round)
42+
echo "ERROR: $1 has been removed in TandemKit 1.0.9 — watchers now rely on status-field resets, not round numbers. Drop this flag." >&2
43+
exit 1
44+
;;
4245
*) _ARGS+=("$1"); shift ;;
4346
esac
4447
done
4548
set -- "${_ARGS[@]+"${_ARGS[@]}"}"
4649

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-
5250
STATE_FILE="$MISSION_DIR/State.json"
5351

5452
if [[ ! -d "$MISSION_DIR" ]]; then
@@ -91,14 +89,6 @@ with open('$STATE_FILE') as f:
9189
" 2>/dev/null || echo "unknown"
9290
}
9391

94-
read_round() {
95-
python3 -c "
96-
import json
97-
with open('$STATE_FILE') as f:
98-
print(json.load(f).get('round', 0))
99-
" 2>/dev/null || echo "0"
100-
}
101-
10292
file_hash() {
10393
md5 -q "$STATE_FILE" 2>/dev/null || md5sum "$STATE_FILE" 2>/dev/null | cut -d' ' -f1 || echo "none"
10494
}
@@ -110,29 +100,6 @@ check_condition() {
110100
return 1
111101
fi
112102

113-
# Check round constraint first (if specified)
114-
if [[ -n "$REQUIRED_ROUND" ]]; then
115-
local actual_round
116-
actual_round=$(read_round)
117-
if [[ "$actual_round" != "$REQUIRED_ROUND" ]]; then
118-
return 1
119-
fi
120-
fi
121-
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-
136103
local current
137104
current=$(read_field)
138105

@@ -168,10 +135,7 @@ INITIAL_HASH=$(file_hash)
168135
# Not ready — enter watch loop
169136
if [[ "$QUIET" != true ]]; then
170137
if [[ -n "$FIELD" ]]; then
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..."
138+
echo "WAITING: $FIELD not yet ${VALUES[*]:-non-null}. Watching $STATE_FILE..."
175139
else
176140
echo "WAITING: Watching $STATE_FILE for any change..."
177141
fi

skills/evaluator/SKILL.md

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -253,12 +253,18 @@ 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 (use `--min-round`, NOT `--round`):**
256+
1. **Next round watcher:**
257257
```bash
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
258+
bash "$HOME/.claude/plugins/cache/FlineDev/tandemkit/latest/scripts/wait-for-state.sh" "$(pwd)/TandemKit/NNN-MissionName" generatorStatus ready-for-eval
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".
261+
No round filter is needed. Your verdict write leaves `generatorStatus` at its
262+
value from the Generator's ready-for-eval signal; the Generator's next round-
263+
start flips it to `"working"` first (or sometimes straight to `"ready-for-eval"`
264+
again for the new round). Either way, the next time `generatorStatus ==
265+
ready-for-eval` is the next round's signal, regardless of how many rounds the
266+
Generator has burned through since your last wake. Read `round` from
267+
`State.json` at wake time to know which `Round-NN.md` to evaluate.
262268
263269
2. **Completion watcher:**
264270
```bash

skills/generator/SKILL.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,9 @@ The user invokes this skill with `/tandemkit:generator NNN-MissionName`. First r
8181
6. **Signal the Evaluator**: Update State.json — `generatorStatus: "ready-for-eval"`, `evaluatorStatus: "pending"`, `phase: "evaluation"`, `round: N`. Read-modify-write only your fields.
8282
7. **Wait for evaluation**: Use `wait-for-state.sh`:
8383
```bash
84-
bash "$HOME/.claude/plugins/cache/FlineDev/tandemkit/latest/scripts/wait-for-state.sh" "$(pwd)/TandemKit/NNN-MissionName" evaluatorStatus done --round N
84+
bash "$HOME/.claude/plugins/cache/FlineDev/tandemkit/latest/scripts/wait-for-state.sh" "$(pwd)/TandemKit/NNN-MissionName" evaluatorStatus done
8585
```
86-
Run with `run_in_background: true`. When `evaluatorStatus` is `"done"` and round matches, read `Evaluator/Round-NN.md`.
86+
Run with `run_in_background: true`. When `evaluatorStatus` flips to `"done"`, read `Evaluator/Round-NN.md` where `N` is whatever `round` is in State.json at that moment. No round filter is needed — step 6 reset `evaluatorStatus` to `"pending"` when signalling, so the next `"done"` is always your round's verdict.
8787

8888
════════════════════════════════════════
8989
→ DONE — Waiting for Evaluator
@@ -213,10 +213,10 @@ If the user says "abort": confirm, set State.json `phase: "abandoned"`, Config.j
213213
Use `wait-for-state.sh` for ALL State.json watching. Do NOT use raw watchman-wait.
214214

215215
```bash
216-
bash "$HOME/.claude/plugins/cache/FlineDev/tandemkit/latest/scripts/wait-for-state.sh" "$(pwd)/TandemKit/NNN-MissionName" evaluatorStatus done --round N
216+
bash "$HOME/.claude/plugins/cache/FlineDev/tandemkit/latest/scripts/wait-for-state.sh" "$(pwd)/TandemKit/NNN-MissionName" evaluatorStatus done
217217
```
218218

219-
Run with `run_in_background: true`. The script checks immediately, then enters a watch loop. When it prints "READY", re-read State.json. The `--round N` parameter ensures you don't match stale values from a previous round.
219+
Run with `run_in_background: true`. The script checks immediately, then enters a watch loop. When it prints "READY", re-read State.json. No round filter — the status-field reset at step 6 guarantees the next `done` is your round's verdict.
220220
221221
## File Reading Limits
222222

0 commit comments

Comments
 (0)