@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 +38 -0
- package/dist/cli.js +11 -2
- package/dist/estimator.d.ts +20 -0
- package/dist/estimator.js +47 -3
- package/package.json +1 -1
- package/registry.json +14 -0
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));
|
package/dist/estimator.d.ts
CHANGED
|
@@ -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 =
|
|
115
|
+
const actionDay = actionPerRun * runsPerDay;
|
|
79
116
|
const realisticPerRun = cost.tokens_noop * mix.noop +
|
|
80
117
|
cost.tokens_report * mix.report +
|
|
81
|
-
|
|
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:
|
|
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
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": [
|