create-byan-agent 2.39.0 → 2.42.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/CHANGELOG.md +153 -1
- package/install/bin/byan-handoff.js +139 -0
- package/install/bin/create-byan-agent-v2.js +70 -9
- package/install/lib/codex-autodelegate-setup.js +100 -0
- package/install/lib/codex-native-setup.js +94 -2
- package/install/lib/project-handoff.js +300 -0
- package/install/lib/rtk-integration.js +81 -14
- package/install/templates/.claude/CLAUDE.md +16 -0
- package/install/templates/.claude/hooks/codex-autodelegate.js +74 -0
- package/install/templates/.claude/hooks/lib/autodelegate-decision.js +152 -0
- package/install/templates/.claude/hooks/lib/perf-routing.js +47 -0
- package/install/templates/.claude/hooks/lib/usage-estimator.js +262 -0
- package/install/templates/.claude/hooks/tier-script-guard.js +185 -0
- package/install/templates/.claude/rules/native-workflows.md +33 -7
- package/install/templates/.claude/settings.json +35 -22
- package/install/templates/.claude/skills/byan-byan/SKILL.md +21 -2
- package/install/templates/.claude/skills/byan-codex/SKILL.md +7 -0
- package/install/templates/.claude/skills/byan-hermes-dispatch/SKILL.md +3 -1
- package/install/templates/.claude/workflows/sprint-planning.js +2 -2
- package/install/templates/.claude/workflows/testarch-trace.js +3 -2
- package/install/templates/.codex/skills/byan/SKILL.md +77 -0
- package/install/templates/_byan/INDEX.md +6 -2
- package/install/templates/_byan/_config/workflow-manifest.csv +1 -1
- package/install/templates/_byan/mcp/byan-mcp-server/bin/byan-tier-script.js +52 -0
- package/install/templates/_byan/mcp/byan-mcp-server/lib/dispatch.js +22 -0
- package/install/templates/_byan/mcp/byan-mcp-server/lib/native-tiers.js +26 -9
- package/install/templates/_byan/mcp/byan-mcp-server/lib/tier-script.js +104 -0
- package/install/templates/_byan/mcp/byan-mcp-server/lib/workflows-lint.js +92 -6
- package/install/templates/_byan/mcp/byan-mcp-server/server.js +22 -7
- package/install/templates/_byan/mcp/byan-mcp-server/skill-bundles-manifest.json +3 -3
- package/install/templates/_byan/workflow/simple/byan/project-handoff-workflow.md +90 -0
- package/install/templates/dist/skill-bundles/byan-byan.zip +0 -0
- package/install/templates/dist/skill-bundles/byan-codex.zip +0 -0
- package/install/templates/dist/skill-bundles/byan-hermes-dispatch.zip +0 -0
- package/install/templates/docs/codex-auto-delegation.md +140 -0
- package/install/templates/docs/native-workflows-contract.md +55 -12
- package/package.json +2 -1
- package/src/loadbalancer/capability-matrix.js +14 -0
- package/src/loadbalancer/degradation-ladder.js +121 -0
- package/src/loadbalancer/loadbalancer.default.yaml +23 -1
- package/src/loadbalancer/mcp-server.js +200 -18
- package/src/loadbalancer/providers/codex-provider.js +260 -0
- package/src/loadbalancer/providers/factory.js +36 -0
- package/src/loadbalancer/subscription-window.js +142 -0
- package/src/loadbalancer/switch-tolerance.js +63 -0
- package/src/loadbalancer/tools/index.js +17 -2
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// Per-script tiering report + gate decision (the engine under the tier hook
|
|
2
|
+
// and the byan-tier-script bin).
|
|
3
|
+
//
|
|
4
|
+
// The Workflow tool runs every agent() leaf on the session model unless the
|
|
5
|
+
// script pins opts.model, and the sandbox forbids importing native-tiers at
|
|
6
|
+
// runtime — so the ONLY moment the tiering doctrine can reach an AD-HOC script
|
|
7
|
+
// is when the script text crosses the Workflow tool boundary. This module
|
|
8
|
+
// analyzes that text: one verdict per statically-labelled leaf, against the
|
|
9
|
+
// same source of truth the repo linter enforces (native-tiers.js).
|
|
10
|
+
//
|
|
11
|
+
// The gate is deny-ONCE and never rewrites the script (STRICT-2 No Downgrade:
|
|
12
|
+
// a wrong auto-stamp would be the exact regression the doctrine forbids). The
|
|
13
|
+
// author fixes the listed leaves, or asserts the deep choices are deliberate
|
|
14
|
+
// with the acknowledgment marker. An identical resubmission after a deny
|
|
15
|
+
// passes — the gate forces a decision, it does not trap the turn.
|
|
16
|
+
|
|
17
|
+
import crypto from 'node:crypto';
|
|
18
|
+
import { classifyLeaf, tierFor, TIER_MODEL, TIERS, LEAF_TYPES } from './native-tiers.js';
|
|
19
|
+
import { stripComments, extractLabelledLeaves } from './workflows-lint.js';
|
|
20
|
+
|
|
21
|
+
// Acknowledgment marker, raw-text (comment form survives comment-stripping
|
|
22
|
+
// concerns by design — mirrors the BYAN-BENCH marker family). Writing it is an
|
|
23
|
+
// explicit authoring act: "I reviewed the tiering; the deep leaves are
|
|
24
|
+
// deliberate."
|
|
25
|
+
export const ACK_RE = /BYAN-TIER:\s*reviewed\b/;
|
|
26
|
+
|
|
27
|
+
function verdictFor(cls, model) {
|
|
28
|
+
const expected = TIER_MODEL[tierFor(cls)]; // 'haiku' | 'sonnet' | null (inherit)
|
|
29
|
+
if (expected === null) {
|
|
30
|
+
// Protected class: any pinned model is a downgrade or a pin-up — both wrong.
|
|
31
|
+
return model === null ? 'ok' : 'violation';
|
|
32
|
+
}
|
|
33
|
+
if (model === null) return 'missing-tier';
|
|
34
|
+
if (model === expected) return 'ok';
|
|
35
|
+
// Exploration may ride ABOVE its floor (sonnet on a cheap leaf wastes a
|
|
36
|
+
// little, breaks nothing). Anything else — haiku on mech-, opus anywhere —
|
|
37
|
+
// is below a declared tier or an unknown pin.
|
|
38
|
+
if (cls === LEAF_TYPES.EXPLORATION && model === TIER_MODEL[TIERS.BALANCED]) return 'ok';
|
|
39
|
+
return 'violation';
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// analyzeScript(src) -> { acknowledged, agentCalls, leaves, gaps, violations }
|
|
43
|
+
// leaves: [{ label, model, class, expectedModel, verdict }] for every
|
|
44
|
+
// statically-labelled opts object. Unlabelled agent() calls classify to the
|
|
45
|
+
// deep default and cannot gap, so they are only counted (agentCalls).
|
|
46
|
+
export function analyzeScript(src) {
|
|
47
|
+
const leaves = extractLabelledLeaves(src).map(({ label, model }) => {
|
|
48
|
+
const cls = classifyLeaf({ label });
|
|
49
|
+
return {
|
|
50
|
+
label,
|
|
51
|
+
model,
|
|
52
|
+
class: cls,
|
|
53
|
+
expectedModel: TIER_MODEL[tierFor(cls)],
|
|
54
|
+
verdict: verdictFor(cls, model),
|
|
55
|
+
};
|
|
56
|
+
});
|
|
57
|
+
return {
|
|
58
|
+
acknowledged: ACK_RE.test(String(src)),
|
|
59
|
+
agentCalls: (stripComments(src).match(/\bagent\s*\(/g) || []).length,
|
|
60
|
+
leaves,
|
|
61
|
+
gaps: leaves.filter((l) => l.verdict === 'missing-tier'),
|
|
62
|
+
violations: leaves.filter((l) => l.verdict === 'violation'),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// One line per offending leaf, then the two exits (fix or acknowledge). The
|
|
67
|
+
// reason is the whole teaching surface of the gate — it must carry the exact
|
|
68
|
+
// label and the exact value to write.
|
|
69
|
+
export function formatGateReason(analysis) {
|
|
70
|
+
const lines = ['BYAN tier gate: this workflow script has undecided or invalid model tiers.'];
|
|
71
|
+
for (const g of analysis.gaps) {
|
|
72
|
+
lines.push(`- leaf '${g.label}' (${g.class}) has no model: add model: '${g.expectedModel}'`);
|
|
73
|
+
}
|
|
74
|
+
for (const v of analysis.violations) {
|
|
75
|
+
const fix = v.expectedModel === null ? 'omit model: (deep leaves inherit the session model)' : `use model: '${v.expectedModel}'`;
|
|
76
|
+
lines.push(`- leaf '${v.label}' (${v.class}) carries model: '${v.model}': ${fix}`);
|
|
77
|
+
}
|
|
78
|
+
lines.push(
|
|
79
|
+
"Fix the listed leaves, or add the comment '// BYAN-TIER: reviewed' to assert the current tiers are deliberate. This gate denies once: an identical resubmission passes."
|
|
80
|
+
);
|
|
81
|
+
return lines.join('\n');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Pure gate decision (the hook is only the I/O shell around this).
|
|
85
|
+
// Precedence: escape hatch > acknowledgment > clean > deny-once memory > deny.
|
|
86
|
+
export function decideTierGate({ analysis, escaped = false, scriptHash = null, priorDenyHash = null } = {}) {
|
|
87
|
+
if (escaped) return { decision: 'allow', code: 'escape-hatch' };
|
|
88
|
+
if (analysis.acknowledged) return { decision: 'allow', code: 'acknowledged' };
|
|
89
|
+
if (analysis.gaps.length === 0 && analysis.violations.length === 0) {
|
|
90
|
+
return { decision: 'allow', code: 'clean' };
|
|
91
|
+
}
|
|
92
|
+
if (scriptHash && priorDenyHash && scriptHash === priorDenyHash) {
|
|
93
|
+
return { decision: 'allow', code: 'unchanged-after-deny' };
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
decision: 'deny',
|
|
97
|
+
code: analysis.violations.length ? 'violations' : 'gaps',
|
|
98
|
+
reason: formatGateReason(analysis),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function hashScript(src) {
|
|
103
|
+
return crypto.createHash('sha1').update(String(src)).digest('hex');
|
|
104
|
+
}
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
import fs from 'node:fs';
|
|
14
14
|
import path from 'node:path';
|
|
15
|
-
import { isKnownTierModel, isDowngradeModel, classifyLeaf, LEAF_TYPES } from './native-tiers.js';
|
|
15
|
+
import { isKnownTierModel, isDowngradeModel, classifyLeaf, LEAF_TYPES, TIER_MODEL } from './native-tiers.js';
|
|
16
16
|
|
|
17
17
|
// Strip /* block */ and // line comments. Preserve "://" inside strings (URLs)
|
|
18
18
|
// by only treating // as a comment when not preceded by a colon.
|
|
@@ -104,6 +104,30 @@ export function metaLiteralViolations(src) {
|
|
|
104
104
|
const MODEL_RE = /\bmodel:\s*(['"`])([^'"`]*)\1/g;
|
|
105
105
|
const LABEL_RE = /\blabel:\s*(['"`])([^'"`]*)\1/g;
|
|
106
106
|
|
|
107
|
+
// Remove the `export const meta = { ... }` literal before scanning model
|
|
108
|
+
// tokens. The harness meta spec allows `model` on a phases entry (a per-phase
|
|
109
|
+
// display/override declaration) — it carries no label by design and must not
|
|
110
|
+
// read as a leaf downgrade. Balanced-brace walk; no-op when unbalanced.
|
|
111
|
+
// String-unaware by choice: a brace inside a meta string unbalances the walk,
|
|
112
|
+
// which then no-ops and the meta model token stays visible to the scan — the
|
|
113
|
+
// failure mode OVER-reports (fails closed), never hides a real violation.
|
|
114
|
+
// Exported: the routing integration test scans sources with its own regexes
|
|
115
|
+
// (an independent double-check) but must share THIS meta-handling.
|
|
116
|
+
export function stripMetaLiteral(code) {
|
|
117
|
+
const start = code.search(/export const meta\s*=\s*\{/);
|
|
118
|
+
if (start === -1) return code;
|
|
119
|
+
const open = code.indexOf('{', start);
|
|
120
|
+
let depth = 0;
|
|
121
|
+
for (let i = open; i < code.length; i++) {
|
|
122
|
+
if (code[i] === '{') depth += 1;
|
|
123
|
+
else if (code[i] === '}') {
|
|
124
|
+
depth -= 1;
|
|
125
|
+
if (depth === 0) return code.slice(0, open + 1) + code.slice(i);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return code;
|
|
129
|
+
}
|
|
130
|
+
|
|
107
131
|
function nearestLabelBefore(code, modelIndex) {
|
|
108
132
|
const before = code.slice(0, modelIndex);
|
|
109
133
|
let last = null;
|
|
@@ -120,7 +144,7 @@ function nearestLabelBefore(code, modelIndex) {
|
|
|
120
144
|
}
|
|
121
145
|
|
|
122
146
|
export function modelRoutingViolations(src) {
|
|
123
|
-
const code = stripComments(src);
|
|
147
|
+
const code = stripMetaLiteral(stripComments(src));
|
|
124
148
|
const out = [];
|
|
125
149
|
let m;
|
|
126
150
|
MODEL_RE.lastIndex = 0;
|
|
@@ -137,14 +161,56 @@ export function modelRoutingViolations(src) {
|
|
|
137
161
|
if (!label) {
|
|
138
162
|
out.push({
|
|
139
163
|
id: 'downgrade-without-label',
|
|
140
|
-
msg: `a model downgrade ('${model}') must sit on a labelled exploration leaf; no label found in this opts object`,
|
|
164
|
+
msg: `a model downgrade ('${model}') must sit on a labelled exploration/mech- leaf; no label found in this opts object`,
|
|
141
165
|
});
|
|
142
166
|
continue;
|
|
143
167
|
}
|
|
144
|
-
if (isDowngradeModel(model)
|
|
168
|
+
if (!isDowngradeModel(model)) continue;
|
|
169
|
+
// Per-class floor: exploration accepts any downgrade tier (haiku or sonnet,
|
|
170
|
+
// both at-or-above its cheap floor); a mech- leaf accepts exactly the
|
|
171
|
+
// balanced tier (haiku would sit BELOW the tier its label declares); every
|
|
172
|
+
// protected class refuses both (STRICT-2 No Downgrade).
|
|
173
|
+
const cls = classifyLeaf({ label });
|
|
174
|
+
if (cls === LEAF_TYPES.EXPLORATION) continue;
|
|
175
|
+
if (cls === LEAF_TYPES.MECHANICAL) {
|
|
176
|
+
if (model !== TIER_MODEL.balanced) {
|
|
177
|
+
out.push({
|
|
178
|
+
id: 'mechanical-below-tier',
|
|
179
|
+
msg: `mech- leaf '${label}' carries '${model}' but the mechanical tier is '${TIER_MODEL.balanced}'; a declared-mechanical check must not drop further`,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
out.push({
|
|
185
|
+
id: 'protected-leaf-downgraded',
|
|
186
|
+
msg: `leaf '${label}' is protected (${cls}) but carries downgrade model '${model}'; only exploration (read/load/parse/detect) and explicit mech- leaves may downgrade (STRICT-2 No Downgrade)`,
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
return out;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// MECHANICAL opt-in consistency (HARD rule, part of validateContract).
|
|
193
|
+
//
|
|
194
|
+
// A mech- label is an explicit authoring declaration: "this check is binary and
|
|
195
|
+
// judgment-free, run it on the balanced tier". Declaring it and then omitting
|
|
196
|
+
// opts.model half-applies the opt-in — the leaf silently runs deep, which is
|
|
197
|
+
// exactly the waste the label promised to avoid. Since the prefix exists only
|
|
198
|
+
// as this convention (no legacy labels carry it), enforcing it hard cannot trip
|
|
199
|
+
// legitimate work. Order-independent via sameOptsObjectText, comment-stripped.
|
|
200
|
+
export function mechanicalLabelViolations(src) {
|
|
201
|
+
const code = stripComments(src);
|
|
202
|
+
const out = [];
|
|
203
|
+
let m;
|
|
204
|
+
LABEL_RE.lastIndex = 0;
|
|
205
|
+
while ((m = LABEL_RE.exec(code))) {
|
|
206
|
+
const label = m[2];
|
|
207
|
+
if (classifyLeaf({ label }) !== LEAF_TYPES.MECHANICAL) continue;
|
|
208
|
+
const objText = sameOptsObjectText(code, m.index, m.index + m[0].length);
|
|
209
|
+
MODEL_RE.lastIndex = 0;
|
|
210
|
+
if (!MODEL_RE.exec(objText)) {
|
|
145
211
|
out.push({
|
|
146
|
-
id: '
|
|
147
|
-
msg: `leaf '${label}'
|
|
212
|
+
id: 'mechanical-without-model',
|
|
213
|
+
msg: `mech- leaf '${label}' declares a mechanical check but omits opts.model; add model: '${TIER_MODEL.balanced}' (or drop the mech- prefix if the check bears judgment)`,
|
|
148
214
|
});
|
|
149
215
|
}
|
|
150
216
|
}
|
|
@@ -208,6 +274,25 @@ export function untieredExplorationViolations(src) {
|
|
|
208
274
|
return out;
|
|
209
275
|
}
|
|
210
276
|
|
|
277
|
+
// Shared parsing primitive — every statically-labelled opts object with its
|
|
278
|
+
// order-independent model (or null). This is the ONE place that knows how to
|
|
279
|
+
// read agent() opts out of a script source; tier-script.js (the per-leaf
|
|
280
|
+
// report + hook gate) consumes it instead of re-owning the regexes. Fresh
|
|
281
|
+
// regex instances per call: no shared lastIndex across modules.
|
|
282
|
+
export function extractLabelledLeaves(src) {
|
|
283
|
+
const code = stripComments(src);
|
|
284
|
+
const out = [];
|
|
285
|
+
const labelRe = new RegExp(LABEL_RE.source, 'g');
|
|
286
|
+
let m;
|
|
287
|
+
while ((m = labelRe.exec(code))) {
|
|
288
|
+
const objText = sameOptsObjectText(code, m.index, m.index + m[0].length);
|
|
289
|
+
const modelRe = new RegExp(MODEL_RE.source, 'g');
|
|
290
|
+
const mm = modelRe.exec(objText);
|
|
291
|
+
out.push({ label: m[2], model: mm ? mm[2] : null });
|
|
292
|
+
}
|
|
293
|
+
return out;
|
|
294
|
+
}
|
|
295
|
+
|
|
211
296
|
// Full native-workflow contract: state-coupling (comment-stripped) + clock/RNG
|
|
212
297
|
// (raw) + meta-literal-first + model-routing anti-downgrade. Returns the
|
|
213
298
|
// combined [{ id, msg }] violations.
|
|
@@ -226,6 +311,7 @@ export function validateContract(src) {
|
|
|
226
311
|
...clockRngViolations(src),
|
|
227
312
|
...metaLiteralViolations(src),
|
|
228
313
|
...modelRoutingViolations(src),
|
|
314
|
+
...mechanicalLabelViolations(src),
|
|
229
315
|
];
|
|
230
316
|
}
|
|
231
317
|
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
CallToolRequestSchema,
|
|
9
9
|
ListToolsRequestSchema,
|
|
10
10
|
} from '@modelcontextprotocol/sdk/types.js';
|
|
11
|
-
import { dispatch } from './lib/dispatch.js';
|
|
11
|
+
import { dispatch, dispatchBatch } from './lib/dispatch.js';
|
|
12
12
|
import { resolveConfig } from './lib/resolve-config.js';
|
|
13
13
|
import { harvest as harvestInsights, renderDigest as renderInsightDigest } from './lib/insight-harvest.js';
|
|
14
14
|
import { appendOutcome } from './lib/outcome-buffer.js';
|
|
@@ -333,11 +333,11 @@ const tools = [
|
|
|
333
333
|
{
|
|
334
334
|
name: 'byan_dispatch',
|
|
335
335
|
description:
|
|
336
|
-
'BYAN Dispatcher: routes a unit of work along two independent axes. STRATEGY (where it runs: main-thread / agent-subagent-worktree / mcp-worker) from the scalar score + parallelizable. TIER (which model) from the task NATURE via native-tiers (the single source of truth):
|
|
336
|
+
'BYAN Dispatcher: routes a unit of work along two independent axes. STRATEGY (where it runs: main-thread / agent-subagent-worktree / mcp-worker) from the scalar score + parallelizable. TIER (which model) from the task NATURE via native-tiers (the single source of truth): exploration downgrades to haiku, explicit mechanical checks to sonnet; implementation/verification/analysis stay deep (inherit the session model); never pins up to opus. Rule-based, no API call. Returns { score, strategy, nature, tier, model, reasoning }. BATCH mode: pass `leaves` (array of { label, nature? }) to tier every agent() leaf of a workflow script BEFORE writing it — returns one { label, nature, tier, model } per leaf, no strategy axis.',
|
|
337
337
|
inputSchema: {
|
|
338
338
|
type: 'object',
|
|
339
339
|
properties: {
|
|
340
|
-
task: { type: 'string', description: 'Short task description.' },
|
|
340
|
+
task: { type: 'string', description: 'Short task description. Required unless `leaves` is passed (batch mode).' },
|
|
341
341
|
complexity: {
|
|
342
342
|
type: 'number',
|
|
343
343
|
description: 'Complexity score 0-100 (optional, will estimate from task length if absent).',
|
|
@@ -348,11 +348,26 @@ const tools = [
|
|
|
348
348
|
},
|
|
349
349
|
nature: {
|
|
350
350
|
type: 'string',
|
|
351
|
-
enum: ['exploration', 'implementation', 'verification', 'analysis'],
|
|
352
|
-
description: 'Optional task nature. A valid value sets the model tier directly; otherwise the nature is classified from the task text.
|
|
351
|
+
enum: ['exploration', 'mechanical', 'implementation', 'verification', 'analysis'],
|
|
352
|
+
description: 'Optional task nature. A valid value sets the model tier directly; otherwise the nature is classified from the task text. Exploration (haiku) and mechanical (sonnet) are the only downgrade-safe natures.',
|
|
353
|
+
},
|
|
354
|
+
leaves: {
|
|
355
|
+
type: 'array',
|
|
356
|
+
description: 'Batch mode: the planned agent() leaves of a workflow script, each { label, nature? }. Returns the opts.model value per leaf; write model: only where it is non-null.',
|
|
357
|
+
items: {
|
|
358
|
+
type: 'object',
|
|
359
|
+
properties: {
|
|
360
|
+
label: { type: 'string', description: 'The leaf label (the curated signal classifyLeaf keys on).' },
|
|
361
|
+
nature: {
|
|
362
|
+
type: 'string',
|
|
363
|
+
enum: ['exploration', 'mechanical', 'implementation', 'verification', 'analysis'],
|
|
364
|
+
description: 'Optional explicit nature; wins over label classification.',
|
|
365
|
+
},
|
|
366
|
+
},
|
|
367
|
+
additionalProperties: false,
|
|
368
|
+
},
|
|
353
369
|
},
|
|
354
370
|
},
|
|
355
|
-
required: ['task'],
|
|
356
371
|
additionalProperties: false,
|
|
357
372
|
},
|
|
358
373
|
},
|
|
@@ -1561,7 +1576,7 @@ export function createByanServer({ token, remoteOnly = false } = {}) {
|
|
|
1561
1576
|
}
|
|
1562
1577
|
|
|
1563
1578
|
if (name === 'byan_dispatch') {
|
|
1564
|
-
const result = dispatch(args);
|
|
1579
|
+
const result = Array.isArray(args.leaves) ? dispatchBatch(args.leaves) : dispatch(args);
|
|
1565
1580
|
return {
|
|
1566
1581
|
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
1567
1582
|
};
|
|
@@ -94,7 +94,7 @@
|
|
|
94
94
|
"name": "byan-byan",
|
|
95
95
|
"module": "core",
|
|
96
96
|
"tier": "connector-bound",
|
|
97
|
-
"sourceHash": "
|
|
97
|
+
"sourceHash": "6798beb68df9831e0b9bf3ca17100174316c51b2671d96b2ef5bff6def4eabc7"
|
|
98
98
|
},
|
|
99
99
|
"byan-byan-v2": {
|
|
100
100
|
"name": "byan-byan-v2",
|
|
@@ -154,7 +154,7 @@
|
|
|
154
154
|
"name": "byan-codex",
|
|
155
155
|
"module": "core",
|
|
156
156
|
"tier": "standalone",
|
|
157
|
-
"sourceHash": "
|
|
157
|
+
"sourceHash": "32f2ae4a9bfe7a0f90f2ca9787e472c57b847bc36ac9bc6fa0050af70c0af27c"
|
|
158
158
|
},
|
|
159
159
|
"byan-drawio": {
|
|
160
160
|
"name": "byan-drawio",
|
|
@@ -184,7 +184,7 @@
|
|
|
184
184
|
"name": "byan-hermes-dispatch",
|
|
185
185
|
"module": "core",
|
|
186
186
|
"tier": "connector-bound",
|
|
187
|
-
"sourceHash": "
|
|
187
|
+
"sourceHash": "e890a80916fedcf888170f550fc4d7ce289f6dc15243d00675e72e80c8579fdb"
|
|
188
188
|
},
|
|
189
189
|
"byan-insight": {
|
|
190
190
|
"name": "byan-insight",
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: project-handoff
|
|
3
|
+
description: "Export/import portable Markdown project state between Claude Code and Codex"
|
|
4
|
+
version: "1.0.0"
|
|
5
|
+
module: byan
|
|
6
|
+
phases: 4
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# BYAN Project Handoff Workflow
|
|
10
|
+
|
|
11
|
+
## Purpose
|
|
12
|
+
|
|
13
|
+
Use this workflow when a BYAN session must move between Claude Code and Codex,
|
|
14
|
+
usually because one assistant is close to a usage limit. The handoff artifact is
|
|
15
|
+
a Markdown file under `_byan-output/handoffs/`; it is portable repo state, not
|
|
16
|
+
native assistant memory.
|
|
17
|
+
|
|
18
|
+
## Contract
|
|
19
|
+
|
|
20
|
+
- Source of truth: repository files plus `_byan/` and `_byan-output/`.
|
|
21
|
+
- Artifact: `_byan-output/handoffs/<timestamp>-<from>-to-<to>-<task>.md`.
|
|
22
|
+
- Format: human-readable Markdown with a parseable `json byan-handoff` block.
|
|
23
|
+
- Direction: Claude -> Codex and Codex -> Claude use the same format.
|
|
24
|
+
- Native memories (`~/.claude`, `~/.codex`) may help locally but are not needed
|
|
25
|
+
to resume.
|
|
26
|
+
|
|
27
|
+
## Commands
|
|
28
|
+
|
|
29
|
+
Export from the current assistant:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
byan-handoff export --from claude --to codex \
|
|
33
|
+
--task "current feature" \
|
|
34
|
+
--summary "what happened so far" \
|
|
35
|
+
--next "first action for the next assistant"
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Import on the next assistant:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
byan-handoff latest --from claude --prompt
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Or import a specific file:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
byan-handoff import _byan-output/handoffs/<file>.md --prompt
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Phase 1: Export
|
|
51
|
+
|
|
52
|
+
1. Re-read the active request and any locked BYAN strict scope.
|
|
53
|
+
2. Run `byan-handoff export` with explicit `--from`, `--to`, `--task`,
|
|
54
|
+
`--summary`, and at least one `--next`.
|
|
55
|
+
3. Add useful optional details:
|
|
56
|
+
- `--decision "..."` for architectural or product decisions.
|
|
57
|
+
- `--command "..."` for tests or checks already run.
|
|
58
|
+
- `--blocker "..."` for gaps, failures, or risks.
|
|
59
|
+
- `--note "..."` for context the next assistant should not rediscover.
|
|
60
|
+
4. Report the generated file path to the user.
|
|
61
|
+
|
|
62
|
+
## Phase 2: Transfer
|
|
63
|
+
|
|
64
|
+
The user opens the same repository with the other assistant. No hidden copy step
|
|
65
|
+
is required when both assistants share the same working tree. If the handoff is
|
|
66
|
+
moving to another machine, copy the Markdown file with the repo or paste its
|
|
67
|
+
`Resume Prompt` section.
|
|
68
|
+
|
|
69
|
+
## Phase 3: Import
|
|
70
|
+
|
|
71
|
+
1. Run `byan-handoff latest --from <source> --prompt` or
|
|
72
|
+
`byan-handoff import <file> --prompt`.
|
|
73
|
+
2. Treat the printed prompt as the starting context.
|
|
74
|
+
3. Inspect the listed files before editing.
|
|
75
|
+
4. Check `_byan-output/fd-state.json` and strict status if the handoff names an
|
|
76
|
+
active FD.
|
|
77
|
+
|
|
78
|
+
## Phase 4: Continue
|
|
79
|
+
|
|
80
|
+
1. Continue from the `Next Actions` section.
|
|
81
|
+
2. Update the same FD rather than starting a parallel one unless the handoff says
|
|
82
|
+
the old FD is complete or aborted.
|
|
83
|
+
3. Before switching back, create a new handoff in the opposite direction.
|
|
84
|
+
|
|
85
|
+
## Validation Checklist
|
|
86
|
+
|
|
87
|
+
- [ ] Handoff file exists under `_byan-output/handoffs/`.
|
|
88
|
+
- [ ] `byan-handoff latest --from <source> --prompt` prints a usable resume prompt.
|
|
89
|
+
- [ ] The next assistant inspected files listed in `Files Touched`.
|
|
90
|
+
- [ ] Any strict-mode gaps are surfaced, not hidden.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# Codex Auto-Delegation
|
|
2
|
+
|
|
3
|
+
Hand delegable work to Codex on your **ChatGPT subscription** (no API credit)
|
|
4
|
+
when Claude nears its 5h limit — automatically, from inside Claude Code.
|
|
5
|
+
|
|
6
|
+
This is the native, opt-in layer on top of the Codex load-balancer pool
|
|
7
|
+
(`docs/loadbalancer-multipool.md`). It does not replace judgment: only delegable
|
|
8
|
+
work crosses the line.
|
|
9
|
+
|
|
10
|
+
## What it does
|
|
11
|
+
|
|
12
|
+
Every turn, a `UserPromptSubmit` hook estimates how much of the Claude 5h window
|
|
13
|
+
you have burned and looks at the task you just asked for. If the task is
|
|
14
|
+
delegable (code / mechanical) — or if you are over the pressure threshold — BYAN
|
|
15
|
+
injects a one-line advisory nudge proposing you hand it to Codex via
|
|
16
|
+
`codex:codex-rescue --model gpt-5.4`. You still decide, and you still verify
|
|
17
|
+
Codex's output before commit.
|
|
18
|
+
|
|
19
|
+
Three triggers, in priority order:
|
|
20
|
+
|
|
21
|
+
1. **Pressure** — estimated Claude 5h usage `>= threshold` (default **80%**) ->
|
|
22
|
+
propose offloading **everything** delegable this session.
|
|
23
|
+
2. **Nature** — the request looks like delegable coding work -> propose handing
|
|
24
|
+
**that** task over. Works with no gauge at all.
|
|
25
|
+
3. **Perf** (opt-in, off by default) — a configurable forces table says Codex is
|
|
26
|
+
reputed stronger for this kind of task. See "Perf routing" below.
|
|
27
|
+
|
|
28
|
+
**The red line** (held): only delegable natures are proposed. Judgment,
|
|
29
|
+
analysis, soul and verification stay on Claude.
|
|
30
|
+
|
|
31
|
+
## Enable it
|
|
32
|
+
|
|
33
|
+
At install (`npx create-byan-agent`), the Codex step asks:
|
|
34
|
+
|
|
35
|
+
> Add Codex as a backup pool?
|
|
36
|
+
|
|
37
|
+
Before that optional backup-pool prompt, the yanstaller performs the native Codex
|
|
38
|
+
setup whenever Codex is selected (Codex-only or Claude+Codex):
|
|
39
|
+
|
|
40
|
+
1. it writes the BYAN MCP entry into `~/.codex/config.toml`;
|
|
41
|
+
2. it installs BYAN's native Codex skills into `~/.codex/skills`, including the
|
|
42
|
+
`byan` entrypoint skill used by `$byan`;
|
|
43
|
+
3. it keeps the project-local `.codex/prompts/` stubs for backward
|
|
44
|
+
compatibility.
|
|
45
|
+
|
|
46
|
+
If `~/.codex` does not exist yet, the yanstaller creates it because Codex was
|
|
47
|
+
explicitly selected.
|
|
48
|
+
|
|
49
|
+
Those are separate from auto-delegation. Without the native skills, Codex may
|
|
50
|
+
have the MCP server wired but still fail to expose `$byan` / BYAN skill
|
|
51
|
+
invocations on a fresh machine.
|
|
52
|
+
|
|
53
|
+
Answer yes to the backup-pool prompt and it links your local Codex (the
|
|
54
|
+
`codex login` session — your ChatGPT subscription, **not** an API key) and writes
|
|
55
|
+
`_byan/_config/autodelegate.json`, which **arms** the hook. Without that file the
|
|
56
|
+
hook is a silent no-op (disarmed by default).
|
|
57
|
+
|
|
58
|
+
Non-interactive install: set `BYAN_CODEX_AUTODELEGATE=1`.
|
|
59
|
+
|
|
60
|
+
If Codex is not linked yet, the installer prints the device-flow to run on the
|
|
61
|
+
machine (needed on a headless server, where the default localhost browser
|
|
62
|
+
redirect fails):
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
codex login --device-auth
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
then re-run the installer.
|
|
69
|
+
|
|
70
|
+
### The entitled model (why gpt-5.4)
|
|
71
|
+
|
|
72
|
+
On a ChatGPT subscription the OpenAI backend rejects every `-codex`-suffixed
|
|
73
|
+
model (`gpt-5-codex`, `gpt-5.x-codex`) — those ids are API-key only. The plain
|
|
74
|
+
`gpt-5.x` family (e.g. `gpt-5.4`) is the entitled one. That is why the invocation
|
|
75
|
+
pins `--model gpt-5.4`. See `src/loadbalancer/providers/codex-provider.js`
|
|
76
|
+
(`resolveCodexModel`).
|
|
77
|
+
|
|
78
|
+
## Configure it
|
|
79
|
+
|
|
80
|
+
`_byan/_config/autodelegate.json`:
|
|
81
|
+
|
|
82
|
+
| Key | Default | Meaning |
|
|
83
|
+
|-----|---------|---------|
|
|
84
|
+
| `enabled` | `true` (once written) | Master switch. Delete the file or set `false` to disarm. |
|
|
85
|
+
| `threshold` | `80` | Pressure percentage that flips to "offload everything delegable". |
|
|
86
|
+
| `budget` | `null` | Your plan's rough token ceiling for the 5h window. Without it, `pct` is null and only the nature trigger fires (see caveat). |
|
|
87
|
+
| `invocation` | `codex:codex-rescue --model gpt-5.4` | What the nudge tells BYAN to run. |
|
|
88
|
+
| `perfRouting` | `false` | Enable the perf trigger. |
|
|
89
|
+
| `perfForces` | `[]` | The forces table (see below). |
|
|
90
|
+
|
|
91
|
+
Session escape hatch: `touch .byan-codex-autodelegate/off` silences the nudge
|
|
92
|
+
without editing the config; remove it to re-enable.
|
|
93
|
+
|
|
94
|
+
## The honest caveats
|
|
95
|
+
|
|
96
|
+
- **The 5h gauge is an ESTIMATE, not Anthropic's number.** No provider exposes a
|
|
97
|
+
machine-readable 5h quota; `/usage` fetches the authoritative figure live and
|
|
98
|
+
does not persist it. The estimator sums the real per-message token counts from
|
|
99
|
+
your local transcripts (`~/.claude/projects/*/*.jsonl`) over the rolling 5h
|
|
100
|
+
window, weighting cache reads at 0.1 (they are billed roughly a tenth and
|
|
101
|
+
repeat every turn). It is proportional, not exact — and `pct` is only computed
|
|
102
|
+
when you set a `budget`. Without a budget, delegation runs on the nature
|
|
103
|
+
trigger alone.
|
|
104
|
+
- **`~/.claude` is a native accelerator, not a source of truth** (PORTABLE-3).
|
|
105
|
+
If it is absent the estimator degrades to `pct: null` and the feature falls
|
|
106
|
+
back to nature-based delegation. The feature keeps working.
|
|
107
|
+
- **Perf routing is a heuristic below the L2 floor.** "Model X is better at task
|
|
108
|
+
Y" is a performance claim; BYAN's fact-check floor for performance is L2 (a
|
|
109
|
+
reproducible benchmark). A community arena such as designarena.ai is weaker
|
|
110
|
+
than that. So the forces table ships empty — it asserts nothing until you
|
|
111
|
+
populate it, and every match is tagged `heuristic`, not presented as measured.
|
|
112
|
+
|
|
113
|
+
## Perf routing (opt-in)
|
|
114
|
+
|
|
115
|
+
Set `perfRouting: true` and fill `perfForces` with entries:
|
|
116
|
+
|
|
117
|
+
```json
|
|
118
|
+
{
|
|
119
|
+
"perfRouting": true,
|
|
120
|
+
"perfForces": [
|
|
121
|
+
{ "category": "bulk-refactor", "pattern": "rename across|codemod", "favors": "codex" }
|
|
122
|
+
]
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
`pattern` is a case-insensitive regex matched against your request. `favors:
|
|
127
|
+
"codex"` pushes a delegation nudge even below the pressure threshold. Populate it
|
|
128
|
+
from your own benchmarks, or from an arena taken as a weak signal — BYAN tags it
|
|
129
|
+
heuristic either way.
|
|
130
|
+
|
|
131
|
+
## Files
|
|
132
|
+
|
|
133
|
+
| Piece | File |
|
|
134
|
+
|-------|------|
|
|
135
|
+
| Usage estimator | `.claude/hooks/lib/usage-estimator.js` |
|
|
136
|
+
| Decision core + nudge | `.claude/hooks/lib/autodelegate-decision.js` |
|
|
137
|
+
| Perf routing | `.claude/hooks/lib/perf-routing.js` |
|
|
138
|
+
| Hook | `.claude/hooks/codex-autodelegate.js` |
|
|
139
|
+
| Installer opt-in | `install/lib/codex-autodelegate-setup.js` |
|
|
140
|
+
| Native Codex MCP + skills setup | `install/lib/codex-native-setup.js` |
|