create-byan-agent 2.44.0 → 2.47.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 +95 -0
- package/install/templates/.claude/CLAUDE.md +11 -0
- package/install/templates/.claude/hooks/agent-gate-check.js +53 -0
- package/install/templates/.claude/hooks/inject-voice-anchor.js +34 -1
- package/install/templates/.claude/hooks/lib/agent-gate.js +127 -0
- package/install/templates/.claude/hooks/lib/plain-language.js +155 -0
- package/install/templates/.claude/hooks/plain-language-check.js +53 -0
- package/install/templates/.claude/rules/agent-entry-gate.md +83 -0
- package/install/templates/.claude/rules/plain-language.md +73 -0
- package/install/templates/.claude/settings.json +8 -0
- package/install/templates/.claude/skills/byan-byan/SKILL.md +14 -0
- package/install/templates/.claude/workflows/intelligent-dispatch.js +68 -0
- package/install/templates/_byan/agent/byan/byan-tao.md +12 -0
- package/install/templates/_byan/mcp/byan-mcp-server/lib/agent-matcher.js +167 -0
- package/install/templates/_byan/mcp/byan-mcp-server/lib/codex-bridge.js +176 -0
- package/install/templates/_byan/mcp/byan-mcp-server/lib/dispatch-blackboard.js +114 -0
- package/install/templates/_byan/mcp/byan-mcp-server/lib/dispatch-orchestrator.js +116 -0
- package/install/templates/_byan/mcp/byan-mcp-server/lib/dispatch-router.js +149 -0
- package/install/templates/_byan/mcp/byan-mcp-server/server.js +37 -0
- package/install/templates/_byan/mcp/byan-mcp-server/skill-bundles-manifest.json +1 -1
- package/install/templates/_byan/workflow/simple/byan/data/mantras.yaml +15 -4
- package/install/templates/dist/skill-bundles/byan-byan.zip +0 -0
- package/install/templates/docs/intelligent-dispatch.md +84 -0
- package/package.json +1 -1
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// F4 — the ORCHESTRATOR core: the loop that makes an architect (Claude) and a
|
|
2
|
+
// dev (Codex or Claude, per F1) exchange turn by turn through the shared board
|
|
3
|
+
// (F3) until the exchange converges, then returns the result. This is the durable,
|
|
4
|
+
// unit-testable heart of "launch a workflow and the agents talk to each other".
|
|
5
|
+
//
|
|
6
|
+
// It runs as full Node (main thread or an MCP worker), NOT inside a sandboxed
|
|
7
|
+
// .claude/workflows script — that sandbox has no imports and no filesystem, so it
|
|
8
|
+
// could never use F1/F2/F3. A native workflow script can only be a THIN launch
|
|
9
|
+
// façade over this core (see docs). The core here owns the routing, the loop, and
|
|
10
|
+
// the convergence rule.
|
|
11
|
+
//
|
|
12
|
+
// Executors are INJECTED so the loop is testable without real agents or Codex:
|
|
13
|
+
// executors.architect(ctx) -> { content, question? } (always Claude)
|
|
14
|
+
// executors.claude(ctx) -> { content, question? } (dev work on Claude)
|
|
15
|
+
// executors.codex(ctx) -> { content, question?, diff? } (dev work on Codex)
|
|
16
|
+
// The orchestrator picks executors[route.runtime] for the dev turn; the architect
|
|
17
|
+
// is always Claude (judgment stays on Claude — the verification red line's sibling).
|
|
18
|
+
//
|
|
19
|
+
// Pure control flow: no direct I/O beyond the injected board writer/reader, which
|
|
20
|
+
// default to F3's fs-backed pair but are overridable in tests.
|
|
21
|
+
|
|
22
|
+
import { dispatch } from './dispatch-router.js';
|
|
23
|
+
import {
|
|
24
|
+
KINDS,
|
|
25
|
+
makeEntry,
|
|
26
|
+
renderForAgent,
|
|
27
|
+
pendingQuestions,
|
|
28
|
+
appendEntry as fsAppend,
|
|
29
|
+
readEntries as fsRead,
|
|
30
|
+
} from './dispatch-blackboard.js';
|
|
31
|
+
|
|
32
|
+
export const ARCHITECT = 'claude-architect';
|
|
33
|
+
export const DEV = 'dev';
|
|
34
|
+
export const DEFAULT_MAX_ROUNDS = 4;
|
|
35
|
+
|
|
36
|
+
// A board handle backed by F3's fs sidecar. Tests pass an in-memory one.
|
|
37
|
+
export function fsBoard(projectDir, sessionId) {
|
|
38
|
+
return {
|
|
39
|
+
append: (entry) => fsAppend(projectDir, sessionId, entry),
|
|
40
|
+
read: () => fsRead(projectDir, sessionId),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// An in-memory board (no I/O) — the default when no projectDir is given, and what
|
|
45
|
+
// tests use. Same shape as fsBoard.
|
|
46
|
+
export function memoryBoard(seed = []) {
|
|
47
|
+
const entries = seed.map(makeEntry);
|
|
48
|
+
return {
|
|
49
|
+
append: (raw) => { const e = makeEntry(raw); entries.push(e); return e; },
|
|
50
|
+
read: () => entries.slice(),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// orchestrateTask — run ONE task to convergence. Returns
|
|
55
|
+
// { route, rounds, entries, status, diffs }
|
|
56
|
+
// status: 'converged' (dev produced a result with no open question)
|
|
57
|
+
// | 'max-rounds' (hit the round cap still exchanging)
|
|
58
|
+
// | 'dev-failed' (the dev executor reported a failure -> caller may fall back)
|
|
59
|
+
export function orchestrateTask({ task = {}, board, executors = {}, maxRounds = DEFAULT_MAX_ROUNDS } = {}) {
|
|
60
|
+
const route = dispatch({ nature: task.nature, complexity: task.complexity });
|
|
61
|
+
const devExec = executors[route.runtime];
|
|
62
|
+
const architectExec = executors.architect;
|
|
63
|
+
if (typeof devExec !== 'function' || typeof architectExec !== 'function') {
|
|
64
|
+
throw new Error(`orchestrateTask: missing executor for runtime "${route.runtime}" or architect`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Monotonic message counter (NOT per-round): each append gets the next turn, so
|
|
68
|
+
// an architect ANSWER always carries a turn strictly greater than the dev
|
|
69
|
+
// QUESTION it follows — which is what pendingQuestions needs to mark it resolved.
|
|
70
|
+
let turn = 0;
|
|
71
|
+
const post = (from, to, kind, content) => board.append({ turn: turn++, from, to, kind, content: content || '' });
|
|
72
|
+
|
|
73
|
+
// The architect opens with the design brief (addressed to dev).
|
|
74
|
+
const opening = architectExec({ task, route, round: 0, transcript: renderForAgent(board.read(), ARCHITECT) });
|
|
75
|
+
post(ARCHITECT, DEV, KINDS.DESIGN, (opening && opening.content) || task.brief || '');
|
|
76
|
+
|
|
77
|
+
const diffs = [];
|
|
78
|
+
let status = 'max-rounds';
|
|
79
|
+
for (let round = 1; round <= maxRounds; round++) {
|
|
80
|
+
// Dev turn: reads the board, does the work on its routed runtime.
|
|
81
|
+
const devOut = devExec({ task, route, round, transcript: renderForAgent(board.read(), DEV) }) || {};
|
|
82
|
+
if (devOut.ok === false || devOut.failed === true) {
|
|
83
|
+
post(DEV, ARCHITECT, KINDS.NOTE, `dev failed: ${devOut.detail || 'unknown'}`);
|
|
84
|
+
status = 'dev-failed';
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
if (devOut.diff) diffs.push(devOut.diff);
|
|
88
|
+
post(DEV, ARCHITECT, devOut.question ? KINDS.WORK : KINDS.RESULT, devOut.content);
|
|
89
|
+
if (devOut.question) post(DEV, ARCHITECT, KINDS.QUESTION, devOut.question);
|
|
90
|
+
|
|
91
|
+
// Converged: dev delivered a result and left no open question.
|
|
92
|
+
if (!pendingQuestions(board.read()).length) { status = 'converged'; break; }
|
|
93
|
+
|
|
94
|
+
// Architect answers the open question(s); the higher turn resolves them.
|
|
95
|
+
const archOut = architectExec({ task, route, round, transcript: renderForAgent(board.read(), ARCHITECT) }) || {};
|
|
96
|
+
post(ARCHITECT, DEV, KINDS.ANSWER, archOut.content);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return { route, messages: board.read().filter((e) => e.turn > 0).length, entries: board.read(), status, diffs };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// orchestrate — run a whole task list. Each task gets its own board namespace when
|
|
103
|
+
// an fs board factory is supplied; otherwise an in-memory board per task. Returns
|
|
104
|
+
// one result per task, in order. Never routes verification to Codex (guaranteed by
|
|
105
|
+
// F1's router, re-asserted here as defence in depth).
|
|
106
|
+
export function orchestrate({ tasks = [], executors = {}, maxRounds = DEFAULT_MAX_ROUNDS, boardFor } = {}) {
|
|
107
|
+
return (Array.isArray(tasks) ? tasks : []).map((task, i) => {
|
|
108
|
+
const board = typeof boardFor === 'function' ? boardFor(task, i) : memoryBoard();
|
|
109
|
+
const result = orchestrateTask({ task, board, executors, maxRounds });
|
|
110
|
+
if (result.route.runtime === 'codex' && /verif|review|validate|audit/i.test(String(task.nature || ''))) {
|
|
111
|
+
// Should be impossible (router forces Claude), but fail loud if it ever regresses.
|
|
112
|
+
throw new Error(`orchestrate: verification task "${task.id || i}" routed to Codex — red line breached`);
|
|
113
|
+
}
|
|
114
|
+
return { taskId: task.id != null ? task.id : i, ...result };
|
|
115
|
+
});
|
|
116
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
// F1 — the dispatch BRAIN. Single source of truth for the intelligent dispatch:
|
|
2
|
+
// given a task's NATURE and COMPLEXITY, decide which RUNTIME runs it (Codex or
|
|
3
|
+
// Claude), which MODEL, and (Codex only) which reasoning EFFORT.
|
|
4
|
+
//
|
|
5
|
+
// It composes with, does not duplicate, native-tiers.js: the Claude-side model
|
|
6
|
+
// tier is delegated to that module's TIER_MODEL vocabulary (which, by design,
|
|
7
|
+
// only ever yields haiku / sonnet / null — so Fable can never leak in from the
|
|
8
|
+
// Claude side either). This module adds the two NEW axes the intelligent dispatch
|
|
9
|
+
// needs — runtime selection and Codex reasoning effort — on top of it.
|
|
10
|
+
//
|
|
11
|
+
// Routing table is the cross-checked result (5 independent sources, see
|
|
12
|
+
// docs / CHANGELOG): Codex wins autonomous execution, shell/CI/DevOps, deploy,
|
|
13
|
+
// browser/computer use ; Claude wins architecture, refactor-at-repo-scale,
|
|
14
|
+
// quality, planning, and ALL verification. Two hard red lines are enforced here,
|
|
15
|
+
// not left to the caller:
|
|
16
|
+
// 1. Fable is NEVER emitted (long-term product decision).
|
|
17
|
+
// 2. Verification is NEVER routed to Codex (a runtime must not grade its own
|
|
18
|
+
// work ; the reviewer stays on Claude).
|
|
19
|
+
//
|
|
20
|
+
// Pure: no I/O, no clock, deterministic. The Codex transport (F2) and the
|
|
21
|
+
// orchestrating loop (F4) consume this; they never re-decide routing.
|
|
22
|
+
|
|
23
|
+
import { TIER_MODEL } from './native-tiers.js';
|
|
24
|
+
|
|
25
|
+
export const RUNTIMES = Object.freeze({ CODEX: 'codex', CLAUDE: 'claude' });
|
|
26
|
+
|
|
27
|
+
// Codex runs on the ChatGPT-subscription entitled model. Kept as a named constant
|
|
28
|
+
// so a plan change is a one-line edit, not a scatter of string literals.
|
|
29
|
+
export const CODEX_MODEL = 'gpt-5.4';
|
|
30
|
+
|
|
31
|
+
export const EFFORTS = Object.freeze({ LOW: 'low', MEDIUM: 'medium', HIGH: 'high' });
|
|
32
|
+
|
|
33
|
+
// Models we refuse to emit, ever. Fable is excluded by explicit long-term
|
|
34
|
+
// decision; the guard makes that refusal mechanical rather than a convention.
|
|
35
|
+
export const FORBIDDEN_MODELS = Object.freeze(['fable', 'claude-fable-5']);
|
|
36
|
+
|
|
37
|
+
// Task natures that route to Codex. Everything NOT here (and not a verification
|
|
38
|
+
// nature) falls through to Claude — Claude is the safe default, so an unknown or
|
|
39
|
+
// ambiguous nature keeps judgment on Claude rather than gambling it on Codex.
|
|
40
|
+
const CODEX_NATURES = Object.freeze([
|
|
41
|
+
'execution', 'exec', 'shell', 'terminal', 'command',
|
|
42
|
+
'deploy', 'deployment', 'devops', 'ci', 'cd', 'pipeline',
|
|
43
|
+
'scripting', 'script', 'automation', 'browser', 'computer-use', 'e2e-run',
|
|
44
|
+
]);
|
|
45
|
+
|
|
46
|
+
// Natures that MUST stay on Claude even if a future edit mislabels them — the
|
|
47
|
+
// verification red line. Checked before the Codex table so it can never be
|
|
48
|
+
// overridden by a nature that also looks like execution (e.g. "run-and-verify").
|
|
49
|
+
const VERIFICATION_NATURES = Object.freeze([
|
|
50
|
+
'verification', 'verify', 'validate', 'review', 'audit', 'check', 'qa',
|
|
51
|
+
]);
|
|
52
|
+
|
|
53
|
+
function normalize(value) {
|
|
54
|
+
return String(value == null ? '' : value).trim().toLowerCase();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function matchesAny(text, list) {
|
|
58
|
+
return list.some((kw) => text.includes(kw));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// assertNoFable(model) — mechanical enforcement of red line #1. Throws rather
|
|
62
|
+
// than silently substituting, so a Fable request is a loud failure at the source.
|
|
63
|
+
export function assertNoFable(model) {
|
|
64
|
+
const m = normalize(model);
|
|
65
|
+
if (FORBIDDEN_MODELS.some((f) => m.includes(f))) {
|
|
66
|
+
throw new Error(`dispatch-router: forbidden model "${model}" (Fable is excluded by long-term policy)`);
|
|
67
|
+
}
|
|
68
|
+
return model;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// isVerification(nature) — red line #2 helper. A verification nature never leaves
|
|
72
|
+
// Claude.
|
|
73
|
+
export function isVerification(nature) {
|
|
74
|
+
return matchesAny(normalize(nature), VERIFICATION_NATURES);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// routeRuntime(nature) -> 'codex' | 'claude'. Verification wins first (stays
|
|
78
|
+
// Claude), then the Codex table, else Claude (safe default).
|
|
79
|
+
export function routeRuntime(nature) {
|
|
80
|
+
const n = normalize(nature);
|
|
81
|
+
if (isVerification(n)) return RUNTIMES.CLAUDE;
|
|
82
|
+
if (matchesAny(n, CODEX_NATURES)) return RUNTIMES.CODEX;
|
|
83
|
+
return RUNTIMES.CLAUDE;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Map a complexity input to a coarse bucket. Accepts a 0-100 number (the
|
|
87
|
+
// complexity-scorer scale) OR a label (trivial/low/medium/high/hard). Anything
|
|
88
|
+
// unrecognised is treated as medium — a safe middle that neither over-spends nor
|
|
89
|
+
// under-powers.
|
|
90
|
+
export function complexityBucket(complexity) {
|
|
91
|
+
if (typeof complexity === 'number' && Number.isFinite(complexity)) {
|
|
92
|
+
if (complexity < 34) return EFFORTS.LOW;
|
|
93
|
+
if (complexity < 67) return EFFORTS.MEDIUM;
|
|
94
|
+
return EFFORTS.HIGH;
|
|
95
|
+
}
|
|
96
|
+
const c = normalize(complexity);
|
|
97
|
+
if (['trivial', 'low', 'simple', 'easy'].includes(c)) return EFFORTS.LOW;
|
|
98
|
+
if (['high', 'hard', 'complex', 'frontier'].includes(c)) return EFFORTS.HIGH;
|
|
99
|
+
return EFFORTS.MEDIUM;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// effortForComplexity(complexity) -> a Codex reasoning-effort value. Codex is the
|
|
103
|
+
// only side with a real effort knob (`codex exec -c model_reasoning_effort=...`);
|
|
104
|
+
// this maps complexity onto it 1:1 with the bucket.
|
|
105
|
+
export function effortForComplexity(complexity) {
|
|
106
|
+
return complexityBucket(complexity);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// claudeModelForComplexity(complexity) -> 'haiku' | 'sonnet' | null(omit=inherit).
|
|
110
|
+
// Delegates to native-tiers' TIER_MODEL so the Claude vocabulary stays in one
|
|
111
|
+
// place: low -> cheap(haiku), medium -> balanced(sonnet), high -> deep(null =
|
|
112
|
+
// inherit the session model, i.e. Opus on an Opus session). Fable is structurally
|
|
113
|
+
// impossible here (TIER_MODEL has no Fable entry), but we still assert to make the
|
|
114
|
+
// guarantee explicit and catch a future TIER_MODEL drift.
|
|
115
|
+
export function claudeModelForComplexity(complexity) {
|
|
116
|
+
const bucket = complexityBucket(complexity);
|
|
117
|
+
const model = bucket === EFFORTS.LOW
|
|
118
|
+
? TIER_MODEL.cheap
|
|
119
|
+
: bucket === EFFORTS.MEDIUM
|
|
120
|
+
? TIER_MODEL.balanced
|
|
121
|
+
: TIER_MODEL.deep; // null = inherit (never pinned to Fable)
|
|
122
|
+
return model == null ? null : assertNoFable(model);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// dispatch({ nature, complexity }) -> the full routing decision:
|
|
126
|
+
// { runtime, model, effort, reasoning }
|
|
127
|
+
// - Codex: model = CODEX_MODEL, effort = complexity bucket (the real knob).
|
|
128
|
+
// - Claude: model = haiku/sonnet/null(inherit), effort = null (Claude has no
|
|
129
|
+
// effort knob — its "effort" IS the model tier).
|
|
130
|
+
// Both red lines are enforced here regardless of caller input.
|
|
131
|
+
export function dispatch({ nature, complexity } = {}) {
|
|
132
|
+
const runtime = routeRuntime(nature);
|
|
133
|
+
if (runtime === RUNTIMES.CODEX) {
|
|
134
|
+
return {
|
|
135
|
+
runtime,
|
|
136
|
+
model: assertNoFable(CODEX_MODEL),
|
|
137
|
+
effort: effortForComplexity(complexity),
|
|
138
|
+
reasoning: `nature "${normalize(nature)}" routes to Codex ; effort scaled to complexity`,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
runtime,
|
|
143
|
+
model: claudeModelForComplexity(complexity), // null = inherit session model
|
|
144
|
+
effort: null, // Claude effort = model tier, no separate knob
|
|
145
|
+
reasoning: isVerification(nature)
|
|
146
|
+
? 'verification stays on Claude (red line: a runtime never grades its own work)'
|
|
147
|
+
: `nature "${normalize(nature)}" stays on Claude (default-safe) ; model scaled to complexity`,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
@@ -1004,6 +1004,27 @@ const tools = [
|
|
|
1004
1004
|
additionalProperties: false,
|
|
1005
1005
|
},
|
|
1006
1006
|
},
|
|
1007
|
+
// Selective RAG retrieval — returns the top-k most relevant knowledge bodies
|
|
1008
|
+
// VERBATIM (never truncated; negations/prohibitions are returned intact).
|
|
1009
|
+
// GET /api/projects/:projectId/knowledge/retrieve?q=...&k=10&tokenBudget=0
|
|
1010
|
+
// Backed by PG FTS (ts_rank) in prod, LIKE-degraded on SQLite (dev/tests).
|
|
1011
|
+
// Use this instead of knowledge_list when you only need a focused subset.
|
|
1012
|
+
{
|
|
1013
|
+
name: 'byan_api_knowledge_retrieve',
|
|
1014
|
+
description:
|
|
1015
|
+
'Retrieve the top-k most relevant knowledge entries for a query using full-text search (PG) or LIKE fallback (SQLite). Returns bodies VERBATIM — negations and prohibitions are never truncated. GET /api/projects/:projectId/knowledge/retrieve?q=...&k=10&tokenBudget=0. RBAC viewer required. projectId and q are required. Requires BYAN_API_TOKEN.',
|
|
1016
|
+
inputSchema: {
|
|
1017
|
+
type: 'object',
|
|
1018
|
+
properties: {
|
|
1019
|
+
projectId: { type: 'string', description: 'Project id (required — RBAC guard).' },
|
|
1020
|
+
q: { type: 'string', description: 'Search query (required).' },
|
|
1021
|
+
k: { type: 'number', description: 'Max number of results (default 10).' },
|
|
1022
|
+
tokenBudget: { type: 'number', description: 'Total token budget (0 = unlimited).' },
|
|
1023
|
+
},
|
|
1024
|
+
required: ['projectId', 'q'],
|
|
1025
|
+
additionalProperties: false,
|
|
1026
|
+
},
|
|
1027
|
+
},
|
|
1007
1028
|
|
|
1008
1029
|
// ─── Memory ───────────────────────────────────────────────────────────
|
|
1009
1030
|
// Route: GET /api/projects/:projectId/memory (RBAC viewer)
|
|
@@ -1437,6 +1458,7 @@ const REMOTE_SAFE_TOOLS = new Set([
|
|
|
1437
1458
|
'byan_api_workflow_runs_get',
|
|
1438
1459
|
'byan_api_knowledge_list',
|
|
1439
1460
|
'byan_api_knowledge_get',
|
|
1461
|
+
'byan_api_knowledge_retrieve',
|
|
1440
1462
|
'byan_api_memory_list',
|
|
1441
1463
|
'byan_api_memory_search',
|
|
1442
1464
|
'byan_api_custom_agents_list',
|
|
@@ -1982,6 +2004,21 @@ export function createByanServer({ token, remoteOnly = false } = {}) {
|
|
|
1982
2004
|
return { content: [{ type: 'text', text: JSON.stringify(body, null, 2) }] };
|
|
1983
2005
|
}
|
|
1984
2006
|
|
|
2007
|
+
if (name === 'byan_api_knowledge_retrieve') {
|
|
2008
|
+
requireToken();
|
|
2009
|
+
if (!args.projectId) throw new Error('projectId is required (RBAC: knowledge is project-scoped).');
|
|
2010
|
+
if (!args.q || String(args.q).trim() === '') throw new Error('q (query) is required.');
|
|
2011
|
+
const qs = buildQuery({
|
|
2012
|
+
q: args.q,
|
|
2013
|
+
k: args.k,
|
|
2014
|
+
tokenBudget: args.tokenBudget,
|
|
2015
|
+
});
|
|
2016
|
+
const body = await apiRequest(
|
|
2017
|
+
`/api/projects/${encodeURIComponent(args.projectId)}/knowledge/retrieve${qs}`
|
|
2018
|
+
);
|
|
2019
|
+
return { content: [{ type: 'text', text: JSON.stringify(body, null, 2) }] };
|
|
2020
|
+
}
|
|
2021
|
+
|
|
1985
2022
|
if (name === 'byan_api_memory_list') {
|
|
1986
2023
|
requireToken();
|
|
1987
2024
|
if (!args.projectId) throw new Error('projectId is required (RBAC: memory is project-scoped).');
|
|
@@ -94,7 +94,7 @@
|
|
|
94
94
|
"name": "byan-byan",
|
|
95
95
|
"module": "core",
|
|
96
96
|
"tier": "connector-bound",
|
|
97
|
-
"sourceHash": "
|
|
97
|
+
"sourceHash": "523ca56a08c8b85edba423e1bfdd0d9c777442bcb266b5a74e65c14814da128d"
|
|
98
98
|
},
|
|
99
99
|
"byan-byan-v2": {
|
|
100
100
|
"name": "byan-byan-v2",
|
|
@@ -262,11 +262,22 @@ agents_ia:
|
|
|
262
262
|
- "Contraintes légales/business"
|
|
263
263
|
- "TODOs avec ticket"
|
|
264
264
|
|
|
265
|
+
- id: "IA-26"
|
|
266
|
+
name: "Parler Réel"
|
|
267
|
+
category: "code_quality"
|
|
268
|
+
description: "Parler en français réel et cohérent à l'utilisateur : pas d'anglais gratuit, pas de jargon interne brut, pas de métaphore collée de travers. Un terme technique sans équivalent est gardé mais expliqué une fois. Le test : le lecteur comprend sans dictionnaire."
|
|
269
|
+
priority: "critique"
|
|
270
|
+
forbidden:
|
|
271
|
+
- "Anglais quand le français existe (cutoff, fallback, housekeeping)"
|
|
272
|
+
- "Jargon interne balancé brut (leaf, tier, downgrade, gate, inline, advisory)"
|
|
273
|
+
- "Métaphore collée de travers (forger un token)"
|
|
274
|
+
rule_file: ".claude/rules/plain-language.md"
|
|
275
|
+
|
|
265
276
|
metadata:
|
|
266
|
-
total_mantras:
|
|
277
|
+
total_mantras: 65
|
|
267
278
|
conception_count: 39
|
|
268
|
-
agents_ia_count:
|
|
269
|
-
version: "1.0.
|
|
270
|
-
last_updated: "2026-
|
|
279
|
+
agents_ia_count: 26
|
|
280
|
+
version: "1.0.1"
|
|
281
|
+
last_updated: "2026-07-15"
|
|
271
282
|
authors: ["Yan", "Carson"]
|
|
272
283
|
methodology: "Merise Agile + TDD"
|
|
Binary file
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Intelligent dispatch — Codex/Claude routing + architect-dev loop
|
|
2
|
+
|
|
3
|
+
Route each task to the runtime that is genuinely better for it (Codex or Claude),
|
|
4
|
+
on the right model and effort for its complexity, and let an architect (Claude)
|
|
5
|
+
and a dev (Codex or Claude) exchange turn by turn until the work converges.
|
|
6
|
+
|
|
7
|
+
This is "option B": a deterministic orchestration loop (the durable, testable
|
|
8
|
+
backbone), with the Codex transport behind a swappable adapter so the tighter
|
|
9
|
+
`codex mcp-server` coupling can slot in later without touching the core.
|
|
10
|
+
|
|
11
|
+
## The routing table (cross-checked, 5 independent sources)
|
|
12
|
+
|
|
13
|
+
| Task nature | Runtime | Why |
|
|
14
|
+
|-------------|---------|-----|
|
|
15
|
+
| execution / shell / terminal / deploy / devops / CI / scripting / browser / automation | Codex | Codex leads autonomous execution, terminal-task benchmarks, browser/computer use |
|
|
16
|
+
| architecture / design / planning / refactor / quality / analysis / exploration / anything unknown | Claude | Claude leads planning, repo-scale refactor, code quality ; unknown stays on Claude (safe default) |
|
|
17
|
+
| verification / review / validate / audit / qa | Claude (hard rule) | A runtime must not grade its own work |
|
|
18
|
+
|
|
19
|
+
The raw "who is better overall" score (SWE-bench Verified) is a near tie whose
|
|
20
|
+
leader flips by leaderboard, so it is left out of the routing signal — only the
|
|
21
|
+
task-type signal is used, which is stable across sources.
|
|
22
|
+
|
|
23
|
+
## Model and effort by complexity
|
|
24
|
+
|
|
25
|
+
- **Claude side**: complexity picks the model tier via `native-tiers` —
|
|
26
|
+
low -> haiku, medium -> sonnet, high -> inherit the session model (Opus). There
|
|
27
|
+
is no separate effort knob on Claude: the model tier IS the effort. Fable is
|
|
28
|
+
excluded (the router does not emit it).
|
|
29
|
+
- **Codex side**: fixed model (`gpt-5.4`, the entitled subscription model) with a
|
|
30
|
+
real reasoning-effort knob — low / medium / high — scaled to complexity via
|
|
31
|
+
`codex exec -c model_reasoning_effort=...`.
|
|
32
|
+
|
|
33
|
+
## The three red lines (enforced in code, not left to the caller)
|
|
34
|
+
|
|
35
|
+
1. **No Fable.** `assertNoFable` throws rather than emit a Fable model.
|
|
36
|
+
2. **Verification stays on Claude.** The router forces a verification nature to
|
|
37
|
+
Claude ; the orchestrator re-asserts it and throws if it ever regresses.
|
|
38
|
+
3. **Codex does not write.** Codex runs read-only and returns a unified diff ;
|
|
39
|
+
Claude applies it with `git apply`. This works under a locked-down sandbox
|
|
40
|
+
(e.g. Landlock) and keeps the apply + later verification on Claude.
|
|
41
|
+
|
|
42
|
+
## How the agents "talk" (the honest live-chat answer)
|
|
43
|
+
|
|
44
|
+
There is no simultaneous free-form chat between two models — that does not exist
|
|
45
|
+
natively. What delivers the same outcome is a turn-by-turn loop through a shared
|
|
46
|
+
board, like a chat server holding a conversation:
|
|
47
|
+
|
|
48
|
+
1. The architect (Claude) posts the design to the board.
|
|
49
|
+
2. The dev (Codex or Claude) reads the board, does the work, posts a result — or
|
|
50
|
+
a question.
|
|
51
|
+
3. If there is an open question, the architect answers ; the loop continues.
|
|
52
|
+
4. Convergence when the dev delivers a result with no open question (or a round
|
|
53
|
+
cap is hit). If the dev runtime fails (Codex unavailable/errored), the loop
|
|
54
|
+
reports `dev-failed` so the caller falls back to Claude.
|
|
55
|
+
|
|
56
|
+
## Files
|
|
57
|
+
|
|
58
|
+
| File | Role |
|
|
59
|
+
|------|------|
|
|
60
|
+
| `lib/dispatch-router.js` | F1 — pure routing brain (nature+complexity -> runtime+model+effort) |
|
|
61
|
+
| `lib/codex-bridge.js` | F2 — Codex transport: `codex exec` -> diff, `git apply`, adapter seam (exec shipped, mcp a V2 slot) |
|
|
62
|
+
| `lib/dispatch-blackboard.js` | F3 — the turn-by-turn shared board (build/render pure, JSONL I/O isolated) |
|
|
63
|
+
| `lib/dispatch-orchestrator.js` | F4 — the loop core (routes, runs architect<->dev, converges) ; executors injected |
|
|
64
|
+
| `.claude/workflows/intelligent-dispatch.js` | Native launch facade for one routed task (design -> implement -> verify) |
|
|
65
|
+
|
|
66
|
+
All four libs are pure/injectable and unit-tested under `test/dispatch-*.test.js`
|
|
67
|
+
and `test/codex-bridge.test.js`.
|
|
68
|
+
|
|
69
|
+
## Launching it
|
|
70
|
+
|
|
71
|
+
The orchestrator runs as full Node on the main thread (or an MCP worker), because
|
|
72
|
+
a native workflow script has no imports and no filesystem — it cannot use these
|
|
73
|
+
libs directly. So the main-thread FD / hermes skill decides routing with F1,
|
|
74
|
+
provides the executors (a Claude subagent for Claude turns, the Codex bridge for
|
|
75
|
+
Codex turns), and drives `orchestrate({ tasks, executors })`. The native workflow
|
|
76
|
+
script `intelligent-dispatch.js` is the thin facade for the single-task case: its
|
|
77
|
+
dev leaf holds the Bash tool and is what actually invokes `codex exec`.
|
|
78
|
+
|
|
79
|
+
## V2 slot — tighter coupling
|
|
80
|
+
|
|
81
|
+
`getAdapter('codex-mcp')` is a declared but unavailable adapter. When
|
|
82
|
+
`codex mcp-server` is wired, Codex becomes a tool Claude can call mid-reasoning
|
|
83
|
+
(and Codex can call BYAN tools back) — the closest thing to live. It drops in as a
|
|
84
|
+
new transport behind the same F2 seam, with no change to F1 or the loop.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-byan-agent",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.47.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": {
|