@chllming/wave-orchestration 0.8.5 → 0.8.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/CHANGELOG.md +24 -0
- package/README.md +14 -9
- package/docs/README.md +3 -1
- package/docs/context7/bundles.json +19 -20
- package/docs/context7/planner-agent/README.md +4 -1
- package/docs/guides/author-and-run-waves.md +4 -1
- package/docs/guides/planner.md +3 -1
- package/docs/guides/signal-wrappers.md +165 -0
- package/docs/guides/terminal-surfaces.md +13 -0
- package/docs/plans/context7-wave-orchestrator.md +24 -7
- package/docs/plans/current-state.md +7 -3
- package/docs/plans/end-state-architecture.md +16 -4
- package/docs/plans/examples/wave-example-design-handoff.md +1 -1
- package/docs/plans/examples/wave-example-live-proof.md +1 -1
- package/docs/plans/migration.md +165 -72
- package/docs/plans/wave-orchestrator.md +3 -0
- package/docs/reference/cli-reference.md +11 -2
- package/docs/reference/npmjs-trusted-publishing.md +2 -2
- package/docs/reference/sample-waves.md +5 -5
- package/docs/reference/skills.md +9 -1
- package/docs/reference/wave-control.md +2 -0
- package/package.json +1 -1
- package/releases/manifest.json +19 -0
- package/scripts/context7-api-check.sh +57 -13
- package/scripts/wave-orchestrator/control-cli.mjs +19 -0
- package/scripts/wave-orchestrator/coordination.mjs +35 -0
- package/scripts/wave-orchestrator/install.mjs +2 -0
- package/scripts/wave-orchestrator/launcher-runtime.mjs +11 -0
- package/scripts/wave-orchestrator/launcher.mjs +20 -0
- package/scripts/wave-orchestrator/session-supervisor.mjs +113 -0
- package/scripts/wave-orchestrator/shared.mjs +1 -0
- package/scripts/wave-orchestrator/signals.mjs +681 -0
- package/scripts/wave-orchestrator/wave-control-schema.mjs +2 -0
- package/scripts/wave-status.sh +200 -0
- package/scripts/wave-watch.sh +200 -0
- package/skills/README.md +3 -0
- package/skills/signal-hygiene/SKILL.md +51 -0
- package/skills/signal-hygiene/skill.json +20 -0
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
lane="main"
|
|
5
|
+
wave=""
|
|
6
|
+
agent=""
|
|
7
|
+
run_id=""
|
|
8
|
+
dry_run="0"
|
|
9
|
+
json_output="0"
|
|
10
|
+
|
|
11
|
+
usage() {
|
|
12
|
+
cat <<'EOF'
|
|
13
|
+
Usage:
|
|
14
|
+
scripts/wave-status.sh [--lane <lane>] [--wave <n>] [--agent <id>] [--run <id>] [--dry-run] [--json]
|
|
15
|
+
|
|
16
|
+
Exit codes:
|
|
17
|
+
0 completed
|
|
18
|
+
10 waiting or running
|
|
19
|
+
20 input required
|
|
20
|
+
40 failed
|
|
21
|
+
EOF
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
while [ "$#" -gt 0 ]; do
|
|
25
|
+
case "$1" in
|
|
26
|
+
--lane)
|
|
27
|
+
lane="${2:-}"
|
|
28
|
+
shift 2
|
|
29
|
+
;;
|
|
30
|
+
--wave)
|
|
31
|
+
wave="${2:-}"
|
|
32
|
+
shift 2
|
|
33
|
+
;;
|
|
34
|
+
--agent)
|
|
35
|
+
agent="${2:-}"
|
|
36
|
+
shift 2
|
|
37
|
+
;;
|
|
38
|
+
--run)
|
|
39
|
+
run_id="${2:-}"
|
|
40
|
+
shift 2
|
|
41
|
+
;;
|
|
42
|
+
--dry-run)
|
|
43
|
+
dry_run="1"
|
|
44
|
+
shift
|
|
45
|
+
;;
|
|
46
|
+
--json)
|
|
47
|
+
json_output="1"
|
|
48
|
+
shift
|
|
49
|
+
;;
|
|
50
|
+
--help|-h)
|
|
51
|
+
usage
|
|
52
|
+
exit 0
|
|
53
|
+
;;
|
|
54
|
+
*)
|
|
55
|
+
echo "Unknown argument: $1" >&2
|
|
56
|
+
usage >&2
|
|
57
|
+
exit 2
|
|
58
|
+
;;
|
|
59
|
+
esac
|
|
60
|
+
done
|
|
61
|
+
|
|
62
|
+
run_wave_cli() {
|
|
63
|
+
if [ -n "${WAVE_WRAPPER_ENTRY:-}" ]; then
|
|
64
|
+
node "$WAVE_WRAPPER_ENTRY" "$@"
|
|
65
|
+
return
|
|
66
|
+
fi
|
|
67
|
+
if [ -f "scripts/wave.mjs" ]; then
|
|
68
|
+
node "scripts/wave.mjs" "$@"
|
|
69
|
+
return
|
|
70
|
+
fi
|
|
71
|
+
if [ -f "node_modules/@chllming/wave-orchestration/scripts/wave.mjs" ]; then
|
|
72
|
+
node "node_modules/@chllming/wave-orchestration/scripts/wave.mjs" "$@"
|
|
73
|
+
return
|
|
74
|
+
fi
|
|
75
|
+
if command -v pnpm >/dev/null 2>&1; then
|
|
76
|
+
pnpm exec wave "$@"
|
|
77
|
+
return
|
|
78
|
+
fi
|
|
79
|
+
echo "Unable to locate Wave CLI. Set WAVE_WRAPPER_ENTRY or install the package locally." >&2
|
|
80
|
+
exit 2
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
infer_wave() {
|
|
84
|
+
node - "$lane" "$run_id" "$dry_run" <<'NODE'
|
|
85
|
+
const fs = require("node:fs");
|
|
86
|
+
const path = require("node:path");
|
|
87
|
+
|
|
88
|
+
const lane = process.argv[2] || "main";
|
|
89
|
+
const runId = process.argv[3] || "";
|
|
90
|
+
const dryRun = process.argv[4] === "1";
|
|
91
|
+
const stateDir = runId
|
|
92
|
+
? path.join(process.cwd(), ".tmp", `${lane}-wave-launcher`, "adhoc", runId, dryRun ? "dry-run" : "")
|
|
93
|
+
: path.join(process.cwd(), ".tmp", `${lane}-wave-launcher`, dryRun ? "dry-run" : "");
|
|
94
|
+
const runStatePath = path.join(stateDir, "run-state.json");
|
|
95
|
+
let payload = null;
|
|
96
|
+
try {
|
|
97
|
+
payload = JSON.parse(fs.readFileSync(runStatePath, "utf8"));
|
|
98
|
+
} catch {
|
|
99
|
+
payload = null;
|
|
100
|
+
}
|
|
101
|
+
const waves = Object.values(payload?.waves || {})
|
|
102
|
+
.filter((entry) => entry && typeof entry === "object")
|
|
103
|
+
.map((entry) => ({
|
|
104
|
+
wave: Number.parseInt(String(entry.wave ?? ""), 10),
|
|
105
|
+
state: String(entry.currentState || "").trim().toLowerCase(),
|
|
106
|
+
}))
|
|
107
|
+
.filter((entry) => Number.isFinite(entry.wave))
|
|
108
|
+
.sort((left, right) => left.wave - right.wave);
|
|
109
|
+
const active = waves.findLast(
|
|
110
|
+
(entry) => !["completed", "failed", "timed_out", "timed-out"].includes(entry.state),
|
|
111
|
+
);
|
|
112
|
+
if (active) {
|
|
113
|
+
process.stdout.write(String(active.wave));
|
|
114
|
+
process.exit(0);
|
|
115
|
+
}
|
|
116
|
+
const completed = Array.isArray(payload?.completedWaves)
|
|
117
|
+
? payload.completedWaves
|
|
118
|
+
.map((value) => Number.parseInt(String(value), 10))
|
|
119
|
+
.filter((value) => Number.isFinite(value))
|
|
120
|
+
.sort((left, right) => left - right)
|
|
121
|
+
: [];
|
|
122
|
+
process.stdout.write(String(completed.at(-1) ?? 0));
|
|
123
|
+
NODE
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if [ -z "$wave" ]; then
|
|
127
|
+
wave="$(infer_wave)"
|
|
128
|
+
fi
|
|
129
|
+
|
|
130
|
+
status_args=(control status --lane "$lane" --wave "$wave" --json)
|
|
131
|
+
if [ -n "$agent" ]; then
|
|
132
|
+
status_args+=(--agent "$agent")
|
|
133
|
+
fi
|
|
134
|
+
if [ -n "$run_id" ]; then
|
|
135
|
+
status_args+=(--run "$run_id")
|
|
136
|
+
fi
|
|
137
|
+
if [ "$dry_run" = "1" ]; then
|
|
138
|
+
status_args+=(--dry-run)
|
|
139
|
+
fi
|
|
140
|
+
|
|
141
|
+
payload="$(run_wave_cli "${status_args[@]}")"
|
|
142
|
+
|
|
143
|
+
if [ "$json_output" = "1" ]; then
|
|
144
|
+
printf '%s\n' "$payload"
|
|
145
|
+
exit 0
|
|
146
|
+
fi
|
|
147
|
+
|
|
148
|
+
PAYLOAD="$payload" node - "$lane" "$wave" "$agent" <<'NODE'
|
|
149
|
+
const fs = require("node:fs");
|
|
150
|
+
|
|
151
|
+
const lane = process.argv[2] || "main";
|
|
152
|
+
const wave = Number.parseInt(String(process.argv[3] || "0"), 10) || 0;
|
|
153
|
+
const agentId = String(process.argv[4] || "").trim();
|
|
154
|
+
const payload = JSON.parse(process.env.PAYLOAD || "{}");
|
|
155
|
+
const signals = payload?.signals || {};
|
|
156
|
+
const snapshot = agentId
|
|
157
|
+
? (Array.isArray(signals.agents) ? signals.agents.find((entry) => entry.agentId === agentId) : null)
|
|
158
|
+
: signals.wave;
|
|
159
|
+
const effective = snapshot || {
|
|
160
|
+
signal: payload?.blockingEdge?.kind === "human-input" ? "feedback-requested" : "waiting",
|
|
161
|
+
lane,
|
|
162
|
+
wave,
|
|
163
|
+
phase: payload?.phase || "unknown",
|
|
164
|
+
status: payload?.blockingEdge ? "blocked" : "running",
|
|
165
|
+
blocking: payload?.blockingEdge || null,
|
|
166
|
+
attempt: payload?.activeAttempt?.attemptNumber || 0,
|
|
167
|
+
targetAgentIds: agentId ? [agentId] : [],
|
|
168
|
+
shouldWake: agentId ? true : null,
|
|
169
|
+
version: 0,
|
|
170
|
+
};
|
|
171
|
+
const targetKey = agentId ? "agent" : "agents";
|
|
172
|
+
const targetValue = agentId || (effective.targetAgentIds || []).join(",") || "none";
|
|
173
|
+
const blocking = effective?.blocking?.kind || "none";
|
|
174
|
+
const shouldWake =
|
|
175
|
+
typeof effective.shouldWake === "boolean" ? (effective.shouldWake ? "yes" : "no") : "n/a";
|
|
176
|
+
console.log(
|
|
177
|
+
[
|
|
178
|
+
`signal=${effective.signal || "waiting"}`,
|
|
179
|
+
`lane=${lane}`,
|
|
180
|
+
`wave=${wave}`,
|
|
181
|
+
`phase=${effective.phase || "unknown"}`,
|
|
182
|
+
`status=${effective.status || "running"}`,
|
|
183
|
+
`blocking=${blocking}`,
|
|
184
|
+
`attempt=${effective.attempt || 0}`,
|
|
185
|
+
`${targetKey}=${targetValue}`,
|
|
186
|
+
`version=${effective.version || 0}`,
|
|
187
|
+
`should_wake=${shouldWake}`,
|
|
188
|
+
].join(" "),
|
|
189
|
+
);
|
|
190
|
+
if (String(effective.signal || "").trim().toLowerCase() === "completed") {
|
|
191
|
+
process.exit(0);
|
|
192
|
+
}
|
|
193
|
+
if (String(effective.signal || "").trim().toLowerCase() === "failed") {
|
|
194
|
+
process.exit(40);
|
|
195
|
+
}
|
|
196
|
+
if (String(effective.signal || "").trim().toLowerCase() === "feedback-requested") {
|
|
197
|
+
process.exit(20);
|
|
198
|
+
}
|
|
199
|
+
process.exit(10);
|
|
200
|
+
NODE
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
lane="main"
|
|
5
|
+
wave=""
|
|
6
|
+
agent=""
|
|
7
|
+
run_id=""
|
|
8
|
+
dry_run="0"
|
|
9
|
+
mode="follow"
|
|
10
|
+
refresh_ms="2000"
|
|
11
|
+
|
|
12
|
+
usage() {
|
|
13
|
+
cat <<'EOF'
|
|
14
|
+
Usage:
|
|
15
|
+
scripts/wave-watch.sh [--lane <lane>] [--wave <n>] [--agent <id>] [--run <id>] [--dry-run] [--refresh-ms <n>] [--follow|--until-change]
|
|
16
|
+
|
|
17
|
+
Exit codes:
|
|
18
|
+
0 completed
|
|
19
|
+
20 input required
|
|
20
|
+
30 watched signal changed while the wave remained active
|
|
21
|
+
40 failed
|
|
22
|
+
EOF
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
while [ "$#" -gt 0 ]; do
|
|
26
|
+
case "$1" in
|
|
27
|
+
--lane)
|
|
28
|
+
lane="${2:-}"
|
|
29
|
+
shift 2
|
|
30
|
+
;;
|
|
31
|
+
--wave)
|
|
32
|
+
wave="${2:-}"
|
|
33
|
+
shift 2
|
|
34
|
+
;;
|
|
35
|
+
--agent)
|
|
36
|
+
agent="${2:-}"
|
|
37
|
+
shift 2
|
|
38
|
+
;;
|
|
39
|
+
--run)
|
|
40
|
+
run_id="${2:-}"
|
|
41
|
+
shift 2
|
|
42
|
+
;;
|
|
43
|
+
--dry-run)
|
|
44
|
+
dry_run="1"
|
|
45
|
+
shift
|
|
46
|
+
;;
|
|
47
|
+
--refresh-ms)
|
|
48
|
+
refresh_ms="${2:-}"
|
|
49
|
+
shift 2
|
|
50
|
+
;;
|
|
51
|
+
--follow)
|
|
52
|
+
mode="follow"
|
|
53
|
+
shift
|
|
54
|
+
;;
|
|
55
|
+
--until-change)
|
|
56
|
+
mode="until-change"
|
|
57
|
+
shift
|
|
58
|
+
;;
|
|
59
|
+
--help|-h)
|
|
60
|
+
usage
|
|
61
|
+
exit 0
|
|
62
|
+
;;
|
|
63
|
+
*)
|
|
64
|
+
echo "Unknown argument: $1" >&2
|
|
65
|
+
usage >&2
|
|
66
|
+
exit 2
|
|
67
|
+
;;
|
|
68
|
+
esac
|
|
69
|
+
done
|
|
70
|
+
|
|
71
|
+
status_script="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/wave-status.sh"
|
|
72
|
+
common_args=(--lane "$lane")
|
|
73
|
+
if [ -n "$wave" ]; then
|
|
74
|
+
common_args+=(--wave "$wave")
|
|
75
|
+
fi
|
|
76
|
+
if [ -n "$agent" ]; then
|
|
77
|
+
common_args+=(--agent "$agent")
|
|
78
|
+
fi
|
|
79
|
+
if [ -n "$run_id" ]; then
|
|
80
|
+
common_args+=(--run "$run_id")
|
|
81
|
+
fi
|
|
82
|
+
if [ "$dry_run" = "1" ]; then
|
|
83
|
+
common_args+=(--dry-run)
|
|
84
|
+
fi
|
|
85
|
+
|
|
86
|
+
extract_field() {
|
|
87
|
+
local payload="$1"
|
|
88
|
+
local field="$2"
|
|
89
|
+
PAYLOAD="$payload" node - "$agent" "$field" <<'NODE'
|
|
90
|
+
const agentId = String(process.argv[2] || "").trim();
|
|
91
|
+
const field = String(process.argv[3] || "").trim();
|
|
92
|
+
const payload = JSON.parse(process.env.PAYLOAD || "{}");
|
|
93
|
+
const signals = payload?.signals || {};
|
|
94
|
+
const snapshot = agentId
|
|
95
|
+
? (Array.isArray(signals.agents) ? signals.agents.find((entry) => entry.agentId === agentId) : null)
|
|
96
|
+
: signals.wave;
|
|
97
|
+
const value = snapshot?.[field];
|
|
98
|
+
process.stdout.write(value === undefined || value === null ? "" : String(value));
|
|
99
|
+
NODE
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
print_line() {
|
|
103
|
+
local payload="$1"
|
|
104
|
+
PAYLOAD="$payload" node - "$lane" "${wave:-0}" "$agent" <<'NODE'
|
|
105
|
+
const lane = process.argv[2] || "main";
|
|
106
|
+
const wave = Number.parseInt(String(process.argv[3] || "0"), 10) || 0;
|
|
107
|
+
const agentId = String(process.argv[4] || "").trim();
|
|
108
|
+
const payload = JSON.parse(process.env.PAYLOAD || "{}");
|
|
109
|
+
const signals = payload?.signals || {};
|
|
110
|
+
const snapshot = agentId
|
|
111
|
+
? (Array.isArray(signals.agents) ? signals.agents.find((entry) => entry.agentId === agentId) : null)
|
|
112
|
+
: signals.wave;
|
|
113
|
+
const effective = snapshot || {
|
|
114
|
+
signal: payload?.blockingEdge?.kind === "human-input" ? "feedback-requested" : "waiting",
|
|
115
|
+
phase: payload?.phase || "unknown",
|
|
116
|
+
status: payload?.blockingEdge ? "blocked" : "running",
|
|
117
|
+
blocking: payload?.blockingEdge || null,
|
|
118
|
+
attempt: payload?.activeAttempt?.attemptNumber || 0,
|
|
119
|
+
version: 0,
|
|
120
|
+
shouldWake: agentId ? true : null,
|
|
121
|
+
targetAgentIds: agentId ? [agentId] : [],
|
|
122
|
+
};
|
|
123
|
+
const targetKey = agentId ? "agent" : "agents";
|
|
124
|
+
const targetValue = agentId || (effective.targetAgentIds || []).join(",") || "none";
|
|
125
|
+
const shouldWake =
|
|
126
|
+
typeof effective.shouldWake === "boolean" ? (effective.shouldWake ? "yes" : "no") : "n/a";
|
|
127
|
+
console.log(
|
|
128
|
+
[
|
|
129
|
+
`signal=${effective.signal || "waiting"}`,
|
|
130
|
+
`lane=${lane}`,
|
|
131
|
+
`wave=${wave}`,
|
|
132
|
+
`phase=${effective.phase || "unknown"}`,
|
|
133
|
+
`status=${effective.status || "running"}`,
|
|
134
|
+
`blocking=${effective?.blocking?.kind || "none"}`,
|
|
135
|
+
`attempt=${effective.attempt || 0}`,
|
|
136
|
+
`${targetKey}=${targetValue}`,
|
|
137
|
+
`version=${effective.version || 0}`,
|
|
138
|
+
`should_wake=${shouldWake}`,
|
|
139
|
+
].join(" "),
|
|
140
|
+
);
|
|
141
|
+
NODE
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
exit_code_for_payload() {
|
|
145
|
+
local payload="$1"
|
|
146
|
+
PAYLOAD="$payload" node - "$agent" <<'NODE'
|
|
147
|
+
const agentId = String(process.argv[2] || "").trim();
|
|
148
|
+
const payload = JSON.parse(process.env.PAYLOAD || "{}");
|
|
149
|
+
const signals = payload?.signals || {};
|
|
150
|
+
const snapshot = agentId
|
|
151
|
+
? (Array.isArray(signals.agents) ? signals.agents.find((entry) => entry.agentId === agentId) : null)
|
|
152
|
+
: signals.wave;
|
|
153
|
+
const signal = String(snapshot?.signal || "").trim().toLowerCase();
|
|
154
|
+
if (signal === "completed") {
|
|
155
|
+
process.exit(0);
|
|
156
|
+
}
|
|
157
|
+
if (signal === "failed") {
|
|
158
|
+
process.exit(40);
|
|
159
|
+
}
|
|
160
|
+
if (signal === "feedback-requested") {
|
|
161
|
+
process.exit(20);
|
|
162
|
+
}
|
|
163
|
+
process.exit(10);
|
|
164
|
+
NODE
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
payload="$("$status_script" "${common_args[@]}" --json)"
|
|
168
|
+
version="$(extract_field "$payload" "version")"
|
|
169
|
+
print_line "$payload"
|
|
170
|
+
|
|
171
|
+
if exit_code_for_payload "$payload"; then
|
|
172
|
+
exit 0
|
|
173
|
+
else
|
|
174
|
+
status=$?
|
|
175
|
+
if [ "$status" -eq 20 ] || [ "$status" -eq 40 ]; then
|
|
176
|
+
exit "$status"
|
|
177
|
+
fi
|
|
178
|
+
fi
|
|
179
|
+
|
|
180
|
+
while true; do
|
|
181
|
+
sleep "$(awk "BEGIN { printf \"%.3f\", ${refresh_ms}/1000 }")"
|
|
182
|
+
payload="$("$status_script" "${common_args[@]}" --json)"
|
|
183
|
+
next_version="$(extract_field "$payload" "version")"
|
|
184
|
+
if [ "$next_version" = "$version" ]; then
|
|
185
|
+
continue
|
|
186
|
+
fi
|
|
187
|
+
version="$next_version"
|
|
188
|
+
print_line "$payload"
|
|
189
|
+
if exit_code_for_payload "$payload"; then
|
|
190
|
+
exit 0
|
|
191
|
+
else
|
|
192
|
+
status=$?
|
|
193
|
+
if [ "$status" -eq 20 ] || [ "$status" -eq 40 ]; then
|
|
194
|
+
exit "$status"
|
|
195
|
+
fi
|
|
196
|
+
fi
|
|
197
|
+
if [ "$mode" = "until-change" ]; then
|
|
198
|
+
exit 30
|
|
199
|
+
fi
|
|
200
|
+
done
|
package/skills/README.md
CHANGED
|
@@ -180,6 +180,7 @@ Runtime:
|
|
|
180
180
|
Design reference:
|
|
181
181
|
|
|
182
182
|
- `tui-design`
|
|
183
|
+
- `signal-hygiene`
|
|
183
184
|
|
|
184
185
|
Provider:
|
|
185
186
|
|
|
@@ -205,6 +206,8 @@ Provider skills are configured by deploy kind, but the shipped manifests further
|
|
|
205
206
|
|
|
206
207
|
For terminal or operator-surface design work, keep `role-design` as the packet contract and add `tui-design` explicitly in the wave's `### Skills`.
|
|
207
208
|
|
|
209
|
+
For long-running watcher agents, add `signal-hygiene` explicitly in the wave's `### Skills`. Do not attach it to normal one-shot implementation agents. For the wrapper and ack-loop contract, read [../docs/guides/signal-wrappers.md](../docs/guides/signal-wrappers.md).
|
|
210
|
+
|
|
208
211
|
## Further Reading
|
|
209
212
|
|
|
210
213
|
- [Skills Reference](../docs/reference/skills.md)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: signal-hygiene
|
|
3
|
+
description: Use for long-running resident or waiting agents that must stay idle until the orchestrator writes a new signal version, then acknowledge that change before acting.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Signal Hygiene
|
|
7
|
+
|
|
8
|
+
Use this skill only when the agent is intentionally long-running.
|
|
9
|
+
|
|
10
|
+
This is not a generic polling skill for normal one-shot implementation work.
|
|
11
|
+
|
|
12
|
+
## Core loop
|
|
13
|
+
|
|
14
|
+
- Treat the signal state file as the orchestrator-controlled wakeup surface.
|
|
15
|
+
- Treat the signal ack file as your durable confirmation that you observed a specific signal version.
|
|
16
|
+
- Stay idle while the signal version is unchanged.
|
|
17
|
+
- Act once when the signal version increases and the new signal is actionable.
|
|
18
|
+
|
|
19
|
+
## Required behavior
|
|
20
|
+
|
|
21
|
+
- Read the signal state path provided in the prompt before deciding whether to keep waiting or resume work.
|
|
22
|
+
- If the signal file is missing, assume the orchestrator has not published a new signal yet and keep waiting.
|
|
23
|
+
- Compare the signal file's `version` to the version already recorded in the signal ack file.
|
|
24
|
+
- When the signal version increases, write the ack file immediately before you act on the change.
|
|
25
|
+
- Write the ack file as JSON with exactly these keys:
|
|
26
|
+
- `agentId`
|
|
27
|
+
- `version`
|
|
28
|
+
- `signal`
|
|
29
|
+
- `observedAt`
|
|
30
|
+
- After acknowledging the new version, re-read the inbox, shared summary, message board, and any explicitly referenced artifacts before taking action.
|
|
31
|
+
- If the signal kind is `completed` or `failed`, stop the waiting loop and finish cleanly.
|
|
32
|
+
|
|
33
|
+
## Do not do this
|
|
34
|
+
|
|
35
|
+
- Do not busy-loop or emit repeated status chatter while the signal version is unchanged.
|
|
36
|
+
- Do not keep re-processing the same signal version.
|
|
37
|
+
- Do not invent your own wakeup surface when the orchestrator already provided signal and ack paths.
|
|
38
|
+
- Do not stay resident forever once the signal clearly becomes terminal.
|
|
39
|
+
|
|
40
|
+
## Actionability rule
|
|
41
|
+
|
|
42
|
+
Treat these signal kinds as actionable by default:
|
|
43
|
+
|
|
44
|
+
- `feedback-requested`
|
|
45
|
+
- `feedback-answered`
|
|
46
|
+
- `coordination-action`
|
|
47
|
+
- `resume-ready`
|
|
48
|
+
- `completed`
|
|
49
|
+
- `failed`
|
|
50
|
+
|
|
51
|
+
Treat `waiting` and `stable` as non-actionable until the version changes again.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "signal-hygiene",
|
|
3
|
+
"title": "Long-Running Signal Hygiene",
|
|
4
|
+
"description": "Use for long-running resident or waiting agents that must stay idle until the orchestrator writes a new signal version, then acknowledge that change before acting.",
|
|
5
|
+
"activation": {
|
|
6
|
+
"when": "Attach explicitly when an agent is expected to stay resident, wait for human feedback or coordination changes, and react only when the orchestrator updates the signal state file.",
|
|
7
|
+
"roles": [],
|
|
8
|
+
"runtimes": [],
|
|
9
|
+
"deployKinds": []
|
|
10
|
+
},
|
|
11
|
+
"termination": "Stop when the signal reaches a terminal state or the agent is no longer expected to stay resident and wait for follow-up work.",
|
|
12
|
+
"permissions": {
|
|
13
|
+
"network": [],
|
|
14
|
+
"shell": [],
|
|
15
|
+
"mcpServers": []
|
|
16
|
+
},
|
|
17
|
+
"trust": {
|
|
18
|
+
"tier": "repo-owned"
|
|
19
|
+
}
|
|
20
|
+
}
|