@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 +38 -0
- package/dist/cli.js +19 -2
- package/dist/estimator.d.ts +22 -0
- package/dist/estimator.js +54 -3
- package/package.json +1 -1
- package/registry.json +15 -1
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));
|
package/dist/estimator.d.ts
CHANGED
|
@@ -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 =
|
|
115
|
+
const actionDay = actionPerRun * runsPerDay;
|
|
72
116
|
const realisticPerRun = cost.tokens_noop * mix.noop +
|
|
73
117
|
cost.tokens_report * mix.report +
|
|
74
|
-
|
|
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:
|
|
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
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/
|
|
296
|
+
"starter": "starters/issue-triage",
|
|
283
297
|
"week_one_mode": "L1",
|
|
284
298
|
"token_cost": "low",
|
|
285
299
|
"cost": {
|