@cobusgreyling/loop-cost 1.0.2 → 1.1.0

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
@@ -27,6 +27,7 @@ npm test
27
27
  | `--pattern` | Pattern id (see `--list`) |
28
28
  | `--cadence` | Override cadence (e.g. `15m`, `1d`) |
29
29
  | `--level` | `L1`, `L2`, or `L3` (default `L1`) |
30
+ | `--orchestration` | Multi-agent action cost: `single` (default), `maker-checker`, `parallel:N`, `debate:R` |
30
31
  | `--conservative` | Use slower cadence from ranges |
31
32
  | `--json` | Machine-readable output |
32
33
 
@@ -41,4 +42,41 @@ Each estimate includes:
41
42
 
42
43
  Pair with `loop-budget.md` (scaffolded by `loop-init`) and `loop-audit` cost observability checks.
43
44
 
45
+ ## Orchestration cost
46
+
47
+ The scenarios above assume one implementer pass. A loop that fans out to
48
+ multiple agents costs more on the **action path** (the no-op scan and single
49
+ triage pass are unaffected). `--orchestration` applies a multiplier so the
50
+ estimate — and the realistic per-run cap that feeds the circuit breaker —
51
+ reflects the real shape of the run:
52
+
53
+ | Mode | Multiplier | Models |
54
+ |------|-----------|--------|
55
+ | `single` (default) | 1x | One implementer pass, self-checked |
56
+ | `maker-checker` | 2x | Implementer + an independent verifier pass (the L2+ default) |
57
+ | `parallel:N` | N+1 | N candidate agents fan out, plus a merge/arbiter pass |
58
+ | `debate:R` | 1+R | One proposer plus R critique-and-revise rounds |
59
+
60
+ ```bash
61
+ npx @cobusgreyling/loop-cost --pattern ci-sweeper --level L2 --orchestration maker-checker
62
+ npx @cobusgreyling/loop-cost --pattern ci-sweeper --level L2 --orchestration parallel:3
63
+ ```
64
+
65
+ Multipliers above 2x emit a warning — deep fan-out or debate is easy to enable
66
+ and expensive to run unattended.
67
+
68
+ ## Feed the circuit breaker
69
+
70
+ [`loop-context`](../loop-context)'s breaker can resolve `--token-budget` directly
71
+ from a pattern's realistic per-run estimate instead of a hand-typed guess:
72
+
73
+ ```bash
74
+ npx @cobusgreyling/loop-context --check --ledger loop-ledger.json \
75
+ --budget-from-pattern ci-sweeper --budget-level L2
76
+ ```
77
+
78
+ `loop-context` shells out to this CLI's built output to do it — the packages stay
79
+ independent at the source level, no shared runtime state. An explicit
80
+ `--token-budget` on the `loop-context` call always overrides the derived value.
81
+
44
82
  See [docs/operating-loops.md](../../docs/operating-loops.md).
package/dist/cli.js CHANGED
@@ -3,7 +3,7 @@ import { readFile, access } from 'node:fs/promises';
3
3
  import path from 'node:path';
4
4
  import { fileURLToPath } from 'node:url';
5
5
  import yaml from 'yaml';
6
- import { estimateCost, formatEstimateHuman, } from './estimator.js';
6
+ import { assertValidLevel, estimateCost, formatEstimateHuman, parseOrchestration, } from './estimator.js';
7
7
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
8
8
  const PACKAGE_ROOT = path.resolve(__dirname, '..');
9
9
  function parseArgs(argv) {
@@ -13,6 +13,7 @@ function parseArgs(argv) {
13
13
  let conservative = false;
14
14
  let json = false;
15
15
  let list = false;
16
+ let orchestration = 'single';
16
17
  for (let i = 0; i < argv.length; i++) {
17
18
  const a = argv[i];
18
19
  if (a === '--pattern' || a === '-p')
@@ -21,6 +22,8 @@ function parseArgs(argv) {
21
22
  cadence = argv[++i];
22
23
  else if (a === '--level' || a === '-l')
23
24
  level = argv[++i];
25
+ else if (a === '--orchestration' || a === '-o')
26
+ orchestration = argv[++i];
24
27
  else if (a === '--conservative')
25
28
  conservative = true;
26
29
  else if (a === '--json')
@@ -30,7 +33,7 @@ function parseArgs(argv) {
30
33
  else if (a === '--help' || a === '-h')
31
34
  return { help: true };
32
35
  }
33
- return { help: false, pattern, cadence, level, conservative, json, list };
36
+ return { help: false, pattern, cadence, level, conservative, json, list, orchestration };
34
37
  }
35
38
  async function loadRegistry() {
36
39
  const candidates = [
@@ -63,6 +66,9 @@ Options:
63
66
  -p, --pattern <id> Pattern id (default: daily-triage)
64
67
  -c, --cadence <spec> Override cadence (e.g. 15m, 1d, 5m-15m)
65
68
  -l, --level <L1|L2|L3> Readiness level (default: L1)
69
+ -o, --orchestration <mode>
70
+ Multi-agent action cost: single (default),
71
+ maker-checker, parallel:N, debate:R
66
72
  --conservative Use slower cadence from ranges (e.g. 15m not 5m)
67
73
  --json Machine-readable output
68
74
  --list List pattern ids
@@ -70,6 +76,7 @@ Options:
70
76
 
71
77
  Examples:
72
78
  loop-cost --pattern ci-sweeper --cadence 15m --level L2
79
+ loop-cost --pattern ci-sweeper --level L2 --orchestration maker-checker
73
80
  loop-cost --pattern daily-triage --level L1 --json
74
81
  loop-cost --list
75
82
  `);
@@ -87,6 +94,15 @@ Examples:
87
94
  console.error(`Unknown pattern: ${args.pattern}. Use --list for ids.`);
88
95
  process.exit(1);
89
96
  }
97
+ try {
98
+ assertValidLevel(args.level);
99
+ parseOrchestration(args.orchestration);
100
+ }
101
+ catch (err) {
102
+ const msg = err instanceof Error ? err.message : String(err);
103
+ console.error(msg);
104
+ process.exit(1);
105
+ }
90
106
  if (!pattern.cost) {
91
107
  console.error(`Pattern ${args.pattern} has no cost block in registry.`);
92
108
  process.exit(1);
@@ -96,6 +112,7 @@ Examples:
96
112
  cadence: args.cadence,
97
113
  level: args.level,
98
114
  conservative: args.conservative,
115
+ orchestration: args.orchestration,
99
116
  });
100
117
  if (args.json)
101
118
  console.log(JSON.stringify(result, null, 2));
@@ -1,4 +1,6 @@
1
1
  export type ReadinessLevel = 'L1' | 'L2' | 'L3';
2
+ export declare const VALID_READINESS_LEVELS: ReadinessLevel[];
3
+ export declare function assertValidLevel(level: string): asserts level is ReadinessLevel;
2
4
  export interface PatternCost {
3
5
  tokens_noop: number;
4
6
  tokens_report: number;
@@ -16,11 +18,30 @@ export interface RegistryPattern {
16
18
  export interface RegistryDoc {
17
19
  patterns: RegistryPattern[];
18
20
  }
21
+ export interface Orchestration {
22
+ /** Normalized mode string, e.g. 'single', 'maker-checker', 'parallel:3', 'debate:2'. */
23
+ mode: string;
24
+ /** Multiplier applied to the action-path token cost. */
25
+ multiplier: number;
26
+ }
27
+ /**
28
+ * Parse an orchestration spec into a multiplier on the action path.
29
+ *
30
+ * The action path (implementer + verifier work) is where multi-agent
31
+ * orchestration lands; the no-op scan and single triage pass are unaffected.
32
+ *
33
+ * single 1x one implementer pass, self-checked (default)
34
+ * maker-checker 2x implementer + an independent verifier pass
35
+ * parallel:N N+1 N candidate agents fan out, plus a merge/arbiter pass
36
+ * debate:R 1+R one proposer plus R critique-and-revise rounds
37
+ */
38
+ export declare function parseOrchestration(spec: string | undefined): Orchestration;
19
39
  export interface EstimateInput {
20
40
  pattern: RegistryPattern;
21
41
  cadence?: string;
22
42
  level: ReadinessLevel;
23
43
  conservative?: boolean;
44
+ orchestration?: string;
24
45
  }
25
46
  export interface EstimateResult {
26
47
  patternId: string;
@@ -31,6 +52,7 @@ export interface EstimateResult {
31
52
  tokenCostTier: string;
32
53
  suggestedDailyCap: number;
33
54
  earlyExitRequired: boolean;
55
+ orchestration: Orchestration;
34
56
  scenarios: {
35
57
  noop: {
36
58
  tokensPerRun: number;
package/dist/estimator.js CHANGED
@@ -1,3 +1,42 @@
1
+ export const VALID_READINESS_LEVELS = ['L1', 'L2', 'L3'];
2
+ export function assertValidLevel(level) {
3
+ if (!VALID_READINESS_LEVELS.includes(level)) {
4
+ throw new Error(`Invalid level: ${level}. Valid: ${VALID_READINESS_LEVELS.join(', ')}`);
5
+ }
6
+ }
7
+ /**
8
+ * Parse an orchestration spec into a multiplier on the action path.
9
+ *
10
+ * The action path (implementer + verifier work) is where multi-agent
11
+ * orchestration lands; the no-op scan and single triage pass are unaffected.
12
+ *
13
+ * single 1x one implementer pass, self-checked (default)
14
+ * maker-checker 2x implementer + an independent verifier pass
15
+ * parallel:N N+1 N candidate agents fan out, plus a merge/arbiter pass
16
+ * debate:R 1+R one proposer plus R critique-and-revise rounds
17
+ */
18
+ export function parseOrchestration(spec) {
19
+ const s = (spec ?? 'single').trim().toLowerCase();
20
+ if (s === '' || s === 'single')
21
+ return { mode: 'single', multiplier: 1 };
22
+ if (s === 'maker-checker')
23
+ return { mode: 'maker-checker', multiplier: 2 };
24
+ const parallel = s.match(/^parallel:(\d+)$/);
25
+ if (parallel) {
26
+ const n = Number(parallel[1]);
27
+ if (n < 2)
28
+ throw new Error(`Invalid orchestration: parallel:N needs N>=2 (got ${n}).`);
29
+ return { mode: `parallel:${n}`, multiplier: n + 1 };
30
+ }
31
+ const debate = s.match(/^debate:(\d+)$/);
32
+ if (debate) {
33
+ const r = Number(debate[1]);
34
+ if (r < 1)
35
+ throw new Error(`Invalid orchestration: debate:R needs R>=1 (got ${r}).`);
36
+ return { mode: `debate:${r}`, multiplier: 1 + r };
37
+ }
38
+ throw new Error(`Invalid orchestration: ${spec}. Valid: single, maker-checker, parallel:N, debate:R`);
39
+ }
1
40
  const INTERVAL_MS = {
2
41
  m: 60_000,
3
42
  h: 3_600_000,
@@ -62,16 +101,21 @@ function formatTokens(n) {
62
101
  return String(n);
63
102
  }
64
103
  export function estimateCost(input) {
104
+ assertValidLevel(input.level);
65
105
  const cadence = input.cadence ?? input.pattern.cadence;
66
106
  const runsPerDay = cadenceToRunsPerDay(cadence, input.conservative);
67
107
  const { cost, token_cost: tokenCostTier } = input.pattern;
68
108
  const mix = realisticMix(input.level, cost.early_exit_required);
109
+ const orchestration = parseOrchestration(input.orchestration);
110
+ // Orchestration overhead lands on the action path only — the no-op scan and
111
+ // the single triage pass do not fan out.
112
+ const actionPerRun = Math.round(cost.tokens_action * orchestration.multiplier);
69
113
  const noopDay = cost.tokens_noop * runsPerDay;
70
114
  const reportDay = cost.tokens_report * runsPerDay;
71
- const actionDay = cost.tokens_action * runsPerDay;
115
+ const actionDay = actionPerRun * runsPerDay;
72
116
  const realisticPerRun = cost.tokens_noop * mix.noop +
73
117
  cost.tokens_report * mix.report +
74
- cost.tokens_action * mix.action;
118
+ actionPerRun * mix.action;
75
119
  const realisticDay = Math.round(realisticPerRun * runsPerDay);
76
120
  const warnings = [];
77
121
  if (cost.early_exit_required) {
@@ -86,6 +130,9 @@ export function estimateCost(input) {
86
130
  if (runsPerDay >= 96) {
87
131
  warnings.push(`High cadence (${runsPerDay} runs/day) — verify early-exit is working.`);
88
132
  }
133
+ if (orchestration.multiplier > 2) {
134
+ warnings.push(`Orchestration ${orchestration.mode} multiplies action cost ${orchestration.multiplier}x — confirm the fan-out or debate depth is justified.`);
135
+ }
89
136
  return {
90
137
  patternId: input.pattern.id,
91
138
  patternName: input.pattern.name,
@@ -95,10 +142,11 @@ export function estimateCost(input) {
95
142
  tokenCostTier,
96
143
  suggestedDailyCap: cost.suggested_daily_cap,
97
144
  earlyExitRequired: cost.early_exit_required,
145
+ orchestration,
98
146
  scenarios: {
99
147
  noop: { tokensPerRun: cost.tokens_noop, tokensPerDay: noopDay },
100
148
  report: { tokensPerRun: cost.tokens_report, tokensPerDay: reportDay },
101
- action: { tokensPerRun: cost.tokens_action, tokensPerDay: actionDay },
149
+ action: { tokensPerRun: actionPerRun, tokensPerDay: actionDay },
102
150
  realistic: {
103
151
  tokensPerRun: Math.round(realisticPerRun),
104
152
  tokensPerDay: realisticDay,
@@ -115,6 +163,9 @@ export function formatEstimateHuman(r) {
115
163
  lines.push('═'.repeat(50));
116
164
  lines.push(`Cadence: ${r.cadence} → ${r.runsPerDay} runs/day`);
117
165
  lines.push(`Level: ${r.level} · Registry tier: ${r.tokenCostTier}`);
166
+ if (r.orchestration.multiplier > 1) {
167
+ lines.push(`Orchestration: ${r.orchestration.mode} · action x${r.orchestration.multiplier}`);
168
+ }
118
169
  lines.push(`Suggested daily cap: ${formatTokens(r.suggestedDailyCap)} tokens`);
119
170
  lines.push('');
120
171
  lines.push('Daily token estimates:');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cobusgreyling/loop-cost",
3
- "version": "1.0.2",
3
+ "version": "1.1.0",
4
4
  "description": "Estimate token budgets and daily costs for loop engineering patterns. By cadence, readiness level (L1/L2/L3), and coding agent tool.",
5
5
  "type": "module",
6
6
  "bin": {
package/registry.json CHANGED
@@ -11,6 +11,8 @@
11
11
  "grok",
12
12
  "claude-code",
13
13
  "codex",
14
+ "openclaw",
15
+ "opencode",
14
16
  "github-actions"
15
17
  ],
16
18
  "skills": [
@@ -54,6 +56,8 @@
54
56
  "grok",
55
57
  "claude-code",
56
58
  "codex",
59
+ "openclaw",
60
+ "opencode",
57
61
  "github-actions"
58
62
  ],
59
63
  "skills": [
@@ -92,6 +96,8 @@
92
96
  "grok",
93
97
  "claude-code",
94
98
  "codex",
99
+ "openclaw",
100
+ "opencode",
95
101
  "github-actions"
96
102
  ],
97
103
  "skills": [
@@ -133,6 +139,8 @@
133
139
  "grok",
134
140
  "claude-code",
135
141
  "codex",
142
+ "openclaw",
143
+ "opencode",
136
144
  "github-actions"
137
145
  ],
138
146
  "skills": [
@@ -173,6 +181,8 @@
173
181
  "grok",
174
182
  "claude-code",
175
183
  "codex",
184
+ "openclaw",
185
+ "opencode",
176
186
  "github-actions"
177
187
  ],
178
188
  "skills": [
@@ -216,6 +226,8 @@
216
226
  "grok",
217
227
  "claude-code",
218
228
  "codex",
229
+ "openclaw",
230
+ "opencode",
219
231
  "github-actions"
220
232
  ],
221
233
  "skills": [
@@ -259,6 +271,8 @@
259
271
  "grok",
260
272
  "claude-code",
261
273
  "codex",
274
+ "openclaw",
275
+ "opencode",
262
276
  "github-actions"
263
277
  ],
264
278
  "skills": [
@@ -279,7 +293,7 @@
279
293
  "ambiguous-duplicates",
280
294
  "stale-closures"
281
295
  ],
282
- "starter": "starters/minimal-loop",
296
+ "starter": "starters/issue-triage",
283
297
  "week_one_mode": "L1",
284
298
  "token_cost": "low",
285
299
  "cost": {