create-byan-agent 2.50.0 → 2.52.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 +59 -0
- package/install/package.json +1 -1
- package/install/templates/.claude/hooks/codex-delegate-guard.js +66 -74
- 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/rules/native-workflows.md +17 -7
- package/install/templates/.claude/skills/byan-byan/SKILL.md +16 -10
- 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
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,65 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
9
9
|
|
|
10
10
|
## [Unreleased]
|
|
11
11
|
|
|
12
|
+
## [2.52.0] - 2026-07-20
|
|
13
|
+
|
|
14
|
+
### Changed — Delegation Codex v3 : pression-only + obeissance au routeur (F1, F2)
|
|
15
|
+
- **F1 — la delegation ne se declenche plus que sous PRESSION budget.** La nature
|
|
16
|
+
d'une tache (code / mecanique) n'est plus un declencheur d'auto-delegation a elle
|
|
17
|
+
seule. Deleguer au Codex de l'abonnement (qui reste en dessous de Claude en
|
|
18
|
+
qualite) vaut le coup pour epargner le budget Claude, pas par tache de code. Hors
|
|
19
|
+
pression, le code tourne sur Claude sans rappel. Le seuil par defaut passe a 75 %
|
|
20
|
+
(`autodelegate-decision.js` + `codex-delegate-guard.js` alignes sur un seul
|
|
21
|
+
nombre). Sans jauge `budget`, la pression n'est pas calculee et rien n'est
|
|
22
|
+
propose (documente, non bloquant).
|
|
23
|
+
- **F2 — le garde `codex-delegate-guard` obeit au routeur.** Le blocage ne mord
|
|
24
|
+
que pour une tache que le `dispatch-router` route lui-meme vers Codex (nature
|
|
25
|
+
execution / shell / deploiement / devops / navigateur). Une tache
|
|
26
|
+
architecture / refactor / qualite / planif — et toute verification — route vers
|
|
27
|
+
Claude, donc le garde reste silencieux. Le garde recalcule router + pression
|
|
28
|
+
uniquement dans la branche ou un blocage est encore possible (les chemins
|
|
29
|
+
d'autorisation courants ne paient rien) ; les deux signaux echouent en mode
|
|
30
|
+
ouvert (signal irresolu -> on autorise, sans blocage a tort).
|
|
31
|
+
|
|
32
|
+
### Changed — Echelle de modele par complexite + Fable dernier recours (F3)
|
|
33
|
+
- **Cote routeur (`dispatch-router.claudeModelForComplexity`)** : echelle a quatre
|
|
34
|
+
rungs — `haiku` (bas) -> `sonnet` (moyen) -> `opus` (haut) -> `fable` (extreme,
|
|
35
|
+
dernier recours, ~2x le prix d'Opus). C'est une RECOMMANDATION par tache, pas un
|
|
36
|
+
changement du modele de session en cours (Claude Code n'expose pas ce levier).
|
|
37
|
+
- **Cote leaves de workflow (`native-tiers` + linter)** : un up-tier explicite
|
|
38
|
+
(`opus` / `fable`) devient un choix d'auteur AUTORISE sur n'importe quel leaf,
|
|
39
|
+
miroir de l'echelle du routeur. Le plancher anti-downgrade est conserve (haiku /
|
|
40
|
+
sonnet interdits sur un leaf protege) ; seul le plafond est ouvert (le pin-up
|
|
41
|
+
n'est plus une violation). Nouveaux : `UP_TIER_MODELS`, `isUpTierModel`.
|
|
42
|
+
- **Revirement d'invariant assume** : l'ancienne ligne rouge "pas de Fable /
|
|
43
|
+
pas de pin-up" est levee cote Claude. Cote Codex elle tient (Codex ne peut pas
|
|
44
|
+
lancer un modele Claude) : `assertNoFable` garde le chemin Codex + `codex-bridge`.
|
|
45
|
+
- Doctrine alignee : `native-workflows.md`, `native-workflows-contract.md`,
|
|
46
|
+
`intelligent-dispatch.md`, `codex-auto-delegation.md`, skill `byan-byan`.
|
|
47
|
+
|
|
48
|
+
## [2.51.0] - 2026-07-20
|
|
49
|
+
|
|
50
|
+
### Changed — Delegation Codex : fermeture de l'auto-esquive (option B)
|
|
51
|
+
- Le blocage `codex-delegate-guard` ne peut plus etre contourne par BYAN lui-meme.
|
|
52
|
+
Retire : la marque de contenu auto-ecrite (`// BYAN-DELEGATE: reviewed`) et
|
|
53
|
+
l'auto-passe par re-soumission/fenetre de grace — les deux permettaient a Claude
|
|
54
|
+
de s'auto-accorder la sortie. Les seules sorties valides sont desormais : une
|
|
55
|
+
delegation Codex reelle ce tour, Codex indisponible (detecte via `codex --version`),
|
|
56
|
+
ou un opt-out HUMAIN ("reste sur claude" / "sans codex" dans la demande, ou le
|
|
57
|
+
fichier `.byan-codex-autodelegate/off`). Le motif "script court / latence / je
|
|
58
|
+
verifie" n'est plus une raison valide. Coeur pur `lib/codex-delegate-gate.js`
|
|
59
|
+
(`humanOptOutFromText`, `decideDelegateGate` durci) + `codex-delegate-guard.js`
|
|
60
|
+
(probe Codex, opt-out depuis la demande). Le rendu injecte (`autodelegate-decision`
|
|
61
|
+
renderNudge), le skill `byan-byan` et `docs/codex-auto-delegation.md` sont alignes.
|
|
62
|
+
|
|
63
|
+
### Notes
|
|
64
|
+
- Constat de session : le CLI Codex de l'abo ChatGPT est plafonne a gpt-5.5 (5.6
|
|
65
|
+
refuse). Recherche croisee : gpt-5.5 est sous le meilleur Claude sur tous les
|
|
66
|
+
domaines mesures. Donc la delegation Codex vaut pour economiser le budget Claude,
|
|
67
|
+
pas pour la qualite. Prochain chantier (FD dedie) : delegation declenchee par la
|
|
68
|
+
PRESSION budget (~75%) au lieu de par-nature ; le blocage obeit au routeur ;
|
|
69
|
+
echelle modele Claude par complexite (haiku/sonnet/opus) + Fable en dernier recours.
|
|
70
|
+
|
|
12
71
|
## [2.50.0] - 2026-07-17
|
|
13
72
|
|
|
14
73
|
### Added — BYAN souverainete (les concepts BYAN pilotent Claude, ne se font plus doubler)
|
package/install/package.json
CHANGED
|
@@ -1,35 +1,31 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
// PreToolUse hook —
|
|
4
|
+
// PreToolUse hook — the armed Codex-delegation guard (v3 : router-obedient +
|
|
5
|
+
// pressure-gated). See lib/codex-delegate-gate.js for the policy.
|
|
5
6
|
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
7
|
+
// It DENIES a code Write/Edit only when ALL hold : the lane is armed (yanstaller
|
|
8
|
+
// option on AND Codex linked), the ROUTER routes the task to Codex, we are under
|
|
9
|
+
// Claude budget PRESSURE, Codex is available, and no delegation happened this turn
|
|
10
|
+
// — and the human has not opted out. Otherwise it allows. Never traps a turn on an
|
|
11
|
+
// internal error (fails open), always exits 0.
|
|
11
12
|
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
// - `// BYAN-DELEGATE: reviewed` in the file acknowledges a deliberate
|
|
16
|
-
// Claude-lane choice (Codex unavailable, judgment/verify) and passes.
|
|
17
|
-
// - deny-once : an identical resubmission passes, and a grace window after a
|
|
18
|
-
// deny lets a multi-file build proceed without nagging on every file.
|
|
19
|
-
//
|
|
20
|
-
// Never traps a turn, always exits 0. Pure decision in lib/codex-delegate-gate.js;
|
|
21
|
-
// armed/linked detection reused from codex-autodelegate.js; delegability from
|
|
22
|
-
// autodelegate-decision.js; the turn transcript via transcript-read.js.
|
|
13
|
+
// The two computed signals (router decision, budget pressure) are only evaluated
|
|
14
|
+
// in the would-deny branch, so the common allow paths pay nothing. Both fail open
|
|
15
|
+
// (an unresolvable signal -> allow, never a wrong block).
|
|
23
16
|
|
|
24
17
|
const fs = require('fs');
|
|
25
18
|
const path = require('path');
|
|
19
|
+
const { spawnSync } = require('child_process');
|
|
20
|
+
const { pathToFileURL } = require('url');
|
|
26
21
|
const gate = require('./lib/codex-delegate-gate');
|
|
27
22
|
const { loadConfig, codexLinked, toggledOff } = require('./codex-autodelegate');
|
|
28
|
-
const {
|
|
29
|
-
const { extractRecentMessages,
|
|
23
|
+
const { estimateClaudeUsage } = require('./lib/usage-estimator');
|
|
24
|
+
const { extractRecentMessages, contentToText } = require('./lib/transcript-read');
|
|
30
25
|
|
|
31
26
|
const ROOT = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
32
|
-
const
|
|
27
|
+
const ROUTER_PATH = path.resolve(__dirname, '../../_byan/mcp/byan-mcp-server/lib/dispatch-router.js');
|
|
28
|
+
const DEFAULT_PRESSURE_THRESHOLD = 75; // % of the Claude budget window
|
|
33
29
|
|
|
34
30
|
function readStdin() {
|
|
35
31
|
return new Promise((resolve) => {
|
|
@@ -40,44 +36,47 @@ function readStdin() {
|
|
|
40
36
|
});
|
|
41
37
|
}
|
|
42
38
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function deny(reason) {
|
|
48
|
-
return {
|
|
49
|
-
hookSpecificOutput: {
|
|
50
|
-
hookEventName: 'PreToolUse',
|
|
51
|
-
permissionDecision: 'deny',
|
|
52
|
-
permissionDecisionReason: reason,
|
|
53
|
-
},
|
|
54
|
-
};
|
|
55
|
-
}
|
|
39
|
+
const allow = () => ({ hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'allow' } });
|
|
40
|
+
const deny = (reason) => ({ hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'deny', permissionDecisionReason: reason } });
|
|
56
41
|
|
|
57
|
-
|
|
58
|
-
|
|
42
|
+
// Codex availability probe : `codex --version` exits 0. Absent/broken -> false ->
|
|
43
|
+
// the guard must NOT deny (staying on Claude is the legit fallback).
|
|
44
|
+
function probeCodexAvailable(runner = spawnSync) {
|
|
45
|
+
try {
|
|
46
|
+
const res = runner('codex', ['--version'], { timeout: 8000, encoding: 'utf8' });
|
|
47
|
+
return res && res.status === 0;
|
|
48
|
+
} catch (_e) {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
59
51
|
}
|
|
60
52
|
|
|
61
|
-
|
|
53
|
+
// routerSaysCodex(text) : does dispatch-router route this task's text to Codex?
|
|
54
|
+
// The router keyword-matches on the text (execution/shell/deploy/script...). ESM
|
|
55
|
+
// module dynamically imported from this CJS hook. Fails open (false = don't block)
|
|
56
|
+
// if the router cannot be loaded.
|
|
57
|
+
async function defaultRouteProbe(text) {
|
|
62
58
|
try {
|
|
63
|
-
const
|
|
64
|
-
return
|
|
59
|
+
const mod = await import(pathToFileURL(ROUTER_PATH).href);
|
|
60
|
+
return mod.routeRuntime(String(text || '')) === 'codex';
|
|
65
61
|
} catch (_e) {
|
|
66
|
-
return
|
|
62
|
+
return false;
|
|
67
63
|
}
|
|
68
64
|
}
|
|
69
65
|
|
|
70
|
-
|
|
66
|
+
// underPressure : Claude budget usage >= threshold. Needs a budget in the config
|
|
67
|
+
// (no budget -> pct null -> not under pressure -> no delegation, documented).
|
|
68
|
+
function computeUnderPressure(config) {
|
|
71
69
|
try {
|
|
72
|
-
|
|
73
|
-
|
|
70
|
+
const budget = config && config.budget ? config.budget : null;
|
|
71
|
+
if (!budget) return false;
|
|
72
|
+
const threshold = typeof config.threshold === 'number' ? config.threshold : DEFAULT_PRESSURE_THRESHOLD;
|
|
73
|
+
const usage = estimateClaudeUsage({ budget });
|
|
74
|
+
return typeof usage.pct === 'number' && usage.pct >= threshold;
|
|
74
75
|
} catch (_e) {
|
|
75
|
-
|
|
76
|
+
return false;
|
|
76
77
|
}
|
|
77
78
|
}
|
|
78
79
|
|
|
79
|
-
// The last user message text this turn, for the delegability signal (a code
|
|
80
|
-
// target already suffices, this only broadens it).
|
|
81
80
|
function lastUserText(payload) {
|
|
82
81
|
const msgs = extractRecentMessages(payload, 12);
|
|
83
82
|
if (!Array.isArray(msgs)) return '';
|
|
@@ -87,45 +86,38 @@ function lastUserText(payload) {
|
|
|
87
86
|
return '';
|
|
88
87
|
}
|
|
89
88
|
|
|
90
|
-
//
|
|
91
|
-
|
|
92
|
-
|
|
89
|
+
// runGuard — async (the router import is async). opts inject the probes for tests.
|
|
90
|
+
async function runGuard(payload, {
|
|
91
|
+
root = ROOT,
|
|
92
|
+
codexProbe = probeCodexAvailable,
|
|
93
|
+
routeProbe = defaultRouteProbe,
|
|
94
|
+
pressureProbe = computeUnderPressure,
|
|
95
|
+
} = {}) {
|
|
93
96
|
const toolName = payload.tool_name || payload.toolName || '';
|
|
94
97
|
if (toolName !== 'Write' && toolName !== 'Edit') return allow();
|
|
95
98
|
|
|
96
99
|
const config = loadConfig(root);
|
|
97
100
|
const armed = config.enabled === true && codexLinked();
|
|
98
|
-
|
|
101
|
+
if (!armed) return allow(); // fast path, no transcript/probe work when off
|
|
99
102
|
|
|
103
|
+
const escaped = toggledOff(root);
|
|
100
104
|
const input = payload.tool_input || payload.toolInput || {};
|
|
101
105
|
const filePath = input.file_path || input.filePath || '';
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
: String(input.content || '');
|
|
105
|
-
|
|
106
|
-
const userText = lastUserText(payload);
|
|
107
|
-
const delegable = gate.targetIsCode(filePath) || looksDelegable(userText);
|
|
108
|
-
const reviewedMarker = gate.hasReviewedMarker(content) || gate.hasReviewedMarker(extractLastAssistantText(payload));
|
|
106
|
+
const isCode = gate.targetIsCode(filePath);
|
|
107
|
+
const humanOptOut = gate.humanOptOutFromText(lastUserText(payload));
|
|
109
108
|
const delegationSeen = gate.delegationSeenThisTurn(extractRecentMessages(payload, 12) || []);
|
|
110
|
-
|
|
109
|
+
|
|
110
|
+
// Only compute the two expensive signals when a deny is otherwise possible.
|
|
111
|
+
const maybeDeny = !escaped && !humanOptOut && isCode && !delegationSeen;
|
|
112
|
+
const routerSaysCodex = maybeDeny ? await routeProbe(lastUserText(payload)) : false;
|
|
113
|
+
const underPressure = (maybeDeny && routerSaysCodex) ? pressureProbe(config) : false;
|
|
114
|
+
const codexAvailable = (maybeDeny && routerSaysCodex && underPressure) ? codexProbe() : true;
|
|
111
115
|
|
|
112
116
|
const decision = gate.decideDelegateGate({
|
|
113
|
-
armed,
|
|
114
|
-
|
|
115
|
-
delegationSeen,
|
|
116
|
-
escaped,
|
|
117
|
-
reviewedMarker,
|
|
118
|
-
priorDeny: readPriorDeny(root),
|
|
119
|
-
turnHash,
|
|
120
|
-
now,
|
|
121
|
-
graceMs: GRACE_MS,
|
|
117
|
+
armed, routerSaysCodex, targetIsCode: isCode, underPressure,
|
|
118
|
+
delegationSeen, escaped, humanOptOut, codexAvailable,
|
|
122
119
|
});
|
|
123
|
-
|
|
124
|
-
if (decision.decision === 'deny') {
|
|
125
|
-
writePriorDeny(root, { hash: turnHash, ts: now });
|
|
126
|
-
return deny(decision.reason);
|
|
127
|
-
}
|
|
128
|
-
return allow();
|
|
120
|
+
return decision.decision === 'deny' ? deny(decision.reason) : allow();
|
|
129
121
|
}
|
|
130
122
|
|
|
131
123
|
if (require.main === module) {
|
|
@@ -133,7 +125,7 @@ if (require.main === module) {
|
|
|
133
125
|
let out;
|
|
134
126
|
try {
|
|
135
127
|
const payload = JSON.parse((await readStdin()) || '{}');
|
|
136
|
-
out = runGuard(payload);
|
|
128
|
+
out = await runGuard(payload);
|
|
137
129
|
} catch (_e) {
|
|
138
130
|
out = allow();
|
|
139
131
|
}
|
|
@@ -142,4 +134,4 @@ if (require.main === module) {
|
|
|
142
134
|
})();
|
|
143
135
|
}
|
|
144
136
|
|
|
145
|
-
module.exports = { runGuard,
|
|
137
|
+
module.exports = { runGuard, probeCodexAvailable, defaultRouteProbe, computeUnderPressure, lastUserText };
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
|
|
23
23
|
const { perfFavors } = require('./perf-routing');
|
|
24
24
|
|
|
25
|
-
const DEFAULT_THRESHOLD =
|
|
25
|
+
const DEFAULT_THRESHOLD = 75;
|
|
26
26
|
const DEFAULT_INVOCATION = 'codex:codex-rescue --model gpt-5.4';
|
|
27
27
|
const RED_LINE = 'delegable work only (code / mechanical) — judgment, analysis, soul and verification stay on Claude';
|
|
28
28
|
|
|
@@ -71,16 +71,12 @@ function decideAutodelegation({ requestText = '', usage = null, config = {} } =
|
|
|
71
71
|
};
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
redLine: RED_LINE,
|
|
81
|
-
invocation,
|
|
82
|
-
};
|
|
83
|
-
}
|
|
74
|
+
// v3 : NATURE alone is no longer an auto-delegate trigger. The user's decision
|
|
75
|
+
// is PRESSURE-only — delegating to the subscription Codex (which trails Claude
|
|
76
|
+
// on quality) is worth it to spare the Claude budget, not per coding task. So a
|
|
77
|
+
// delegable task off-pressure does NOT propose Codex. `delegable` is kept only as
|
|
78
|
+
// a signal the perf-routing opt-in below can still use.
|
|
79
|
+
void delegable;
|
|
84
80
|
|
|
85
81
|
// PERF (opt-in): the forces table (user-populated) may favor Codex for this
|
|
86
82
|
// kind of task even at low pressure. Off by default; always heuristic.
|
|
@@ -141,8 +137,10 @@ function renderNudge(decision) {
|
|
|
141
137
|
`[BYAN auto-delegate]${gauge}: ${scope} via \`${invocation}\` `
|
|
142
138
|
+ '(runs on the ChatGPT subscription, no API credit).',
|
|
143
139
|
`Red line: ${decision.redLine}. The lane is ARMED (option on AND Codex linked), so this is a `
|
|
144
|
-
+ 'DIRECTIVE, not a
|
|
145
|
-
+
|
|
140
|
+
+ 'DIRECTIVE, not a suggestion: delegate the delegable part. The ONLY valid reasons to stay on '
|
|
141
|
+
+ 'Claude are (1) Codex unavailable, or (2) the user asked to stay on Claude. "Small script / '
|
|
142
|
+
+ 'latency / I verify anyway" is NOT a valid reason — the codex-delegate-guard denies a delegable '
|
|
143
|
+
+ "code write that skips Codex. Verify Codex's output before commit.",
|
|
146
144
|
].join(' ');
|
|
147
145
|
}
|
|
148
146
|
|
|
Binary file
|
|
@@ -26,7 +26,8 @@ A native workflow script mutates FD/strict state only through the `byan_fd_*` /
|
|
|
26
26
|
|
|
27
27
|
Each `agent()` leaf runs on the session model unless the call sets `opts.model`.
|
|
28
28
|
The tiering decision lives in one place — `_byan/mcp/byan-mcp-server/lib/native-tiers.js`
|
|
29
|
-
(tier vocabulary, leaf classifier, model map). Three tiers
|
|
29
|
+
(tier vocabulary, leaf classifier, model map). Three AUTO-routed tiers, plus an
|
|
30
|
+
explicit UP-TIER an author may pin by complexity (v3):
|
|
30
31
|
|
|
31
32
|
- **cheap (`model: 'haiku'`)** — a pure EXPLORATION leaf (read/load/parse/detect).
|
|
32
33
|
- **balanced (`model: 'sonnet'`)** — two classes land here. (1) MECHANICAL
|
|
@@ -40,16 +41,25 @@ The tiering decision lives in one place — `_byan/mcp/byan-mcp-server/lib/nativ
|
|
|
40
41
|
leaf may carry `model: 'sonnet'` (its tier) but not `haiku` (`analysis-below-tier`).
|
|
41
42
|
Semantic/adversarial VERIFICATION is NOT analysis and stays deep.
|
|
42
43
|
- **deep (OMIT `opts.model`)** — implementation + verification leaves inherit the
|
|
43
|
-
session model (no pin-up
|
|
44
|
-
deep with the `deep-` label prefix
|
|
45
|
-
`mech-` opt-in.
|
|
44
|
+
session model (the cost-safe default: no AUTO pin-up). A genuinely frontier
|
|
45
|
+
ANALYSIS leaf opts back to deep with the `deep-` label prefix
|
|
46
|
+
(`deep-assess-architecture`), the mirror of the `mech-` opt-in.
|
|
47
|
+
- **up-tier (`model: 'opus'` / `model: 'fable'`)** — an EXPLICIT authoring choice
|
|
48
|
+
to raise a genuinely complex leaf ABOVE the inherited tier (v3). `opus` for a
|
|
49
|
+
hard leaf, `fable` as the last-resort top-reasoning model for extreme complexity
|
|
50
|
+
(~2x Opus price). This is the workflow-leaf mirror of the dispatch-router
|
|
51
|
+
complexity ladder (haiku -> sonnet -> opus -> fable). Auto-routing does not pick
|
|
52
|
+
it — the author writes it deliberately. The anti-downgrade floor does not apply
|
|
53
|
+
upward, so the linter allows an up-tier pin on any leaf (it costs more, it does
|
|
54
|
+
not regress quality). The old blanket "no pin-up / no Fable" ban is lifted.
|
|
46
55
|
|
|
47
56
|
The linter splits the two directions:
|
|
48
57
|
|
|
49
|
-
- **Floor (HARD, blocks the commit)** — a
|
|
50
|
-
|
|
58
|
+
- **Floor (HARD, blocks the commit)** — a DOWNGRADE model (haiku/sonnet) on a
|
|
59
|
+
PROTECTED leaf, or a half-applied `mech-` opt-in, is a contract violation
|
|
51
60
|
(`modelRoutingViolations` + `mechanicalLabelViolations`). This is the STRICT-2
|
|
52
|
-
No Downgrade net.
|
|
61
|
+
No Downgrade net. An UP-TIER pin (opus/fable) is not a violation — the floor
|
|
62
|
+
guards downward only.
|
|
53
63
|
- **Ceiling (ADVISORY, non-blocking)** — an exploration- OR analysis-labelled leaf
|
|
54
64
|
that runs deep is *reported* (`byan-lint-workflows.js --advise`:
|
|
55
65
|
`untiered-exploration` -> `model: 'haiku'`, `untiered-analysis` -> `model: 'sonnet'`),
|
|
@@ -21,8 +21,10 @@ the user should not have to ask for it. Four moments:
|
|
|
21
21
|
(`_byan/mcp/byan-mcp-server/lib/agent-matcher.js`), the right model + effort
|
|
22
22
|
(`dispatch-router.js` / native-tiers), and the right runtime — Codex for
|
|
23
23
|
execution / shell / deploy / devops / browser ; Claude for architecture /
|
|
24
|
-
refactor / quality / planning ; verification stays on Claude
|
|
25
|
-
|
|
24
|
+
refactor / quality / planning ; verification stays on Claude. The Claude model
|
|
25
|
+
scales on a four-rung complexity ladder (haiku -> sonnet -> opus -> fable, fable
|
|
26
|
+
the extreme last resort) ; Fable is refused on the Codex path only. No user ask.
|
|
27
|
+
A suited agent -> use it. NO suited agent -> interview to
|
|
26
28
|
frame the need, web-research the trade's competencies + best practices, create
|
|
27
29
|
the tailored agent (the one place the human is required upstream).
|
|
28
30
|
3. **Execute in party-mode with a live visual.** The user SEES what happens, live:
|
|
@@ -30,13 +32,17 @@ the user should not have to ask for it. Four moments:
|
|
|
30
32
|
`TaskCreate` / `TaskUpdate`) plus the dispatch table shown at the top of BUILD.
|
|
31
33
|
- **Codex-lane** — when the lane is ARMED (yanstaller option on AND Codex
|
|
32
34
|
linked, i.e. `_byan/_config/autodelegate.json` present with `enabled:true`
|
|
33
|
-
AND `~/.codex/auth.json` or `CODEX_API_KEY`) and the task is delegable,
|
|
35
|
+
AND `~/.codex/auth.json` or `CODEX_API_KEY`) and the task is delegable code,
|
|
34
36
|
ACTUALLY delegate via `codex-bridge.js` (`codex exec` -> unified diff -> YOU
|
|
35
|
-
apply it)
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
apply it) or the `codex:codex-rescue` agent. This is a DIRECTIVE, enforced by
|
|
38
|
+
the `codex-delegate-guard` PreToolUse tooth : it DENIES a delegable code
|
|
39
|
+
Write/Edit that skipped Codex. The ONLY valid reasons to stay on Claude are
|
|
40
|
+
(1) Codex unavailable (auto-detected), or (2) the USER asked to stay on Claude
|
|
41
|
+
("reste sur Claude" / "sans codex", or the `.byan-codex-autodelegate/off`
|
|
42
|
+
switch). "Small script / latency / I verify anyway" is NOT valid — that is
|
|
43
|
+
exactly the self-dodge the tooth closes (option B). You cannot self-grant a
|
|
44
|
+
bypass. When NOT armed (option off or Codex not linked), there is no
|
|
45
|
+
delegation — you run it on Claude.
|
|
40
46
|
- **Claude-lane** — run it on Claude at the chosen model.
|
|
41
47
|
4. **User gate at the END.** The user-validation gate lands at the end, once the
|
|
42
48
|
feature/task is done and there is a deliverable to review — not upstream. (In a
|
|
@@ -129,9 +135,9 @@ Never call `byan_update_apply` without explicit user consent. That tool returns
|
|
|
129
135
|
- nature `exploration` (load/read/scan/list/parse/fetch...) → `haiku`
|
|
130
136
|
- nature `mechanical` (binary judgment-free checks : JSON parses, schema matches, lint passes — label prefix `mech-`, explicit opt-in only) → `sonnet`
|
|
131
137
|
- nature `implementation` / `verification` / `analysis` / unknown → deep = **inherit the session model**
|
|
132
|
-
- Keep protected work (verify/analysis/implement) off haiku/sonnet regardless of size
|
|
138
|
+
- Keep protected work (verify/analysis/implement) off haiku/sonnet regardless of size. Auto-routing does not pin up ; but an author MAY deliberately UP-TIER a genuinely complex leaf to `opus` (hard) or `fable` (extreme, last resort) — the linter allows it (the floor guards downward only). Pass an explicit `nature` to `byan_dispatch` when you know it.
|
|
133
139
|
- **Inside native workflow scripts** (`.claude/workflows/*.js` OR ad-hoc) the SAME tiering applies per `agent()` leaf via `opts.model`, enforced as a FLOOR not a ceiling on TWO nets :
|
|
134
|
-
- **Repo linter** (committed scripts, pre-commit) : `modelRoutingViolations` HARD-blocks a downgrade on a protected leaf,
|
|
140
|
+
- **Repo linter** (committed scripts, pre-commit) : `modelRoutingViolations` HARD-blocks a downgrade (haiku/sonnet) on a protected leaf, or a half-applied `mech-` opt-in (`mechanical-without-model` / `mechanical-below-tier`) ; an UP-TIER pin (opus/fable) is allowed, not blocked (the floor guards downward only) ; an exploration-labelled leaf left deep is a NON-blocking ADVISORY (`byan-lint-workflows.js --advise`), since many such leaves bear a gate/classification/exact-conversion and must stay deep — the human owns that call.
|
|
135
141
|
- **Tier gate hook** (EVERY Workflow invocation, inline or scriptPath) : `tier-script-guard.js` (PreToolUse) runs the same analysis (`lib/tier-script.js`) and DENIES ONCE when exploration/`mech-` leaves have no tier, with the exact leaf list to fix. Acknowledge deliberate deep choices with the `// BYAN-TIER: reviewed` comment marker ; an identical resubmission passes (deny-once by design, no trap). Every decision lands in `_byan-output/tier-ledger.jsonl` (the measurement basis for token gains). Escape hatch : `.byan-tier/off`.
|
|
136
142
|
- **Authoring aid** : BEFORE writing a script, call `byan_dispatch` with `{ leaves: [{ label, nature? }] }` (batch mode) to get the `opts.model` per leaf ; write `model:` only where non-null. Report with `node _byan/mcp/byan-mcp-server/bin/byan-tier-script.js <file> [--json]`.
|
|
137
143
|
- No per-leaf effort knob exists (the API exposes only `model`), so effort-by-complexity reduces to model-by-complexity.
|
|
@@ -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": "fcea02854887283c8ff1d4b7d09778d62fc7ce52959a00577e87091e7c31f991"
|
|
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 ;
|
|
@@ -95,16 +95,21 @@ tier vocabulary, the leaf classifier, and the model map.
|
|
|
95
95
|
|
|
96
96
|
| Tier | `opts.model` | Used for |
|
|
97
97
|
|------|--------------|----------|
|
|
98
|
+
| `up-tier` | `opus` / `fable` | EXPLICIT authoring choice: raise a genuinely complex leaf above the inherited tier (opus for hard, fable for extreme, last resort) |
|
|
98
99
|
| `deep` | **omitted** (inherit the session model) | implement, verify, analysis — the default |
|
|
99
100
|
| `balanced` | `sonnet` | MECHANICAL verification, opt-in only via the `mech-` label prefix |
|
|
100
101
|
| `cheap` | `haiku` | a pure exploration leaf: read / load / parse / detect |
|
|
101
102
|
|
|
102
103
|
Three hard rules:
|
|
103
104
|
|
|
104
|
-
- **No pin-up
|
|
105
|
-
leaf
|
|
106
|
-
|
|
107
|
-
|
|
105
|
+
- **No AUTO pin-up, but an explicit up-tier is allowed (v3).** Auto-routing does
|
|
106
|
+
not raise a leaf above the inherited tier: `deep` is an omission, letting a leaf
|
|
107
|
+
inherit whatever the session runs (Opus by default, Sonnet if the user chose
|
|
108
|
+
Sonnet). An author MAY still deliberately pin `opus` or `fable` on a genuinely
|
|
109
|
+
complex leaf — the workflow-leaf mirror of the dispatch-router complexity ladder
|
|
110
|
+
(haiku -> sonnet -> opus -> fable). The anti-downgrade floor does not apply
|
|
111
|
+
upward, so the linter allows an up-tier pin; the old blanket "no pin-up / no
|
|
112
|
+
Fable" ban is lifted.
|
|
108
113
|
- **Only exploration and mech- downgrade.** A leaf is pinned to `cheap` only
|
|
109
114
|
when it is unambiguous read/extract work. `classifyLeaf` keys off the LABEL
|
|
110
115
|
(the prompt is too noisy — an exploration leaf often says "report what you
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-byan-agent",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.52.0",
|
|
4
4
|
"description": "BYAN v2.8 - Intelligent AI agent creator with ELO trust system + scientific fact-check + Hermes universal dispatcher + native Claude Code integration (hooks, skills, MCP server). Multi-platform (Claude Code, Codex). Merise Agile + TDD + 71 Mantras. ~54% LLM cost savings.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|