opencode-usage-coach 0.3.1 → 0.3.2
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/README.md +33 -0
- package/agents/usage-coach-harness.md +1 -0
- package/dist/index.js +35 -15
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -52,6 +52,39 @@ cp dist/index.js ~/.config/opencode/plugins/opencode-usage-coach.js
|
|
|
52
52
|
# ~/.config/opencode/tui.json: { "plugin": ["/abs/path/dist/tui.js"] }
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
+
## Prompting the harness
|
|
56
|
+
|
|
57
|
+
How to trigger and use the harness loop for substantive work.
|
|
58
|
+
|
|
59
|
+
### Triggering
|
|
60
|
+
With the harness agent mode active (`agents/usage-coach-harness.md` installed), ask for substantive work — explicitly or just describe a multi-step task:
|
|
61
|
+
- `"run this through the harness: write CONTRIBUTING.md from git log"`
|
|
62
|
+
- `"harness: add TypeScript strict mode across the repo"`
|
|
63
|
+
- Or just describe the work; the agent triages and enters the loop.
|
|
64
|
+
|
|
65
|
+
Trivial requests (a one-line fix, a direct answer) are done directly — the loop only runs when generate→grade adds value.
|
|
66
|
+
|
|
67
|
+
### Independent vs dependent tasks
|
|
68
|
+
The harness picks the loop path based on task dependency:
|
|
69
|
+
- **Independent** (task B doesn't need A's output) → `generate_batch` runs all tasks in **parallel** (faster). Example: `"CONTRIBUTING.md, PR template, issue template"` — three separate docs.
|
|
70
|
+
- **Dependent** (B needs A) → **sequential** `generate` calls. Example: `"1) schema, 2) migration, 3) API"` — each needs the prior.
|
|
71
|
+
|
|
72
|
+
### Deterministic NEXT directives
|
|
73
|
+
Each tool appends a `[usage-coach NEXT]` line to its return value, telling the agent exactly what to call next:
|
|
74
|
+
- `generate` → NEXT: grade the work
|
|
75
|
+
- `grade` PASS → NEXT: mark completed, proceed
|
|
76
|
+
- `grade` FAIL → NEXT: revise (up to 2x) or mark failed
|
|
77
|
+
|
|
78
|
+
The agent follows NEXT — you just describe the work, the loop runs itself.
|
|
79
|
+
|
|
80
|
+
### Quota-aware (automatic)
|
|
81
|
+
You don't manage quota — the tools adapt:
|
|
82
|
+
- **GO** + headroom → strong generator, full parallel
|
|
83
|
+
- **THROTTLE** → auto-switch to `lighterModel`, concurrency capped at 2
|
|
84
|
+
- **STOP** → loop halts
|
|
85
|
+
|
|
86
|
+
Set `lighterModel` in `harness.config.json` to enable THROTTLE switching.
|
|
87
|
+
|
|
55
88
|
## Config
|
|
56
89
|
|
|
57
90
|
This plugin has **four config surfaces**. Only the first two are required to run.
|
|
@@ -58,6 +58,7 @@ DEPENDENT tasks (B needs A) → always sequential `generate` calls, regardless o
|
|
|
58
58
|
3. When all tasks are done → `harness_done()`.
|
|
59
59
|
|
|
60
60
|
## Rules
|
|
61
|
+
- **Follow the [usage-coach NEXT] directive each tool returns.** `harness_start`, `generate`, and `grade` all append a `NEXT` line telling you exactly what to call next. This makes the loop deterministic — do not improvise the sequence, follow `NEXT`.
|
|
61
62
|
- In the loop, do NOT do the work yourself — call `generate`/`grade` (they run the configured models). You orchestrate. (Outside the loop, for trivial requests, act directly.)
|
|
62
63
|
- Call `task_update` on every state transition — the sidebar panel reads it for live visibility.
|
|
63
64
|
- Grading criteria come from the user's request, or sensible defaults; ask the user only if it is truly ambiguous and grading matters.
|
package/dist/index.js
CHANGED
|
@@ -340,7 +340,27 @@ async function UsageCoachPlugin(input) {
|
|
|
340
340
|
args: { name: tool.schema.string(), total: tool.schema.number() },
|
|
341
341
|
async execute(args, ctx) {
|
|
342
342
|
writeHarness(ctx.sessionID, { name: args.name, total: args.total, current: 0, tasks: [], usage: {}, active: true, startedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
343
|
-
return `Harness '${args.name}' started (${args.total} tasks).
|
|
343
|
+
return `Harness '${args.name}' started (${args.total} tasks).
|
|
344
|
+
|
|
345
|
+
DETERMINISTIC LOOP \u2014 first classify the tasks:
|
|
346
|
+
INDEPENDENT = task B does NOT need task A's output -> use PATH A (parallel, faster)
|
|
347
|
+
DEPENDENT = task B needs task A's output -> use PATH B (sequential)
|
|
348
|
+
|
|
349
|
+
PATH A \u2014 INDEPENDENT (parallel via generate_batch):
|
|
350
|
+
1. task_update(1..${args.total}, title, "generating")
|
|
351
|
+
2. generate_batch({tasks: [{id:1, prompt:"Task: <title1>. Perform it."}, ...]}) -> all results + NEXT
|
|
352
|
+
3. for each i: task_update(i, title, "grading") + grade({prompt:"Evaluate... PASS/FAIL first line. Task: <title>"}) -> verdict + NEXT
|
|
353
|
+
4. for each i: PASS -> task_update(i, title, "completed", "PASS"); FAIL -> revise (up to 2x) or task_update(i, title, "failed", "FAIL")
|
|
354
|
+
|
|
355
|
+
PATH B \u2014 DEPENDENT (sequential):
|
|
356
|
+
for i in 1..${args.total}:
|
|
357
|
+
1. task_update(i, title, "generating")
|
|
358
|
+
2. generate({prompt:"Task: <title>. Perform it."}) -> work + NEXT
|
|
359
|
+
3. task_update(i, title, "grading")
|
|
360
|
+
4. grade(...) -> verdict + NEXT
|
|
361
|
+
5. PASS -> task_update(i, title, "completed", "PASS"); FAIL -> revise (up to 2x) or failed
|
|
362
|
+
|
|
363
|
+
Then: harness_done(). Follow the [usage-coach NEXT] directive each tool returns. Do NOT improvise the sequence.`;
|
|
344
364
|
}
|
|
345
365
|
}),
|
|
346
366
|
task_update: tool({
|
|
@@ -392,7 +412,8 @@ async function UsageCoachPlugin(input) {
|
|
|
392
412
|
const model = throttle ? cfg.lighterModel : cfg.generator;
|
|
393
413
|
const out = await runModel(input.client, model, args.prompt, ctx.directory);
|
|
394
414
|
return out + (throttle ? `
|
|
395
|
-
[usage-coach] quota THROTTLE \u2014 used lighter model ${cfg.lighterModel}` : "")
|
|
415
|
+
[usage-coach] quota THROTTLE \u2014 used lighter model ${cfg.lighterModel}` : "") + `
|
|
416
|
+
[usage-coach NEXT] call task_update(i, title, "grading"), then grade to evaluate this work.`;
|
|
396
417
|
}
|
|
397
418
|
}),
|
|
398
419
|
generate_batch: tool({
|
|
@@ -425,25 +446,24 @@ async function UsageCoachPlugin(input) {
|
|
|
425
446
|
}
|
|
426
447
|
}),
|
|
427
448
|
grade: tool({
|
|
428
|
-
description: "Run the GRADER model on a prompt. Returns PASS/FAIL on the first line. Falls back to generator if grader quota is out.",
|
|
449
|
+
description: "Run the GRADER model on a prompt. Returns PASS/FAIL on the first line + a [usage-coach NEXT] directive. Falls back to generator if grader quota is out.",
|
|
429
450
|
args: { prompt: tool.schema.string() },
|
|
430
451
|
async execute(args, ctx) {
|
|
431
452
|
const cfg = readHarnessCfg(ctx.directory);
|
|
432
453
|
const model = cfg.grader ?? cfg.generator;
|
|
433
|
-
if (!model) return
|
|
454
|
+
if (!model) return "FAIL\n(ERROR: no grader/generator model configured.)\n[usage-coach NEXT] configure grader in harness.config.json, then retry grade.";
|
|
434
455
|
const out = await runModel(input.client, model, args.prompt, ctx.directory);
|
|
435
|
-
|
|
436
|
-
(
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
if (!isPass && !isFail) {
|
|
442
|
-
return `FAIL
|
|
443
|
-
(grader did not output PASS/FAIL on the first line; defaulting to FAIL to trigger a revise)
|
|
444
|
-
${out}`;
|
|
456
|
+
let verdict = "FAIL";
|
|
457
|
+
if (!out.startsWith("ERROR:")) {
|
|
458
|
+
const f = (out.split("\n").find((l) => l.trim()) ?? "").trim();
|
|
459
|
+
if (/^pass\b/i.test(f)) verdict = "PASS";
|
|
460
|
+
else if (/^fail\b/i.test(f)) verdict = "FAIL";
|
|
461
|
+
else verdict = "FAIL";
|
|
445
462
|
}
|
|
446
|
-
|
|
463
|
+
const next = verdict === "PASS" ? `
|
|
464
|
+
[usage-coach NEXT] PASS -> call task_update(i, title, "completed", "PASS"), then proceed to next task (or harness_done if last).` : `
|
|
465
|
+
[usage-coach NEXT] FAIL -> if revisions < 2: task_update(i, title, "revising", revisions+1) + generate({prompt: "Apply feedback:\\n{grade result}\\nTask: {title}"}); else: task_update(i, title, "failed", "FAIL") -> next task.`;
|
|
466
|
+
return out + "\n" + next;
|
|
447
467
|
}
|
|
448
468
|
})
|
|
449
469
|
}
|
package/package.json
CHANGED