@polderlabs/bizar 10.23.7 → 10.23.9

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/AGENTS.md CHANGED
@@ -58,21 +58,18 @@ still deny prohibited actions and escalate externally visible or irreversible
58
58
  actions with `permissionDecision: "ask"`; that escalation list is the
59
59
  authoritative floor, not a starting point.
60
60
 
61
- Native dynamic workflows under `config/workflows/` and `~/.claude/workflows/`
62
- are the required dispatch mechanism for every primary request except an
63
- unmistakably tiny single-target copy/style/format edit with no behavior or test
64
- change. Mike may execute that narrow exception directly. All other work enters
65
- the matching research / implement / debug / review workflow and uses at least
66
- one explicitly modeled editing subagent with call-level worktree isolation.
67
- Do not add unnecessary phases or duplicate workers. For work that needs 3+
68
- long-lived workers with bounded cross-talk,
69
- Mike invokes a workflow that fans out as a native agent team; the team is
61
+ Mike selects the coordination mode after bounded read-only orientation and one
62
+ user clarification checkpoint: direct work only for an unmistakably tiny
63
+ single-target copy/style/format edit; one isolated Agent for a clear bounded
64
+ change; a native workflow for repeatable phased work; parallel Agents for
65
+ disjoint scopes; and an Agent team for 3+ sustained roles that genuinely need
66
+ cross-talk. Do not add unnecessary phases or duplicate workers. The team is
70
67
  host-side state under `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1`, and per
71
- Anthropic's docs `team_name` is deprecated and ignored. When two or more
72
- subtasks have non-overlapping writable scopes and no data dependency, the
73
- orchestrator MUST dispatch them concurrently through `parallel([...])`, each
74
- with call-level `isolation: "worktree"`. Sequential dispatch is reserved for
75
- dependent phases and integration; never create artificial parallel work.
68
+ Anthropic's docs `team_name` is deprecated and ignored. Every editing dispatch
69
+ uses an explicit configured model and call-level `isolation: "worktree"`.
70
+ When two or more subtasks have non-overlapping writable scopes and no data
71
+ dependency, dispatch them concurrently; serialize only dependencies and
72
+ integration.
76
73
 
77
74
  Agent roles are model-agnostic. Mike selects the cheapest sufficient enabled
78
75
  configured-tier model for each dispatch, with explicit user picks taking
@@ -136,11 +133,13 @@ The autonomy and approval policy above governs this execution model. The project
136
133
  Every non-empty primary request enters Bizar through `office-manager` (`@mike`).
137
134
  The installer sets Claude Code's global `agent` setting to Mike's frontmatter
138
135
  name (`mike`),
139
- and a session-scoped routing guard requires a successful native Workflow before
140
- substantive primary-session mutation. Read-only inspection remains available.
141
- Mike directly executes only the tiny edit exception above. Every other request
142
- must invoke a native Bizar workflow before mutation; the workflow dispatches
143
- worktree-isolated subagents while Mike owns integration and final verification.
136
+ and the routing hook supplies the adaptive coordination policy. For non-tiny
137
+ work, Mike first gathers only bounded read-only context, asks one concise
138
+ clarification question that names the inferred outcome and proposed mode, then
139
+ continues autonomously after the answer. A native workflow is one available
140
+ mode, not a universal gate; Mike may select an isolated Agent, parallel Agents,
141
+ or an Agent team when that better fits the work. Mike owns integration and
142
+ final verification.
144
143
  A Bizar custom agent already executing its assigned role does not recursively
145
144
  dispatch itself.
146
145
 
@@ -152,8 +151,8 @@ relevant page. Guess-and-try integration work is prohibited. When official
152
151
  documentation is unavailable or ambiguous, inspect authoritative source code
153
152
  and report the evidence gap.
154
153
 
155
- For workflow-routed requests, `office-manager` uses only the phases that reduce
156
- a known risk; only the tiny edit exception skips this pipeline:
154
+ For a workflow or team, `office-manager` uses only phases and members that
155
+ reduce a known risk:
157
156
 
158
157
  1. Research: `greg` (`research-analyst.md`) plus an implementation-context specialist.
159
158
  2. Plan: `planner` drafts; `qa-reviewer` challenges assumptions and test shape.
@@ -947,8 +947,9 @@ export function partitionStalePicks({ liveIds, pickedIds, disabledProviders }) {
947
947
  *
948
948
  * Behavior:
949
949
  * - Reads `settings.json` if present; preserves every other field.
950
- * - Writes `modelOverrides` as a sparse object for picked IDs that are
951
- * also in `liveIds`; Claude Code still dispatches each literal value.
950
+ * - Maps every recognized Claude alias into picked IDs that are also in
951
+ * `liveIds`, preventing internal alias normalization from reaching an
952
+ * unconfigured provider default.
952
953
  * - Atomic replace via temp-file + rename (matches `applyModels`).
953
954
  * - When `settingsJsonPath` is provided (tests), uses that instead of
954
955
  * `~/.claude/settings.json`.
@@ -1007,6 +1008,12 @@ export function applyModelOverrides({ settingsJsonPath, pickedIds, liveIds = [],
1007
1008
  // must be keys; configured gateway aliases are values. This also suppresses
1008
1009
  // print-mode `[claude-code:unrecognized_model]` diagnostics for Agent SDK calls.
1009
1010
  settings.modelOverrides = buildClaudeModelOverrides(synced);
1011
+ if (requiresGatewayModelDiscovery(synced)) {
1012
+ settings.env = {
1013
+ ...(settings.env || {}),
1014
+ CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY: '1',
1015
+ };
1016
+ }
1010
1017
  const previousModel = typeof settings.model === 'string' ? settings.model : null;
1011
1018
  const previousContext = profiles?.[previousModel]?.limits?.contextTokens;
1012
1019
  const nextModel = synced[0] || null;
@@ -1068,8 +1075,20 @@ export function buildClaudeModelOverrides(modelIds) {
1068
1075
  const unique = [...new Set((Array.isArray(modelIds) ? modelIds : [])
1069
1076
  .filter((id) => typeof id === 'string' && id.trim())
1070
1077
  .map((id) => id.trim()))];
1071
- return Object.fromEntries(unique.slice(0, CLAUDE_MODEL_OVERRIDE_KEYS.length)
1072
- .map((id, index) => [CLAUDE_MODEL_OVERRIDE_KEYS[index], id]));
1078
+ if (unique.length === 0) return {};
1079
+ // Cover every built-in alias: the workflow runtime may normalize an Agent
1080
+ // request through one of these names, and no alias may escape to an
1081
+ // unconfigured Anthropic default.
1082
+ return Object.fromEntries(CLAUDE_MODEL_OVERRIDE_KEYS
1083
+ .map((key, index) => [key, unique[index % unique.length]]));
1084
+ }
1085
+
1086
+ /** Custom gateway IDs must be discoverable to Claude's SDK/subagent path. */
1087
+ export function requiresGatewayModelDiscovery(modelIds) {
1088
+ return (Array.isArray(modelIds) ? modelIds : []).some((id) => {
1089
+ if (typeof id !== 'string' || !id.trim()) return false;
1090
+ return !/^(?:claude(?:-|$)|anthropic(?:[./-]|$))/i.test(id.trim());
1091
+ });
1073
1092
  }
1074
1093
 
1075
1094
  /**
package/cli/doctor.mjs CHANGED
@@ -42,6 +42,7 @@ import {
42
42
  REQUIRED_HOOKS,
43
43
  } from './commands/validate.mjs';
44
44
  import { configuredEnabledModels, listModels, resolveEndpoint } from './commands/models.mjs';
45
+ import { validateNativeWorkflowDirectory } from '../config/workflows/lib/native-contract.mjs';
45
46
 
46
47
  const REQUIRED_RULES = [
47
48
  'general.md', 'git.md', 'javascript.md', 'python.md',
@@ -157,6 +158,13 @@ async function checkHookFilesInstalled() {
157
158
  return `all ${REQUIRED_HOOKS.length} hook entrypoints installed`;
158
159
  }
159
160
 
161
+ async function checkNativeWorkflowsInstalled() {
162
+ const dir = join(claudeDir(), 'workflows');
163
+ if (!existsSync(dir)) throw new Error(`workflows dir missing: ${dir} — run \`bizar update\``);
164
+ const result = validateNativeWorkflowDirectory(dir);
165
+ return `${result.count} native workflows parser-compatible and filename-addressable`;
166
+ }
167
+
160
168
  /**
161
169
  * Lenient: passes if at least one of semble/skills/claude is on PATH.
162
170
  * These are informational — none of them are strictly required for
@@ -216,6 +224,7 @@ const CHECKS = [
216
224
  { name: 'skill-files-installed', run: checkSkillFilesInstalled },
217
225
  { name: 'rule-files-installed', run: checkRuleFilesInstalled },
218
226
  { name: 'hook-files-installed', run: checkHookFilesInstalled },
227
+ { name: 'native-workflows-valid', run: checkNativeWorkflowsInstalled },
219
228
  { name: 'tools-on-path', run: checkToolsAvailable },
220
229
  { name: 'bizar-home', run: checkBizarHome },
221
230
  { name: 'provider-reachable', run: checkProviderReachable },
package/cli/provision.mjs CHANGED
@@ -32,7 +32,12 @@ import { homedir } from 'node:os';
32
32
  import { dirname, join, resolve, sep } from 'node:path';
33
33
  import { fileURLToPath } from 'node:url';
34
34
  import { resolveBizarHome } from './config-paths.mjs';
35
- import { buildClaudeModelOverrides, configuredEnabledModels } from './commands/models.mjs';
35
+ import {
36
+ buildClaudeModelOverrides,
37
+ configuredEnabledModels,
38
+ requiresGatewayModelDiscovery,
39
+ } from './commands/models.mjs';
40
+ import { validateNativeWorkflowDirectory } from '../config/workflows/lib/native-contract.mjs';
36
41
 
37
42
  const __filename = fileURLToPath(import.meta.url);
38
43
  const __dirname = dirname(__filename);
@@ -921,7 +926,6 @@ export function writeClaudeSettings({ dryRun = false, force = false } = {}) {
921
926
  pickEnv('CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS')
922
927
  || shipped.env?.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS
923
928
  || '1',
924
- ...(pickEnv('ANTHROPIC_MODEL') ? { ANTHROPIC_MODEL: pickEnv('ANTHROPIC_MODEL') } : {}),
925
929
  },
926
930
  hooks: {
927
931
  UserPromptSubmit: [{ hooks: [hook('user-prompt-submit', 10)] }],
@@ -968,6 +972,10 @@ export function writeClaudeSettings({ dryRun = false, force = false } = {}) {
968
972
  if (installModel) {
969
973
  bizarSettings.model = installModel;
970
974
  bizarSettings.modelOverrides = buildClaudeModelOverrides(installModels);
975
+ bizarSettings.env.ANTHROPIC_MODEL = installModel;
976
+ if (operatorGatewayUrl && requiresGatewayModelDiscovery(installModels)) {
977
+ bizarSettings.env.CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY = '1';
978
+ }
971
979
  const configuredContext = pickEnv('CLAUDE_CODE_MAX_CONTEXT_TOKENS') || installContextTokens;
972
980
  if (configuredContext) bizarSettings.env.CLAUDE_CODE_MAX_CONTEXT_TOKENS = String(configuredContext);
973
981
  } else {
@@ -1013,6 +1021,9 @@ export function writeClaudeSettings({ dryRun = false, force = false } = {}) {
1013
1021
  if (installModel) {
1014
1022
  merged.model = installModel;
1015
1023
  merged.modelOverrides = bizarSettings.modelOverrides;
1024
+ merged.env = { ...(merged.env || {}), ANTHROPIC_MODEL: installModel };
1025
+ } else if (merged.env && typeof merged.env === 'object') {
1026
+ delete merged.env.ANTHROPIC_MODEL;
1016
1027
  }
1017
1028
 
1018
1029
  // Auto-compaction is part of the Bizar reliability contract. Remove legacy
@@ -1348,9 +1359,9 @@ export async function syncConfigExtras({ dryRun = false } = {}) {
1348
1359
  const workflowsSrc = join(REPO_ROOT, 'config', 'workflows');
1349
1360
  if (existsSync(workflowsSrc)) {
1350
1361
  const workflowsDst = join(CLAUDE_DIR, 'workflows');
1362
+ validateNativeWorkflowDirectory(workflowsSrc);
1351
1363
  await copyDirIfExists(workflowsSrc, workflowsDst);
1352
- counts.workflows = readdirSync(workflowsSrc, { withFileTypes: true })
1353
- .filter((entry) => entry.isFile() && entry.name.endsWith('.js')).length;
1364
+ counts.workflows = validateNativeWorkflowDirectory(workflowsDst).count;
1354
1365
  }
1355
1366
 
1356
1367
  return { ok: true, message: `synced (${counts.commands} commands, ${counts.skills} skills, ${counts.hooks} hooks, ${counts.rules} rules, ${counts.workflows} workflows)`, counts };
@@ -1374,7 +1385,6 @@ export const FORCE_CLEAN_PRESERVE_ENV_KEYS = Object.freeze([
1374
1385
  'ANTHROPIC_AUTH_TOKEN',
1375
1386
  'BIZAR_MODEL_ROUTER_URL',
1376
1387
  'BIZAR_HOME',
1377
- 'ANTHROPIC_MODEL',
1378
1388
  'CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY',
1379
1389
  'CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS',
1380
1390
  'CLAUDE_CODE_MAX_CONTEXT_TOKENS',
@@ -69,21 +69,18 @@ still deny prohibited actions and escalate externally visible or irreversible
69
69
  actions with `permissionDecision: "ask"`; that escalation list is the
70
70
  authoritative floor, not a starting point.
71
71
 
72
- Native dynamic workflows under `config/workflows/` and `~/.claude/workflows/`
73
- are the required dispatch mechanism for every primary request except an
74
- unmistakably tiny single-target copy/style/format edit with no behavior or test
75
- change. Mike may execute that narrow exception directly. All other work enters
76
- the matching research / implement / debug / review workflow and uses at least
77
- one explicitly modeled editing subagent with call-level worktree isolation.
78
- Do not add unnecessary phases or duplicate workers. For work that needs 3+
79
- long-lived workers with bounded cross-talk,
80
- Mike invokes a workflow that fans out as a native agent team; the team is
72
+ Mike selects the coordination mode after bounded read-only orientation and one
73
+ user clarification checkpoint: direct work only for an unmistakably tiny
74
+ single-target copy/style/format edit; one isolated Agent for a clear bounded
75
+ change; a native workflow for repeatable phased work; parallel Agents for
76
+ disjoint scopes; and an Agent team for 3+ sustained roles that genuinely need
77
+ cross-talk. Do not add unnecessary phases or duplicate workers. The team is
81
78
  host-side state under `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1`, and per
82
- Anthropic's docs `team_name` is deprecated and ignored. When two or more
83
- subtasks have non-overlapping writable scopes and no data dependency, the
84
- orchestrator MUST dispatch them concurrently through `parallel([...])`, each
85
- with call-level `isolation: "worktree"`. Sequential dispatch is reserved for
86
- dependent phases and integration; never create artificial parallel work.
79
+ Anthropic's docs `team_name` is deprecated and ignored. Every editing dispatch
80
+ uses an explicit configured model and call-level `isolation: "worktree"`.
81
+ When two or more subtasks have non-overlapping writable scopes and no data
82
+ dependency, dispatch them concurrently; serialize only dependencies and
83
+ integration.
87
84
 
88
85
  Agent roles are model-agnostic. Mike selects the cheapest sufficient enabled
89
86
  configured-tier model for each dispatch, with explicit user picks taking
@@ -147,11 +144,13 @@ The autonomy and approval policy above governs this execution model. The project
147
144
  Every non-empty primary request enters Bizar through `office-manager` (`@mike`).
148
145
  The installer sets Claude Code's global `agent` setting to Mike's frontmatter
149
146
  name (`mike`),
150
- and a session-scoped routing guard requires a successful native Workflow before
151
- substantive primary-session mutation. Read-only inspection remains available.
152
- Mike directly executes only the tiny edit exception above. Every other request
153
- must invoke a native Bizar workflow before mutation; the workflow dispatches
154
- worktree-isolated subagents while Mike owns integration and final verification.
147
+ and the routing hook supplies the adaptive coordination policy. For non-tiny
148
+ work, Mike first gathers only bounded read-only context, asks one concise
149
+ clarification question that names the inferred outcome and proposed mode, then
150
+ continues autonomously after the answer. A native workflow is one available
151
+ mode, not a universal gate; Mike may select an isolated Agent, parallel Agents,
152
+ or an Agent team when that better fits the work. Mike owns integration and
153
+ final verification.
155
154
  A Bizar custom agent already executing its assigned role does not recursively
156
155
  dispatch itself.
157
156
 
@@ -163,8 +162,8 @@ relevant page. Guess-and-try integration work is prohibited. When official
163
162
  documentation is unavailable or ambiguous, inspect authoritative source code
164
163
  and report the evidence gap.
165
164
 
166
- For workflow-routed requests, `office-manager` uses only the phases that reduce
167
- a known risk; only the tiny edit exception skips this pipeline:
165
+ For a workflow or team, `office-manager` uses only phases and members that
166
+ reduce a known risk:
168
167
 
169
168
  1. Research: `greg` (`research-analyst.md`) plus an implementation-context specialist.
170
169
  2. Plan: `planner` drafts; `qa-reviewer` challenges assumptions and test shape.
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: mike
3
- description: Mike — workflow-first orchestrator with a tiny direct-edit exception.
4
- tools: Workflow, Agent, Read, Write, Edit, Bash, Grep, Glob, WebFetch, WebSearch, Skill
3
+ description: Mike — adaptive orchestrator that selects the lightest safe coordination mode.
4
+ tools: Workflow, Agent, Read, Write, Edit, Bash, Grep, Glob, WebFetch, WebSearch, Skill, AskUserQuestion
5
5
  skills:
6
6
  - i-have-adhd
7
7
  ---
@@ -12,23 +12,45 @@ Follow `_shared/AGENT_BASELINE.md`. You own the user outcome, integration, and
12
12
  final verification. Direct execution is a narrow exception; workflows are the
13
13
  default for meaningful work.
14
14
 
15
- ## Route, then reassess if scope expands
15
+ ## Orient, clarify, then select the coordination mode
16
16
 
17
17
  | Shape | Signals | Execution |
18
18
  |---|---|---|
19
19
  | Tiny direct | one obvious copy, typo, comment, whitespace, or single style-token edit; one target; no behavior or test change | inspect, make the micro-edit, run the smallest proving check yourself |
20
- | Bounded workflow | known non-trivial implementation, including a logical bug or any behavioral change | invoke `bizar-implement`; it dispatches at least one editing worker with `isolation: "worktree"`; merge and verify |
21
- | Debug workflow | failing behavior, unclear cause, regression, or interacting state | invoke `bizar-debug`; keep diagnosis and fix evidence separate |
22
- | Research/shaped | external/version-sensitive behavior, architecture/security, broad review, or interacting components | invoke `bizar-research` or the matching `ultracode*` workflow; parallelize independent lanes and serialize dependencies |
23
-
24
- If a request could reasonably require a regression test, touch multiple files,
25
- or needs inspection to discover its scope, it is not tiny: invoke a workflow
26
- before editing. The primary session does not substitute an ad-hoc Agent call
27
- for the workflow. Research current official docs only for external or
20
+ | Single isolated worker | one bounded implementation after scope is clear | dispatch one worktree-isolated Agent with an explicit Bizar model; integrate and verify |
21
+ | Native workflow | repeatable diagnosis, research, review, or an implementation needing visible phase barriers | invoke the matching Bizar workflow with explicit Bizar routing |
22
+ | Agent team | three or more sustained, independent roles need bounded cross-talk or coordinated handoff | use the native Agent-team capability; writers use worktrees and explicit Bizar models |
23
+ | Parallel agents | two disjoint writable scopes with no cross-talk needed | dispatch concurrently with explicit models and worktree isolation |
24
+
25
+ For every non-tiny request, first make only enough read-only inspection to
26
+ understand the repository boundary and current constraints. Then ask the user
27
+ one concise clarification checkpoint: state the inferred outcome, the material
28
+ choice or risk, and the proposed coordination mode. Wait for the answer before
29
+ writing, dispatching editors, creating branches, or running tests. If the user
30
+ explicitly says to proceed without questions, record that choice and continue.
31
+ After the answer, work autonomously until the requested outcome and verification
32
+ are complete. Research current official docs only for external or
28
33
  version-sensitive claims. Inspect installed skills before hard or specialized
29
34
  work; if stuck with no match, search skills.sh and review the candidate before
30
35
  proposing installation.
31
36
 
37
+ Before every Workflow, Agent, or Agent-team call, read the global Bizar model router and construct a
38
+ small `args.routing` object whose `default`, `medium`, and `high` values are
39
+ explicit enabled configured model IDs (user picks win; otherwise use enabled
40
+ tier candidates). Include the user's task in the same args object under the
41
+ workflow's documented task field. Never pass only a string and never use
42
+ `inherit`, `sonnet`, `opus`, or another provider default. If no configured ID
43
+ exists, stop and ask the operator to run `bizar models`.
44
+
45
+ Invoke the selected workflow by `name` first. If Claude reports that the Bizar
46
+ name is unavailable, resolve the active Claude config directory and retry once
47
+ with the absolute installed `scriptPath` at
48
+ `<CLAUDE_CONFIG_DIR>/workflows/<name>.js` (normally
49
+ `~/.claude/workflows/<name>.js`). Never retry a bare filename or a repository
50
+ relative path. If that file is missing or invalid, stop with `bizar update`
51
+ and `bizar doctor` as the repair commands; do not improvise a primary-session
52
+ implementation around a broken workflow installation.
53
+
32
54
  ## Models
33
55
 
34
56
  For every Agent call, select the cheapest sufficient enabled configured model
@@ -41,9 +63,10 @@ only when the router explicitly supplies it.
41
63
 
42
64
  ## Worktree Discipline and integration
43
65
 
44
- Every editing subagent call uses call-level `isolation: "worktree"`. Parallel
45
- writers receive disjoint file ownership and sibling scopes. Read-only research
46
- stays foreground. When a writer finishes, merge its queued branch with
66
+ Every editing subagent call uses call-level `isolation: "worktree"`. Use teams
67
+ only when collaboration changes the result; do not manufacture a team or a
68
+ workflow for a simple isolated task. Parallel writers receive disjoint file
69
+ ownership and sibling scopes. Read-only research stays foreground. When a writer finishes, merge its queued branch with
47
70
  `bizar worktree-merge`; report conflicts instead of guessing. The integration
48
71
  branch runs final tests once after all required results are incorporated.
49
72
  Worktree branches use `wt/<agent_type>-<short-task-id>`.
@@ -11,7 +11,7 @@ All hooks read Claude Code JSON from stdin and emit either no decision, addition
11
11
  | `simplify-guard.mjs` | PostToolUse Skill + PreToolUse Bash | require one `/simplify` per commit attempt |
12
12
  | `posttooluse-editwrite.mjs` | PostToolUse writes | local telemetry and test reminder |
13
13
  | `worker-suggest.mjs` | UserPromptSubmit | ranked skill/agent suggestions |
14
- | `workflow-route-guard.mjs` | UserPromptSubmit, PreToolUse, PostToolUse | requires a proven successful native workflow before substantive primary mutation; permits narrowly whitelisted, redirect-free Git inspection |
14
+ | `workflow-route-guard.mjs` | UserPromptSubmit, PreToolUse, PostToolUse | records adaptive Bizar routing state without blocking Mike's selected coordination mode; workflow success clears the pending route record |
15
15
  | `thinking-route.mjs` | UserPromptSubmit | slash and mental-model routing |
16
16
  | `telemetry.mjs` | SessionStart/UserPromptSubmit | local correlation and rejection categories |
17
17
  | `sessionstart-prime.mjs` | SessionStart | bounded project and handoff context |
@@ -36,11 +36,11 @@
36
36
  * validated by `pickFailover` against the same registry.
37
37
  * - The hard deny of out-of-pool models STILL APPLIES when
38
38
  * `routingDecisionId` is absent — the contract is opt-in.
39
- * - Registry load failures still fail open (F-176 advisory): the orchestrator
40
- * already chose a model, let Claude Code validate it once.
39
+ * - Registry load failures fail closed: allowing the dispatch would hand
40
+ * model choice back to Claude Code's unconfigured provider default.
41
41
  *
42
- * The contract is intentionally additive. Existing callers that only pass
43
- * `model` see no behavior change.
42
+ * Callers that pass only `model` are accepted only when that literal ID is in
43
+ * the enabled configured pool. Missing or inherited model selection is denied.
44
44
  */
45
45
 
46
46
  import { readFileSync } from 'node:fs';
@@ -48,16 +48,6 @@ import { pathToFileURL } from 'node:url';
48
48
 
49
49
  import { loadModelRouter } from '../../../config/agents/model-assignment.mjs';
50
50
 
51
- function advise(reason) {
52
- return {
53
- hookSpecificOutput: {
54
- hookEventName: 'PreToolUse',
55
- permissionDecision: 'allow',
56
- additionalContext: `🟡 Model override guidance: ${reason} The dispatch will proceed regardless.`,
57
- },
58
- };
59
- }
60
-
61
51
  function deny(reason) {
62
52
  return {
63
53
  hookSpecificOutput: {
@@ -72,8 +62,6 @@ function deny(reason) {
72
62
  * 10.22.0 / Phase 4: extract the operator's `disabledProviders` list from
73
63
  * the loaded registry. Whitespace-trimmed + lowercased at read time.
74
64
  * Returns `[]` for legacy configs that lack the key (no in-code default).
75
- * The hook MUST fail open on parse errors — the orchestrator already
76
- * chose a model; let Claude Code validate it once.
77
65
  */
78
66
  function readDisabledProvidersFromRegistry(registry) {
79
67
  if (!registry || typeof registry !== 'object') return [];
@@ -90,14 +78,15 @@ function readDisabledProvidersFromRegistry(registry) {
90
78
  }
91
79
 
92
80
  /**
93
- * 10.22.0 / Phase 4: case-sensitive prefix filter against the (lowercase)
94
- * disabled list. Empty / missing prefix list is a no-op (returns input).
81
+ * 10.22.0 / Phase 4: case-insensitive prefix filter against the normalized
82
+ * disabled list. Empty / missing prefix list is a no-op.
95
83
  */
96
84
  function isDisabledId(id, prefixes) {
97
85
  if (!Array.isArray(prefixes) || prefixes.length === 0) return false;
98
86
  if (typeof id !== 'string' || !id) return false;
87
+ const normalized = id.toLowerCase();
99
88
  for (const p of prefixes) {
100
- if (typeof p === 'string' && p && id.startsWith(p)) return true;
89
+ if (typeof p === 'string' && p && normalized.startsWith(p)) return true;
101
90
  }
102
91
  return false;
103
92
  }
@@ -171,18 +160,15 @@ export async function guardAgentModel(input, options = {}) {
171
160
  const failoverBlock = readFailoverBlock(toolInput);
172
161
  const hasFailoverContract = Boolean(failoverBlock.routingDecisionId) && Boolean(failoverBlock.fallback);
173
162
 
174
- // The workflow dispatcher enforces a model override. This permissive branch
175
- // keeps the hook compatible with non-Bizar Agent callers.
176
- if (!requested) return {};
177
- if (requested === 'inherit') return {};
163
+ if (!requested || requested === 'inherit') {
164
+ return deny('Bizar Agent dispatch blocked: every Agent call requires an explicit enabled model from `bizar models`; session/provider inheritance is prohibited.');
165
+ }
178
166
 
179
167
  let registry;
180
168
  try {
181
169
  registry = options.registry || loadModelRouter(options.routerPath);
182
170
  } catch {
183
- // A broken optional router must not strand subagents. The caller already
184
- // chose a model; let Claude Code/provider validate it once.
185
- return {};
171
+ return deny('Bizar Agent dispatch blocked: the global model router is missing or invalid. Run `bizar models`, then retry.');
186
172
  }
187
173
 
188
174
  // Disabled is an enforceable operator boundary. Falling through here would
@@ -201,17 +187,17 @@ export async function guardAgentModel(input, options = {}) {
201
187
  // already computed by `pickFailover` against this same registry.
202
188
  if (hasFailoverContract) {
203
189
  if (!userPicks.has(failoverBlock.fallback)) {
204
- return advise(`Bizar Agent dispatch: fallback ${failoverBlock.fallback} is outside the user-selected pool; select an enabled configured fallback.`);
190
+ return deny(`Bizar Agent dispatch blocked: fallback ${failoverBlock.fallback} is outside the user-selected pool; select an enabled configured fallback.`);
205
191
  }
206
192
  if (!allowed.has(requested)) {
207
- return advise(`Bizar Agent dispatch blocked: model override ${requested} is outside the configured dynamic tiers and the user-selected pool. Pick it via \`bizar models\`.`);
193
+ return deny(`Bizar Agent dispatch blocked: model override ${requested} is outside the configured dynamic tiers and the user-selected pool. Pick it via \`bizar models\`.`);
208
194
  }
209
195
  // Both IDs are user-selected. Accept without re-probing the gateway.
210
196
  return {};
211
197
  }
212
198
 
213
199
  if (!allowed.has(requested)) {
214
- return advise(`Bizar Agent dispatch blocked: model override ${requested} is outside the configured dynamic tiers and the user-selected pool. Pick it via \`bizar models\`.`);
200
+ return deny(`Bizar Agent dispatch blocked: model override ${requested} is outside the configured dynamic tiers and the user-selected pool. Pick it via \`bizar models\`.`);
215
201
  }
216
202
 
217
203
  // User-selected models bypass live-discovery validation. The picker is the
@@ -220,7 +206,7 @@ export async function guardAgentModel(input, options = {}) {
220
206
  if (!fromUserPick && Array.isArray(options.availableModelIds)) {
221
207
  const available = new Set(options.availableModelIds);
222
208
  if (!available.has(requested)) {
223
- return advise(`Bizar Agent dispatch blocked: ${requested} was not reported by live discovery. Select another enabled configured model; do not retry aliases.`);
209
+ return deny(`Bizar Agent dispatch blocked: ${requested} was not reported by live discovery. Select another enabled configured model; do not retry aliases.`);
224
210
  }
225
211
  }
226
212
 
@@ -122,6 +122,10 @@ function filterDisabled(ids, disabled) {
122
122
  return kept;
123
123
  }
124
124
 
125
+ function requiresGatewayModelDiscovery(modelIds) {
126
+ return modelIds.some((id) => !/^(?:claude(?:-|$)|anthropic(?:[./-]|$))/i.test(id));
127
+ }
128
+
125
129
  function readSettingsPath() {
126
130
  return join(resolveClaudeConfigDir(), 'settings.json');
127
131
  }
@@ -234,8 +238,14 @@ function syncOnce() {
234
238
  ];
235
239
  settings.modelPicker = { options };
236
240
  settings.modelOverrides = Object.fromEntries(
237
- liveIds.slice(0, overrideKeys.length).map((id, index) => [overrideKeys[index], id]),
241
+ overrideKeys.map((key, index) => [key, liveIds[index % liveIds.length]]),
238
242
  );
243
+ if (requiresGatewayModelDiscovery(liveIds)) {
244
+ settings.env = {
245
+ ...(settings.env || {}),
246
+ CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY: '1',
247
+ };
248
+ }
239
249
 
240
250
  let modelChanged = false;
241
251
  if (typeof settings.model !== 'string' || !liveIds.includes(settings.model)) {
@@ -186,13 +186,13 @@ function startupBriefing(cwd, featureBrief, recentCommits, projectLine, progress
186
186
  }
187
187
  }
188
188
  if (progressLast) lines.push(`- Progress: ${progressLast}.`);
189
- lines.push('- You are @mike. Only an unmistakably tiny single-target copy/style/format edit is direct; every other change enters the matching native workflow before mutation. Use one isolated writer by default and parallel worktrees only for independent scopes.');
189
+ lines.push('- You are @mike. For non-tiny work, do bounded read-only orientation, ask one clarification checkpoint, then choose a single isolated worker, native workflow, parallel workers, or an Agent team by actual dependency. Use worktrees for editors and explicit Bizar models for every Agent.');
190
190
  lines.push('- External/version-sensitive work requires current official docs via WebSearch/WebFetch. Use relevant installed skills; apply i-have-adhd to user output. WIP=1.');
191
191
  lines.push('- TaskCompleted/SubagentStop/<task-notification> is terminal: consume <result>, mark done/failed, merge queued work, continue the objective.');
192
192
  // Default-first-stop hint when nothing is active yet.
193
193
  if (featureBrief && featureBrief.active.length === 0) {
194
194
  lines.push(
195
- '- First move: read PROGRESS.md and feature_list.json; then choose the matching workflow, except for an unmistakably tiny direct edit.',
195
+ '- First move: read PROGRESS.md and feature_list.json, then do bounded orientation and ask the clarification checkpoint before choosing the fitting coordination mode.',
196
196
  );
197
197
  }
198
198
  return lines.join('\n');
@@ -203,14 +203,14 @@ function clearBriefing(cwd, recentCommits, progressLast) {
203
203
  if (progressLast) lines.push(`- Progress: ${progressLast}.`);
204
204
  if (recentCommits.length > 0) lines.push(`- Last commit: ${recentCommits[0]}.`);
205
205
  lines.push('- Context preserved in same repo / cwd — only the model turn was reset.');
206
- lines.push('- You are @mike: continue through the active workflow; only an unmistakably tiny edit may stay direct.');
206
+ lines.push('- You are @mike: continue the active coordination plan; adapt it when new evidence changes the fit.');
207
207
  lines.push('- First move: continue from where the model left off; no need to reread project files.');
208
208
  return lines.join('\n');
209
209
  }
210
210
 
211
211
  function resumeBriefing(cwd, state) {
212
212
  const lines = ['Bizar SessionStart (resume):'];
213
- lines.push('- You are @mike: restore state, then continue the active workflow; only an unmistakably tiny edit may stay direct.');
213
+ lines.push('- You are @mike: restore state, then continue the active coordination plan and adapt it if the evidence changed.');
214
214
  if (state) {
215
215
  if (state.activeFeature) lines.push(`- Last active feature: ${state.activeFeature}.`);
216
216
  if (state.reason) lines.push(`- Last session ended with: ${state.reason}.`);
@@ -6,7 +6,8 @@
6
6
  *
7
7
  * Runs on every user prompt. Only unmistakably tiny, single-scope edits take
8
8
  * a cheap fast path. Every other request is routed into a native Bizar
9
- * workflow that owns subagent dispatch.
9
+ * adaptive coordination mode selected by Mike after bounded orientation and a
10
+ * clarification checkpoint.
10
11
  *
11
12
  * Uses import.meta.url + dynamic import() to resolve the sibling CLI module so
12
13
  * the hook works regardless of install path (fixes ERR_MODULE_NOT_FOUND after
@@ -56,10 +57,10 @@ const FAST_ROUTE_POLICY = [
56
57
  ].join('\n');
57
58
 
58
59
  const ROUTE_POLICY = [
59
- 'Workflow-required Bizar routing policy:',
60
- '- If this is the primary session, you ARE @mike. Do not implement this request directly in the primary session. Before any edit or mutation, invoke the matching native Bizar workflow; the primary owns routing, integration, and final verification.',
61
- '- Use bizar-implement for known bounded changes, bizar-debug for bugs needing diagnosis, bizar-research for external or uncertain implementation context, and ultracode / ultracode-research / ultracode-review for broad, high-risk, or review-heavy objectives. Use only phases that reduce a concrete risk.',
62
- '- The workflow must dispatch at least one implementation subagent with an explicit configured model and call-level `isolation: "worktree"`. For genuinely disjoint writable scopes, dispatch them concurrently; otherwise use one isolated writer. Never create duplicate workers merely to satisfy fan-out.',
60
+ 'Adaptive Bizar routing policy:',
61
+ '- If this is the primary session, you ARE @mike. First do only bounded read-only orientation. Then ask one concise clarification question describing the inferred outcome, the material choice/risk, and your proposed coordination mode. Wait for the answer before edits, branches, tests, or editor dispatch. If the user explicitly waives questions, continue autonomously.',
62
+ '- After clarification, choose the lightest coordination mode: a direct tiny edit, one isolated Agent for a bounded change, a native Bizar Workflow for repeatable phased work, parallel Agents for disjoint scopes, or an Agent team only when 3+ sustained roles need cross-talk. Do not force a workflow or team when it adds no value.',
63
+ '- Every Agent or team member receives an explicit enabled configured Bizar model; use no provider alias, inheritance, or default. Every editing worker uses call-level `isolation: "worktree"`. For genuinely disjoint writable scopes, dispatch concurrently; otherwise use one owner.',
63
64
  '- Consume terminal agent results, merge queued worktrees with bizar worktree-merge, and run integration checks in the primary session. A subagent may not recursively dispatch itself.',
64
65
  '- Do NOT execute any tool you do not have. If a tool you need is missing from your tools list, dispatch to a subagent that has it — do not pretend you have it.',
65
66
  '- If you are already running as a Bizar custom agent, follow your assigned role and do not recursively dispatch yourself.',