opencode-swarm-plugin 0.38.0 → 0.39.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/.env +2 -0
- package/.hive/eval-results.json +26 -0
- package/.hive/issues.jsonl +11 -0
- package/.hive/memories.jsonl +23 -1
- package/.opencode/eval-history.jsonl +12 -0
- package/CHANGELOG.md +130 -0
- package/README.md +29 -12
- package/bin/swarm.test.ts +475 -0
- package/bin/swarm.ts +383 -0
- package/dist/compaction-hook.d.ts +1 -1
- package/dist/compaction-hook.d.ts.map +1 -1
- package/dist/compaction-prompt-scoring.d.ts +124 -0
- package/dist/compaction-prompt-scoring.d.ts.map +1 -0
- package/dist/eval-capture.d.ts +81 -1
- package/dist/eval-capture.d.ts.map +1 -1
- package/dist/eval-gates.d.ts +84 -0
- package/dist/eval-gates.d.ts.map +1 -0
- package/dist/eval-history.d.ts +117 -0
- package/dist/eval-history.d.ts.map +1 -0
- package/dist/eval-learning.d.ts +216 -0
- package/dist/eval-learning.d.ts.map +1 -0
- package/dist/index.d.ts +44 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +370 -13
- package/dist/plugin.js +203 -13
- package/dist/post-compaction-tracker.d.ts +133 -0
- package/dist/post-compaction-tracker.d.ts.map +1 -0
- package/dist/swarm-orchestrate.d.ts +23 -0
- package/dist/swarm-orchestrate.d.ts.map +1 -1
- package/dist/swarm-prompts.d.ts +25 -1
- package/dist/swarm-prompts.d.ts.map +1 -1
- package/dist/swarm.d.ts +4 -0
- package/dist/swarm.d.ts.map +1 -1
- package/evals/README.md +589 -105
- package/evals/compaction-prompt.eval.ts +149 -0
- package/evals/coordinator-behavior.eval.ts +8 -8
- package/evals/fixtures/compaction-prompt-cases.ts +305 -0
- package/evals/lib/compaction-loader.test.ts +248 -0
- package/evals/lib/compaction-loader.ts +320 -0
- package/evals/lib/data-loader.test.ts +345 -0
- package/evals/lib/data-loader.ts +107 -6
- package/evals/scorers/compaction-prompt-scorers.ts +145 -0
- package/evals/scorers/compaction-scorers.ts +13 -13
- package/evals/scorers/coordinator-discipline.evalite-test.ts +3 -2
- package/evals/scorers/coordinator-discipline.ts +13 -13
- package/examples/plugin-wrapper-template.ts +117 -0
- package/package.json +7 -5
- package/scripts/migrate-unknown-sessions.ts +349 -0
- package/src/compaction-capture.integration.test.ts +257 -0
- package/src/compaction-hook.test.ts +42 -0
- package/src/compaction-hook.ts +81 -0
- package/src/compaction-prompt-scorers.test.ts +299 -0
- package/src/compaction-prompt-scoring.ts +298 -0
- package/src/eval-capture.test.ts +422 -0
- package/src/eval-capture.ts +94 -2
- package/src/eval-gates.test.ts +306 -0
- package/src/eval-gates.ts +218 -0
- package/src/eval-history.test.ts +508 -0
- package/src/eval-history.ts +214 -0
- package/src/eval-learning.test.ts +378 -0
- package/src/eval-learning.ts +360 -0
- package/src/index.ts +61 -1
- package/src/post-compaction-tracker.test.ts +251 -0
- package/src/post-compaction-tracker.ts +237 -0
- package/src/swarm-decompose.ts +2 -2
- package/src/swarm-orchestrate.ts +2 -2
- package/src/swarm-prompts.ts +2 -2
- package/src/swarm-review.ts +3 -3
- /package/evals/{evalite.config.ts → evalite.config.ts.bak} +0 -0
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Post-Compaction Tool Call Tracker
|
|
3
|
+
*
|
|
4
|
+
* Tracks tool calls after compaction resumption to detect coordinator violations
|
|
5
|
+
* and provide learning signals for eval-driven development.
|
|
6
|
+
*
|
|
7
|
+
* ## Purpose
|
|
8
|
+
*
|
|
9
|
+
* When context is compacted, the continuation agent needs observation to learn
|
|
10
|
+
* if it's following coordinator discipline. This tracker:
|
|
11
|
+
*
|
|
12
|
+
* 1. Emits resumption_started on first tool call (marks compaction exit)
|
|
13
|
+
* 2. Tracks up to N tool calls (default 20) with violation detection
|
|
14
|
+
* 3. Stops tracking after limit to avoid noise in long sessions
|
|
15
|
+
*
|
|
16
|
+
* ## Coordinator Violations Detected
|
|
17
|
+
*
|
|
18
|
+
* - **Edit/Write**: Coordinators NEVER edit files - spawn worker instead
|
|
19
|
+
* - **swarmmail_reserve/agentmail_reserve**: Workers reserve, not coordinators
|
|
20
|
+
*
|
|
21
|
+
* ## Integration
|
|
22
|
+
*
|
|
23
|
+
* Used by compaction hook to wire tool.call events → eval capture.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```typescript
|
|
27
|
+
* const tracker = createPostCompactionTracker({
|
|
28
|
+
* sessionId: "session-123",
|
|
29
|
+
* epicId: "bd-epic-456",
|
|
30
|
+
* onEvent: captureCompactionEvent,
|
|
31
|
+
* });
|
|
32
|
+
*
|
|
33
|
+
* // Wire to OpenCode hook
|
|
34
|
+
* hooks["tool.call"] = (input) => {
|
|
35
|
+
* tracker.trackToolCall({
|
|
36
|
+
* tool: input.tool,
|
|
37
|
+
* args: input.args,
|
|
38
|
+
* timestamp: Date.now(),
|
|
39
|
+
* });
|
|
40
|
+
* };
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Tool call event structure
|
|
46
|
+
*/
|
|
47
|
+
export interface ToolCallEvent {
|
|
48
|
+
tool: string;
|
|
49
|
+
args: Record<string, unknown>;
|
|
50
|
+
timestamp: number;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Compaction event payload (matches eval-capture.ts structure)
|
|
55
|
+
*/
|
|
56
|
+
export interface CompactionEvent {
|
|
57
|
+
session_id: string;
|
|
58
|
+
epic_id: string;
|
|
59
|
+
compaction_type:
|
|
60
|
+
| "detection_complete"
|
|
61
|
+
| "prompt_generated"
|
|
62
|
+
| "context_injected"
|
|
63
|
+
| "resumption_started"
|
|
64
|
+
| "tool_call_tracked";
|
|
65
|
+
payload: {
|
|
66
|
+
session_id?: string;
|
|
67
|
+
epic_id?: string;
|
|
68
|
+
tool?: string;
|
|
69
|
+
args?: Record<string, unknown>;
|
|
70
|
+
call_number?: number;
|
|
71
|
+
is_coordinator_violation?: boolean;
|
|
72
|
+
violation_reason?: string;
|
|
73
|
+
timestamp?: number;
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Tracker configuration
|
|
79
|
+
*/
|
|
80
|
+
export interface PostCompactionTrackerConfig {
|
|
81
|
+
sessionId: string;
|
|
82
|
+
epicId: string;
|
|
83
|
+
onEvent: (event: CompactionEvent) => void;
|
|
84
|
+
maxCalls?: number;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Post-compaction tracker instance
|
|
89
|
+
*/
|
|
90
|
+
export interface PostCompactionTracker {
|
|
91
|
+
trackToolCall(event: ToolCallEvent): void;
|
|
92
|
+
isTracking(): boolean;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ============================================================================
|
|
96
|
+
// Constants
|
|
97
|
+
// ============================================================================
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Default maximum number of tool calls to track
|
|
101
|
+
*
|
|
102
|
+
* Chosen to balance:
|
|
103
|
+
* - Enough data for pattern detection (20 calls is ~2-3 minutes of coordinator work)
|
|
104
|
+
* - Avoiding noise pollution in long sessions
|
|
105
|
+
*/
|
|
106
|
+
export const DEFAULT_MAX_TRACKED_CALLS = 20;
|
|
107
|
+
|
|
108
|
+
// ============================================================================
|
|
109
|
+
// Coordinator Violation Detection
|
|
110
|
+
// ============================================================================
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Tools that coordinators are NEVER allowed to use
|
|
114
|
+
*
|
|
115
|
+
* Key insight from semantic memory: coordinators lose identity after compaction
|
|
116
|
+
* and start doing implementation work. These violations are observable signals
|
|
117
|
+
* that the coordinator mandate wasn't preserved in continuation prompt.
|
|
118
|
+
*/
|
|
119
|
+
const FORBIDDEN_COORDINATOR_TOOLS: Record<string, string> = {
|
|
120
|
+
edit: "Coordinators NEVER edit files - spawn worker instead",
|
|
121
|
+
write: "Coordinators NEVER write files - spawn worker instead",
|
|
122
|
+
swarmmail_reserve: "Coordinators NEVER reserve files - workers reserve files",
|
|
123
|
+
agentmail_reserve: "Coordinators NEVER reserve files - workers reserve files",
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Check if tool call is a coordinator violation
|
|
128
|
+
*
|
|
129
|
+
* @param tool - Tool name from OpenCode tool.call hook
|
|
130
|
+
* @returns Violation status with reason if forbidden
|
|
131
|
+
*
|
|
132
|
+
* @example
|
|
133
|
+
* ```typescript
|
|
134
|
+
* const result = isCoordinatorViolation("edit");
|
|
135
|
+
* // { isViolation: true, reason: "Coordinators NEVER edit..." }
|
|
136
|
+
*
|
|
137
|
+
* const result = isCoordinatorViolation("read");
|
|
138
|
+
* // { isViolation: false }
|
|
139
|
+
* ```
|
|
140
|
+
*/
|
|
141
|
+
export function isCoordinatorViolation(tool: string): {
|
|
142
|
+
isViolation: boolean;
|
|
143
|
+
reason?: string;
|
|
144
|
+
} {
|
|
145
|
+
const reason = FORBIDDEN_COORDINATOR_TOOLS[tool];
|
|
146
|
+
return {
|
|
147
|
+
isViolation: !!reason,
|
|
148
|
+
reason,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// ============================================================================
|
|
153
|
+
// Tracker Factory
|
|
154
|
+
// ============================================================================
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Create a post-compaction tool call tracker
|
|
158
|
+
*
|
|
159
|
+
* @example
|
|
160
|
+
* ```typescript
|
|
161
|
+
* const tracker = createPostCompactionTracker({
|
|
162
|
+
* sessionId: "session-123",
|
|
163
|
+
* epicId: "bd-epic-456",
|
|
164
|
+
* onEvent: (event) => captureCompactionEvent(event),
|
|
165
|
+
* maxCalls: 20
|
|
166
|
+
* });
|
|
167
|
+
*
|
|
168
|
+
* // Track tool calls
|
|
169
|
+
* tracker.trackToolCall({
|
|
170
|
+
* tool: "read",
|
|
171
|
+
* args: { filePath: "/test.ts" },
|
|
172
|
+
* timestamp: Date.now()
|
|
173
|
+
* });
|
|
174
|
+
* ```
|
|
175
|
+
*/
|
|
176
|
+
export function createPostCompactionTracker(
|
|
177
|
+
config: PostCompactionTrackerConfig,
|
|
178
|
+
): PostCompactionTracker {
|
|
179
|
+
const {
|
|
180
|
+
sessionId,
|
|
181
|
+
epicId,
|
|
182
|
+
onEvent,
|
|
183
|
+
maxCalls = DEFAULT_MAX_TRACKED_CALLS,
|
|
184
|
+
} = config;
|
|
185
|
+
|
|
186
|
+
let callCount = 0;
|
|
187
|
+
let resumptionEmitted = false;
|
|
188
|
+
|
|
189
|
+
return {
|
|
190
|
+
trackToolCall(event: ToolCallEvent): void {
|
|
191
|
+
// Stop tracking after max calls reached
|
|
192
|
+
if (callCount >= maxCalls) {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Emit resumption_started on first call
|
|
197
|
+
if (!resumptionEmitted) {
|
|
198
|
+
onEvent({
|
|
199
|
+
session_id: sessionId,
|
|
200
|
+
epic_id: epicId,
|
|
201
|
+
compaction_type: "resumption_started",
|
|
202
|
+
payload: {
|
|
203
|
+
session_id: sessionId,
|
|
204
|
+
epic_id: epicId,
|
|
205
|
+
timestamp: event.timestamp,
|
|
206
|
+
},
|
|
207
|
+
});
|
|
208
|
+
resumptionEmitted = true;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Increment before emitting so call_number is 1-based
|
|
212
|
+
callCount++;
|
|
213
|
+
|
|
214
|
+
// Check for coordinator violations
|
|
215
|
+
const violation = isCoordinatorViolation(event.tool);
|
|
216
|
+
|
|
217
|
+
// Emit tool_call_tracked event
|
|
218
|
+
onEvent({
|
|
219
|
+
session_id: sessionId,
|
|
220
|
+
epic_id: epicId,
|
|
221
|
+
compaction_type: "tool_call_tracked",
|
|
222
|
+
payload: {
|
|
223
|
+
tool: event.tool,
|
|
224
|
+
args: event.args,
|
|
225
|
+
call_number: callCount,
|
|
226
|
+
is_coordinator_violation: violation.isViolation,
|
|
227
|
+
violation_reason: violation.reason,
|
|
228
|
+
timestamp: event.timestamp,
|
|
229
|
+
},
|
|
230
|
+
});
|
|
231
|
+
},
|
|
232
|
+
|
|
233
|
+
isTracking(): boolean {
|
|
234
|
+
return callCount < maxCalls;
|
|
235
|
+
},
|
|
236
|
+
};
|
|
237
|
+
}
|
package/src/swarm-decompose.ts
CHANGED
|
@@ -753,7 +753,7 @@ export const swarm_delegate_planning = tool({
|
|
|
753
753
|
.default(true)
|
|
754
754
|
.describe("Query CASS for similar past tasks (default: true)"),
|
|
755
755
|
},
|
|
756
|
-
async execute(args) {
|
|
756
|
+
async execute(args, _ctx) {
|
|
757
757
|
// Import needed modules
|
|
758
758
|
const { selectStrategy, formatStrategyGuidelines } =
|
|
759
759
|
await import("./swarm-strategies");
|
|
@@ -777,7 +777,7 @@ export const swarm_delegate_planning = tool({
|
|
|
777
777
|
// Capture strategy selection decision
|
|
778
778
|
try {
|
|
779
779
|
captureCoordinatorEvent({
|
|
780
|
-
session_id:
|
|
780
|
+
session_id: _ctx.sessionID || "unknown",
|
|
781
781
|
epic_id: "planning", // No epic ID yet - this is pre-decomposition
|
|
782
782
|
timestamp: new Date().toISOString(),
|
|
783
783
|
event_type: "DECISION",
|
package/src/swarm-orchestrate.ts
CHANGED
|
@@ -1740,7 +1740,7 @@ Files touched: ${args.files_touched?.join(", ") || "none recorded"}`,
|
|
|
1740
1740
|
try {
|
|
1741
1741
|
const durationMs = args.start_time ? Date.now() - args.start_time : 0;
|
|
1742
1742
|
captureCoordinatorEvent({
|
|
1743
|
-
session_id:
|
|
1743
|
+
session_id: _ctx.sessionID || "unknown",
|
|
1744
1744
|
epic_id: epicId,
|
|
1745
1745
|
timestamp: new Date().toISOString(),
|
|
1746
1746
|
event_type: "OUTCOME",
|
|
@@ -1849,7 +1849,7 @@ Files touched: ${args.files_touched?.join(", ") || "none recorded"}`,
|
|
|
1849
1849
|
try {
|
|
1850
1850
|
const durationMs = args.start_time ? Date.now() - args.start_time : 0;
|
|
1851
1851
|
captureCoordinatorEvent({
|
|
1852
|
-
session_id:
|
|
1852
|
+
session_id: _ctx.sessionID || "unknown",
|
|
1853
1853
|
epic_id: epicId,
|
|
1854
1854
|
timestamp: new Date().toISOString(),
|
|
1855
1855
|
event_type: "OUTCOME",
|
package/src/swarm-prompts.ts
CHANGED
|
@@ -1358,7 +1358,7 @@ export const swarm_spawn_subtask = tool({
|
|
|
1358
1358
|
.optional()
|
|
1359
1359
|
.describe("Optional explicit model override (auto-selected if not provided)"),
|
|
1360
1360
|
},
|
|
1361
|
-
async execute(args) {
|
|
1361
|
+
async execute(args, _ctx) {
|
|
1362
1362
|
const prompt = formatSubtaskPromptV2({
|
|
1363
1363
|
bead_id: args.bead_id,
|
|
1364
1364
|
epic_id: args.epic_id,
|
|
@@ -1404,7 +1404,7 @@ export const swarm_spawn_subtask = tool({
|
|
|
1404
1404
|
// Capture worker spawn decision
|
|
1405
1405
|
try {
|
|
1406
1406
|
captureCoordinatorEvent({
|
|
1407
|
-
session_id:
|
|
1407
|
+
session_id: _ctx.sessionID || "unknown",
|
|
1408
1408
|
epic_id: args.epic_id,
|
|
1409
1409
|
timestamp: new Date().toISOString(),
|
|
1410
1410
|
event_type: "DECISION",
|
package/src/swarm-review.ts
CHANGED
|
@@ -470,7 +470,7 @@ export const swarm_review_feedback = tool({
|
|
|
470
470
|
.optional()
|
|
471
471
|
.describe("JSON array of ReviewIssue objects (for needs_changes)"),
|
|
472
472
|
},
|
|
473
|
-
async execute(args): Promise<string> {
|
|
473
|
+
async execute(args, _ctx): Promise<string> {
|
|
474
474
|
// Parse issues if provided
|
|
475
475
|
let parsedIssues: ReviewIssue[] = [];
|
|
476
476
|
if (args.issues) {
|
|
@@ -512,7 +512,7 @@ export const swarm_review_feedback = tool({
|
|
|
512
512
|
// Capture review approval decision
|
|
513
513
|
try {
|
|
514
514
|
captureCoordinatorEvent({
|
|
515
|
-
session_id:
|
|
515
|
+
session_id: _ctx.sessionID || "unknown",
|
|
516
516
|
epic_id: epicId,
|
|
517
517
|
timestamp: new Date().toISOString(),
|
|
518
518
|
event_type: "DECISION",
|
|
@@ -562,7 +562,7 @@ You may now complete the task with \`swarm_complete\`.`,
|
|
|
562
562
|
// Capture review rejection decision
|
|
563
563
|
try {
|
|
564
564
|
captureCoordinatorEvent({
|
|
565
|
-
session_id:
|
|
565
|
+
session_id: _ctx.sessionID || "unknown",
|
|
566
566
|
epic_id: epicId,
|
|
567
567
|
timestamp: new Date().toISOString(),
|
|
568
568
|
event_type: "DECISION",
|
|
File without changes
|