opencode-usage-coach 0.3.0 → 0.3.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.
@@ -36,7 +36,12 @@ The user's message is the task source. If it has multiple distinct parts, decomp
36
36
 
37
37
  **Multi-model, 1 terminal:** the work runs via the plugin tools `generate` and `grade`, which use the configured models (generator/grader from harness.config.json) on the same server — so you get e.g. a paid generator + a free grader without a second terminal. Do NOT use the `task` tool for harness work; use `generate`/`grade`.
38
38
 
39
- **Parallel independent tasks:** if the decomposed tasks are INDEPENDENT, prefer `generate_batch` (one call, all results at once) over multiple sequential `generate` calls. Cap by quota coaching (big-OK 3-4; throttle 1-2; STOP → none). DEPENDENT tasks (B needs A) sequential `generate` calls.
39
+ **Quota-aware loop strategy (the tools handle this):** the quota coaching injected into your system prompt drives loop strategy. You do NOT pick the model — `generate`/`generate_batch` auto-switch to a lighter model on THROTTLE (if `lighterModel` is configured) and refuse on STOP. You pick the **task size and parallelism**:
40
+ - **GO + headroom** → big tasks OK, use `generate_batch` for independent tasks (full parallel).
41
+ - **THROTTLE** → small tasks only, sequential or limited parallelism. Tools cap concurrency at 2 and use a lighter model automatically.
42
+ - **STOP** → finish the in-progress task, then halt. `generate_batch` returns an error on STOP — call `task_update(current, "halted_quota")` and stop.
43
+
44
+ DEPENDENT tasks (B needs A) → always sequential `generate` calls, regardless of quota.
40
45
 
41
46
  1. Call `harness_start(name, N)` to register the run on the panel.
42
47
  2. For each task i (1..N):
package/dist/index.js CHANGED
@@ -375,28 +375,53 @@ async function UsageCoachPlugin(input) {
375
375
  return "Harness complete.";
376
376
  }
377
377
  }),
378
- // Per-role model execution (config-driven, same server, no deadlock).
378
+ // Per-role model execution (config-driven, quota-aware, same server, no deadlock).
379
+ // P1: quota decision drives model selection + concurrency.
379
380
  generate: tool({
380
- description: "Run the GENERATOR model on a prompt. The generator can use tools (write/edit files). Returns the model's text response.",
381
+ description: "Run the GENERATOR model on a prompt. Quota-aware: on THROTTLE, auto-switches to lighterModel if configured. Returns the model's text response.",
381
382
  args: { prompt: tool.schema.string() },
382
383
  async execute(args, ctx) {
383
384
  const cfg = readHarnessCfg(ctx.directory);
384
385
  if (!cfg.generator) return 'ERROR: no generator model configured. Set "generator" in harness.config.json (see harness.config.example.json).';
385
- const out = await runModel(input.client, cfg.generator, args.prompt, ctx.directory);
386
- return out;
386
+ let decision = "GO";
387
+ try {
388
+ decision = current().decision;
389
+ } catch {
390
+ }
391
+ const throttle = decision === "THROTTLE" && cfg.lighterModel;
392
+ const model = throttle ? cfg.lighterModel : cfg.generator;
393
+ const out = await runModel(input.client, model, args.prompt, ctx.directory);
394
+ return out + (throttle ? `
395
+ [usage-coach] quota THROTTLE \u2014 used lighter model ${cfg.lighterModel}` : "");
387
396
  }
388
397
  }),
389
398
  generate_batch: tool({
390
- description: "Run the GENERATOR model on MULTIPLE tasks in PARALLEL. Pass an array of {id, prompt}. Returns all results at once. Use for INDEPENDENT tasks (much faster than sequential generate calls).",
399
+ description: "Run the GENERATOR model on MULTIPLE tasks. Quota-aware: GO = full parallel; THROTTLE = lighter model + concurrency capped at 2; STOP = refused. Use for INDEPENDENT tasks.",
391
400
  args: { tasks: tool.schema.array(tool.schema.object({ id: tool.schema.number(), prompt: tool.schema.string() })) },
392
401
  async execute(args, ctx) {
393
402
  const cfg = readHarnessCfg(ctx.directory);
394
403
  if (!cfg.generator) return 'ERROR: no generator model configured. Set "generator" in harness.config.json (see harness.config.example.json).';
395
- const results = await Promise.all(args.tasks.map(async (t) => {
396
- const out = await runModel(input.client, cfg.generator, t.prompt, ctx.directory);
397
- return `[task ${t.id}] ${out}`;
398
- }));
399
- return results.join("\n\n");
404
+ let decision = "GO";
405
+ try {
406
+ decision = current().decision;
407
+ } catch {
408
+ }
409
+ if (decision === "STOP") return 'ERROR: quota STOP \u2014 halt the harness loop now. Call task_update(current, "halted_quota") and stop.';
410
+ const throttle = decision === "THROTTLE" && cfg.lighterModel;
411
+ const model = throttle ? cfg.lighterModel : cfg.generator;
412
+ const limit = decision === "THROTTLE" ? 2 : args.tasks.length;
413
+ const results = [];
414
+ for (let i = 0; i < args.tasks.length; i += limit) {
415
+ const batch = args.tasks.slice(i, i + limit);
416
+ const out = await Promise.all(batch.map(async (t) => {
417
+ const r = await runModel(input.client, model, t.prompt, ctx.directory);
418
+ return `[task ${t.id}] ${r}`;
419
+ }));
420
+ results.push(...out);
421
+ }
422
+ const note = throttle ? `
423
+ [usage-coach] quota THROTTLE \u2014 lighter model ${cfg.lighterModel}, concurrency capped at ${limit}` : "";
424
+ return results.join("\n\n") + note;
400
425
  }
401
426
  }),
402
427
  grade: tool({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-usage-coach",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
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",