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.
Files changed (24) hide show
  1. package/CHANGELOG.md +95 -0
  2. package/install/templates/.claude/CLAUDE.md +11 -0
  3. package/install/templates/.claude/hooks/agent-gate-check.js +53 -0
  4. package/install/templates/.claude/hooks/inject-voice-anchor.js +34 -1
  5. package/install/templates/.claude/hooks/lib/agent-gate.js +127 -0
  6. package/install/templates/.claude/hooks/lib/plain-language.js +155 -0
  7. package/install/templates/.claude/hooks/plain-language-check.js +53 -0
  8. package/install/templates/.claude/rules/agent-entry-gate.md +83 -0
  9. package/install/templates/.claude/rules/plain-language.md +73 -0
  10. package/install/templates/.claude/settings.json +8 -0
  11. package/install/templates/.claude/skills/byan-byan/SKILL.md +14 -0
  12. package/install/templates/.claude/workflows/intelligent-dispatch.js +68 -0
  13. package/install/templates/_byan/agent/byan/byan-tao.md +12 -0
  14. package/install/templates/_byan/mcp/byan-mcp-server/lib/agent-matcher.js +167 -0
  15. package/install/templates/_byan/mcp/byan-mcp-server/lib/codex-bridge.js +176 -0
  16. package/install/templates/_byan/mcp/byan-mcp-server/lib/dispatch-blackboard.js +114 -0
  17. package/install/templates/_byan/mcp/byan-mcp-server/lib/dispatch-orchestrator.js +116 -0
  18. package/install/templates/_byan/mcp/byan-mcp-server/lib/dispatch-router.js +149 -0
  19. package/install/templates/_byan/mcp/byan-mcp-server/server.js +37 -0
  20. package/install/templates/_byan/mcp/byan-mcp-server/skill-bundles-manifest.json +1 -1
  21. package/install/templates/_byan/workflow/simple/byan/data/mantras.yaml +15 -4
  22. package/install/templates/dist/skill-bundles/byan-byan.zip +0 -0
  23. package/install/templates/docs/intelligent-dispatch.md +84 -0
  24. 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": "6798beb68df9831e0b9bf3ca17100174316c51b2671d96b2ef5bff6def4eabc7"
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: 64
277
+ total_mantras: 65
267
278
  conception_count: 39
268
- agents_ia_count: 25
269
- version: "1.0.0"
270
- last_updated: "2026-02-02"
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"
@@ -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.44.0",
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": {