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 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). Now report each task's status via task_update and run the loop.`;
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 'FAIL\n(ERROR: no grader/generator model configured. Set "grader" and "generator" in harness.config.json.)';
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
- if (out.startsWith("ERROR:")) return `FAIL
436
- (${out})`;
437
- const firstLine = out.split("\n").find((l) => l.trim()) ?? "";
438
- const f = firstLine.trim();
439
- const isPass = /^pass\b/i.test(f);
440
- const isFail = /^fail\b/i.test(f);
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
- return out;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-usage-coach",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "description": "opencode closed-loop usage coach \u2014 quota SENSE -> coaching DECIDE -> loop ACT + TUI integration",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",