@cobusgreyling/loop-cost 1.0.3 → 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 { assertValidLevel, 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
  `);
@@ -89,6 +96,7 @@ Examples:
89
96
  }
90
97
  try {
91
98
  assertValidLevel(args.level);
99
+ parseOrchestration(args.orchestration);
92
100
  }
93
101
  catch (err) {
94
102
  const msg = err instanceof Error ? err.message : String(err);
@@ -104,6 +112,7 @@ Examples:
104
112
  cadence: args.cadence,
105
113
  level: args.level,
106
114
  conservative: args.conservative,
115
+ orchestration: args.orchestration,
107
116
  });
108
117
  if (args.json)
109
118
  console.log(JSON.stringify(result, null, 2));
@@ -18,11 +18,30 @@ export interface RegistryPattern {
18
18
  export interface RegistryDoc {
19
19
  patterns: RegistryPattern[];
20
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;
21
39
  export interface EstimateInput {
22
40
  pattern: RegistryPattern;
23
41
  cadence?: string;
24
42
  level: ReadinessLevel;
25
43
  conservative?: boolean;
44
+ orchestration?: string;
26
45
  }
27
46
  export interface EstimateResult {
28
47
  patternId: string;
@@ -33,6 +52,7 @@ export interface EstimateResult {
33
52
  tokenCostTier: string;
34
53
  suggestedDailyCap: number;
35
54
  earlyExitRequired: boolean;
55
+ orchestration: Orchestration;
36
56
  scenarios: {
37
57
  noop: {
38
58
  tokensPerRun: number;
package/dist/estimator.js CHANGED
@@ -4,6 +4,39 @@ export function assertValidLevel(level) {
4
4
  throw new Error(`Invalid level: ${level}. Valid: ${VALID_READINESS_LEVELS.join(', ')}`);
5
5
  }
6
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
+ }
7
40
  const INTERVAL_MS = {
8
41
  m: 60_000,
9
42
  h: 3_600_000,
@@ -73,12 +106,16 @@ export function estimateCost(input) {
73
106
  const runsPerDay = cadenceToRunsPerDay(cadence, input.conservative);
74
107
  const { cost, token_cost: tokenCostTier } = input.pattern;
75
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);
76
113
  const noopDay = cost.tokens_noop * runsPerDay;
77
114
  const reportDay = cost.tokens_report * runsPerDay;
78
- const actionDay = cost.tokens_action * runsPerDay;
115
+ const actionDay = actionPerRun * runsPerDay;
79
116
  const realisticPerRun = cost.tokens_noop * mix.noop +
80
117
  cost.tokens_report * mix.report +
81
- cost.tokens_action * mix.action;
118
+ actionPerRun * mix.action;
82
119
  const realisticDay = Math.round(realisticPerRun * runsPerDay);
83
120
  const warnings = [];
84
121
  if (cost.early_exit_required) {
@@ -93,6 +130,9 @@ export function estimateCost(input) {
93
130
  if (runsPerDay >= 96) {
94
131
  warnings.push(`High cadence (${runsPerDay} runs/day) — verify early-exit is working.`);
95
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
+ }
96
136
  return {
97
137
  patternId: input.pattern.id,
98
138
  patternName: input.pattern.name,
@@ -102,10 +142,11 @@ export function estimateCost(input) {
102
142
  tokenCostTier,
103
143
  suggestedDailyCap: cost.suggested_daily_cap,
104
144
  earlyExitRequired: cost.early_exit_required,
145
+ orchestration,
105
146
  scenarios: {
106
147
  noop: { tokensPerRun: cost.tokens_noop, tokensPerDay: noopDay },
107
148
  report: { tokensPerRun: cost.tokens_report, tokensPerDay: reportDay },
108
- action: { tokensPerRun: cost.tokens_action, tokensPerDay: actionDay },
149
+ action: { tokensPerRun: actionPerRun, tokensPerDay: actionDay },
109
150
  realistic: {
110
151
  tokensPerRun: Math.round(realisticPerRun),
111
152
  tokensPerDay: realisticDay,
@@ -122,6 +163,9 @@ export function formatEstimateHuman(r) {
122
163
  lines.push('═'.repeat(50));
123
164
  lines.push(`Cadence: ${r.cadence} → ${r.runsPerDay} runs/day`);
124
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
+ }
125
169
  lines.push(`Suggested daily cap: ${formatTokens(r.suggestedDailyCap)} tokens`);
126
170
  lines.push('');
127
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.3",
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": [