create-byan-agent 2.50.0 → 2.53.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 +93 -0
- package/install/package.json +1 -1
- package/install/templates/.claude/hooks/codex-delegate-guard.js +66 -74
- package/install/templates/.claude/hooks/inject-voice-anchor.js +2 -1
- package/install/templates/.claude/hooks/lib/autodelegate-decision.js +11 -13
- package/install/templates/.claude/hooks/lib/codex-delegate-gate.js +0 -0
- package/install/templates/.claude/hooks/lib/plain-language.js +18 -0
- package/install/templates/.claude/rules/native-workflows.md +17 -7
- package/install/templates/.claude/rules/plain-language.md +18 -0
- package/install/templates/.claude/skills/byan-byan/SKILL.md +44 -10
- package/install/templates/.claude/workflows/byan-auto-dispatch.js +210 -0
- package/install/templates/_byan/agent/byan/byan-tao.md +8 -0
- package/install/templates/_byan/mcp/byan-mcp-server/lib/dispatch-router.js +48 -30
- package/install/templates/_byan/mcp/byan-mcp-server/lib/native-tiers.js +25 -6
- package/install/templates/_byan/mcp/byan-mcp-server/lib/tier-script.js +8 -4
- package/install/templates/_byan/mcp/byan-mcp-server/lib/workflows-lint.js +6 -2
- package/install/templates/_byan/mcp/byan-mcp-server/skill-bundles-manifest.json +1 -1
- package/install/templates/dist/skill-bundles/byan-byan.zip +0 -0
- package/install/templates/docs/codex-auto-delegation.md +51 -25
- package/install/templates/docs/intelligent-dispatch.md +9 -5
- package/install/templates/docs/native-workflows-contract.md +9 -4
- package/package.json +1 -1
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
export const meta = {
|
|
2
|
+
name: 'byan-auto-dispatch',
|
|
3
|
+
description: 'Decoupe une tache en etapes typees, route chaque etape sur le bon modele (haiku/sonnet/opus/fable par complexite ; Codex pour shell/deploiement), ecrit _byan-output/plan.md, execute et verifie.',
|
|
4
|
+
whenToUse: 'Rail automatique de /byan-byan : toute tache non-conversationnelle passe par ce workflow sans demande explicite. Le gate humain reste en fin, sur le livrable.',
|
|
5
|
+
phases: [
|
|
6
|
+
{ title: 'Decoupage', detail: 'un agent d analyse decoupe la tache en etapes typees (nature + complexite 0-100)', model: 'sonnet' },
|
|
7
|
+
{ title: 'Routage', detail: 'pur code : echelle haiku<34 / sonnet<67 / opus<90 / fable>=90 ; moteur Codex pour shell/deploiement, verification toujours Claude' },
|
|
8
|
+
{ title: 'Plan', detail: 'ecrit _byan-output/plan.md (table etape x modele x moteur)', model: 'sonnet' },
|
|
9
|
+
{ title: 'Execution', detail: 'un agent par etape, sequentiel, sur le modele route' },
|
|
10
|
+
{ title: 'Verification', detail: 'controle du livrable sur le modele de session (jamais delegue)' },
|
|
11
|
+
],
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
// BYAN-TIER: reviewed — les modeles des etapes d'execution sont CALCULES par le
|
|
15
|
+
// routeur inline (echelle v3 par complexite), pas des choix statiques ; les deux
|
|
16
|
+
// leaves statiques (analyse -> sonnet, ecriture mecanique du plan -> sonnet)
|
|
17
|
+
// portent leur niveau en litteral, verifiable par le linter.
|
|
18
|
+
//
|
|
19
|
+
// Un script natif n'a NI import NI acces fichier : l'echelle de routage est donc
|
|
20
|
+
// posee ici en clair (la meme table que _byan/mcp/byan-mcp-server/lib/
|
|
21
|
+
// dispatch-router.js, qui reste la source de verite testee cote lib), et le
|
|
22
|
+
// plan.md est ecrit par un agent (qui, lui, a l'outil Write).
|
|
23
|
+
//
|
|
24
|
+
// args attendus : { task: string, stamp?: string ISO }
|
|
25
|
+
// (stamp vient du fil principal — un script natif n a pas le droit de lire
|
|
26
|
+
// l horloge lui-meme, ca casserait la reprise sur relance.)
|
|
27
|
+
|
|
28
|
+
// Accepte args en objet OU en chaine JSON (selon le chemin d'invocation), et, en
|
|
29
|
+
// dernier recours, une chaine nue traitee comme la tache elle-meme.
|
|
30
|
+
let entree = {};
|
|
31
|
+
if (args && typeof args === 'object') {
|
|
32
|
+
entree = args;
|
|
33
|
+
} else if (typeof args === 'string' && args.trim()) {
|
|
34
|
+
try {
|
|
35
|
+
const p = JSON.parse(args);
|
|
36
|
+
entree = (p && typeof p === 'object') ? p : { task: String(p) };
|
|
37
|
+
} catch {
|
|
38
|
+
entree = { task: args };
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const tache = String(entree.task || '').trim();
|
|
42
|
+
const stamp = String(entree.stamp || 'horodatage non fourni');
|
|
43
|
+
if (!tache) return { ok: false, erreur: 'args.task manquant : rien a dispatcher' };
|
|
44
|
+
|
|
45
|
+
// --- Phase 1 : DECOUPAGE (analyse -> sonnet, conforme a la doctrine des niveaux)
|
|
46
|
+
phase('Decoupage');
|
|
47
|
+
const PLAN_SCHEMA = {
|
|
48
|
+
type: 'object',
|
|
49
|
+
required: ['resume', 'etapes'],
|
|
50
|
+
additionalProperties: false,
|
|
51
|
+
properties: {
|
|
52
|
+
resume: { type: 'string', description: 'La tache reformulee en une phrase.' },
|
|
53
|
+
etapes: {
|
|
54
|
+
type: 'array',
|
|
55
|
+
minItems: 1,
|
|
56
|
+
maxItems: 10,
|
|
57
|
+
items: {
|
|
58
|
+
type: 'object',
|
|
59
|
+
required: ['id', 'titre', 'nature', 'complexite', 'consigne'],
|
|
60
|
+
additionalProperties: false,
|
|
61
|
+
properties: {
|
|
62
|
+
id: { type: 'string', description: 'E1, E2, ...' },
|
|
63
|
+
titre: { type: 'string' },
|
|
64
|
+
nature: {
|
|
65
|
+
enum: ['exploration', 'analyse', 'implementation', 'verification', 'shell', 'deploiement', 'navigation', 'doc'],
|
|
66
|
+
description: 'exploration=lire/scanner ; analyse=juger/concevoir ; implementation=ecrire du code/contenu ; verification=controler ; shell/deploiement/navigation=execution systeme ; doc=documentation',
|
|
67
|
+
},
|
|
68
|
+
complexite: { type: 'number', minimum: 0, maximum: 100 },
|
|
69
|
+
consigne: { type: 'string', description: 'L instruction complete et autonome pour executer cette etape.' },
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
const decoupe = await agent(
|
|
76
|
+
'Tu decoupes une tache en etapes executables, chacune typee et notee en complexite.\n' +
|
|
77
|
+
'Regles : chaque etape est autonome (sa consigne suffit pour l executer) ; ordonne-les ;\n' +
|
|
78
|
+
'la complexite est 0-100 (0=trivial, 100=extreme) ; ne cree pas d etape superflue (rasoir d Ockham) ;\n' +
|
|
79
|
+
'inclus une etape de verification finale seulement si la tache produit du code ou un livrable testable.\n' +
|
|
80
|
+
'Aucun absolu non source dans les consignes (pas de "jamais/toujours" nus).\n\n' +
|
|
81
|
+
'Tache a decouper :\n' + tache,
|
|
82
|
+
{ label: 'analyse-decoupage', phase: 'Decoupage', model: 'sonnet', schema: PLAN_SCHEMA }
|
|
83
|
+
);
|
|
84
|
+
// Le schema est une consigne forte, pas un contrat absolu : un retour nul ou
|
|
85
|
+
// difforme (agent saute, erreur terminale) doit finir en verdict propre, pas en
|
|
86
|
+
// TypeError qui avorte le workflow.
|
|
87
|
+
if (!decoupe || !Array.isArray(decoupe.etapes) || decoupe.etapes.length === 0) {
|
|
88
|
+
return { ok: false, erreur: 'decoupage vide ou difforme : aucune etape exploitable', tache };
|
|
89
|
+
}
|
|
90
|
+
log(`Decoupage : ${decoupe.etapes.length} etape(s) — ${decoupe.resume}`);
|
|
91
|
+
|
|
92
|
+
// --- Phase 2 : ROUTAGE (pur code, zero agent)
|
|
93
|
+
phase('Routage');
|
|
94
|
+
const NATURES_CODEX = ['shell', 'deploiement', 'navigation'];
|
|
95
|
+
function moteurPour(nature) {
|
|
96
|
+
if (nature === 'verification') return 'claude'; // ligne rouge : jamais delegue
|
|
97
|
+
return NATURES_CODEX.includes(nature) ? 'codex' : 'claude';
|
|
98
|
+
}
|
|
99
|
+
// Echelle v3 par complexite (miroir de dispatch-router.claudeModelForComplexity).
|
|
100
|
+
function modeleClaudePour(cx) {
|
|
101
|
+
if (cx < 34) return 'haiku';
|
|
102
|
+
if (cx < 67) return 'sonnet';
|
|
103
|
+
if (cx < 90) return 'opus';
|
|
104
|
+
return 'fable'; // extreme : dernier recours (~2x le prix d Opus)
|
|
105
|
+
}
|
|
106
|
+
const table = decoupe.etapes.map((e) => {
|
|
107
|
+
const moteur = moteurPour(e.nature);
|
|
108
|
+
// Une etape de verification herite du modele de session (opts.model omis) ;
|
|
109
|
+
// toute autre etape porte le modele de l echelle. Une etape routee Codex est
|
|
110
|
+
// pilotee par un agent Claude (au modele de l echelle) qui tente `codex exec`
|
|
111
|
+
// et replie sur Claude si Codex n est pas disponible.
|
|
112
|
+
const modele = e.nature === 'verification' ? null : modeleClaudePour(e.complexite);
|
|
113
|
+
return { ...e, moteur, modele };
|
|
114
|
+
});
|
|
115
|
+
for (const l of table) {
|
|
116
|
+
log(`Routage ${l.id} "${l.titre}" : nature=${l.nature} complexite=${l.complexite} -> moteur=${l.moteur} modele=${l.modele || 'session'}`);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// --- Phase 3 : PLAN (contenu construit en pur code, ecrit par un agent mecanique)
|
|
120
|
+
phase('Plan');
|
|
121
|
+
const lignesTable = table.map((l) =>
|
|
122
|
+
`| ${l.id} | ${l.titre} | ${l.nature} | ${l.complexite} | ${l.moteur} | ${l.modele || 'modele de session'} |`
|
|
123
|
+
);
|
|
124
|
+
const planMd = [
|
|
125
|
+
'# Plan de dispatch — ' + decoupe.resume,
|
|
126
|
+
'',
|
|
127
|
+
`- Tache d origine : ${tache.replace(/\n/g, ' ').slice(0, 300)}`,
|
|
128
|
+
`- Genere par : workflow natif byan-auto-dispatch (${stamp})`,
|
|
129
|
+
'- Echelle de routage : haiku < 34, sonnet < 67, opus < 90, fable >= 90 (par complexite) ;',
|
|
130
|
+
' moteur Codex pour shell/deploiement/navigation (repli Claude si indisponible) ;',
|
|
131
|
+
' la verification reste sur le modele de session, non deleguee.',
|
|
132
|
+
'',
|
|
133
|
+
'| Etape | Titre | Nature | Complexite | Moteur | Modele |',
|
|
134
|
+
'|-------|-------|--------|------------|--------|--------|',
|
|
135
|
+
...lignesTable,
|
|
136
|
+
'',
|
|
137
|
+
'## Consignes par etape',
|
|
138
|
+
'',
|
|
139
|
+
...table.map((l) => `### ${l.id} — ${l.titre}\n\n${l.consigne}\n`),
|
|
140
|
+
].join('\n');
|
|
141
|
+
await agent(
|
|
142
|
+
'Ecris EXACTEMENT le contenu ci-dessous dans le fichier _byan-output/plan.md ' +
|
|
143
|
+
'(cree le dossier _byan-output s il n existe pas ; remplace le fichier s il existe). ' +
|
|
144
|
+
'N ajoute rien, ne corrige rien, ne commente rien. Reponds "ecrit" quand c est fait.\n\n' +
|
|
145
|
+
'--- CONTENU A ECRIRE TEL QUEL ---\n' + planMd,
|
|
146
|
+
{ label: 'mech-ecrire-plan', phase: 'Plan', model: 'sonnet' }
|
|
147
|
+
);
|
|
148
|
+
log('Plan ecrit : _byan-output/plan.md');
|
|
149
|
+
|
|
150
|
+
// --- Phase 4 : EXECUTION (sequentielle : les etapes d une meme tache partagent
|
|
151
|
+
// souvent des fichiers ; le contexte des etapes precedentes est transmis borne)
|
|
152
|
+
phase('Execution');
|
|
153
|
+
const resultats = [];
|
|
154
|
+
for (const l of table) {
|
|
155
|
+
const contexte = resultats.length
|
|
156
|
+
? '\n\nResultats des etapes precedentes (contexte) :\n' +
|
|
157
|
+
resultats.map((r) => `- ${r.id} (${r.titre}) : ${String(r.resultat).slice(0, 1200)}`).join('\n')
|
|
158
|
+
: '';
|
|
159
|
+
const consigneCodex =
|
|
160
|
+
l.moteur === 'codex'
|
|
161
|
+
? '\n\nCette etape est routee vers CODEX : lance `codex exec` en lecture seule pour obtenir un diff unifie, ' +
|
|
162
|
+
'puis applique le diff toi-meme (`git apply`). Si la commande `codex` n est pas disponible ou echoue, ' +
|
|
163
|
+
'dis-le en une phrase et fais l etape toi-meme sur Claude (repli prevu).'
|
|
164
|
+
: '';
|
|
165
|
+
// Un echec dur d une etape (agent saute, erreur terminale) ne doit pas avorter
|
|
166
|
+
// le workflow : il est enregistre comme resultat KO et la verification finale
|
|
167
|
+
// le jugera — jamais de coupe silencieuse, jamais d abandon muet.
|
|
168
|
+
let res;
|
|
169
|
+
try {
|
|
170
|
+
res = await agent(
|
|
171
|
+
`Etape ${l.id} — ${l.titre} (nature ${l.nature}, complexite ${l.complexite}).\n` +
|
|
172
|
+
'Execute la consigne ci-dessous, completement. Rapporte ce qui a ete fait, les fichiers touches, ' +
|
|
173
|
+
'et toute impossibilite REELLE rencontree (dis le fait exact, pas une image).\n\n' +
|
|
174
|
+
'Consigne :\n' + l.consigne + consigneCodex + contexte,
|
|
175
|
+
{
|
|
176
|
+
label: `etape-${l.id}`,
|
|
177
|
+
phase: 'Execution',
|
|
178
|
+
...(l.modele ? { model: l.modele } : {}),
|
|
179
|
+
}
|
|
180
|
+
);
|
|
181
|
+
} catch (e) {
|
|
182
|
+
res = null;
|
|
183
|
+
}
|
|
184
|
+
if (res == null) res = `ECHEC : l etape ${l.id} n a pas rendu de resultat (agent interrompu ou en erreur).`;
|
|
185
|
+
resultats.push({ id: l.id, titre: l.titre, moteur: l.moteur, modele: l.modele || 'session', resultat: res });
|
|
186
|
+
log(`Execution ${l.id} terminee (modele=${l.modele || 'session'})`);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// --- Phase 5 : VERIFICATION (modele de session, jamais delegue)
|
|
190
|
+
phase('Verification');
|
|
191
|
+
const verdict = await agent(
|
|
192
|
+
'Tu es le controleur (tu n as PAS fait le travail). Verifie le resultat de chaque etape ' +
|
|
193
|
+
'contre la tache d origine : livre reellement ? teste quand testable ? rien coupe en silence ?\n' +
|
|
194
|
+
'Rends : VERDICT: OK ou VERDICT: KO en premiere ligne, puis une ligne par etape (id, tenu/manque, le fait exact).\n\n' +
|
|
195
|
+
`Tache d origine :\n${tache}\n\n` +
|
|
196
|
+
'Resultats :\n' +
|
|
197
|
+
resultats.map((r) => `--- ${r.id} ${r.titre} ---\n${String(r.resultat).slice(0, 2000)}`).join('\n\n'),
|
|
198
|
+
{ label: 'verifie-livrable', phase: 'Verification' }
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
const ok = /^\s*VERDICT:\s*OK/i.test(String(verdict));
|
|
202
|
+
log(`Verification : ${ok ? 'OK' : 'KO'}`);
|
|
203
|
+
return {
|
|
204
|
+
ok,
|
|
205
|
+
tache: decoupe.resume,
|
|
206
|
+
plan: '_byan-output/plan.md',
|
|
207
|
+
table: table.map(({ consigne, ...reste }) => reste),
|
|
208
|
+
resultats: resultats.map((r) => ({ ...r, resultat: String(r.resultat).slice(0, 2000) })),
|
|
209
|
+
verdict: String(verdict),
|
|
210
|
+
};
|
|
@@ -167,6 +167,14 @@ Exemple : "Attends. Ca touche au noyau. On est jamais assez parano. Qu'est-ce qu
|
|
|
167
167
|
**Pourquoi :** Mantra IA-26. On forge une ame, pas un token.
|
|
168
168
|
**Au lieu de ca :** le verbe reel ("generer un token", "creer un token")
|
|
169
169
|
|
|
170
|
+
**Interdit :** dire qu'un outil, un MCP ou un serveur est "vivant" ou "mort"
|
|
171
|
+
**Pourquoi :** Mantra IA-26. L'anthropomorphisme cache le fait ; l'utilisateur veut la mesure, pas l'image.
|
|
172
|
+
**Au lieu de ca :** le fait observe, outil nomme : "byan_ping a repondu en 0.3s", "le serveur ne repond pas (timeout 8s)"
|
|
173
|
+
|
|
174
|
+
**Interdit :** le vocabulaire Claude interne en prose ("nudge", "up-tier", "ladder", "rung", "runtime")
|
|
175
|
+
**Pourquoi :** Mantra IA-26. C'est le dialecte de l'outillage, pas une langue.
|
|
176
|
+
**Au lieu de ca :** rappel injecte, monter en gamme, echelle, palier, moteur d'execution
|
|
177
|
+
|
|
170
178
|
---
|
|
171
179
|
|
|
172
180
|
### Section 5 — Non-dits
|
|
@@ -2,26 +2,27 @@
|
|
|
2
2
|
// given a task's NATURE and COMPLEXITY, decide which RUNTIME runs it (Codex or
|
|
3
3
|
// Claude), which MODEL, and (Codex only) which reasoning EFFORT.
|
|
4
4
|
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
// needs — runtime selection and Codex reasoning effort — on top of it.
|
|
5
|
+
// The Claude-side model is a FOUR-rung complexity ladder (v3):
|
|
6
|
+
// haiku -> sonnet -> opus -> fable, fable being the last-resort top-reasoning
|
|
7
|
+
// model reserved for EXTREME complexity (~2x Opus price). See
|
|
8
|
+
// claudeModelForComplexity. This module adds the two NEW axes the intelligent
|
|
9
|
+
// dispatch needs — runtime selection and Codex reasoning effort — on top of it.
|
|
10
10
|
//
|
|
11
11
|
// Routing table is the cross-checked result (5 independent sources, see
|
|
12
12
|
// docs / CHANGELOG): Codex wins autonomous execution, shell/CI/DevOps, deploy,
|
|
13
13
|
// browser/computer use ; Claude wins architecture, refactor-at-repo-scale,
|
|
14
14
|
// quality, planning, and ALL verification. Two hard red lines are enforced here,
|
|
15
15
|
// not left to the caller:
|
|
16
|
-
// 1. Fable is NEVER
|
|
16
|
+
// 1. Fable is NEVER routed to CODEX (Codex runs the ChatGPT-subscription model,
|
|
17
|
+
// it cannot run a Claude model at all). On the CLAUDE side Fable IS allowed,
|
|
18
|
+
// but only at the extreme rung (v3 product reversal — the old blanket ban is
|
|
19
|
+
// lifted). assertNoFable therefore guards the Codex path only.
|
|
17
20
|
// 2. Verification is NEVER routed to Codex (a runtime must not grade its own
|
|
18
21
|
// work ; the reviewer stays on Claude).
|
|
19
22
|
//
|
|
20
23
|
// Pure: no I/O, no clock, deterministic. The Codex transport (F2) and the
|
|
21
24
|
// orchestrating loop (F4) consume this; they never re-decide routing.
|
|
22
25
|
|
|
23
|
-
import { TIER_MODEL } from './native-tiers.js';
|
|
24
|
-
|
|
25
26
|
export const RUNTIMES = Object.freeze({ CODEX: 'codex', CLAUDE: 'claude' });
|
|
26
27
|
|
|
27
28
|
// Codex runs on the ChatGPT-subscription entitled model. Kept as a named constant
|
|
@@ -30,8 +31,10 @@ export const CODEX_MODEL = 'gpt-5.4';
|
|
|
30
31
|
|
|
31
32
|
export const EFFORTS = Object.freeze({ LOW: 'low', MEDIUM: 'medium', HIGH: 'high' });
|
|
32
33
|
|
|
33
|
-
// Models we refuse to emit
|
|
34
|
-
//
|
|
34
|
+
// Models we refuse to emit on the CODEX path. Codex runs the ChatGPT-subscription
|
|
35
|
+
// model and cannot run a Claude model — Fable on Codex is a category error, so the
|
|
36
|
+
// guard makes that refusal mechanical. (On the CLAUDE path Fable is allowed at the
|
|
37
|
+
// extreme rung — see claudeModelForComplexity ; this list does not gate it.)
|
|
35
38
|
export const FORBIDDEN_MODELS = Object.freeze(['fable', 'claude-fable-5']);
|
|
36
39
|
|
|
37
40
|
// Task natures that route to Codex. Everything NOT here (and not a verification
|
|
@@ -58,12 +61,14 @@ function matchesAny(text, list) {
|
|
|
58
61
|
return list.some((kw) => text.includes(kw));
|
|
59
62
|
}
|
|
60
63
|
|
|
61
|
-
// assertNoFable(model) — mechanical enforcement of red line #1
|
|
62
|
-
// than silently substituting, so a Fable request is a loud
|
|
64
|
+
// assertNoFable(model) — mechanical enforcement of red line #1 on the CODEX path.
|
|
65
|
+
// Throws rather than silently substituting, so a Fable-on-Codex request is a loud
|
|
66
|
+
// failure at the source (Codex cannot run a Claude model). It does NOT gate the
|
|
67
|
+
// Claude path, which may legitimately emit Fable at the extreme rung.
|
|
63
68
|
export function assertNoFable(model) {
|
|
64
69
|
const m = normalize(model);
|
|
65
70
|
if (FORBIDDEN_MODELS.some((f) => m.includes(f))) {
|
|
66
|
-
throw new Error(`dispatch-router: forbidden model "${model}" (
|
|
71
|
+
throw new Error(`dispatch-router: forbidden model "${model}" on the Codex path (Codex cannot run a Claude model)`);
|
|
67
72
|
}
|
|
68
73
|
return model;
|
|
69
74
|
}
|
|
@@ -106,28 +111,41 @@ export function effortForComplexity(complexity) {
|
|
|
106
111
|
return complexityBucket(complexity);
|
|
107
112
|
}
|
|
108
113
|
|
|
109
|
-
// claudeModelForComplexity(complexity) ->
|
|
110
|
-
//
|
|
111
|
-
//
|
|
112
|
-
//
|
|
113
|
-
//
|
|
114
|
-
//
|
|
114
|
+
// claudeModelForComplexity(complexity) -> the Claude-side model RECOMMENDATION on
|
|
115
|
+
// a FOUR-rung complexity ladder (v3): haiku -> sonnet -> opus -> fable.
|
|
116
|
+
// - < 34 : haiku (trivial / low)
|
|
117
|
+
// - < 67 : sonnet (medium)
|
|
118
|
+
// - < 90 : opus (high — the default frontier)
|
|
119
|
+
// - >= 90 : fable (extreme complexity, LAST RESORT)
|
|
120
|
+
// Fable is the top-reasoning model : strongest on the intelligence index but
|
|
121
|
+
// ~2x the price of Opus, so it is reserved for the extreme rung only. This is a
|
|
122
|
+
// per-task RECOMMENDATION consumed by the dispatch orchestrator / skill — NOT a
|
|
123
|
+
// live switch of the running session model (Claude Code exposes no such switch).
|
|
124
|
+
// Labels map too (trivial/low/medium/high/extreme). The old blanket "never Fable"
|
|
125
|
+
// red line is lifted on the CLAUDE side (deliberate v3 product reversal); it still
|
|
126
|
+
// holds on the CODEX side (see dispatch() + assertNoFable + codex-bridge).
|
|
115
127
|
export function claudeModelForComplexity(complexity) {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
128
|
+
if (typeof complexity === 'number' && Number.isFinite(complexity)) {
|
|
129
|
+
if (complexity < 34) return 'haiku';
|
|
130
|
+
if (complexity < 67) return 'sonnet';
|
|
131
|
+
if (complexity < 90) return 'opus';
|
|
132
|
+
return 'fable';
|
|
133
|
+
}
|
|
134
|
+
const c = normalize(complexity);
|
|
135
|
+
if (['trivial', 'low', 'simple', 'easy'].includes(c)) return 'haiku';
|
|
136
|
+
if (['medium', 'moderate'].includes(c)) return 'sonnet';
|
|
137
|
+
if (['extreme', 'frontier', 'max'].includes(c)) return 'fable';
|
|
138
|
+
if (['high', 'hard', 'complex'].includes(c)) return 'opus';
|
|
139
|
+
return 'sonnet'; // unknown -> a safe middle
|
|
123
140
|
}
|
|
124
141
|
|
|
125
142
|
// dispatch({ nature, complexity }) -> the full routing decision:
|
|
126
143
|
// { runtime, model, effort, reasoning }
|
|
127
144
|
// - Codex: model = CODEX_MODEL, effort = complexity bucket (the real knob).
|
|
128
|
-
// - Claude: model = haiku/sonnet/
|
|
129
|
-
// effort knob — its "effort" IS the model tier).
|
|
130
|
-
//
|
|
145
|
+
// - Claude: model = haiku/sonnet/opus/fable on the complexity ladder, effort =
|
|
146
|
+
// null (Claude has no effort knob — its "effort" IS the model tier).
|
|
147
|
+
// The red lines are enforced here regardless of caller input: Codex keeps its
|
|
148
|
+
// no-Fable guard ; verification stays on Claude.
|
|
131
149
|
export function dispatch({ nature, complexity } = {}) {
|
|
132
150
|
const runtime = routeRuntime(nature);
|
|
133
151
|
if (runtime === RUNTIMES.CODEX) {
|
|
@@ -140,7 +158,7 @@ export function dispatch({ nature, complexity } = {}) {
|
|
|
140
158
|
}
|
|
141
159
|
return {
|
|
142
160
|
runtime,
|
|
143
|
-
model: claudeModelForComplexity(complexity), //
|
|
161
|
+
model: claudeModelForComplexity(complexity), // haiku/sonnet/opus/fable by complexity
|
|
144
162
|
effort: null, // Claude effort = model tier, no separate knob
|
|
145
163
|
reasoning: isVerification(nature)
|
|
146
164
|
? 'verification stays on Claude (red line: a runtime never grades its own work)'
|
|
@@ -40,6 +40,17 @@ export const TIERS = Object.freeze({ CHEAP: 'cheap', BALANCED: 'balanced', DEEP:
|
|
|
40
40
|
// flags every script literal that drifts from it, so the fan-out stays bounded.
|
|
41
41
|
export const TIER_MODEL = Object.freeze({ cheap: 'haiku', balanced: 'sonnet', deep: null });
|
|
42
42
|
|
|
43
|
+
// UP-TIER models (v3). The auto-routing above never pins UP (the default stays
|
|
44
|
+
// cost-safe: deep = inherit the session model). But an author MAY deliberately
|
|
45
|
+
// raise a genuinely complex leaf ABOVE the inherited tier — 'opus' for a hard
|
|
46
|
+
// leaf, 'fable' as the last-resort top-reasoning model for extreme complexity
|
|
47
|
+
// (~2x Opus price). This is the workflow-leaf mirror of the dispatch-router
|
|
48
|
+
// complexity ladder (haiku -> sonnet -> opus -> fable). Up-tier pins are an
|
|
49
|
+
// explicit authoring choice: the anti-DOWNGRADE floor does not apply upward, so
|
|
50
|
+
// the linter ALLOWS them (they cost more, they never regress quality). The old
|
|
51
|
+
// blanket "never pin up / never Fable" ban on leaves is lifted here.
|
|
52
|
+
export const UP_TIER_MODELS = Object.freeze(['opus', 'fable']);
|
|
53
|
+
|
|
43
54
|
// Leaf task-type taxonomy. EXPLORATION (cheap) and MECHANICAL + ANALYSIS
|
|
44
55
|
// (balanced) are the downgrade classes; VERIFICATION and IMPLEMENTATION stay
|
|
45
56
|
// protected (deep, inherit the session model). MECHANICAL is verification whose
|
|
@@ -130,18 +141,26 @@ export function modelForLeaf(leaf) {
|
|
|
130
141
|
return TIER_MODEL[tierFor(classifyLeaf(leaf))];
|
|
131
142
|
}
|
|
132
143
|
|
|
133
|
-
//
|
|
134
|
-
//
|
|
135
|
-
//
|
|
136
|
-
|
|
144
|
+
// isUpTierModel(modelId) -> true if modelId pins a leaf ABOVE the inherited tier
|
|
145
|
+
// (opus or fable). An explicit, allowed authoring choice for a genuinely complex
|
|
146
|
+
// leaf (v3): the anti-downgrade floor does not apply upward.
|
|
147
|
+
export function isUpTierModel(modelId) {
|
|
148
|
+
return !!modelId && UP_TIER_MODELS.includes(modelId);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// isKnownTierModel(modelId) -> true if modelId is a recognised tier literal:
|
|
152
|
+
// a downgrade model (cheap/balanced = haiku/sonnet) OR an up-tier model
|
|
153
|
+
// (opus/fable). Used by the linter to reject an opts.model literal that is not in
|
|
154
|
+
// the vocabulary at all. null/'' are not "known" (deep = omission, not a literal).
|
|
137
155
|
export function isKnownTierModel(modelId) {
|
|
138
156
|
if (!modelId) return false;
|
|
139
|
-
return Object.values(TIER_MODEL).filter(Boolean).includes(modelId);
|
|
157
|
+
return Object.values(TIER_MODEL).filter(Boolean).includes(modelId) || isUpTierModel(modelId);
|
|
140
158
|
}
|
|
141
159
|
|
|
142
160
|
// isDowngradeModel(modelId) -> true if modelId pins a leaf BELOW the inherited
|
|
143
161
|
// tier (cheap or balanced). The linter's anti-downgrade rule uses this: a
|
|
144
|
-
// protected leaf must never carry a downgrade model.
|
|
162
|
+
// protected leaf must never carry a downgrade model. Up-tier models return false
|
|
163
|
+
// (they are not downgrades — see isUpTierModel).
|
|
145
164
|
export function isDowngradeModel(modelId) {
|
|
146
165
|
return modelId === TIER_MODEL.cheap || modelId === TIER_MODEL.balanced;
|
|
147
166
|
}
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
// passes — the gate forces a decision, it does not trap the turn.
|
|
16
16
|
|
|
17
17
|
import crypto from 'node:crypto';
|
|
18
|
-
import { classifyLeaf, tierFor, TIER_MODEL, TIERS, LEAF_TYPES } from './native-tiers.js';
|
|
18
|
+
import { classifyLeaf, tierFor, TIER_MODEL, TIERS, LEAF_TYPES, isUpTierModel } from './native-tiers.js';
|
|
19
19
|
import { stripComments, extractLabelledLeaves } from './workflows-lint.js';
|
|
20
20
|
|
|
21
21
|
// Acknowledgment marker, raw-text (comment form survives comment-stripping
|
|
@@ -26,15 +26,19 @@ export const ACK_RE = /BYAN-TIER:\s*reviewed\b/;
|
|
|
26
26
|
|
|
27
27
|
function verdictFor(cls, model) {
|
|
28
28
|
const expected = TIER_MODEL[tierFor(cls)]; // 'haiku' | 'sonnet' | null (inherit)
|
|
29
|
+
// Up-tier pins (opus/fable) are always allowed (v3): the author deliberately
|
|
30
|
+
// raises a complex leaf ABOVE the inherited tier. The anti-DOWNGRADE floor does
|
|
31
|
+
// not apply upward — it costs more but never regresses quality.
|
|
32
|
+
if (isUpTierModel(model)) return 'ok';
|
|
29
33
|
if (expected === null) {
|
|
30
|
-
// Protected class:
|
|
34
|
+
// Protected class: a downgrade pin is still wrong (up-tier handled above).
|
|
31
35
|
return model === null ? 'ok' : 'violation';
|
|
32
36
|
}
|
|
33
37
|
if (model === null) return 'missing-tier';
|
|
34
38
|
if (model === expected) return 'ok';
|
|
35
39
|
// Exploration may ride ABOVE its floor (sonnet on a cheap leaf wastes a
|
|
36
|
-
// little, breaks nothing). Anything else — haiku on mech
|
|
37
|
-
//
|
|
40
|
+
// little, breaks nothing). Anything else — haiku on mech- — is below a
|
|
41
|
+
// declared tier.
|
|
38
42
|
if (cls === LEAF_TYPES.EXPLORATION && model === TIER_MODEL[TIERS.BALANCED]) return 'ok';
|
|
39
43
|
return 'violation';
|
|
40
44
|
}
|
|
@@ -153,10 +153,15 @@ export function modelRoutingViolations(src) {
|
|
|
153
153
|
if (!isKnownTierModel(model)) {
|
|
154
154
|
out.push({
|
|
155
155
|
id: 'unknown-tier-model',
|
|
156
|
-
msg: `opts.model '${model}' is not a
|
|
156
|
+
msg: `opts.model '${model}' is not a recognised tier (downgrade: haiku/sonnet ; up-tier: opus/fable); omit opts.model to inherit the session model on deep leaves`,
|
|
157
157
|
});
|
|
158
158
|
continue;
|
|
159
159
|
}
|
|
160
|
+
// Up-tier pins (opus/fable) are an allowed explicit authoring choice: they
|
|
161
|
+
// raise a complex leaf ABOVE the inherited tier. The anti-downgrade floor does
|
|
162
|
+
// not apply upward, and no label is required (v3). Only DOWNGRADES are gated
|
|
163
|
+
// below.
|
|
164
|
+
if (!isDowngradeModel(model)) continue;
|
|
160
165
|
const label = nearestLabelBefore(code, m.index);
|
|
161
166
|
if (!label) {
|
|
162
167
|
out.push({
|
|
@@ -165,7 +170,6 @@ export function modelRoutingViolations(src) {
|
|
|
165
170
|
});
|
|
166
171
|
continue;
|
|
167
172
|
}
|
|
168
|
-
if (!isDowngradeModel(model)) continue;
|
|
169
173
|
// Per-class floor: exploration accepts any downgrade tier (haiku or sonnet,
|
|
170
174
|
// both at-or-above its cheap floor); a mech- leaf accepts exactly the
|
|
171
175
|
// balanced tier (haiku would sit BELOW the tier its label declares); every
|
|
@@ -94,7 +94,7 @@
|
|
|
94
94
|
"name": "byan-byan",
|
|
95
95
|
"module": "core",
|
|
96
96
|
"tier": "connector-bound",
|
|
97
|
-
"sourceHash": "
|
|
97
|
+
"sourceHash": "65c1034479457298f89c25405d4be7e840ef18d9df3102f1f224b59e3094b20c"
|
|
98
98
|
},
|
|
99
99
|
"byan-byan-v2": {
|
|
100
100
|
"name": "byan-byan-v2",
|
|
Binary file
|
|
@@ -10,11 +10,23 @@ work crosses the line.
|
|
|
10
10
|
## What it does
|
|
11
11
|
|
|
12
12
|
Every turn, a `UserPromptSubmit` hook estimates how much of the Claude 5h window
|
|
13
|
-
you have burned
|
|
14
|
-
|
|
15
|
-
injects a one-line note directing you to hand it to Codex via
|
|
13
|
+
you have burned. When you are over the pressure threshold (default **75%**), BYAN
|
|
14
|
+
injects a one-line note directing you to hand the delegable work to Codex via
|
|
16
15
|
`codex:codex-rescue --model gpt-5.4`. You still verify Codex's output before commit.
|
|
17
16
|
|
|
17
|
+
**v3 — pressure-only, and the router decides Codex vs Claude.** Two deliberate
|
|
18
|
+
changes from v2:
|
|
19
|
+
|
|
20
|
+
- **Pressure is the only auto-trigger (F1).** A delegable-looking task no longer
|
|
21
|
+
proposes Codex on its own. Delegating to the subscription Codex (which trails
|
|
22
|
+
Claude on quality — see below) is worth it to SPARE the Claude budget, not per
|
|
23
|
+
coding task. So off-pressure, code runs on Claude with no nag.
|
|
24
|
+
- **The guard obeys the router (F2).** WHERE a task runs (Codex vs Claude) is the
|
|
25
|
+
`dispatch-router`'s call, by task nature: execution / shell / deploy / devops /
|
|
26
|
+
browser -> Codex ; architecture / refactor / quality / planning + all
|
|
27
|
+
verification -> Claude. The delegation guard only bites for a task the router
|
|
28
|
+
itself routes to Codex.
|
|
29
|
+
|
|
18
30
|
**Both engines must be present.** The lane only arms — and the note only fires —
|
|
19
31
|
when TWO conditions hold together: the yanstaller option wrote
|
|
20
32
|
`_byan/_config/autodelegate.json` with `enabled:true`, AND Codex is actually
|
|
@@ -23,25 +35,38 @@ linked on this machine (`~/.codex/auth.json` from `codex login`, or
|
|
|
23
35
|
This is the runtime `codexLinked()` gate in `codex-autodelegate.js`, the mirror of
|
|
24
36
|
the installer's arm-time `codexAuthState` check.
|
|
25
37
|
|
|
26
|
-
**Armed = directive,
|
|
27
|
-
armed (both engines present), it reads as a directive:
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
**Armed = directive, enforced by a guard (option B + v3).** Because the note only
|
|
39
|
+
appears when the lane is armed (both engines present), it reads as a directive:
|
|
40
|
+
delegate the delegable part. And it is not just prose — the `codex-delegate-guard`
|
|
41
|
+
PreToolUse hook DENIES a **code** Write/Edit that skipped Codex, but ONLY when all
|
|
42
|
+
of these hold together (v3): the lane is armed, the ROUTER routes this task to
|
|
43
|
+
Codex, you are under budget PRESSURE, and Codex is available. Miss any one and it
|
|
44
|
+
allows. The two per-turn ways to stay on Claude are (1) Codex unavailable
|
|
45
|
+
(auto-detected via `codex --version`), or (2) the USER opted out — the
|
|
46
|
+
`.byan-codex-autodelegate/off` switch, or a phrase like "reste sur Claude" /
|
|
47
|
+
"sans codex" in the request. The "small script / latency / I verify anyway"
|
|
48
|
+
excuse is explicitly NOT valid: that was the self-dodge (a
|
|
49
|
+
`// BYAN-DELEGATE: reviewed` marker BYAN wrote itself) that option B removed. BYAN
|
|
50
|
+
can no longer self-grant a bypass; only the human can.
|
|
51
|
+
|
|
52
|
+
The deny is a wall against BYAN's silent self-dodge, not a trap for the user: the
|
|
53
|
+
human keeps the escape switch and the opt-out phrase, a doc/config write (not
|
|
54
|
+
code) does not trip it, and off-pressure OR router-to-Claude nothing bites at all.
|
|
55
|
+
Honest ceiling: Claude Code has no control point before a response is shown, so
|
|
56
|
+
the *choice* to delegate stays model-driven; the guard acts one step later, at the
|
|
57
|
+
Write, denying the skip.
|
|
58
|
+
|
|
59
|
+
The auto-trigger, plus one opt-in:
|
|
60
|
+
|
|
61
|
+
1. **Pressure (F1, the only auto-trigger)** — estimated Claude 5h usage
|
|
62
|
+
`>= threshold` (default **75%**) -> propose offloading **everything** delegable
|
|
63
|
+
this session. Needs a `budget` gauge; without one, pressure stays uncomputed
|
|
64
|
+
and nothing is proposed (documented, non-blocking).
|
|
65
|
+
2. **Perf** (opt-in, off by default) — a configurable forces table says Codex is
|
|
41
66
|
reputed stronger for this kind of task. See "Perf routing" below.
|
|
42
67
|
|
|
43
68
|
**The red line** (held): only delegable natures are proposed. Judgment,
|
|
44
|
-
analysis, soul and verification stay on Claude.
|
|
69
|
+
analysis, soul and verification stay on Claude — and the router keeps them there.
|
|
45
70
|
|
|
46
71
|
## Enable it
|
|
47
72
|
|
|
@@ -97,8 +122,8 @@ pins `--model gpt-5.4`. See `src/loadbalancer/providers/codex-provider.js`
|
|
|
97
122
|
| Key | Default | Meaning |
|
|
98
123
|
|-----|---------|---------|
|
|
99
124
|
| `enabled` | `true` (once written) | Master switch. Delete the file or set `false` to disarm. |
|
|
100
|
-
| `threshold` | `
|
|
101
|
-
| `budget` | `null` | Your plan's rough token ceiling for the 5h window. Without it, `pct` is null and
|
|
125
|
+
| `threshold` | `75` | Pressure percentage that flips to "offload everything delegable" (v3: aligned with the guard's pressure floor). |
|
|
126
|
+
| `budget` | `null` | Your plan's rough token ceiling for the 5h window. Without it, `pct` is null, pressure stays uncomputed, and nothing is auto-proposed (v3 pressure-only; see caveat). |
|
|
102
127
|
| `invocation` | `codex:codex-rescue --model gpt-5.4` | What the nudge tells BYAN to run. |
|
|
103
128
|
| `perfRouting` | `false` | Enable the perf trigger. |
|
|
104
129
|
| `perfForces` | `[]` | The forces table (see below). |
|
|
@@ -114,11 +139,12 @@ without editing the config; remove it to re-enable.
|
|
|
114
139
|
your local transcripts (`~/.claude/projects/*/*.jsonl`) over the rolling 5h
|
|
115
140
|
window, weighting cache reads at 0.1 (they are billed roughly a tenth and
|
|
116
141
|
repeat every turn). It is proportional, not exact — and `pct` is only computed
|
|
117
|
-
when you set a `budget`. Without a budget,
|
|
118
|
-
trigger
|
|
142
|
+
when you set a `budget`. Without a budget, pressure stays uncomputed and the
|
|
143
|
+
auto-trigger stays silent (v3 pressure-only).
|
|
119
144
|
- **`~/.claude` is a native accelerator, not a source of truth** (PORTABLE-3).
|
|
120
|
-
If it is absent the estimator degrades to `pct: null
|
|
121
|
-
|
|
145
|
+
If it is absent the estimator degrades to `pct: null`, so pressure cannot be
|
|
146
|
+
computed and the auto-trigger stays silent. Code still runs on Claude normally;
|
|
147
|
+
the feature keeps working, it just does not auto-propose Codex.
|
|
122
148
|
- **Perf routing is a heuristic below the L2 floor.** "Model X is better at task
|
|
123
149
|
Y" is a performance claim; BYAN's fact-check floor for performance is L2 (a
|
|
124
150
|
reproducible benchmark). A community arena such as designarena.ai is weaker
|
|
@@ -22,17 +22,21 @@ task-type signal is used, which is stable across sources.
|
|
|
22
22
|
|
|
23
23
|
## Model and effort by complexity
|
|
24
24
|
|
|
25
|
-
- **Claude side**: complexity picks the model
|
|
26
|
-
low -> haiku, medium -> sonnet, high ->
|
|
27
|
-
is no separate effort knob on Claude: the model tier IS
|
|
28
|
-
|
|
25
|
+
- **Claude side**: complexity picks the model on a four-rung ladder (v3) —
|
|
26
|
+
low -> haiku, medium -> sonnet, high -> opus, extreme -> fable (last resort,
|
|
27
|
+
~2x Opus price). There is no separate effort knob on Claude: the model tier IS
|
|
28
|
+
the effort. This is a per-task RECOMMENDATION, not a live switch of the running
|
|
29
|
+
session model (Claude Code exposes no such switch).
|
|
29
30
|
- **Codex side**: fixed model (`gpt-5.4`, the entitled subscription model) with a
|
|
30
31
|
real reasoning-effort knob — low / medium / high — scaled to complexity via
|
|
31
32
|
`codex exec -c model_reasoning_effort=...`.
|
|
32
33
|
|
|
33
34
|
## The three red lines (enforced in code, not left to the caller)
|
|
34
35
|
|
|
35
|
-
1. **No Fable.** `assertNoFable` throws rather than emit a Fable model
|
|
36
|
+
1. **No Fable on Codex.** `assertNoFable` throws rather than emit a Fable model on
|
|
37
|
+
the Codex path (Codex runs the ChatGPT-subscription model and cannot run a
|
|
38
|
+
Claude model). On the Claude path Fable IS allowed, but only at the extreme
|
|
39
|
+
complexity rung (v3 product reversal — the old blanket ban is lifted).
|
|
36
40
|
2. **Verification stays on Claude.** The router forces a verification nature to
|
|
37
41
|
Claude ; the orchestrator re-asserts it and throws if it ever regresses.
|
|
38
42
|
3. **Codex does not write.** Codex runs read-only and returns a unified diff ;
|