sneakoscope 4.6.4 → 4.7.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 (44) hide show
  1. package/README.md +18 -4
  2. package/crates/sks-core/Cargo.lock +1 -1
  3. package/crates/sks-core/Cargo.toml +1 -1
  4. package/crates/sks-core/src/main.rs +1 -1
  5. package/dist/bin/sks.js +1 -1
  6. package/dist/cli/install-helpers.js +73 -6
  7. package/dist/commands/codex-app.js +34 -3
  8. package/dist/commands/doctor.js +6 -1
  9. package/dist/core/agents/agent-effort-policy.js +110 -5
  10. package/dist/core/agents/agent-orchestrator.js +27 -3
  11. package/dist/core/agents/agent-plan.js +2 -2
  12. package/dist/core/agents/agent-roster.js +8 -0
  13. package/dist/core/agents/agent-worker-slot.js +4 -0
  14. package/dist/core/codex/agent-config-file-repair.js +4 -4
  15. package/dist/core/codex-app/glm-model-profile.js +1 -1
  16. package/dist/core/codex-app/glm-profile-installer.js +111 -7
  17. package/dist/core/codex-app/glm-profile-schema.js +21 -2
  18. package/dist/core/codex-control/codex-sdk-config-policy.js +2 -1
  19. package/dist/core/codex-model-guard.js +3 -1
  20. package/dist/core/commands/fast-mode-command.js +5 -2
  21. package/dist/core/commands/glm-command.js +2 -1
  22. package/dist/core/commands/mad-sks-command.js +24 -0
  23. package/dist/core/doctor/doctor-codex-startup-repair.js +111 -9
  24. package/dist/core/feature-registry.js +3 -3
  25. package/dist/core/fsx.js +1 -1
  26. package/dist/core/harness-guard.js +89 -3
  27. package/dist/core/init.js +9 -9
  28. package/dist/core/managed-assets/managed-assets-manifest.js +9 -8
  29. package/dist/core/pipeline-internals/runtime-core.js +3 -3
  30. package/dist/core/providers/glm/glm-52-profile.js +46 -0
  31. package/dist/core/providers/glm/glm-52-request.js +11 -2
  32. package/dist/core/providers/glm/glm-direct-run.js +1 -0
  33. package/dist/core/providers/glm/glm-mad-launch.js +3 -2
  34. package/dist/core/providers/glm/glm-profile-resolver.js +69 -4
  35. package/dist/core/providers/glm/naruto/glm-naruto-command.js +2 -1
  36. package/dist/core/team-live.js +2 -2
  37. package/dist/core/version.js +1 -1
  38. package/dist/scripts/agent-role-config-repair-check.js +5 -3
  39. package/dist/scripts/codex-native-reference-cache-check.js +8 -1
  40. package/dist/scripts/doctor-codex-startup-repair-check.js +21 -0
  41. package/dist/scripts/naruto-shadow-clone-swarm-check.js +11 -3
  42. package/dist/scripts/release-readiness-report.js +1 -1
  43. package/dist/scripts/sks-3-1-6-directive-check-lib.js +11 -0
  44. package/package.json +1 -1
package/README.md CHANGED
@@ -49,7 +49,7 @@ sks seo-geo-optimizer apply latest --mode seo --apply --json
49
49
  sks seo-geo-optimizer audit --mode geo --target package --offline --json
50
50
  ```
51
51
 
52
- > 📋 **Current release: `v4.6.4`** — full release history lives in [CHANGELOG.md](CHANGELOG.md). This README documents how Sneakoscope works today, not its version-by-version changes. Release readiness is tracked in [docs/release-readiness.md](docs/release-readiness.md).
52
+ > 📋 **Current release: `v4.7.0`** — full release history lives in [CHANGELOG.md](CHANGELOG.md). This README documents how Sneakoscope works today, not its version-by-version changes. Release readiness is tracked in [docs/release-readiness.md](docs/release-readiness.md).
53
53
 
54
54
  ## 🍥 Parallelism, UX, And Integrations
55
55
 
@@ -321,6 +321,8 @@ sks fix-path
321
321
  sks update now
322
322
  ```
323
323
 
324
+ `sks doctor --fix` repairs stale SKS-managed Codex TOML, agent role `config_file` paths, and unsupported managed role fields. It now dedupes repeated TOML tables before repair, keeps the first existing external MCP block such as `context7` or `supabase` unchanged, and only removes/repairs SKS-owned stale MCP blocks such as broken `node_repl`.
325
+
324
326
  ### Open Codex CLI With Zellij
325
327
 
326
328
  ```sh
@@ -330,7 +332,7 @@ sks team open-zellij latest
330
332
  sks team attach-zellij latest
331
333
  ```
332
334
 
333
- Interactive SKS sessions use Zellij layouts. By default SKS launches Codex in Fast service tier with `--model gpt-5.5`, `-c service_tier="fast"`, the selected `model_reasoning_effort`, and `--no-alt-screen` for Zellij-backed interactive panes so terminal scrollback captures the conversation transcript. Non-GLM SKS sessions force the model to `gpt-5.5`; `sks --mad --glm` is the OpenRouter GLM 5.2 exception. `SKS_CODEX_MODEL` and `SKS_CODEX_FAST_HIGH=0` cannot downgrade or remove the non-GLM model pin. You can still set `SKS_CODEX_REASONING` to change reasoning effort, and `SKS_ZELLIJ_CODEX_ALT_SCREEN=1` restores Codex's alternate-screen UI for the next launch. Use `sks --mad --workspace <name>` for an explicit MAD session and `sks help` for CLI help.
335
+ Interactive SKS sessions use Zellij layouts. By default SKS launches Codex in Fast service tier with `--model gpt-5.5`, `-c service_tier="fast"`, the selected `model_reasoning_effort`, and `--no-alt-screen` for Zellij-backed interactive panes so terminal scrollback captures the conversation transcript. Non-GLM SKS sessions force the model to `gpt-5.5`, while the model guard also recognizes `gpt-5.4-mini` as a supported GPT-5.4 mini model; `sks --mad --glm` is the OpenRouter GLM 5.2 exception. `SKS_CODEX_MODEL` and `SKS_CODEX_FAST_HIGH=0` cannot downgrade or remove the non-GLM model pin. You can still set `SKS_CODEX_REASONING` to change reasoning effort, and `SKS_ZELLIJ_CODEX_ALT_SCREEN=1` restores Codex's alternate-screen UI for the next launch. Use `sks --mad --workspace <name>` for an explicit MAD session and `sks help` for CLI help.
334
336
 
335
337
  Before opening the interactive runtime, SKS checks the installed Codex CLI against npm `@openai/codex@latest`. If a newer version exists, it asks `Y/n`; answering `y` updates automatically with `npm i -g @openai/codex@latest` and then opens the runtime with the updated Codex CLI.
336
338
 
@@ -338,6 +340,7 @@ For [codex-lb](https://github.com/Soju06/codex-lb), start the server, create a d
338
340
 
339
341
  ```sh
340
342
  sks codex-lb setup --host https://your-codex-lb.example.com --api-key "sk-clb-..."
343
+ sks codex-lb setup --host https://your-codex-lb.example.com --api-key-stdin --yes
341
344
  sks codex-lb health
342
345
  sks codex-lb repair
343
346
  sks
@@ -353,6 +356,15 @@ sks codex-lb set-key --api-key-stdin # or: sks codex-lb set-key --api-key "sk-
353
356
 
354
357
  (To also change the host, use `sks codex-lb reconfigure --host <domain> --api-key <key>`.)
355
358
 
359
+ For OpenRouter-backed GLM 5.2 in Codex Desktop/SKS, store the key and install the GLM provider/profiles with:
360
+
361
+ ```sh
362
+ sks codex-app set-openrouter-key --api-key-stdin
363
+ sks codex-app glm-profile install
364
+ ```
365
+
366
+ This writes the OpenRouter key to the SKS user secret store, writes redacted metadata only, and installs Codex Desktop-compatible `openrouter` provider plus selectable `sks-glm-52-*` reasoning profiles. The npm package does not patch the Codex Desktop macOS menu binary; a top-level `Set OpenRouter Key` menu/modal requires upstream Codex Desktop UI support. The package-owned command surface above is the supported repair path.
367
+
356
368
  ### Switching auth mode: codex-lb ↔ ChatGPT OAuth
357
369
 
358
370
  Switch between the codex-lb API key and your ChatGPT OAuth login at any time with intent-named commands:
@@ -390,7 +402,7 @@ sks --mad
390
402
  sks --mad --allow-package-install --allow-service-control --allow-network --yes
391
403
  ```
392
404
 
393
- This syncs existing codex-lb provider auth, creates/uses the `sks-mad-high` xhigh maintenance profile, opens the MAD-SKS permission gate for that Zellij run, starts a same-mission read-only native agent swarm, and launches a Codex CLI layout whose right-side lanes read that MAD ledger. Bare `sks --mad` grants target-project file and shell scope only; add explicit `--allow-*` flags for packages, services, network, Computer Use, browser use, generated assets, file permissions, DB writes, or other high-risk scopes. MAD-SKS is not a DB-only unlock and does not create a MadDB capability. Catastrophic database wipe/all-row/project-management safeguards remain active outside the first-class MadDB route, and the pipeline contract still forbids unrequested fallback implementation code.
405
+ This syncs existing codex-lb provider auth, creates/uses the `sks-mad-high` xhigh maintenance profile, opens the MAD-SKS permission gate for that Zellij run, starts a same-mission bounded native agent swarm, and launches a Codex CLI layout whose right-side lanes read that MAD ledger. Bare `sks --mad` grants target-project file and shell scope only; add explicit `--allow-*` flags for packages, services, network, Computer Use, browser use, generated assets, file permissions, DB writes, or other high-risk scopes. MAD-SKS is not a DB-only unlock and does not create a MadDB capability. Catastrophic database wipe/all-row/project-management safeguards remain active outside the first-class MadDB route, and the pipeline contract still forbids unrequested fallback implementation code.
394
406
 
395
407
  Before launching, SKS checks npm for a newer `sneakoscope` and prints a non-blocking update notice when one is available; use `sks update now` or `sks doctor --fix` when you want SKS to update itself. Use `--yes` to approve missing dependency installs automatically. Tune MAD swarm startup with `--mad-agents <n>`, `--mad-swarm-work-items <n>`, and `--mad-swarm-backend <backend>`; `--no-mad-swarm` keeps only the cockpit UI if you need a temporary fallback.
396
408
 
@@ -429,7 +441,7 @@ Manual fan-out syntax:
429
441
  - Team prompt role counts: `$Team <task> executor:8 reviewer:5`
430
442
  - Team CLI flag: `sks team "<task>" --agents 8`
431
443
 
432
- Effort is assigned per agent. Simple read-only/docs slices can run low, ordinary tooling and lease mapping use medium, safety/DB/schema/release lanes use high, and frontier/forensic research can escalate to xhigh. If a lease conflict, schema failure, proof blocker, DB risk, or release risk appears, the parent can escalate that lane while keeping unrelated lanes cheaper and faster.
444
+ Effort and model tier are assigned per agent. Managed native agents use bounded workspace-write TOML profiles, but every write still needs an assigned lease, non-overlap proof, and parent-owned integration. When the main model is GPT, simple bounded code/docs slices can downshift to `gpt-5.4-mini`, ordinary tool/lease work uses `gpt-5.5` with low model reasoning, and safety/DB/schema/release lanes use `gpt-5.5` with high model reasoning. When the main model is GLM, native workers stay locked to `z-ai/glm-5.2` and receive GLM effort tiers (`minimal`, `low`, `high`, or `xhigh`) instead of falling back to GPT. If a lease conflict, schema failure, proof blocker, DB risk, or release risk appears, the parent can escalate that lane while keeping unrelated lanes cheaper and faster.
433
445
 
434
446
  ### Naruto Massive Parallel Work Swarm (`$Naruto`)
435
447
 
@@ -528,6 +540,8 @@ sks codex-app remote-control -- --help
528
540
 
529
541
  `sks codex-app check` reports whether the installed Codex CLI is new enough, whether the required app flags are visible, whether Fast/speed-selector config is unlocked, whether Codex App Git Actions can use Commit, Push, Commit and Push, and PR flows, whether the Codex Chrome Extension path is ready for web/browser/webapp verification, and whether installed OpenAI default plugins such as Browser, Chrome, Computer Use, Documents, Presentations, Spreadsheets, and LaTeX are enabled. `sks-fast-high` intentionally does not pin `sandbox_mode`, so the Codex App/IDE permissions selector owns Full Access vs workspace-write while SKS supplies the model, Fast service tier, approval, and reasoning defaults. `sks codex-app chrome-extension --json` is the rapid preflight for web QA/UX/browser routes. When codex-lb is configured, SKS keeps it selected as the top-level Codex App provider while still preserving required app flags and plugin settings. Codex CLI 0.130.0+ app-server/remote-control threads can pick up config changes live; older CLI/TUI sessions should still be restarted after `.codex/config.toml` or MCP/plugin changes.
530
542
 
543
+ For GLM in Codex Desktop, run `sks codex-app glm-profile install`. This writes the OpenRouter provider and selectable GLM 5.2 profiles into `~/.codex/config.toml`: `sks-glm-52-mad`, `sks-glm-52-minimal`, `sks-glm-52-low`, `sks-glm-52-medium`, `sks-glm-52-high`, and `sks-glm-52-xhigh`. Each profile pins `model = "z-ai/glm-5.2"` and the matching `model_reasoning_effort`, so Desktop profile selection can choose both GLM and the reasoning level without editing TOML by hand.
544
+
531
545
  For web-related verification, SKS follows the official Codex Chrome Extension setup path first: https://developers.openai.com/codex/app/chrome-extension. `$QA-LOOP`, `$UX-Review`, `$Image-UX-Review`, browser smoke, authenticated web checks, localhost checks, and web visual review must halt quickly if that extension is missing or disabled. Only after the user says the extension setup is complete should the pipeline resume. Codex Computer Use is for native Mac/non-web targets only; it must not be used as browser/web-app verification evidence.
532
546
 
533
547
  Imagegen is a core SKS capability, not a decorative add-on. `$Image-UX-Review`, `$UX-Review`, `$Visual-Review`, `$UI-UX-Review`, and PPT generated-review paths require real Codex App `$imagegen`/`gpt-image-2` output before full visual verification can pass. For newest-model image requests, prompts should say "Use ChatGPT Images 2.0 / GPT Image 2.0 with gpt-image-2" while still invoking Codex App `$imagegen` when live generation is needed. Use `imagegen-source-scout` when current official docs plus X/social prompt-workflow signals are needed; social sources are prompt heuristics only, not capability or evidence specs. `npm run imagegen:capability` checks that the official Codex App imagegen surface is visible and records that capability detection is not output proof; OpenAI API, Responses image-generation, codex-lb, or `CODEX_LB_API_KEY` fallbacks are non-Codex paths and do not satisfy Codex App generated-image evidence unless a separate API fallback task is explicitly requested. The README architecture asset uses the same rule: run `npm run imagegen:readme-architecture:prompt` to print/write the official prompt, generate the image in Codex App `$imagegen`, then rerun `npm run imagegen:readme-architecture -- --output <path>` after Codex App creates a real gpt-image-2 output. When exactly one current generated_images candidate exists after the prompt, `npm run imagegen:readme-architecture -- --auto-pick-latest` can select it automatically. To let the verifier wait while Codex App writes the file, use `npm run imagegen:readme-architecture -- --wait-ms <milliseconds>`; it still accepts only one current candidate under `$CODEX_HOME/generated_images`. Env forms such as `SKS_CODEX_APP_IMAGEGEN_OUTPUT=<path>` remain supported for automation. Use the selected file directly under `$CODEX_HOME/generated_images`; moved or copied files are not accepted as provenance evidence. Disabled or missing `image_generation` remains a blocker that `sks codex-app check`, `npm run imagegen:capability`, and selftest cover.
@@ -76,7 +76,7 @@ dependencies = [
76
76
 
77
77
  [[package]]
78
78
  name = "sks-core"
79
- version = "4.6.4"
79
+ version = "4.7.0"
80
80
  dependencies = [
81
81
  "serde_json",
82
82
  ]
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "sks-core"
3
- version = "4.6.4"
3
+ version = "4.7.0"
4
4
  edition = "2021"
5
5
 
6
6
  [dependencies]
@@ -4,7 +4,7 @@ use std::io::{self, Read, Seek, SeekFrom};
4
4
  fn main() {
5
5
  let mut args = std::env::args().skip(1);
6
6
  match args.next().as_deref() {
7
- Some("--version") => println!("sks-rs 4.6.4"),
7
+ Some("--version") => println!("sks-rs 4.7.0"),
8
8
  Some("compact-info") => {
9
9
  let mut input = String::new();
10
10
  let _ = io::stdin().read_to_string(&mut input);
package/dist/bin/sks.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- const FAST_PACKAGE_VERSION = '4.6.4';
2
+ const FAST_PACKAGE_VERSION = '4.7.0';
3
3
  const args = process.argv.slice(2);
4
4
  try {
5
5
  if (args[0] === '--agent' && args[1] === 'worker') {
@@ -14,6 +14,8 @@ import { checkZellijCapability } from '../core/zellij/zellij-capability.js';
14
14
  import { reconcileCodexAppUpgradeProcesses } from '../core/codex-app.js';
15
15
  import { recordCodexLbHealthEvent } from '../core/codex-lb-circuit.js';
16
16
  import { loadCodexLbEnv, writeCodexLbKeychain, codexLbMetadataPath } from '../core/codex-lb/codex-lb-env.js';
17
+ import { GLM_CODEX_CONFIG_PROFILE_ID, GLM_CODEX_CONFIG_PROVIDER_ID, GLM_CODEX_CONFIG_REASONING_PROFILES } from '../core/providers/glm/glm-52-profile.js';
18
+ import { GLM_52_OPENROUTER_MODEL } from '../core/providers/glm/glm-52-settings.js';
17
19
  import { buildCodexLbSetupPlan, codexLbPersistenceSummary, installCodexLbShellProfileSnippet, selectedCodexLbPersistenceModes } from '../core/codex-lb/codex-lb-setup.js';
18
20
  import { runPostinstallGlobalDoctorAndMarkPending } from '../core/update/update-migration-state.js';
19
21
  const DEFAULT_CODEX_APP_PLUGINS = [
@@ -1499,6 +1501,63 @@ export function upsertCodexLbConfig(text = '', baseUrl, selectDefault = true) {
1499
1501
  next = upsertTomlTable(next, 'model_providers.codex-lb', block);
1500
1502
  return `${next.trim()}\n`;
1501
1503
  }
1504
+ export function upsertCodexAppGlmConfig(text = '') {
1505
+ let next = String(text || '');
1506
+ const providerBlock = [
1507
+ `[model_providers.${GLM_CODEX_CONFIG_PROVIDER_ID}]`,
1508
+ 'name = "OpenRouter"',
1509
+ 'base_url = "https://openrouter.ai/api/v1"',
1510
+ 'wire_api = "responses"',
1511
+ 'env_key = "OPENROUTER_API_KEY"',
1512
+ 'requires_openai_auth = false'
1513
+ ].join('\n');
1514
+ next = upsertTomlTable(next, `model_providers.${GLM_CODEX_CONFIG_PROVIDER_ID}`, providerBlock);
1515
+ for (const profile of GLM_CODEX_CONFIG_REASONING_PROFILES) {
1516
+ const profileBlock = [
1517
+ `[profiles.${profile.id}]`,
1518
+ `model_provider = "${GLM_CODEX_CONFIG_PROVIDER_ID}"`,
1519
+ `model = "${GLM_52_OPENROUTER_MODEL}"`,
1520
+ `model_reasoning_effort = "${profile.reasoning_effort}"`,
1521
+ 'service_tier = "default"',
1522
+ 'approval_policy = "on-request"'
1523
+ ].join('\n');
1524
+ next = upsertTomlTable(next, `profiles.${profile.id}`, profileBlock);
1525
+ }
1526
+ return `${next.trim()}\n`;
1527
+ }
1528
+ export async function ensureGlobalCodexAppGlmProfile(opts = {}) {
1529
+ if (process.env.SKS_SKIP_CODEX_GLM_PROFILE_REPAIR === '1' && opts.force !== true) {
1530
+ return { ok: true, status: 'skipped', reason: 'SKS_SKIP_CODEX_GLM_PROFILE_REPAIR=1' };
1531
+ }
1532
+ const home = opts.home || process.env.HOME || os.homedir();
1533
+ const configPath = opts.configPath || codexLbConfigPath(home);
1534
+ try {
1535
+ await ensureDir(path.dirname(configPath));
1536
+ const current = await readText(configPath, '');
1537
+ const next = upsertCodexAppGlmConfig(current);
1538
+ const safeWrite = await safeWriteCodexConfigToml(configPath, current, next, 'glm-profile');
1539
+ return {
1540
+ ...safeWrite,
1541
+ status: safeWrite.status === 'written' ? 'updated' : safeWrite.status,
1542
+ provider: GLM_CODEX_CONFIG_PROVIDER_ID,
1543
+ model: GLM_52_OPENROUTER_MODEL,
1544
+ codex_config_profile: GLM_CODEX_CONFIG_PROFILE_ID,
1545
+ reasoning_profiles: GLM_CODEX_CONFIG_REASONING_PROFILES.map((profile) => profile.id)
1546
+ };
1547
+ }
1548
+ catch (err) {
1549
+ return {
1550
+ ok: false,
1551
+ status: 'failed',
1552
+ config_path: configPath,
1553
+ error: err.message,
1554
+ provider: GLM_CODEX_CONFIG_PROVIDER_ID,
1555
+ model: GLM_52_OPENROUTER_MODEL,
1556
+ codex_config_profile: GLM_CODEX_CONFIG_PROFILE_ID,
1557
+ reasoning_profiles: GLM_CODEX_CONFIG_REASONING_PROFILES.map((profile) => profile.id)
1558
+ };
1559
+ }
1560
+ }
1502
1561
  function detectCodexLbSetupDrift(state = {}) {
1503
1562
  const drift = [];
1504
1563
  if (state.useDefaultProvider && state.selected !== true)
@@ -1582,7 +1641,10 @@ export async function ensureGlobalCodexFastModeDuringInstall(opts = {}) {
1582
1641
  return { status: 'unparseable_config_preserved', config_path: configPath, backup_path: backupPath, parse_smoke: currentSmoke };
1583
1642
  }
1584
1643
  }
1585
- const next = normalizeCodexFastModeUiConfig(current, { forceFastMode: opts.forceFastMode === true });
1644
+ const next = normalizeCodexFastModeUiConfig(current, {
1645
+ forceFastMode: opts.forceFastMode === true,
1646
+ forceFastModeOff: opts.forceFastModeOff === true
1647
+ });
1586
1648
  if (next === ensureTrailingNewline(current))
1587
1649
  return { status: 'present', config_path: configPath };
1588
1650
  // Safety gate 2: never WRITE a config that would not parse.
@@ -1612,9 +1674,12 @@ function normalizeCodexFastModeUiConfigOnce(text = '', opts = {}) {
1612
1674
  next = removeTomlTableKey(next, 'notice', 'fast_default_opt_out');
1613
1675
  next = removeTomlTableKey(next, 'features', 'codex_hooks');
1614
1676
  next = upsertTopLevelTomlStringIfAbsent(next, 'model', 'gpt-5.5');
1615
- next = opts.forceFastMode === true
1616
- ? upsertTopLevelTomlString(next, 'service_tier', 'fast')
1617
- : next;
1677
+ if (opts.forceFastMode === true) {
1678
+ next = upsertTopLevelTomlString(next, 'service_tier', 'fast');
1679
+ }
1680
+ else if (opts.forceFastModeOff === true) {
1681
+ next = upsertTopLevelTomlString(next, 'service_tier', 'default');
1682
+ }
1618
1683
  // Codex App feature flags / fast-mode UI / suppress-warning are SET-IF-ABSENT: a fresh
1619
1684
  // config still gets SKS's defaults, but SKS NEVER overrides (re-enables) a feature the
1620
1685
  // user disabled in the App, and never rejects-then-hides UI by forcing an unrecognized
@@ -1627,10 +1692,12 @@ function normalizeCodexFastModeUiConfigOnce(text = '', opts = {}) {
1627
1692
  'guardian_approval = true', 'tool_suggest = true', 'apps = true', 'plugins = true'
1628
1693
  ])
1629
1694
  next = upsertTomlTableKeyIfAbsent(next, 'features', featureLine);
1630
- if (opts.forceFastMode === true) {
1695
+ if (opts.forceFastMode === true || opts.forceFastModeOff === true) {
1631
1696
  next = upsertTomlTableKey(next, 'user.fast_mode', 'visible = true');
1632
1697
  next = upsertTomlTableKey(next, 'user.fast_mode', 'enabled = true');
1633
- next = upsertTomlTableKey(next, 'user.fast_mode', 'default_profile = "sks-fast-high"');
1698
+ next = opts.forceFastMode === true
1699
+ ? upsertTomlTableKey(next, 'user.fast_mode', 'default_profile = "sks-fast-high"')
1700
+ : removeTomlTableKey(next, 'user.fast_mode', 'default_profile');
1634
1701
  }
1635
1702
  else {
1636
1703
  next = upsertTomlTableKeyIfAbsent(next, 'user.fast_mode', 'visible = true');
@@ -1,8 +1,8 @@
1
- import { flag } from '../cli/args.js';
1
+ import { flag, readOption } from '../cli/args.js';
2
2
  import { printJson } from '../cli/output.js';
3
3
  import { codexAccessTokenStatus, codexAppIntegrationStatus, codexChromeExtensionStatus, codexProductDesignPluginStatus, formatCodexAppStatus, formatCodexProductDesignPluginStatus } from '../core/codex-app.js';
4
4
  import { codexAppRemoteControlCommand } from '../cli/codex-app-command.js';
5
- import { sksRoot } from '../core/fsx.js';
5
+ import { readStdin, sksRoot } from '../core/fsx.js';
6
6
  import { buildCodexAppHarnessMatrix } from '../core/codex-app/codex-app-harness-matrix.js';
7
7
  import { syncCodexSksSkills } from '../core/codex-app/codex-skill-sync.js';
8
8
  import { syncCodexAgentRoles } from '../core/codex-app/codex-agent-role-sync.js';
@@ -11,6 +11,7 @@ import { buildCodexHookLifecycle } from '../core/codex-app/codex-hook-lifecycle.
11
11
  import { resolveCodexAppExecutionProfile } from '../core/codex-app/codex-app-execution-profile.js';
12
12
  import { repairCodexNativeManagedAssets } from '../core/codex-native/codex-native-repair-transaction.js';
13
13
  import { doctorCodexAppGlmProfile, installCodexAppGlmProfile } from '../core/codex-app/glm-profile-installer.js';
14
+ import { promptForOpenRouterKeyHidden, writeStoredOpenRouterKey } from '../core/providers/openrouter/openrouter-secret-store.js';
14
15
  export async function run(_command, args = []) {
15
16
  const action = args[0] || 'check';
16
17
  if (action === 'remote-control' || action === 'remote')
@@ -37,6 +38,28 @@ export async function run(_command, args = []) {
37
38
  : await doctorCodexAppGlmProfile({ root });
38
39
  return printCodexAppResult(args, result);
39
40
  }
41
+ if (action === 'set-openrouter-key' || action === 'openrouter-key') {
42
+ const root = await sksRoot();
43
+ const key = await readOpenRouterKeyFromArgs(args.slice(1));
44
+ if (!key) {
45
+ const result = { schema: 'sks.codex-app-openrouter-key.v1', ok: false, status: 'missing_key', blockers: ['openrouter_key_missing'], warnings: [] };
46
+ return printCodexAppResult(args, result);
47
+ }
48
+ const record = await writeStoredOpenRouterKey(key);
49
+ const profile = await installCodexAppGlmProfile({ root, apply: true });
50
+ const result = {
51
+ schema: 'sks.codex-app-openrouter-key.v1',
52
+ ok: profile.ok,
53
+ status: profile.ok ? 'stored' : 'stored_profile_blocked',
54
+ key_preview: record.key_preview,
55
+ raw_key_recorded: false,
56
+ secret_store: 'sks-openrouter-secret-store',
57
+ glm_profile: profile,
58
+ blockers: profile.blockers || [],
59
+ warnings: profile.warnings || []
60
+ };
61
+ return printCodexAppResult(args, result);
62
+ }
40
63
  if (action === 'product-design' || action === 'design-product' || action === 'ensure-product-design') {
41
64
  const checkOnly = flag(args, '--check-only') || flag(args, '--no-install');
42
65
  const status = await codexProductDesignPluginStatus({
@@ -97,9 +120,17 @@ export async function run(_command, args = []) {
97
120
  process.exitCode = 1;
98
121
  return;
99
122
  }
100
- console.error('Usage: sks codex-app check|status|harness-matrix|skill-sync|agent-role-sync|init-deep|hook-lifecycle|execution-profile|glm-profile [install|doctor]|product-design [--check-only]|ensure-product-design|chrome-extension|pat status|remote-control [--json]');
123
+ console.error('Usage: sks codex-app check|status|harness-matrix|skill-sync|agent-role-sync|init-deep|hook-lifecycle|execution-profile|glm-profile [install|doctor]|set-openrouter-key [--api-key-stdin]|product-design [--check-only]|ensure-product-design|chrome-extension|pat status|remote-control [--json]');
101
124
  process.exitCode = 1;
102
125
  }
126
+ async function readOpenRouterKeyFromArgs(args = []) {
127
+ const key = readOption(args, '--api-key', readOption(args, '--key', ''));
128
+ if (key)
129
+ return String(key).trim();
130
+ if (flag(args, '--api-key-stdin') || flag(args, '--key-stdin'))
131
+ return String(await readStdin()).trim();
132
+ return await promptForOpenRouterKeyHidden() || '';
133
+ }
103
134
  function printCodexAppResult(args = [], result) {
104
135
  if (flag(args, '--json')) {
105
136
  printJson(result);
@@ -746,7 +746,7 @@ async function runDoctor(args = [], root, doctorFix) {
746
746
  console.log(` manual: ${action}`);
747
747
  for (const warning of codexStartupRepair.warnings || [])
748
748
  console.log(` warning: ${warning}`);
749
- console.log(` codex doctor: ${authoritativeCodexDoctor.available ? (authoritativeCodexDoctor.disposition || (authoritativeCodexDoctor.exit_code === 0 ? 'pass' : 'warn')) : 'unavailable'}`);
749
+ console.log(` codex doctor: ${formatCodexDoctorConsoleStatus(authoritativeCodexDoctor)}`);
750
750
  console.log(`Rust acc.: ${rust.mode || (rust.available ? 'rust_accelerated' : 'js_fallback')} ${rust.version || rust.status || ''}`);
751
751
  console.log(`Codex App: ${ready.codex_app_ready ? 'ok' : 'optional_missing'}`);
752
752
  console.log('SKS Runtime Readiness:');
@@ -1166,6 +1166,11 @@ function readOption(args = [], name, fallback = null) {
1166
1166
  const index = args.indexOf(name);
1167
1167
  return index >= 0 && args[index + 1] ? args[index + 1] : fallback;
1168
1168
  }
1169
+ export function formatCodexDoctorConsoleStatus(report) {
1170
+ if (!report || report.available !== true)
1171
+ return 'unavailable';
1172
+ return report.disposition || (report.exit_code === 0 ? 'pass' : 'warn');
1173
+ }
1169
1174
  function mergeObservedCodexStartupWarnings(startupRepair, codexDoctor) {
1170
1175
  const text = `${codexDoctor?.stdout_tail || ''}\n${codexDoctor?.stderr_tail || ''}`;
1171
1176
  const manual = new Set(Array.isArray(startupRepair?.manual_actions) ? startupRepair.manual_actions : []);
@@ -1,8 +1,11 @@
1
1
  import { codexModelEffortCapability } from '../codex-control/codex-model-capabilities.js';
2
+ import { GPT54_MINI_CODEX_MODEL, REQUIRED_CODEX_MODEL } from '../codex-model-guard.js';
3
+ import { GLM_52_OPENROUTER_MODEL } from '../providers/glm/glm-52-settings.js';
2
4
  const XHIGH_SIGNAL_RE = /(frontier|autoresearch|novelty|hypothesis|falsif|forensic|from-chat-img|image\s*work\s*order|새로운\s*연구|가설|포렌식)/i;
3
5
  const HIGH_SIGNAL_RE = /(database|supabase|sql|migration|security|permission|mad|release|publish|deploy|architecture|policy|schema|hook|rollback|db|보안|배포|마이그레이션|데이터베이스|권한|릴리즈)/i;
4
6
  const MEDIUM_SIGNAL_RE = /(tmux|terminal|cli|tool(?:\s|-)?call|router|routing|orchestrat|pipeline|multi[-\s]?session|multi[-\s]?agent|lease|ledger|proof|검증|파이프라인|오케스트레이션|병렬|에이전트)/i;
5
7
  const SIMPLE_SIGNAL_RE = /(tiny|simple|small|one[-\s]?line|typo|copy|label|spacing|rename|readme|docs?|간단|단순|오타|문구|라벨)/i;
8
+ const SIMPLE_CODE_MOD_RE = /(tiny|simple|small|one[-\s]?line|typo|copy|label|spacing|rename|readme|docs?|minor|bounded|간단|단순|작은|오타|문구|라벨|이름\s*변경)/i;
6
9
  export function decideAgentEffort(input = {}) {
7
10
  const persona = input.persona || {};
8
11
  const prompt = String(input.prompt || '');
@@ -27,14 +30,19 @@ export function decideAgentEffort(input = {}) {
27
30
  effort = 'high';
28
31
  reason = 'implementation_lane_capped_at_high';
29
32
  }
30
- const modelCapability = codexModelEffortCapability({ defaultEffort: effort });
33
+ const modelDecision = decideAgentWorkerModel({ effort, prompt, role, agentId, readonly: input.readonly === true || persona.read_only === true, writePolicy: String(persona.write_policy || '') });
34
+ const modelCapability = codexModelEffortCapability({ model: modelDecision.model, defaultEffort: modelDecision.model_reasoning_effort });
31
35
  return {
32
36
  schema: 'sks.agent-effort-decision.v1',
33
37
  policy_version: 1,
34
38
  agent_id: agentId,
35
39
  role,
40
+ model: modelDecision.model,
36
41
  reasoning_effort: effort,
37
- model_reasoning_effort: effort,
42
+ model_reasoning_effort: modelDecision.model_reasoning_effort,
43
+ model_tier: modelDecision.model_tier,
44
+ model_profile: modelDecision.model_profile,
45
+ model_selection_reason: modelDecision.reason,
38
46
  model_effort_capability: modelCapability,
39
47
  reasoning_profile: reasoningProfileName(effort),
40
48
  service_tier: 'fast',
@@ -76,14 +84,19 @@ export function decideNarutoCloneEffort(input = {}) {
76
84
  const writes = !readonly || /write|edit|route-local|workspace|patch|integrat/i.test(writePolicy) || hasActionTool;
77
85
  const toolUse = writes || NARUTO_ACTION_TOOL_RE.test(prompt);
78
86
  const effort = toolUse ? 'medium' : 'low';
79
- const modelCapability = codexModelEffortCapability({ defaultEffort: effort });
87
+ const modelDecision = decideAgentWorkerModel({ effort, prompt, role, agentId, readonly, writePolicy: String(writePolicy || '') });
88
+ const modelCapability = codexModelEffortCapability({ model: modelDecision.model, defaultEffort: modelDecision.model_reasoning_effort });
80
89
  return {
81
90
  schema: 'sks.agent-effort-decision.v1',
82
91
  policy_version: 1,
83
92
  agent_id: agentId,
84
93
  role,
94
+ model: modelDecision.model,
85
95
  reasoning_effort: effort,
86
- model_reasoning_effort: effort,
96
+ model_reasoning_effort: modelDecision.model_reasoning_effort,
97
+ model_tier: modelDecision.model_tier,
98
+ model_profile: modelDecision.model_profile,
99
+ model_selection_reason: modelDecision.reason,
87
100
  model_effort_capability: modelCapability,
88
101
  reasoning_profile: reasoningProfileName(effort),
89
102
  service_tier: 'fast',
@@ -102,7 +115,12 @@ export function buildAgentEffortPolicy(roster = {}) {
102
115
  agent_id: agent.id,
103
116
  session_id: agent.session_id,
104
117
  role: agent.role,
118
+ model: agent.model,
105
119
  reasoning_effort: agent.reasoning_effort,
120
+ model_reasoning_effort: agent.model_reasoning_effort,
121
+ model_tier: agent.model_tier,
122
+ model_profile: agent.model_profile,
123
+ model_selection_reason: agent.model_selection_reason,
106
124
  reasoning_profile: agent.reasoning_profile,
107
125
  reason: agent.reasoning_reason,
108
126
  dynamic: true
@@ -112,13 +130,15 @@ export function buildAgentEffortPolicy(roster = {}) {
112
130
  policy_version: 1,
113
131
  dynamic: true,
114
132
  service_tier: 'fast',
133
+ allowed_models: [GPT54_MINI_CODEX_MODEL, REQUIRED_CODEX_MODEL, GLM_52_OPENROUTER_MODEL],
134
+ model_tiers: ['gpt-5.4-mini', 'gpt-5.5-low', 'gpt-5.5-high', 'glm-5.2-minimal', 'glm-5.2-low', 'glm-5.2-high', 'glm-5.2-xhigh'],
115
135
  allowed_efforts: codexModelEffortCapability().advertised_efforts,
116
136
  model_effort_capability: codexModelEffortCapability(),
117
137
  max_agents: roster.max_agents || 20,
118
138
  agent_count: roster.agent_count || decisions.length,
119
139
  concurrency: roster.concurrency || decisions.length,
120
140
  decisions,
121
- rule: 'Parent orchestration assigns per-agent effort from prompt risk, persona role, lease ownership, and proof state; lanes can escalate on blockers and downshift for narrow read-only work.'
141
+ rule: 'Parent orchestration assigns per-agent model tiers from prompt risk, persona role, lease ownership, and proof state: simple bounded GPT workers can downshift to gpt-5.4-mini; ordinary GPT workers use gpt-5.5 low; risky GPT lanes use gpt-5.5 high. In GLM mode, native workers stay on z-ai/glm-5.2 and receive GLM effort tiers.'
122
142
  };
123
143
  }
124
144
  export function reasoningProfileName(effort) {
@@ -144,4 +164,89 @@ function effortReason(effort) {
144
164
  return 'simple_bounded_slice';
145
165
  return 'default_orchestration_slice';
146
166
  }
167
+ export function decideAgentWorkerModel(input = {}) {
168
+ const mainModel = String(input.mainModel || process.env.SKS_CODEX_MODEL || process.env.CODEX_MODEL || REQUIRED_CODEX_MODEL).trim();
169
+ const glmMain = isGlmWorkerMode(mainModel);
170
+ const gptMain = !glmMain && (!mainModel || /^gpt-/i.test(mainModel));
171
+ const effort = String(input.effort || 'medium');
172
+ const text = [input.prompt, input.role, input.agentId, input.writePolicy].map((item) => String(item || '')).join(' ');
173
+ const simpleText = [input.prompt, input.role, input.agentId].map((item) => String(item || '')).join(' ');
174
+ const risky = HIGH_SIGNAL_RE.test(text) || XHIGH_SIGNAL_RE.test(text);
175
+ const simple = SIMPLE_CODE_MOD_RE.test(simpleText) && !HIGH_SIGNAL_RE.test(simpleText) && !XHIGH_SIGNAL_RE.test(simpleText);
176
+ if (glmMain) {
177
+ const glmEffort = glmWorkerEffort({ effort, risky, simple });
178
+ return {
179
+ model: GLM_52_OPENROUTER_MODEL,
180
+ model_reasoning_effort: glmEffort,
181
+ model_tier: `glm-5.2-${glmEffort === 'none' ? 'minimal' : glmEffort}`,
182
+ model_profile: glmProfileForReasoning(glmEffort),
183
+ reason: `glm_52_${glmEffort}_worker`
184
+ };
185
+ }
186
+ if (gptMain && risky) {
187
+ return {
188
+ model: REQUIRED_CODEX_MODEL,
189
+ model_reasoning_effort: 'high',
190
+ model_tier: 'gpt-5.5-high',
191
+ model_profile: 'sks-agent-gpt-5.5-high-fast',
192
+ reason: 'risk_signal_worker'
193
+ };
194
+ }
195
+ if (gptMain && (simple || effort === 'low')) {
196
+ return {
197
+ model: GPT54_MINI_CODEX_MODEL,
198
+ model_reasoning_effort: 'low',
199
+ model_tier: 'gpt-5.4-mini',
200
+ model_profile: 'sks-agent-gpt-5.4-mini-fast',
201
+ reason: simple ? 'simple_code_or_docs_slice_downshift' : 'low_effort_worker_downshift'
202
+ };
203
+ }
204
+ if (gptMain && (effort === 'high' || effort === 'xhigh')) {
205
+ return {
206
+ model: REQUIRED_CODEX_MODEL,
207
+ model_reasoning_effort: 'high',
208
+ model_tier: 'gpt-5.5-high',
209
+ model_profile: 'sks-agent-gpt-5.5-high-fast',
210
+ reason: 'risk_or_high_effort_worker'
211
+ };
212
+ }
213
+ return {
214
+ model: gptMain ? REQUIRED_CODEX_MODEL : mainModel || REQUIRED_CODEX_MODEL,
215
+ model_reasoning_effort: 'low',
216
+ model_tier: 'gpt-5.5-low',
217
+ model_profile: 'sks-agent-gpt-5.5-low-fast',
218
+ reason: gptMain ? 'ordinary_worker_gpt55_low' : 'non_gpt_main_model_preserved'
219
+ };
220
+ }
221
+ function isGlmWorkerMode(mainModel) {
222
+ const model = String(mainModel || '').trim().toLowerCase();
223
+ return model === GLM_52_OPENROUTER_MODEL
224
+ || model === 'glm-5.2'
225
+ || model === 'glm5.2'
226
+ || process.env.SKS_GLM_MODE === '1'
227
+ || process.env.SKS_GLM_WRAPPER_ACTIVE === '1'
228
+ || process.env.SKS_GLM_MAD_ACTIVE === '1';
229
+ }
230
+ function glmWorkerEffort(input) {
231
+ if (input.effort === 'xhigh')
232
+ return 'xhigh';
233
+ if (input.risky || input.effort === 'high')
234
+ return 'high';
235
+ if (input.simple || input.effort === 'low')
236
+ return 'minimal';
237
+ return 'low';
238
+ }
239
+ function glmProfileForReasoning(effort) {
240
+ if (effort === 'xhigh')
241
+ return 'sks-glm-52-xhigh';
242
+ if (effort === 'high')
243
+ return 'sks-glm-52-high';
244
+ if (effort === 'medium')
245
+ return 'sks-glm-52-medium';
246
+ if (effort === 'low')
247
+ return 'sks-glm-52-low';
248
+ if (effort === 'none')
249
+ return 'sks-glm-52-mad';
250
+ return 'sks-glm-52-minimal';
251
+ }
147
252
  //# sourceMappingURL=agent-effort-policy.js.map
@@ -24,6 +24,7 @@ import { runFakeAgent } from './agent-runner-fake.js';
24
24
  import { runProcessAgent } from './agent-runner-process.js';
25
25
  import { classifyOllamaWorkerSlice, runOllamaAgent } from './agent-runner-ollama.js';
26
26
  import { resolveOllamaWorkerConfig } from './ollama-worker-config.js';
27
+ import { decideAgentWorkerModel } from './agent-effort-policy.js';
27
28
  import { validateAgentWorkerResult } from './agent-worker-pipeline.js';
28
29
  import { writeAgentCleanupReport } from './agent-cleanup.js';
29
30
  import { writeAgentTrustReport } from './agent-trust-report.js';
@@ -93,7 +94,7 @@ export async function runNativeAgentOrchestrator(opts = {}) {
93
94
  const selectedCoreSkill = routeSkillSelection ? skillProofRecord(routeSkillSelection) : null;
94
95
  const namespace = await buildProjectNamespace({ root, missionId });
95
96
  await writeProjectNamespaceArtifact(dir, namespace);
96
- let roster = buildProvidedAgentRoster(opts.roster, { concurrency: opts.concurrency, readonly: opts.readonly, maxAgentCount }) || buildAgentRoster({ agents: opts.agents, concurrency: opts.concurrency, prompt, maxAgentCount, ...(opts.readonly === undefined ? {} : { readonly: opts.readonly }) });
97
+ let roster = buildProvidedAgentRoster(opts.roster, { concurrency: opts.concurrency, readonly: opts.readonly, maxAgentCount, prompt }) || buildAgentRoster({ agents: opts.agents, concurrency: opts.concurrency, prompt, maxAgentCount, ...(opts.readonly === undefined ? {} : { readonly: opts.readonly }) });
97
98
  roster = applyFastModeToRoster(roster, fastModePolicy);
98
99
  roster.roster = roster.roster.map((agent) => ({
99
100
  ...agent,
@@ -1638,6 +1639,19 @@ function buildProvidedAgentRoster(input, opts = {}) {
1638
1639
  const roster = sourceRows.map((entry, index) => {
1639
1640
  const readOnly = opts.readonly === true || entry.read_only === true;
1640
1641
  const id = String(entry.id || entry.agent_id || `agent_${index + 1}`);
1642
+ const reasoningEffort = entry.reasoning_effort || entry.model_reasoning_effort || (readOnly ? 'high' : 'medium');
1643
+ const modelDecision = decideAgentWorkerModel({
1644
+ effort: reasoningEffort,
1645
+ prompt: opts.prompt || input?.prompt || '',
1646
+ role: String(entry.role || 'verifier'),
1647
+ agentId: id,
1648
+ readonly: readOnly,
1649
+ writePolicy: String(entry.write_policy || '')
1650
+ });
1651
+ const suppliedModelEffort = String(entry.model_reasoning_effort || '');
1652
+ const modelReasoningEffort = suppliedModelEffort === 'low' || suppliedModelEffort === 'high'
1653
+ ? suppliedModelEffort
1654
+ : modelDecision.model_reasoning_effort;
1641
1655
  return {
1642
1656
  id,
1643
1657
  session_id: String(entry.session_id || `${id}-session-${String(index + 1).padStart(2, '0')}`),
@@ -1646,8 +1660,12 @@ function buildProvidedAgentRoster(input, opts = {}) {
1646
1660
  index: index + 1,
1647
1661
  write_policy: String(entry.write_policy || (readOnly ? 'read-only' : 'route-local-artifact')),
1648
1662
  status: 'pending',
1649
- reasoning_effort: entry.reasoning_effort || entry.model_reasoning_effort || (readOnly ? 'high' : 'medium'),
1650
- model_reasoning_effort: entry.model_reasoning_effort || entry.reasoning_effort || (readOnly ? 'high' : 'medium'),
1663
+ model: entry.model || modelDecision.model,
1664
+ reasoning_effort: reasoningEffort,
1665
+ model_reasoning_effort: modelReasoningEffort,
1666
+ model_tier: entry.model_tier || modelDecision.model_tier,
1667
+ model_profile: entry.model_profile || modelDecision.model_profile,
1668
+ model_selection_reason: entry.model_selection_reason || modelDecision.reason,
1651
1669
  reasoning_profile: entry.reasoning_profile || (readOnly ? 'sks-logic-high' : 'sks-logic-medium'),
1652
1670
  service_tier: entry.service_tier,
1653
1671
  reasoning_reason: entry.reasoning_reason || 'route_native_agent_plan',
@@ -1690,6 +1708,9 @@ async function runAgentByBackend(backend, agent, slice, opts) {
1690
1708
  sessionId: String(agent.session_id || ''),
1691
1709
  cwd: String(opts.cwd || process.cwd()),
1692
1710
  prompt: buildDirectSdkWorkerPrompt(slice),
1711
+ model: agent.model || null,
1712
+ reasoningEffort: agent.reasoning_effort || null,
1713
+ modelReasoningEffort: agent.model_reasoning_effort || agent.reasoning_effort || null,
1693
1714
  inputFiles: Array.isArray(opts.inputFiles) ? opts.inputFiles.map(String) : [],
1694
1715
  inputImages: Array.isArray(opts.inputImages) ? opts.inputImages.map(String) : [],
1695
1716
  outputSchemaId: CODEX_AGENT_WORKER_RESULT_SCHEMA_ID,
@@ -1723,6 +1744,9 @@ async function runAgentByBackend(backend, agent, slice, opts) {
1723
1744
  structured_output_valid: sdkTask.structuredOutputValid,
1724
1745
  worker_result_path: path.relative(ledgerRoot, sdkTask.workerResultPath),
1725
1746
  patch_envelope_path: sdkTask.patchEnvelopePath ? path.relative(ledgerRoot, sdkTask.patchEnvelopePath) : null,
1747
+ model: agent.model || null,
1748
+ model_reasoning_effort: agent.model_reasoning_effort || null,
1749
+ model_tier: agent.model_tier || null,
1726
1750
  service_tier: opts.serviceTier || 'fast',
1727
1751
  fast_mode: opts.fastMode !== false,
1728
1752
  blockers: sdkTask.blockers
@@ -36,8 +36,8 @@ export function agentPipelineStage(policy = {}) {
36
36
  agent_count: required ? Number(policy.agent_count || DEFAULT_AGENT_COUNT) : 0,
37
37
  max_parallel_native_sessions: Number(policy.agent_count || DEFAULT_AGENT_COUNT),
38
38
  backend: 'native-agent-kernel',
39
- read_only: true,
40
- write_policy: 'read-only analysis; parent-owned integration',
39
+ read_only: false,
40
+ write_policy: 'bounded workspace-write analysis leases; parent-owned integration',
41
41
  outputs: policy.outputs || ['agents/agent-proof-evidence.json']
42
42
  };
43
43
  }
@@ -112,8 +112,12 @@ export function buildAgentRoster(opts = {}) {
112
112
  index: index + 1,
113
113
  write_policy: persona.write_policy,
114
114
  status: 'pending',
115
+ model: effort.model,
115
116
  reasoning_effort: effort.reasoning_effort,
116
117
  model_reasoning_effort: effort.model_reasoning_effort,
118
+ model_tier: effort.model_tier,
119
+ model_profile: effort.model_profile,
120
+ model_selection_reason: effort.model_selection_reason,
117
121
  reasoning_profile: effort.reasoning_profile,
118
122
  service_tier: effort.service_tier,
119
123
  reasoning_reason: effort.reason,
@@ -192,8 +196,12 @@ export function buildNarutoCloneRoster(opts = {}) {
192
196
  index: index + 1,
193
197
  write_policy: cloneReadonly ? 'read-only' : 'exclusive Naruto patch-envelope lease required',
194
198
  status: 'pending',
199
+ model: effort.model,
195
200
  reasoning_effort: effort.reasoning_effort,
196
201
  model_reasoning_effort: effort.model_reasoning_effort,
202
+ model_tier: effort.model_tier,
203
+ model_profile: effort.model_profile,
204
+ model_selection_reason: effort.model_selection_reason,
197
205
  reasoning_profile: effort.reasoning_profile,
198
206
  service_tier: 'fast',
199
207
  fast_mode: true,
@@ -17,7 +17,11 @@ export function createAgentWorkerSlots(roster, targetActiveSlots) {
17
17
  persona_id: String(persona.persona_id || persona.id || `agent_${index + 1}`),
18
18
  role: String(persona.role || 'verifier'),
19
19
  write_policy: String(persona.write_policy || 'read-only'),
20
+ model: persona.model || null,
20
21
  reasoning_effort: persona.reasoning_effort || persona.model_reasoning_effort || null,
22
+ model_reasoning_effort: persona.model_reasoning_effort || null,
23
+ model_tier: persona.model_tier || null,
24
+ model_profile: persona.model_profile || null,
21
25
  reasoning_profile: persona.reasoning_profile || null,
22
26
  service_tier: persona.service_tier || 'fast',
23
27
  fast_mode: persona.fast_mode !== false