opencode-swarm-plugin 0.35.0 → 0.36.1
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/.hive/issues.jsonl +4 -4
- package/.hive/memories.jsonl +274 -1
- package/.turbo/turbo-build.log +4 -4
- package/.turbo/turbo-test.log +307 -307
- package/CHANGELOG.md +133 -0
- package/bin/swarm.ts +234 -179
- package/dist/compaction-hook.d.ts +54 -4
- package/dist/compaction-hook.d.ts.map +1 -1
- package/dist/eval-capture.d.ts +122 -17
- package/dist/eval-capture.d.ts.map +1 -1
- package/dist/index.d.ts +1 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1278 -619
- package/dist/planning-guardrails.d.ts +121 -0
- package/dist/planning-guardrails.d.ts.map +1 -1
- package/dist/plugin.d.ts +9 -9
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +1283 -329
- package/dist/schemas/task.d.ts +0 -1
- package/dist/schemas/task.d.ts.map +1 -1
- package/dist/swarm-decompose.d.ts +0 -8
- package/dist/swarm-decompose.d.ts.map +1 -1
- package/dist/swarm-orchestrate.d.ts.map +1 -1
- package/dist/swarm-prompts.d.ts +0 -4
- package/dist/swarm-prompts.d.ts.map +1 -1
- package/dist/swarm-review.d.ts.map +1 -1
- package/dist/swarm.d.ts +0 -6
- package/dist/swarm.d.ts.map +1 -1
- package/evals/README.md +38 -0
- package/evals/coordinator-session.eval.ts +154 -0
- package/evals/fixtures/coordinator-sessions.ts +328 -0
- package/evals/lib/data-loader.ts +69 -0
- package/evals/scorers/coordinator-discipline.evalite-test.ts +536 -0
- package/evals/scorers/coordinator-discipline.ts +315 -0
- package/evals/scorers/index.ts +12 -0
- package/examples/plugin-wrapper-template.ts +747 -34
- package/package.json +2 -2
- package/src/compaction-hook.test.ts +234 -281
- package/src/compaction-hook.ts +221 -63
- package/src/eval-capture.test.ts +390 -0
- package/src/eval-capture.ts +168 -10
- package/src/index.ts +89 -2
- package/src/learning.integration.test.ts +0 -2
- package/src/planning-guardrails.test.ts +387 -2
- package/src/planning-guardrails.ts +289 -0
- package/src/plugin.ts +10 -10
- package/src/schemas/task.ts +0 -1
- package/src/swarm-decompose.ts +21 -8
- package/src/swarm-orchestrate.ts +44 -0
- package/src/swarm-prompts.ts +20 -0
- package/src/swarm-review.ts +41 -0
- package/src/swarm.integration.test.ts +0 -40
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Coordinator Discipline Scorers - Evaluate coordinator behavior
|
|
3
|
+
*
|
|
4
|
+
* These scorers measure whether a coordinator follows the protocol:
|
|
5
|
+
* 1. Don't edit files directly (spawn workers)
|
|
6
|
+
* 2. Don't run tests directly (workers do verification)
|
|
7
|
+
* 3. Spawn workers for all subtasks
|
|
8
|
+
* 4. Review worker output before accepting
|
|
9
|
+
* 5. Minimize time to first spawn (don't overthink)
|
|
10
|
+
*
|
|
11
|
+
* Inputs: CoordinatorSession from eval-capture
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { createScorer } from "evalite";
|
|
15
|
+
import type { CoordinatorSession } from "../../src/eval-capture.js";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Violation Count Scorer
|
|
19
|
+
*
|
|
20
|
+
* Counts VIOLATION events in the session.
|
|
21
|
+
* Each violation reduces score by 0.2.
|
|
22
|
+
*
|
|
23
|
+
* Violations tracked:
|
|
24
|
+
* - coordinator_edited_file (should spawn worker instead)
|
|
25
|
+
* - coordinator_ran_tests (workers do verification)
|
|
26
|
+
* - coordinator_reserved_files (only workers reserve)
|
|
27
|
+
* - no_worker_spawned (subtask exists but no worker)
|
|
28
|
+
*
|
|
29
|
+
* Score: 1.0 - (0.2 * violation_count), floored at 0.0
|
|
30
|
+
*/
|
|
31
|
+
export const violationCount = createScorer({
|
|
32
|
+
name: "Violation Count",
|
|
33
|
+
description: "Coordinator followed protocol (no direct edits, tests, or reservations)",
|
|
34
|
+
scorer: ({ output }) => {
|
|
35
|
+
try {
|
|
36
|
+
const session = JSON.parse(String(output)) as CoordinatorSession;
|
|
37
|
+
|
|
38
|
+
// Count violations
|
|
39
|
+
const violations = session.events.filter(
|
|
40
|
+
(e) => e.event_type === "VIOLATION"
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
const count = violations.length;
|
|
44
|
+
const score = Math.max(0, 1.0 - count * 0.2);
|
|
45
|
+
|
|
46
|
+
if (count === 0) {
|
|
47
|
+
return {
|
|
48
|
+
score: 1.0,
|
|
49
|
+
message: "Perfect - 0 violations",
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
score,
|
|
55
|
+
message: `${count} violations detected`,
|
|
56
|
+
};
|
|
57
|
+
} catch (error) {
|
|
58
|
+
return {
|
|
59
|
+
score: 0,
|
|
60
|
+
message: `Failed to parse CoordinatorSession: ${error}`,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Spawn Efficiency Scorer
|
|
68
|
+
*
|
|
69
|
+
* Measures whether workers were spawned for all subtasks.
|
|
70
|
+
* Coordinators should delegate work, not do it themselves.
|
|
71
|
+
*
|
|
72
|
+
* Score: workers_spawned / subtasks_planned
|
|
73
|
+
*/
|
|
74
|
+
export const spawnEfficiency = createScorer({
|
|
75
|
+
name: "Spawn Efficiency",
|
|
76
|
+
description: "Workers spawned for all subtasks (delegation ratio)",
|
|
77
|
+
scorer: ({ output }) => {
|
|
78
|
+
try {
|
|
79
|
+
const session = JSON.parse(String(output)) as CoordinatorSession;
|
|
80
|
+
|
|
81
|
+
// Find decomposition_complete event (has subtask count)
|
|
82
|
+
const decomp = session.events.find(
|
|
83
|
+
(e) =>
|
|
84
|
+
e.event_type === "DECISION" &&
|
|
85
|
+
e.decision_type === "decomposition_complete"
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
if (!decomp) {
|
|
89
|
+
return {
|
|
90
|
+
score: 0,
|
|
91
|
+
message: "No decomposition event found",
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const subtaskCount = (decomp.payload as { subtask_count?: number })?.subtask_count || 0;
|
|
96
|
+
|
|
97
|
+
if (subtaskCount === 0) {
|
|
98
|
+
return {
|
|
99
|
+
score: 0,
|
|
100
|
+
message: "No subtasks planned",
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Count worker_spawned events
|
|
105
|
+
const spawned = session.events.filter(
|
|
106
|
+
(e) =>
|
|
107
|
+
e.event_type === "DECISION" && e.decision_type === "worker_spawned"
|
|
108
|
+
).length;
|
|
109
|
+
|
|
110
|
+
const score = spawned / subtaskCount;
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
score,
|
|
114
|
+
message: `${spawned}/${subtaskCount} workers spawned (${(score * 100).toFixed(0)}%)`,
|
|
115
|
+
};
|
|
116
|
+
} catch (error) {
|
|
117
|
+
return {
|
|
118
|
+
score: 0,
|
|
119
|
+
message: `Failed to parse CoordinatorSession: ${error}`,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Review Thoroughness Scorer
|
|
127
|
+
*
|
|
128
|
+
* Measures whether coordinator reviewed worker output.
|
|
129
|
+
* Should have review_completed events for all finished subtasks.
|
|
130
|
+
*
|
|
131
|
+
* Score: reviews_completed / workers_finished
|
|
132
|
+
*/
|
|
133
|
+
export const reviewThoroughness = createScorer({
|
|
134
|
+
name: "Review Thoroughness",
|
|
135
|
+
description: "Coordinator reviewed all worker output",
|
|
136
|
+
scorer: ({ output }) => {
|
|
137
|
+
try {
|
|
138
|
+
const session = JSON.parse(String(output)) as CoordinatorSession;
|
|
139
|
+
|
|
140
|
+
// Count finished workers (subtask_success or subtask_failed)
|
|
141
|
+
const finished = session.events.filter(
|
|
142
|
+
(e) =>
|
|
143
|
+
e.event_type === "OUTCOME" &&
|
|
144
|
+
(e.outcome_type === "subtask_success" ||
|
|
145
|
+
e.outcome_type === "subtask_failed")
|
|
146
|
+
).length;
|
|
147
|
+
|
|
148
|
+
if (finished === 0) {
|
|
149
|
+
return {
|
|
150
|
+
score: 1.0,
|
|
151
|
+
message: "No finished workers to review",
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Count review_completed events
|
|
156
|
+
const reviewed = session.events.filter(
|
|
157
|
+
(e) =>
|
|
158
|
+
e.event_type === "DECISION" && e.decision_type === "review_completed"
|
|
159
|
+
).length;
|
|
160
|
+
|
|
161
|
+
const score = reviewed / finished;
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
score,
|
|
165
|
+
message: `${reviewed}/${finished} workers reviewed (${(score * 100).toFixed(0)}%)`,
|
|
166
|
+
};
|
|
167
|
+
} catch (error) {
|
|
168
|
+
return {
|
|
169
|
+
score: 0,
|
|
170
|
+
message: `Failed to parse CoordinatorSession: ${error}`,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Time to First Spawn Scorer
|
|
178
|
+
*
|
|
179
|
+
* Measures how fast the coordinator spawned the first worker.
|
|
180
|
+
* Overthinking and perfectionism delays workers and blocks progress.
|
|
181
|
+
*
|
|
182
|
+
* Normalization:
|
|
183
|
+
* - < 60s: 1.0 (excellent)
|
|
184
|
+
* - 60-300s: linear decay to 0.5
|
|
185
|
+
* - > 300s: 0.0 (way too slow)
|
|
186
|
+
*
|
|
187
|
+
* Score: normalized to 0-1 (faster is better)
|
|
188
|
+
*/
|
|
189
|
+
export const timeToFirstSpawn = createScorer({
|
|
190
|
+
name: "Time to First Spawn",
|
|
191
|
+
description: "Coordinator spawned workers quickly (no overthinking)",
|
|
192
|
+
scorer: ({ output }) => {
|
|
193
|
+
try {
|
|
194
|
+
const session = JSON.parse(String(output)) as CoordinatorSession;
|
|
195
|
+
|
|
196
|
+
// Find decomposition_complete event
|
|
197
|
+
const decomp = session.events.find(
|
|
198
|
+
(e) =>
|
|
199
|
+
e.event_type === "DECISION" &&
|
|
200
|
+
e.decision_type === "decomposition_complete"
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
if (!decomp) {
|
|
204
|
+
return {
|
|
205
|
+
score: 0,
|
|
206
|
+
message: "No decomposition event found",
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Find first worker_spawned event
|
|
211
|
+
const firstSpawn = session.events.find(
|
|
212
|
+
(e) =>
|
|
213
|
+
e.event_type === "DECISION" && e.decision_type === "worker_spawned"
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
if (!firstSpawn) {
|
|
217
|
+
return {
|
|
218
|
+
score: 0,
|
|
219
|
+
message: "No worker spawned",
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Calculate time delta
|
|
224
|
+
const decompTime = new Date(decomp.timestamp).getTime();
|
|
225
|
+
const spawnTime = new Date(firstSpawn.timestamp).getTime();
|
|
226
|
+
const deltaMs = spawnTime - decompTime;
|
|
227
|
+
|
|
228
|
+
// Normalize: < 60s = 1.0, > 300s = 0.0, linear in between
|
|
229
|
+
const EXCELLENT_MS = 60_000;
|
|
230
|
+
const POOR_MS = 300_000;
|
|
231
|
+
|
|
232
|
+
let score: number;
|
|
233
|
+
if (deltaMs < EXCELLENT_MS) {
|
|
234
|
+
score = 1.0;
|
|
235
|
+
} else if (deltaMs > POOR_MS) {
|
|
236
|
+
score = 0.0;
|
|
237
|
+
} else {
|
|
238
|
+
// Linear decay from 1.0 to 0.0
|
|
239
|
+
score = 1.0 - (deltaMs - EXCELLENT_MS) / (POOR_MS - EXCELLENT_MS);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const seconds = Math.round(deltaMs / 1000);
|
|
243
|
+
|
|
244
|
+
return {
|
|
245
|
+
score,
|
|
246
|
+
message: `First spawn after ${deltaMs}ms (${seconds}s)`,
|
|
247
|
+
};
|
|
248
|
+
} catch (error) {
|
|
249
|
+
return {
|
|
250
|
+
score: 0,
|
|
251
|
+
message: `Failed to parse CoordinatorSession: ${error}`,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
},
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Overall Discipline Scorer
|
|
259
|
+
*
|
|
260
|
+
* Weighted composite of all coordinator discipline metrics.
|
|
261
|
+
*
|
|
262
|
+
* Weights:
|
|
263
|
+
* - Violations: 30% (most critical - breaking protocol)
|
|
264
|
+
* - Spawn efficiency: 25% (delegation is key)
|
|
265
|
+
* - Review thoroughness: 25% (quality gate)
|
|
266
|
+
* - Time to first spawn: 20% (bias toward action)
|
|
267
|
+
*
|
|
268
|
+
* Score: 0.0 to 1.0
|
|
269
|
+
*/
|
|
270
|
+
export const overallDiscipline = createScorer({
|
|
271
|
+
name: "Overall Coordinator Discipline",
|
|
272
|
+
description: "Composite score for coordinator protocol adherence",
|
|
273
|
+
scorer: ({ output, expected }) => {
|
|
274
|
+
try {
|
|
275
|
+
// Run all scorers
|
|
276
|
+
const scores = {
|
|
277
|
+
violations: violationCount.scorer({ output, expected }),
|
|
278
|
+
spawn: spawnEfficiency.scorer({ output, expected }),
|
|
279
|
+
review: reviewThoroughness.scorer({ output, expected }),
|
|
280
|
+
speed: timeToFirstSpawn.scorer({ output, expected }),
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
// Weighted average
|
|
284
|
+
const weights = {
|
|
285
|
+
violations: 0.3,
|
|
286
|
+
spawn: 0.25,
|
|
287
|
+
review: 0.25,
|
|
288
|
+
speed: 0.2,
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
const totalScore =
|
|
292
|
+
scores.violations.score * weights.violations +
|
|
293
|
+
scores.spawn.score * weights.spawn +
|
|
294
|
+
scores.review.score * weights.review +
|
|
295
|
+
scores.speed.score * weights.speed;
|
|
296
|
+
|
|
297
|
+
const details = [
|
|
298
|
+
`Violations: ${(scores.violations.score * 100).toFixed(0)}%`,
|
|
299
|
+
`Spawn: ${(scores.spawn.score * 100).toFixed(0)}%`,
|
|
300
|
+
`Review: ${(scores.review.score * 100).toFixed(0)}%`,
|
|
301
|
+
`Speed: ${(scores.speed.score * 100).toFixed(0)}%`,
|
|
302
|
+
].join(", ");
|
|
303
|
+
|
|
304
|
+
return {
|
|
305
|
+
score: totalScore,
|
|
306
|
+
message: `Overall: ${(totalScore * 100).toFixed(0)}% (${details})`,
|
|
307
|
+
};
|
|
308
|
+
} catch (error) {
|
|
309
|
+
return {
|
|
310
|
+
score: 0,
|
|
311
|
+
message: `Failed to compute composite score: ${error}`,
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
},
|
|
315
|
+
});
|
package/evals/scorers/index.ts
CHANGED
|
@@ -78,6 +78,18 @@ export {
|
|
|
78
78
|
compactionQuality,
|
|
79
79
|
} from "./compaction-scorers.js";
|
|
80
80
|
|
|
81
|
+
// ============================================================================
|
|
82
|
+
// Coordinator discipline scorers
|
|
83
|
+
// ============================================================================
|
|
84
|
+
|
|
85
|
+
export {
|
|
86
|
+
violationCount,
|
|
87
|
+
spawnEfficiency,
|
|
88
|
+
reviewThoroughness,
|
|
89
|
+
timeToFirstSpawn,
|
|
90
|
+
overallDiscipline,
|
|
91
|
+
} from "./coordinator-discipline.js";
|
|
92
|
+
|
|
81
93
|
/**
|
|
82
94
|
* Checks that subtasks cover the full task scope
|
|
83
95
|
*
|