sneakoscope 3.1.0 → 3.1.2

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 (84) hide show
  1. package/README.md +1 -1
  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/.sks-build-stamp.json +4 -4
  6. package/dist/bin/sks.js +1 -1
  7. package/dist/cli/install-helpers.js +6 -7
  8. package/dist/commands/zellij-slot-column-anchor.js +3 -1
  9. package/dist/commands/zellij-slot-pane.js +19 -2
  10. package/dist/core/agents/agent-janitor.js +10 -1
  11. package/dist/core/agents/agent-orchestrator.js +8 -2
  12. package/dist/core/agents/agent-proof-evidence.js +20 -0
  13. package/dist/core/agents/agent-runner-ollama.js +11 -4
  14. package/dist/core/agents/fast-mode-policy.js +7 -5
  15. package/dist/core/agents/intelligent-work-graph.js +93 -14
  16. package/dist/core/agents/native-cli-session-swarm.js +115 -9
  17. package/dist/core/agents/no-subagent-scaling-policy.js +10 -1
  18. package/dist/core/agents/official-subagent-helper-policy.js +62 -0
  19. package/dist/core/codex-app.js +0 -2
  20. package/dist/core/codex-control/codex-task-runner.js +9 -0
  21. package/dist/core/commands/fast-mode-command.js +1 -1
  22. package/dist/core/commands/loop-command.js +86 -13
  23. package/dist/core/commands/naruto-command.js +34 -21
  24. package/dist/core/commands/team-command.js +1 -0
  25. package/dist/core/commands/wiki-command.js +35 -1
  26. package/dist/core/fsx.js +1 -1
  27. package/dist/core/init.js +1 -2
  28. package/dist/core/locks/file-lock.js +88 -0
  29. package/dist/core/loops/loop-artifacts.js +54 -2
  30. package/dist/core/loops/loop-checkpoint.js +22 -0
  31. package/dist/core/loops/loop-concurrency-budget.js +55 -0
  32. package/dist/core/loops/loop-final-arbiter-contract.js +28 -0
  33. package/dist/core/loops/loop-finalizer.js +55 -7
  34. package/dist/core/loops/loop-fixture-policy.js +58 -0
  35. package/dist/core/loops/loop-gate-registry.js +96 -0
  36. package/dist/core/loops/loop-gate-runner.js +206 -17
  37. package/dist/core/loops/loop-gpt-final-arbiter.js +81 -0
  38. package/dist/core/loops/loop-integration-merge.js +80 -0
  39. package/dist/core/loops/loop-interrupt-registry.js +118 -0
  40. package/dist/core/loops/loop-lease.js +35 -20
  41. package/dist/core/loops/loop-merge-strategy.js +105 -0
  42. package/dist/core/loops/loop-mutation-ledger.js +103 -0
  43. package/dist/core/loops/loop-planner.js +36 -5
  44. package/dist/core/loops/loop-runtime-control.js +27 -0
  45. package/dist/core/loops/loop-runtime.js +254 -96
  46. package/dist/core/loops/loop-scheduler.js +14 -5
  47. package/dist/core/loops/loop-side-effect-scanner.js +91 -0
  48. package/dist/core/loops/loop-worker-prompts.js +43 -0
  49. package/dist/core/loops/loop-worker-runtime.js +281 -0
  50. package/dist/core/loops/loop-worktree-runtime.js +92 -0
  51. package/dist/core/naruto/naruto-finalizer.js +7 -2
  52. package/dist/core/naruto/naruto-loop-mesh.js +10 -1
  53. package/dist/core/proof/auto-finalize.js +3 -2
  54. package/dist/core/proof/proof-schema.js +6 -0
  55. package/dist/core/proof/proof-writer.js +5 -2
  56. package/dist/core/proof/root-cause-policy.js +70 -0
  57. package/dist/core/proof/route-adapter.js +18 -1
  58. package/dist/core/proof/route-finalizer.js +71 -6
  59. package/dist/core/proof/route-proof-gate.js +4 -0
  60. package/dist/core/release/release-gate-batch-runner.js +56 -10
  61. package/dist/core/release/release-gate-cache-v2.js +18 -3
  62. package/dist/core/release/release-gate-dag.js +121 -18
  63. package/dist/core/release/release-gate-node.js +2 -1
  64. package/dist/core/release/release-gate-resource-governor.js +27 -6
  65. package/dist/core/skills/core-skill-meta-update.js +24 -0
  66. package/dist/core/skills/core-skill-reflection.js +94 -0
  67. package/dist/core/skills/core-skill-trainer.js +103 -0
  68. package/dist/core/trust-kernel/completion-contract.js +4 -0
  69. package/dist/core/trust-kernel/route-contract.js +4 -1
  70. package/dist/core/version.js +1 -1
  71. package/dist/core/zellij/zellij-right-column-manager.js +13 -2
  72. package/dist/core/zellij/zellij-slot-column-anchor.js +40 -3
  73. package/dist/core/zellij/zellij-slot-pane-renderer.js +36 -11
  74. package/dist/core/zellij/zellij-slot-telemetry.js +96 -44
  75. package/dist/core/zellij/zellij-worker-pane-manager.js +42 -4
  76. package/dist/scripts/lib/native-cli-session-swarm-check-lib.js +14 -2
  77. package/dist/scripts/loop-directive-check-lib.js +225 -2
  78. package/dist/scripts/loop-hardening-check-lib.js +289 -0
  79. package/dist/scripts/loop-worker-fixture-child.js +53 -0
  80. package/dist/scripts/naruto-real-local-gpt-final-smoke.js +10 -1
  81. package/dist/scripts/prepublish-release-check-or-fast.js +38 -10
  82. package/dist/scripts/release-check-stamp.js +29 -4
  83. package/dist/scripts/release-gate-existence-audit.js +1 -0
  84. package/package.json +32 -2
package/README.md CHANGED
@@ -35,7 +35,7 @@ Set up this agent project with Sneakoscope Codex. Use [[mandarange/Sneakoscope-C
35
35
 
36
36
  ## 🚀 Current Release
37
37
 
38
- SKS **3.1.0** is Codex 0.139-aware while it bundles @openai/codex-sdk 0.138.0 at this release boundary. It detects and uses 0.139 features from the external Codex CLI when that CLI supports them, with release gates that include hermetic fixtures and actual real probes for code-mode web search, preserved `oneOf`/`allOf` tool schemas, doctor env redaction, plugin marketplace `source` and cache behavior, the `-P` profile alias, the multi-agent v2 `interrupt_agent` rename, image referenced-path routing, sandbox/proxy preservation, Zellij stacked/fallback pane proof, pane-lock openWorkerPane integration, release cache safety fixtures, runtime proof summaries, and release proof source-truth artifacts. See [docs/codex-0.139-compat.md](docs/codex-0.139-compat.md) and [docs/codex-0.139-real-probes.md](docs/codex-0.139-real-probes.md).
38
+ SKS **3.1.2** hardens Loop Mesh for production use: fixture workers/gates/final arbiters are test-only, `gpt:final-arbiter` is explicitly finalizer-owned, integration merge uses a strategy ladder, side-effect reports come from loop diffs and mutation ledgers, loop kill interrupts active worker sessions, and global loop concurrency budgets prevent worker/model-call oversubscription. It remains Codex 0.139-aware while bundling @openai/codex-sdk 0.138.0 at this release boundary; see [docs/codex-0.139-compat.md](docs/codex-0.139-compat.md), [docs/codex-0.139-real-probes.md](docs/codex-0.139-real-probes.md), [docs/loop-fixture-policy.md](docs/loop-fixture-policy.md), and [docs/loop-merge-strategy.md](docs/loop-merge-strategy.md).
39
39
 
40
40
  SKS 3.0.0 was the parallel-runtime stabilization release. The whole live-swarm experience — what you actually *see* while 5, 20, or 100 workers run — was rebuilt and proven end-to-end.
41
41
 
@@ -76,7 +76,7 @@ dependencies = [
76
76
 
77
77
  [[package]]
78
78
  name = "sks-core"
79
- version = "3.1.0"
79
+ version = "3.1.2"
80
80
  dependencies = [
81
81
  "serde_json",
82
82
  ]
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "sks-core"
3
- version = "3.1.0"
3
+ version = "3.1.2"
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 3.1.0"),
7
+ Some("--version") => println!("sks-rs 3.1.2"),
8
8
  Some("compact-info") => {
9
9
  let mut input = String::new();
10
10
  let _ = io::stdin().read_to_string(&mut input);
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schema": "sks.dist-build-stamp.v1",
3
3
  "package_name": "sneakoscope",
4
- "package_version": "3.1.0",
5
- "source_digest": "0eed1c5f95e3d3e67d263680e28b27bfd1dabe232ff7265720a57d7d377252e8",
6
- "source_file_count": 2392,
7
- "built_at_source_time": 1781256416238
4
+ "package_version": "3.1.2",
5
+ "source_digest": "0312e2e27bae702aee9d6c07009b9492e43274c3d26e3b63a5140196997fea39",
6
+ "source_file_count": 2448,
7
+ "built_at_source_time": 1781367909038
8
8
  }
package/dist/bin/sks.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- const FAST_PACKAGE_VERSION = '3.1.0';
2
+ const FAST_PACKAGE_VERSION = '3.1.2';
3
3
  const args = process.argv.slice(2);
4
4
  try {
5
5
  if (args[0] === '--agent' && args[1] === 'worker') {
@@ -1515,7 +1515,7 @@ function normalizeCodexFastModeUiConfigOnce(text = '', opts = {}) {
1515
1515
  next = upsertTopLevelTomlStringIfAbsent(next, 'model', 'gpt-5.5');
1516
1516
  next = opts.forceFastMode === true
1517
1517
  ? upsertTopLevelTomlString(next, 'service_tier', 'fast')
1518
- : upsertTopLevelTomlStringIfAbsent(next, 'service_tier', 'fast');
1518
+ : next;
1519
1519
  // Codex App feature flags / fast-mode UI / suppress-warning are SET-IF-ABSENT: a fresh
1520
1520
  // config still gets SKS's defaults, but SKS NEVER overrides (re-enables) a feature the
1521
1521
  // user disabled in the App, and never rejects-then-hides UI by forcing an unrecognized
@@ -1536,11 +1536,10 @@ function normalizeCodexFastModeUiConfigOnce(text = '', opts = {}) {
1536
1536
  else {
1537
1537
  next = upsertTomlTableKeyIfAbsent(next, 'user.fast_mode', 'visible = true');
1538
1538
  next = upsertTomlTableKeyIfAbsent(next, 'user.fast_mode', 'enabled = true');
1539
- next = upsertTomlTableKeyIfAbsent(next, 'user.fast_mode', 'default_profile = "sks-fast-high"');
1539
+ next = removeTomlTableKey(next, 'user.fast_mode', 'default_profile');
1540
1540
  }
1541
- // Keep ONLY the sks-fast-high config-profile table: the Codex App fast-mode
1542
- // (`[user.fast_mode] default_profile = "sks-fast-high"`) and the
1543
- // codex-app:ui-preservation gate still expect it. The other SKS config profiles are
1541
+ // Keep ONLY the sks-fast-high config-profile table for explicit fast-mode opt-in
1542
+ // and CLI `--profile` use. The other SKS config profiles are
1544
1543
  // no longer written as `[profiles.sks-*]` tables here (Codex 0.134+ deprecates them);
1545
1544
  // they are managed as per-file `<name>.config.toml` overlays by
1546
1545
  // migrateSksProfilesToPerFile (src/core/auto-review.ts), which also writes the
@@ -2953,8 +2952,8 @@ export async function selftestCodexLb(tmp) {
2953
2952
  });
2954
2953
  if (brokenChain.ok || brokenChain.status !== 'previous_response_not_found' || brokenChain.chain_unhealthy !== true)
2955
2954
  throw new Error('selftest: codex-lb response chain health check did not detect previous_response_not_found');
2956
- if (!/^model = "gpt-5\.5"/m.test(codexLbConfig) || !codexLbConfig.includes('service_tier = "fast"') || !codexLbConfig.includes('hooks = true') || hasDeprecatedCodexHooksFeatureFlag(codexLbConfig) || !codexLbConfig.includes('remote_control = true') || !codexLbConfig.includes('multi_agent = true') || !codexLbConfig.includes('fast_mode = true') || !codexLbConfig.includes('fast_mode_ui = true') || !codexLbConfig.includes('codex_git_commit = true') || !codexLbConfig.includes('computer_use = true') || !codexLbConfig.includes('browser_use = true') || !codexLbConfig.includes('browser_use_external = true') || !codexLbConfig.includes('guardian_approval = true') || !codexLbConfig.includes('tool_suggest = true') || !codexLbConfig.includes('apps = true') || !codexLbConfig.includes('plugins = true') || !codexLbConfig.includes('[plugins."latex@openai-bundled"]') || !codexLbConfig.includes('[plugins."documents@openai-primary-runtime"]') || !codexLbConfig.includes('[user.fast_mode]') || !codexLbConfig.includes('visible = true') || !codexLbConfig.includes('enabled = true') || !codexLbConfig.includes('default_profile = "sks-fast-high"') || !/\[profiles\.custom\][\s\S]*?model_reasoning_effort = "low"/.test(codexLbConfig) || !/\[profiles\.sks-fast-high\][\s\S]*?service_tier = "fast"/.test(codexLbConfig) || codexLbConfig.includes('fast_default_opt_out = true') || hasTopLevelCodexModeLock(codexLbConfig))
2957
- throw new Error('selftest: codex-lb setup did not preserve Codex App feature flags, default plugins, profile-scoped reasoning effort, Fast mode defaults, Codex Git commit generation, force GPT-5.5, or migrate the hooks feature flag');
2955
+ if (!/^model = "gpt-5\.5"/m.test(codexLbConfig) || !codexLbConfig.includes('hooks = true') || hasDeprecatedCodexHooksFeatureFlag(codexLbConfig) || !codexLbConfig.includes('remote_control = true') || !codexLbConfig.includes('multi_agent = true') || !codexLbConfig.includes('fast_mode = true') || !codexLbConfig.includes('fast_mode_ui = true') || !codexLbConfig.includes('codex_git_commit = true') || !codexLbConfig.includes('computer_use = true') || !codexLbConfig.includes('browser_use = true') || !codexLbConfig.includes('browser_use_external = true') || !codexLbConfig.includes('guardian_approval = true') || !codexLbConfig.includes('tool_suggest = true') || !codexLbConfig.includes('apps = true') || !codexLbConfig.includes('plugins = true') || !codexLbConfig.includes('[plugins."latex@openai-bundled"]') || !codexLbConfig.includes('[plugins."documents@openai-primary-runtime"]') || !codexLbConfig.includes('[user.fast_mode]') || !codexLbConfig.includes('visible = true') || !codexLbConfig.includes('enabled = true') || !/\[profiles\.custom\][\s\S]*?model_reasoning_effort = "low"/.test(codexLbConfig) || !/\[profiles\.sks-fast-high\][\s\S]*?service_tier = "fast"/.test(codexLbConfig) || codexLbConfig.includes('fast_default_opt_out = true') || hasTopLevelCodexModeLock(codexLbConfig))
2956
+ throw new Error('selftest: codex-lb setup did not preserve Codex App feature flags, default plugins, profile-scoped reasoning effort, explicit Fast profile, Codex Git commit generation, force GPT-5.5, or migrate the hooks feature flag');
2958
2957
  if (!hasCodexUnstableFeatureWarningSuppression(codexLbConfig))
2959
2958
  throw new Error('selftest: codex-lb setup did not suppress Codex unstable feature warning');
2960
2959
  const codexLbLaunch = `source ${path.join(tmp, '.codex', 'sks-codex-lb.env')} && codex '--model' 'gpt-5.5'`;
@@ -7,7 +7,9 @@ export async function run(_command = 'zellij-slot-column-anchor', args = []) {
7
7
  const intervalMs = Math.max(250, Number(readOption(args, '--interval-ms', '1000') || 1000));
8
8
  for (;;) {
9
9
  const text = await renderZellijSlotColumnAnchorFromArtifacts({ artifactRoot, missionId, mode });
10
- process.stdout.write('\x1Bc' + text + '\n');
10
+ // Cursor-home + clear-to-end redraw; `\x1Bc` (RIS) resets the pane's
11
+ // scrollback/modes every tick and intermittently breaks scrolling.
12
+ process.stdout.write('\x1b[H' + text + '\n\x1b[0J');
11
13
  if (!watch)
12
14
  break;
13
15
  await new Promise((resolve) => setTimeout(resolve, intervalMs));
@@ -1,4 +1,4 @@
1
- import { renderZellijSlotPaneFromArtifacts, renderZellijSlotPaneStatusFromArtifacts } from '../core/zellij/zellij-slot-pane-renderer.js';
1
+ import { renderZellijSlotPaneFromArtifacts, renderZellijSlotPaneStatusFromArtifacts, resolveZellijSlotPaneExit } from '../core/zellij/zellij-slot-pane-renderer.js';
2
2
  export async function run(_command = 'zellij-slot-pane', args = []) {
3
3
  const artifactDir = readOption(args, '--artifact-dir', process.cwd()) || process.cwd();
4
4
  const artifactRoot = readOption(args, '--artifact-root', artifactDir) || artifactDir;
@@ -18,9 +18,19 @@ export async function run(_command = 'zellij-slot-pane', args = []) {
18
18
  }
19
19
  for (;;) {
20
20
  const text = await renderZellijSlotPaneFromArtifacts({ artifactDir, artifactRoot, missionId, slotId, generationIndex, backend, role, mode });
21
- process.stdout.write('\x1Bc' + text + '\n');
21
+ process.stdout.write(redrawFrame(text));
22
22
  if (!watch)
23
23
  break;
24
+ // Root-cause-3 fix: exit the pane once the worker has reached a terminal state and written its
25
+ // result, so the pane closes (or shows the final exited frame) instead of looping forever and
26
+ // perpetually re-reporting telemetry staleness.
27
+ const shouldExit = await resolveZellijSlotPaneExit({ artifactDir, artifactRoot, missionId, slotId, generationIndex }).catch(() => false);
28
+ if (shouldExit) {
29
+ await new Promise((resolve) => setTimeout(resolve, 5000));
30
+ const finalText = await renderZellijSlotPaneFromArtifacts({ artifactDir, artifactRoot, missionId, slotId, generationIndex, backend, role, mode });
31
+ process.stdout.write(redrawFrame(finalText));
32
+ return;
33
+ }
24
34
  await new Promise((resolve) => setTimeout(resolve, intervalMs));
25
35
  }
26
36
  }
@@ -28,6 +38,13 @@ function readOption(args, name, fallback) {
28
38
  const index = args.indexOf(name);
29
39
  return index >= 0 && args[index + 1] ? String(args[index + 1]) : fallback;
30
40
  }
41
+ // Redraw in place with cursor-home + clear-to-end instead of `\x1Bc` (RIS).
42
+ // RIS performs a full terminal reset every tick, which wipes the Zellij pane's
43
+ // scrollback and resets scroll position/modes — the cause of intermittent
44
+ // "scrolling stops working" while a watch pane is refreshing.
45
+ function redrawFrame(text) {
46
+ return '\x1b[H' + text + '\n\x1b[0J';
47
+ }
31
48
  function hasFlag(args, flag) {
32
49
  return args.includes(flag);
33
50
  }
@@ -164,7 +164,16 @@ async function listFiles(dir) {
164
164
  const out = [];
165
165
  if (!(await exists(dir)))
166
166
  return out;
167
- for (const entry of await fsp.readdir(dir, { withFileTypes: true })) {
167
+ let entries;
168
+ try {
169
+ entries = await fsp.readdir(dir, { withFileTypes: true });
170
+ }
171
+ catch (error) {
172
+ if (error?.code === 'ENOENT' || error?.code === 'ENOTDIR')
173
+ return out;
174
+ throw error;
175
+ }
176
+ for (const entry of entries) {
168
177
  const file = path.join(dir, entry.name);
169
178
  if (entry.isDirectory())
170
179
  out.push(...await listFiles(file));
@@ -49,6 +49,7 @@ import { applyFastModeToRoster, resolveFastModePolicy, writeFastModePropagationP
49
49
  import { createNativeCliSessionSwarmRecorder } from './native-cli-session-swarm.js';
50
50
  import { writeNativeCliSessionProof } from './native-cli-session-proof.js';
51
51
  import { writeNoSubagentScalingPolicy } from './no-subagent-scaling-policy.js';
52
+ import { writeOfficialSubagentHelperPolicy } from './official-subagent-helper-policy.js';
52
53
  import { runCodexTask } from '../codex-control/codex-control-plane.js';
53
54
  import { CODEX_AGENT_WORKER_RESULT_SCHEMA_ID, codexAgentWorkerResultSchema } from '../codex-control/schemas/agent-worker-result.schema.js';
54
55
  import { resolveLocalCollaborationPolicy, localCollaborationParticipated } from '../local-llm/local-collaboration-policy.js';
@@ -255,7 +256,7 @@ export async function runNativeAgentOrchestrator(opts = {}) {
255
256
  total_work_items: partition.task_graph?.total_work_items || partition.slices.length,
256
257
  service_tier: fastModePolicy.service_tier,
257
258
  fast_mode: fastModePolicy.fast_mode,
258
- fast_mode_default: true,
259
+ fast_mode_default: fastModePolicy.default_fast_mode,
259
260
  backpressure: 'dynamic scheduler maintains target active slots until the work queue drains',
260
261
  rate_limit_delay_ms: backend === 'codex-sdk' ? 250 : 0,
261
262
  resource_pressure_warnings: roster.agent_count > roster.concurrency ? ['agents_exceed_concurrency_batches'] : []
@@ -477,6 +478,7 @@ export async function runNativeAgentOrchestrator(opts = {}) {
477
478
  visiblePanes: visualLaneCount,
478
479
  expectedWorkerRuntimeMs: targetActiveSlots >= 10 ? 8000 : targetActiveSlots >= 2 ? 2000 : 25,
479
480
  minActiveWorkers: Math.min(targetActiveSlots, desiredWorkItemCount),
481
+ ...(backend === 'codex-sdk' && opts.real === true ? { minSpeedupRatio: 3 } : {}),
480
482
  proofMode: opts.mock === true ? 'mock-process' : 'production',
481
483
  requireWorkerPids: opts.nativeCliSwarm !== false && targetActiveSlots >= 16
482
484
  });
@@ -486,7 +488,8 @@ export async function runNativeAgentOrchestrator(opts = {}) {
486
488
  targetActiveSlots,
487
489
  totalWorkItems: partition.task_graph?.total_work_items || partition.slices.length
488
490
  });
489
- const noSubagentScalingPolicy = await writeNoSubagentScalingPolicy(ledgerRoot, { nativeProof: nativeCliSessionProof });
491
+ const officialSubagentHelperPolicy = await writeOfficialSubagentHelperPolicy(ledgerRoot, { nativeProof: nativeCliSessionProof });
492
+ const noSubagentScalingPolicy = await writeNoSubagentScalingPolicy(ledgerRoot, { nativeProof: nativeCliSessionProof, officialSubagentHelperPolicy });
490
493
  const fastModePropagation = await writeFastModePropagationProof(ledgerRoot, { policy: fastModePolicy, backend, results });
491
494
  const localCollaborationPolicy = resolveLocalCollaborationPolicy();
492
495
  await writeJsonAtomic(path.join(ledgerRoot, 'local-collaboration-policy.json'), localCollaborationPolicy);
@@ -559,6 +562,7 @@ export async function runNativeAgentOrchestrator(opts = {}) {
559
562
  ...(timeoutKill.killed_sessions || []).map((id) => 'timeout_killed:' + id),
560
563
  ...(recursion.ok ? [] : recursion.violations.map((id) => 'recursion:' + id)),
561
564
  ...(nativeCliSessionProof.ok ? [] : nativeCliSessionProof.blockers),
565
+ ...(officialSubagentHelperPolicy.ok ? [] : officialSubagentHelperPolicy.blockers),
562
566
  ...(noSubagentScalingPolicy.ok ? [] : noSubagentScalingPolicy.blockers),
563
567
  ...(fastModePropagation.ok ? [] : fastModePropagation.blockers),
564
568
  ...(gitWorktreeRuntime.required === true && gitWorktreeRuntime.ok === false ? gitWorktreeRuntime.blockers || ['git_worktree_runtime_not_ok'] : []),
@@ -596,6 +600,7 @@ export async function runNativeAgentOrchestrator(opts = {}) {
596
600
  strategyGate,
597
601
  nativeCliSessionProof,
598
602
  noSubagentScalingPolicy,
603
+ officialSubagentHelperPolicy,
599
604
  fastModePolicy,
600
605
  fastModePropagation,
601
606
  gitWorktreeRuntime,
@@ -641,6 +646,7 @@ export async function runNativeAgentOrchestrator(opts = {}) {
641
646
  parallel_write_policy: parallelWritePolicy,
642
647
  native_cli_session_proof: nativeCliSessionProof,
643
648
  no_subagent_scaling_policy: noSubagentScalingPolicy,
649
+ official_subagent_helper_policy: officialSubagentHelperPolicy,
644
650
  fast_mode_policy: fastModePolicy,
645
651
  fast_mode_propagation: fastModePropagation,
646
652
  git_worktree_runtime: gitWorktreeRuntime,
@@ -51,6 +51,7 @@ export async function writeAgentProofEvidence(root, input) {
51
51
  });
52
52
  const nativeCliSessionProof = input.nativeCliSessionProof || await readJson(path.join(root, 'native-cli-session-proof.json'), null);
53
53
  const noSubagentScalingPolicy = input.noSubagentScalingPolicy || await readJson(path.join(root, 'no-subagent-scaling-policy.json'), null);
54
+ const officialSubagentHelperPolicy = input.officialSubagentHelperPolicy || await readJson(path.join(root, 'official-subagent-helper-policy.json'), null);
54
55
  const fastModePropagation = input.fastModePropagation || await readJson(path.join(root, 'fast-mode-propagation-proof.json'), null);
55
56
  const zellijPaneProof = await readJson(path.join(root, 'zellij-pane-proof.json'), null);
56
57
  const cleanupProof = await readJson(path.join(root, 'agent-cleanup-proof.json'), null);
@@ -179,6 +180,7 @@ export async function writeAgentProofEvidence(root, input) {
179
180
  ...(input.results || []).flatMap((result) => result.blockers || []),
180
181
  ...(nativeCliSessionProof?.ok === false ? nativeCliSessionProof.blockers || ['native_cli_session_proof_not_ok'] : []),
181
182
  ...(noSubagentScalingPolicy?.ok === false ? noSubagentScalingPolicy.blockers || ['no_subagent_scaling_policy_not_ok'] : []),
183
+ ...(officialSubagentHelperPolicy?.ok === false ? officialSubagentHelperPolicy.blockers || ['official_subagent_helper_policy_not_ok'] : []),
182
184
  ...(fastModePropagation?.ok === false ? fastModePropagation.blockers || ['fast_mode_propagation_not_ok'] : []),
183
185
  ...(patchSwarm?.ok === false ? patchSwarm.blockers || ['patch_swarm_not_ok'] : []),
184
186
  ...(gitWorktreeRuntime?.required === true && gitWorktreeRuntime?.ok === false ? gitWorktreeRuntime.blockers || ['git_worktree_runtime_not_ok'] : []),
@@ -225,6 +227,23 @@ export async function writeAgentProofEvidence(root, input) {
225
227
  native_cli_session_proof: nativeCliSessionProof ? 'native-cli-session-proof.json' : null,
226
228
  native_cli_session_swarm: nativeCliSessionProof ? 'agent-native-cli-session-swarm.json' : null,
227
229
  no_subagent_scaling_policy: noSubagentScalingPolicy ? 'no-subagent-scaling-policy.json' : null,
230
+ official_subagent_helper_policy: officialSubagentHelperPolicy ? 'official-subagent-helper-policy.json' : null,
231
+ official_subagent_helper_lane_enabled: officialSubagentHelperPolicy?.official_codex_subagent_helper_lane_enabled === true,
232
+ official_subagent_helper_lane_ok: officialSubagentHelperPolicy?.ok === true,
233
+ official_subagent_helper_worker_capacity_credit: Number(officialSubagentHelperPolicy?.worker_capacity_credit || 0),
234
+ official_subagent_helper_observed_subagent_event_count: Number(officialSubagentHelperPolicy?.observed_subagent_event_count || 0),
235
+ official_subagent_helper_events_counted_as_worker_sessions: officialSubagentHelperPolicy?.subagent_events_counted_as_worker_sessions === true,
236
+ official_subagent_helper_capacity_source: officialSubagentHelperPolicy?.worker_capacity_source || null,
237
+ official_subagent_helper_parallel_allowed: officialSubagentHelperPolicy?.helper_lane_may_run_in_parallel_with_native_workers === true,
238
+ official_subagent_helper_capabilities: officialSubagentHelperPolicy?.codex_app_capabilities_allowed || [],
239
+ official_subagent_helper_builtin_agents: officialSubagentHelperPolicy?.built_in_agents_allowed || [],
240
+ official_subagent_helper_required_output_proof_for_generated_images: officialSubagentHelperPolicy?.required_output_proof_for_generated_images || [],
241
+ codex_builtin_imagegen_helper_allowed: officialSubagentHelperPolicy?.codex_builtin_imagegen_helper_allowed === true,
242
+ codex_builtin_imagegen_required_surface: officialSubagentHelperPolicy?.preferred_image_generation_surface || null,
243
+ codex_builtin_imagegen_evidence_class: officialSubagentHelperPolicy?.codex_app_builtin_evidence_class || null,
244
+ imagegen_api_fallback_evidence_class: officialSubagentHelperPolicy?.api_fallback_evidence_class || null,
245
+ imagegen_provider_surface_evidence_required: officialSubagentHelperPolicy?.provider_surface_evidence_required === true,
246
+ imagegen_api_fallback_counts_as_codex_app_evidence: officialSubagentHelperPolicy?.imagegen_api_fallback_counts_as_codex_app_evidence === true,
228
247
  fast_mode_policy: input.fastModePolicy || null,
229
248
  fast_mode_propagation: fastModePropagation ? 'fast-mode-propagation-proof.json' : null,
230
249
  native_cli_worker_process_count: Number(nativeCliSessionProof?.spawned_worker_process_count || 0),
@@ -403,6 +422,7 @@ export async function writeAgentProofEvidence(root, input) {
403
422
  'gpt-final-arbiter/gpt-final-arbiter.json': gptFinalArbiter,
404
423
  'native-cli-session-proof.json': nativeCliSessionProof,
405
424
  'no-subagent-scaling-policy.json': noSubagentScalingPolicy,
425
+ 'official-subagent-helper-policy.json': officialSubagentHelperPolicy,
406
426
  'fast-mode-propagation-proof.json': fastModePropagation
407
427
  }
408
428
  });
@@ -132,19 +132,25 @@ export function classifyOllamaWorkerSlice(slice, input = {}) {
132
132
  input.agent?.persona_id,
133
133
  slice?.role,
134
134
  slice?.domain,
135
+ slice?.kind,
135
136
  slice?.title,
136
137
  slice?.description,
137
138
  ...(Array.isArray(slice?.target_paths) ? slice.target_paths : [])
138
139
  ].map((value) => String(value || '')).join('\n');
139
- const bannedRole = /(?:^|\b)(architect|verifier|safety|integrator|schema|release|ux|db)(?:\b|$)/i.test(String(input.agent?.role || slice?.role || ''));
140
+ const bannedRole = /(?:^|\b)(architect|verifier|checker|reviewer|researcher|safety|integrator|schema|release|ux|db)(?:\b|$)/i.test(String(input.agent?.role || slice?.role || ''));
140
141
  const collection = /\b(collect|gather|extract|inventory|list|scan|grep|tail|summarize|catalog)\b|수집|추출|목록|스캔|인벤토리/i.test(text);
141
142
  const coding = /\b(code|implement|patch|write|edit|fix|mechanical|simple)\b|코드|작성|수정|구현|패치|단순/i.test(text);
142
- const banned = /\b(strategy|strategize|planning|plan|architecture|architect|design|review|verify|verification|safety|risk|consensus|debate|orchestrate|policy|decide|decision|migration|database|schema)\b|전략|기획|설계|디자인|검증|리뷰|안전|위험|합의|토론|결정|마이그레이션|데이터베이스/i.test(text);
143
- const allowed = !bannedRole && !banned && (writePaths.length > 0 || collection || coding);
143
+ const banned = /\b(strategy|strategize|planning|plan|architecture|architect|design|review|verify|verification|audit|inspect|safety|risk|consensus|debate|orchestrate|policy|decide|decision|migration|database|schema)\b|전략|기획|설계|디자인|검증|검수|리뷰|감사|안전|위험|합의|토론|결정|마이그레이션|데이터베이스/i.test(text);
144
+ // Web research / external lookup must run on GPT, never on the local model:
145
+ // local LLMs hallucinate sources and cannot browse. This wins even over the
146
+ // collection allowlist (e.g. "web research and collect docs" stays on GPT).
147
+ const research = /\b(web|research|browse|browser|crawl|fetch docs|websearch|web search|search the web|investigate|context7)\b|웹|리서치|조사|웹서치|웹 ?검색|검색/i.test(text);
148
+ const allowed = !bannedRole && !banned && !research && (writePaths.length > 0 || collection || coding);
144
149
  const blockers = [
145
150
  ...(allowed ? [] : ['ollama_worker_task_not_simple_code_or_collection']),
146
151
  ...(bannedRole ? ['ollama_worker_role_blocked'] : []),
147
- ...(banned ? ['ollama_worker_strategy_planning_design_blocked'] : [])
152
+ ...(banned ? ['ollama_worker_strategy_planning_design_blocked'] : []),
153
+ ...(research ? ['ollama_worker_web_research_blocked'] : [])
148
154
  ];
149
155
  return {
150
156
  schema: OLLAMA_WORKER_POLICY_SCHEMA,
@@ -156,6 +162,7 @@ export function classifyOllamaWorkerSlice(slice, input = {}) {
156
162
  write_path_count: writePaths.length,
157
163
  collection_detected: collection,
158
164
  coding_detected: coding,
165
+ research_detected: research,
159
166
  blockers
160
167
  };
161
168
  }
@@ -18,15 +18,17 @@ export function resolveFastModePolicy(input = {}) {
18
18
  ? 'standard'
19
19
  : preference?.mode === 'standard'
20
20
  ? 'standard'
21
- : 'fast';
21
+ : explicitFast || explicitTier === 'fast' || preference?.mode === 'fast'
22
+ ? 'fast'
23
+ : 'standard';
22
24
  return {
23
25
  schema: FAST_MODE_POLICY_SCHEMA,
24
26
  generated_at: nowIso(),
25
27
  fast_mode: serviceTier === 'fast',
26
28
  service_tier: serviceTier,
27
29
  codex_desktop_service_tier: codexDesktopServiceTier(serviceTier),
28
- default_fast_mode: true,
29
- disabled_by: explicitNoFast ? 'no-fast' : explicitTier === 'standard' ? 'service-tier-standard' : preference?.mode === 'standard' ? 'preference-standard' : 'none',
30
+ default_fast_mode: false,
31
+ disabled_by: explicitNoFast ? 'no-fast' : explicitTier === 'standard' ? 'service-tier-standard' : preference?.mode === 'standard' ? 'preference-standard' : serviceTier === 'standard' ? 'default-standard' : 'none',
30
32
  explicit_fast: explicitFast,
31
33
  explicit_no_fast: explicitNoFast,
32
34
  explicit_service_tier: explicitTier,
@@ -125,7 +127,7 @@ export async function writeFastModePropagationProof(root, input = { policy: reso
125
127
  const agentProcessReports = await collectNamedJson(root, 'agent-process-report.json');
126
128
  const zellijReports = await collectNamedJson(root, 'agent-zellij-report.json');
127
129
  const madReports = await collectNamedJson(root, 'mad-sks-worker-report.json');
128
- const defaultFastExpected = input.policy.disabled_by === 'none';
130
+ const defaultFastExpected = input.policy.fast_mode === true;
129
131
  const childReports = [...workerFastReports, ...workerProcessReports, ...agentProcessReports, ...zellijReports, ...madReports];
130
132
  const missingFast = defaultFastExpected
131
133
  ? childReports.filter((row) => row.json?.fast_mode !== true && row.json?.fast_mode !== 'true')
@@ -144,7 +146,7 @@ export async function writeFastModePropagationProof(root, input = { policy: reso
144
146
  ok: missingFast.length === 0 && missingTier.length === 0 && workerMissing.length === 0 && missingCliOverride.length === 0,
145
147
  policy: input.policy,
146
148
  backend: input.backend || null,
147
- default_fast_mode: true,
149
+ default_fast_mode: input.policy.default_fast_mode,
148
150
  service_tier: input.policy.service_tier,
149
151
  fast_mode: input.policy.fast_mode,
150
152
  worker_fast_report_count: workerFastReports.length,
@@ -112,6 +112,12 @@ export function enhanceTaskGraphWithIntelligence(taskGraph, graph) {
112
112
  }
113
113
  export async function writeIntelligentWorkGraphArtifacts(root, graph) {
114
114
  const compact = compactIntelligentWorkGraph(graph);
115
+ const symbolOwnership = compactSymbolOwnershipMap(graph);
116
+ const routeOwnership = limitRecordOfArrays(graph.route_to_module_ownership, graphRecordLimit(), graphArrayLimit());
117
+ const commandOwnership = limitRecordOfArrays(graph.command_to_module_ownership, graphRecordLimit(), graphArrayLimit());
118
+ const testOwnership = compactTestOwnershipMap(graph.test_ownership_map);
119
+ const criticalPath = compactCriticalPath(graph.critical_path);
120
+ const integrationBottlenecks = compactIntegrationBottlenecks(graph.integration_bottlenecks);
115
121
  await writeJsonAtomic(path.join(root, 'agent-intelligent-work-graph.json'), compact);
116
122
  await writeJsonAtomic(path.join(root, 'agent-intelligent-work-graph-v2.json'), compact);
117
123
  await writeJsonAtomic(path.join(root, 'agent-symbol-ownership-map.json'), {
@@ -119,23 +125,19 @@ export async function writeIntelligentWorkGraphArtifacts(root, graph) {
119
125
  generated_at: graph.generated_at,
120
126
  ok: Object.keys(graph.symbol_to_files).length > 0,
121
127
  ast_coverage: graph.ast_coverage,
122
- file_to_symbols: graph.file_to_symbols,
123
- symbol_to_files: graph.symbol_to_files,
124
- exported_symbols: graph.exported_symbols,
125
- imported_symbols: graph.imported_symbols,
126
- exported_api_ownership: graph.exported_api_ownership
128
+ ...symbolOwnership
127
129
  });
128
130
  await writeJsonAtomic(path.join(root, 'agent-route-ownership-map.json'), {
129
131
  schema: 'sks.agent-route-ownership-map.v1',
130
132
  generated_at: graph.generated_at,
131
133
  ok: Object.keys(graph.route_to_module_ownership).length > 0,
132
- route_to_module_ownership: graph.route_to_module_ownership
134
+ route_to_module_ownership: routeOwnership
133
135
  });
134
136
  await writeJsonAtomic(path.join(root, 'agent-command-ownership-map.json'), {
135
137
  schema: 'sks.agent-command-ownership-map.v1',
136
138
  generated_at: graph.generated_at,
137
139
  ok: Object.keys(graph.command_to_module_ownership).length > 0,
138
- command_to_module_ownership: graph.command_to_module_ownership
140
+ command_to_module_ownership: commandOwnership
139
141
  });
140
142
  await writeJsonAtomic(path.join(root, 'agent-test-ownership-map.json'), {
141
143
  schema: 'sks.agent-test-ownership-map.v1',
@@ -143,7 +145,7 @@ export async function writeIntelligentWorkGraphArtifacts(root, graph) {
143
145
  ok: graph.test_ownership_map.unmapped_sources.length < Math.max(10, graph.source_inventory_count),
144
146
  ast_coverage: graph.ast_coverage,
145
147
  test_ownership_confidence: graph.test_ownership_confidence,
146
- ...graph.test_ownership_map
148
+ ...testOwnership
147
149
  });
148
150
  await writeJsonAtomic(path.join(root, 'agent-source-test-ownership-v2.json'), {
149
151
  schema: 'sks.agent-source-test-ownership.v2',
@@ -151,14 +153,14 @@ export async function writeIntelligentWorkGraphArtifacts(root, graph) {
151
153
  ok: graph.test_ownership_confidence > 0,
152
154
  ast_coverage: graph.ast_coverage,
153
155
  test_ownership_confidence: graph.test_ownership_confidence,
154
- source_to_test_relations: graph.source_to_test_relations,
155
- ...graph.test_ownership_map
156
+ source_to_test_relations: testOwnership.relations,
157
+ ...testOwnership
156
158
  });
157
159
  await writeJsonAtomic(path.join(root, 'agent-critical-path.json'), {
158
160
  schema: 'sks.agent-critical-path.v1',
159
161
  generated_at: graph.generated_at,
160
162
  ok: graph.critical_path.path.length > 0,
161
- ...graph.critical_path
163
+ ...criticalPath
162
164
  });
163
165
  await writeJsonAtomic(path.join(root, 'agent-critical-path-v2.json'), {
164
166
  schema: 'sks.agent-critical-path.v2',
@@ -166,21 +168,21 @@ export async function writeIntelligentWorkGraphArtifacts(root, graph) {
166
168
  ok: graph.critical_path.path.length > 0,
167
169
  ast_coverage: graph.ast_coverage,
168
170
  critical_path_confidence: graph.critical_path.confidence || graph.test_ownership_confidence,
169
- ...graph.critical_path
171
+ ...criticalPath
170
172
  });
171
173
  await writeJsonAtomic(path.join(root, 'agent-integration-bottlenecks.json'), {
172
174
  schema: 'sks.agent-integration-bottlenecks.v1',
173
175
  generated_at: graph.generated_at,
174
176
  ok: true,
175
177
  critical_path_confidence: graph.critical_path.confidence || null,
176
- ...graph.integration_bottlenecks
178
+ ...integrationBottlenecks
177
179
  });
178
180
  await writeJsonAtomic(path.join(root, 'agent-integration-bottlenecks-v2.json'), {
179
181
  schema: 'sks.agent-integration-bottlenecks.v2',
180
182
  generated_at: graph.generated_at,
181
183
  ok: true,
182
184
  critical_path_confidence: graph.critical_path.confidence || null,
183
- ...graph.integration_bottlenecks
185
+ ...integrationBottlenecks
184
186
  });
185
187
  }
186
188
  export function compactIntelligentWorkGraph(graph) {
@@ -222,6 +224,83 @@ export function compactIntelligentWorkGraph(graph) {
222
224
  function limitArray(items, limit) {
223
225
  return Array.isArray(items) ? items.slice(0, limit) : [];
224
226
  }
227
+ function graphRecordLimit() {
228
+ return Math.max(50, Math.floor(Number(process.env.SKS_WORK_GRAPH_RECORD_LIMIT || 500) || 500));
229
+ }
230
+ function graphArrayLimit() {
231
+ return Math.max(10, Math.floor(Number(process.env.SKS_WORK_GRAPH_ARRAY_LIMIT || 50) || 50));
232
+ }
233
+ function graphRelationLimit() {
234
+ return Math.max(100, Math.floor(Number(process.env.SKS_WORK_GRAPH_RELATION_LIMIT || 1000) || 1000));
235
+ }
236
+ function limitRecordOfArrays(record, entryLimit, arrayLimit) {
237
+ const out = {};
238
+ for (const [key, value] of Object.entries(record || {}).slice(0, entryLimit)) {
239
+ out[key] = Array.isArray(value) ? value.slice(0, arrayLimit) : value;
240
+ }
241
+ return out;
242
+ }
243
+ function compactSymbolOwnershipMap(graph) {
244
+ const entryLimit = graphRecordLimit();
245
+ const arrayLimit = graphArrayLimit();
246
+ const exportedApiOwnership = {};
247
+ for (const [symbol, file] of Object.entries(graph.exported_api_ownership || {}).slice(0, entryLimit))
248
+ exportedApiOwnership[symbol] = String(file);
249
+ return {
250
+ compact: true,
251
+ limits: { record_entries: entryLimit, array_items: arrayLimit },
252
+ original_counts: {
253
+ file_to_symbols: Object.keys(graph.file_to_symbols || {}).length,
254
+ symbol_to_files: Object.keys(graph.symbol_to_files || {}).length,
255
+ exported_symbols: Object.keys(graph.exported_symbols || {}).length,
256
+ imported_symbols: Object.keys(graph.imported_symbols || {}).length,
257
+ exported_api_ownership: Object.keys(graph.exported_api_ownership || {}).length
258
+ },
259
+ file_to_symbols: limitRecordOfArrays(graph.file_to_symbols, entryLimit, arrayLimit),
260
+ symbol_to_files: limitRecordOfArrays(graph.symbol_to_files, entryLimit, arrayLimit),
261
+ exported_symbols: limitRecordOfArrays(graph.exported_symbols, entryLimit, arrayLimit),
262
+ imported_symbols: limitRecordOfArrays(graph.imported_symbols, entryLimit, arrayLimit),
263
+ exported_api_ownership: exportedApiOwnership
264
+ };
265
+ }
266
+ function compactTestOwnershipMap(map = {}) {
267
+ const entryLimit = graphRecordLimit();
268
+ const arrayLimit = graphArrayLimit();
269
+ const relationLimit = graphRelationLimit();
270
+ const relations = limitArray((Array.isArray(map.relations) ? map.relations : []).map((row) => ({
271
+ ...row,
272
+ symbol_overlap: limitArray(row?.symbol_overlap, arrayLimit)
273
+ })), relationLimit);
274
+ return {
275
+ compact: true,
276
+ limits: { owner_entries: entryLimit, owner_items: arrayLimit, relations: relationLimit },
277
+ original_counts: {
278
+ owner_by_source: Object.keys(map.owner_by_source || {}).length,
279
+ unmapped_sources: Array.isArray(map.unmapped_sources) ? map.unmapped_sources.length : 0,
280
+ relations: Array.isArray(map.relations) ? map.relations.length : 0
281
+ },
282
+ owner_by_source: limitRecordOfArrays(map.owner_by_source, entryLimit, arrayLimit),
283
+ mapped_source_count: Number(map.mapped_source_count || 0),
284
+ unmapped_sources: limitArray(map.unmapped_sources, entryLimit),
285
+ relations
286
+ };
287
+ }
288
+ function compactCriticalPath(criticalPath = {}) {
289
+ return {
290
+ ...criticalPath,
291
+ path: limitArray(criticalPath.path, 500),
292
+ compact: true,
293
+ original_path_length: Array.isArray(criticalPath.path) ? criticalPath.path.length : 0
294
+ };
295
+ }
296
+ function compactIntegrationBottlenecks(bottlenecks = {}) {
297
+ return {
298
+ ...bottlenecks,
299
+ bottlenecks: limitArray(bottlenecks.bottlenecks, 200),
300
+ changed_bottlenecks: limitArray(bottlenecks.changed_bottlenecks, 200),
301
+ compact: true
302
+ };
303
+ }
225
304
  function buildTestOwnershipMap(sourceFiles, testFiles, ast, dependencyGraph = {}) {
226
305
  const ownerBySource = {};
227
306
  const relations = [];