sneakoscope 2.0.1 → 2.0.4

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 (74) hide show
  1. package/README.md +26 -5
  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/build-manifest.json +28 -8
  8. package/dist/cli/command-registry.js +2 -0
  9. package/dist/commands/doctor.js +29 -3
  10. package/dist/core/agents/agent-command-surface.js +13 -3
  11. package/dist/core/agents/agent-orchestrator.js +92 -4
  12. package/dist/core/agents/agent-output-validator.js +2 -1
  13. package/dist/core/agents/agent-patch-proof.js +5 -0
  14. package/dist/core/agents/agent-patch-schema.js +2 -1
  15. package/dist/core/agents/agent-proof-evidence.js +26 -0
  16. package/dist/core/agents/agent-roster.js +1 -1
  17. package/dist/core/agents/agent-runner-ollama.js +411 -0
  18. package/dist/core/agents/agent-schema.js +1 -1
  19. package/dist/core/agents/intelligent-work-graph.js +45 -3
  20. package/dist/core/agents/native-cli-session-swarm.js +8 -1
  21. package/dist/core/agents/native-cli-worker.js +1 -1
  22. package/dist/core/agents/native-worker-backend-router.js +44 -2
  23. package/dist/core/agents/ollama-worker-config.js +118 -0
  24. package/dist/core/auto-review.js +39 -6
  25. package/dist/core/codex-app/codex-app-fast-ui-repair.js +42 -3
  26. package/dist/core/codex-control/codex-fake-sdk-adapter.js +20 -0
  27. package/dist/core/codex-control/codex-output-schemas.js +5 -1
  28. package/dist/core/codex-control/gpt-final-arbiter.js +160 -0
  29. package/dist/core/codex-control/gpt-final-context-compressor.js +17 -0
  30. package/dist/core/codex-control/gpt-final-proof-pack.js +120 -0
  31. package/dist/core/codex-control/gpt-final-review-schema.js +71 -0
  32. package/dist/core/commands/basic-cli.js +36 -1
  33. package/dist/core/commands/local-model-command.js +120 -0
  34. package/dist/core/commands/mad-sks-command.js +58 -9
  35. package/dist/core/commands/naruto-command.js +77 -5
  36. package/dist/core/commands/run-command.js +33 -1
  37. package/dist/core/commands/team-command.js +31 -2
  38. package/dist/core/doctor/doctor-readiness-matrix.js +19 -0
  39. package/dist/core/feature-fixtures.js +5 -0
  40. package/dist/core/fsx.js +1 -1
  41. package/dist/core/git-simple.js +143 -4
  42. package/dist/core/hooks-runtime.js +1 -1
  43. package/dist/core/init.js +2 -0
  44. package/dist/core/local-llm/local-collaboration-policy.js +93 -0
  45. package/dist/core/local-llm/local-llm-config.js +15 -0
  46. package/dist/core/pipeline/final-gpt-patch-stage.js +31 -0
  47. package/dist/core/pipeline/final-gpt-review-stage.js +5 -0
  48. package/dist/core/provider/provider-context.js +72 -9
  49. package/dist/core/retention.js +11 -0
  50. package/dist/core/routes.js +21 -1
  51. package/dist/core/safety/mutation-guard.js +2 -0
  52. package/dist/core/team-live.js +7 -1
  53. package/dist/core/update-check.js +215 -25
  54. package/dist/core/verification/verification-worker-pool.js +12 -0
  55. package/dist/core/version.js +1 -1
  56. package/dist/core/zellij/zellij-worker-pane-manager.js +19 -2
  57. package/dist/scripts/agent-ast-aware-work-graph-check.js +1 -1
  58. package/dist/scripts/codex-sdk-team-naruto-agent-pipeline-check.js +2 -1
  59. package/dist/scripts/doctor-fixes-codex-app-fast-ui-check.js +12 -2
  60. package/dist/scripts/gpt-final-arbiter-check.js +63 -0
  61. package/dist/scripts/gpt-final-arbiter-performance-check.js +36 -0
  62. package/dist/scripts/local-collab-gpt-final-availability-check.js +58 -0
  63. package/dist/scripts/local-collab-no-local-only-final-check.js +27 -0
  64. package/dist/scripts/local-collab-policy-check.js +17 -0
  65. package/dist/scripts/mad-sks-app-ui-no-mutation-check.js +92 -0
  66. package/dist/scripts/mad-sks-zellij-default-pane-worker-check.js +37 -0
  67. package/dist/scripts/mad-sks-zellij-launch-check.js +2 -1
  68. package/dist/scripts/provider-context-config-toml-check.js +63 -0
  69. package/dist/scripts/release-gate-existence-audit.js +4 -0
  70. package/dist/scripts/runtime-no-mjs-scripts-check.js +3 -2
  71. package/dist/scripts/zellij-worker-pane-manager-check.js +3 -0
  72. package/dist/scripts/zellij-worker-pane-manager-single-owner-check.js +39 -0
  73. package/package.json +13 -4
  74. package/schemas/local-llm/local-collaboration-policy.schema.json +57 -0
package/README.md CHANGED
@@ -16,7 +16,7 @@ Set up this agent project with Sneakoscope Codex. Use [[mandarange/Sneakoscope-C
16
16
 
17
17
  ## Current Release
18
18
 
19
- SKS **2.0.1** is a Codex App Fast UI preservation patch on top of the 2.0 execution layer. Native workers still run through the Codex SDK Control Plane, UltraRouter still classifies orchestrator/worker tasks, Reliability Shield still hardens long SDK streams, and Zellij worker panes now carry provider/service-tier proof while spawning only when scheduler slot generations need them.
19
+ SKS **2.0.4** is a P0 Codex App Fast UI and MAD Zellij worker-pane closure patch on top of the 2.0 execution layer. `sks --mad` now relies on launch-time Fast/high overrides instead of user-level Codex config rewrites, safe Fast UI repair runs through `sks doctor --fix`, provider badges read env/auth/config.toml consistently, and interactive MAD worker panes attach to the real Zellij session as scheduler slots spawn.
20
20
 
21
21
  What changed:
22
22
 
@@ -285,6 +285,7 @@ sks deps check --yes
285
285
  sks codex-app check
286
286
  sks doctor --fix
287
287
  sks fix-path
288
+ sks update now
288
289
  ```
289
290
 
290
291
  ### Open Codex CLI With Zellij
@@ -356,7 +357,7 @@ sks --mad
356
357
  sks --mad --allow-package-install --allow-service-control --allow-network --yes
357
358
  ```
358
359
 
359
- This syncs existing codex-lb provider auth, creates/uses the `sks-mad-high` high-power 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: it is explicit user authorization to widen approved target-project scopes. Catastrophic database wipe/all-row/project-management safeguards remain active, and the pipeline contract still forbids unrequested fallback implementation code.
360
+ 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: it is explicit user authorization to widen approved target-project scopes. Catastrophic database wipe/all-row/project-management safeguards remain active, and the pipeline contract still forbids unrequested fallback implementation code.
360
361
 
361
362
  Before launching, SKS checks npm for a newer `sneakoscope`; answer `y` to update or `n` to continue. 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.
362
363
 
@@ -498,6 +499,8 @@ Then open Codex App and use prompt commands directly in the chat. Examples:
498
499
 
499
500
  ```text
500
501
  $Team implement the checkout fix and verify it
502
+ $with-local-llm-on
503
+ $with-local-llm-off
501
504
  $DFix change this label and spacing only
502
505
  $QA-LOOP dogfood localhost:3000 and fix safe issues
503
506
  $PPT create an investor deck as HTML/PDF
@@ -508,6 +511,23 @@ $Wiki refresh and validate the context pack
508
511
  $DB inspect this migration for destructive risk
509
512
  ```
510
513
 
514
+ ### Optional Local LLM Workers
515
+
516
+ Local Ollama workers are off by default, so SKS stays GPT-only unless you explicitly enable them. Use the Codex App prompt commands:
517
+
518
+ ```text
519
+ $with-local-llm-on
520
+ $with-local-llm-off
521
+ ```
522
+
523
+ When enabled, the local model can only help with policy-eligible simple code patch-envelope work or read-only collection. GPT/Codex still owns strategy, planning, design, review, verification, safety, and integration. Check or tune the machine-local setting from the terminal:
524
+
525
+ ```sh
526
+ sks with-local-llm status --json
527
+ sks with-local-llm on --model rafw007/qwen36-a3b-claude-coder:q4_K_M
528
+ sks with-local-llm off
529
+ ```
530
+
511
531
  Generated app files include:
512
532
 
513
533
  | Path | Purpose |
@@ -568,7 +588,7 @@ SKS_HERMES=1 sks status --json
568
588
 
569
589
  Use these inside Codex App or another agent prompt. They are prompt commands, not terminal commands.
570
590
 
571
- Common prompts: `$Team`, `$From-Chat-IMG`, `$DFix`, `$Answer`, `$SKS`, `$QA-LOOP`, `$PPT`, `$Computer-Use`/`$CU`, `$Goal`, `$Research`, `$AutoResearch`, `$DB`, `$MAD-SKS`, `$GX`, `$Wiki`, and `$Help`.
591
+ Common prompts: `$Team`, `$From-Chat-IMG`, `$with-local-llm-on`, `$with-local-llm-off`, `$DFix`, `$Answer`, `$SKS`, `$QA-LOOP`, `$PPT`, `$Computer-Use`/`$CU`, `$Goal`, `$Research`, `$AutoResearch`, `$DB`, `$MAD-SKS`, `$GX`, `$Wiki`, and `$Help`.
572
592
 
573
593
  ## Common Workflows
574
594
 
@@ -590,7 +610,7 @@ sks
590
610
  # or: sks --mad
591
611
  ```
592
612
 
593
- Use Codex App routes with `$Team`, `$DFix`, `$QA-LOOP`, `$PPT`, `$Goal`, `$Wiki`, and `$DB`. Team missions write artifacts under `.sneakoscope/missions/`; validate them with `sks validate-artifacts latest`.
613
+ Use Codex App routes with `$Team`, `$with-local-llm-on`/`$with-local-llm-off`, `$DFix`, `$QA-LOOP`, `$PPT`, `$Goal`, `$Wiki`, and `$DB`. Team missions write artifacts under `.sneakoscope/missions/`; validate them with `sks validate-artifacts latest`.
594
614
 
595
615
  Refresh context before risky work:
596
616
 
@@ -630,11 +650,12 @@ If PATH or npm has duplicate global installs, `sks doctor --fix` keeps one globa
630
650
 
631
651
  ```sh
632
652
  sks update-check --json
653
+ sks update now
633
654
  npm ls -g sneakoscope --depth=0
634
655
  sks doctor --fix
635
656
  ```
636
657
 
637
- Update prompts compare npm latest against the effective installed version from source metadata, PATH `sks --version`, and global npm package metadata. If a global update succeeded but an old shim remains earlier on PATH, `sks doctor --fix` can remove duplicate global installs and refresh the managed setup.
658
+ Update prompts compare npm latest against the effective installed version from source metadata, PATH `sks --version`, and global npm package metadata. `sks update now` installs through npm global mode instead of mutating the current project's dependencies. If a global update succeeded but an old shim remains earlier on PATH, `sks doctor --fix` can remove duplicate global installs and refresh the managed setup.
638
659
 
639
660
  ### Zellij is missing
640
661
 
@@ -76,7 +76,7 @@ dependencies = [
76
76
 
77
77
  [[package]]
78
78
  name = "sks-core"
79
- version = "2.0.1"
79
+ version = "2.0.4"
80
80
  dependencies = [
81
81
  "serde_json",
82
82
  ]
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "sks-core"
3
- version = "2.0.1"
3
+ version = "2.0.4"
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 2.0.1"),
7
+ Some("--version") => println!("sks-rs 2.0.4"),
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": "2.0.1",
5
- "source_digest": "fc6581fa4e92892f1de963685540cd8754dd02015459f80ded6c7271837a0197",
6
- "source_file_count": 1852,
7
- "built_at_source_time": 1780513745133
4
+ "package_version": "2.0.4",
5
+ "source_digest": "bd67ddf989e2e0b702453d422e2cf991e8ca397d3d4afc14fdfa06b4610452c9",
6
+ "source_file_count": 1879,
7
+ "built_at_source_time": 1780569940553
8
8
  }
package/dist/bin/sks.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- const FAST_PACKAGE_VERSION = '2.0.1';
2
+ const FAST_PACKAGE_VERSION = '2.0.4';
3
3
  const args = process.argv.slice(2);
4
4
  try {
5
5
  if (args[0] === '--agent' && args[1] === 'worker') {
@@ -1,16 +1,16 @@
1
1
  {
2
2
  "schema": "sks.dist-build.v2",
3
- "version": "2.0.1",
4
- "package_version": "2.0.1",
3
+ "version": "2.0.4",
4
+ "package_version": "2.0.4",
5
5
  "typescript": true,
6
6
  "mjs_runtime_files": 0,
7
- "compiled_file_count": 929,
8
- "compiled_js_count": 929,
7
+ "compiled_file_count": 949,
8
+ "compiled_js_count": 949,
9
9
  "compiled_dts_count": 0,
10
- "source_digest": "fc6581fa4e92892f1de963685540cd8754dd02015459f80ded6c7271837a0197",
11
- "source_file_count": 1852,
12
- "source_files_hash": "92f47f9e5a2283d371d4ee267541e14ddbd9c9101e9db1ab8e46cede781f076b",
13
- "source_list_hash": "92f47f9e5a2283d371d4ee267541e14ddbd9c9101e9db1ab8e46cede781f076b",
10
+ "source_digest": "bd67ddf989e2e0b702453d422e2cf991e8ca397d3d4afc14fdfa06b4610452c9",
11
+ "source_file_count": 1879,
12
+ "source_files_hash": "c43d09b591370ad58937365d6bbfca2be0b60fcf04a43fdbb9a56d9288752814",
13
+ "source_list_hash": "c43d09b591370ad58937365d6bbfca2be0b60fcf04a43fdbb9a56d9288752814",
14
14
  "src_mjs_runtime_files": 0,
15
15
  "dist_stamp_schema": "sks.dist-build-stamp.v1",
16
16
  "files": [
@@ -135,6 +135,7 @@
135
135
  "core/agents/agent-roster.js",
136
136
  "core/agents/agent-runner-codex-exec.js",
137
137
  "core/agents/agent-runner-fake.js",
138
+ "core/agents/agent-runner-ollama.js",
138
139
  "core/agents/agent-runner-process.js",
139
140
  "core/agents/agent-runner-zellij.js",
140
141
  "core/agents/agent-scheduler.js",
@@ -159,6 +160,7 @@
159
160
  "core/agents/native-cli-worker.js",
160
161
  "core/agents/native-worker-backend-router.js",
161
162
  "core/agents/no-subagent-scaling-policy.js",
163
+ "core/agents/ollama-worker-config.js",
162
164
  "core/agents/real-codex-parallel-proof.js",
163
165
  "core/agents/route-collaboration-ledger.js",
164
166
  "core/agents/scout-policy.js",
@@ -210,6 +212,10 @@
210
212
  "core/codex-control/codex-sdk-sandbox-policy.js",
211
213
  "core/codex-control/codex-task-runner.js",
212
214
  "core/codex-control/codex-thread-registry.js",
215
+ "core/codex-control/gpt-final-arbiter.js",
216
+ "core/codex-control/gpt-final-context-compressor.js",
217
+ "core/codex-control/gpt-final-proof-pack.js",
218
+ "core/codex-control/gpt-final-review-schema.js",
213
219
  "core/codex-control/schemas/agent-worker-result.schema.js",
214
220
  "core/codex-exec-output-schema.js",
215
221
  "core/codex-hooks/codex-hook-actual-discovery.js",
@@ -259,6 +265,7 @@
259
265
  "core/commands/harness-command.js",
260
266
  "core/commands/hproof-command.js",
261
267
  "core/commands/image-ux-review-command.js",
268
+ "core/commands/local-model-command.js",
262
269
  "core/commands/mad-sks-command.js",
263
270
  "core/commands/naruto-command.js",
264
271
  "core/commands/paths-command.js",
@@ -354,6 +361,8 @@
354
361
  "core/init.js",
355
362
  "core/json-schema-validator.js",
356
363
  "core/language-preference.js",
364
+ "core/local-llm/local-collaboration-policy.js",
365
+ "core/local-llm/local-llm-config.js",
357
366
  "core/loop-blocker.js",
358
367
  "core/mad-sks/audit-ledger.js",
359
368
  "core/mad-sks/authorization-manifest.js",
@@ -395,6 +404,8 @@
395
404
  "core/pipeline.js",
396
405
  "core/pipeline/active-context.js",
397
406
  "core/pipeline/agent-stage-policy.js",
407
+ "core/pipeline/final-gpt-patch-stage.js",
408
+ "core/pipeline/final-gpt-review-stage.js",
398
409
  "core/pipeline/pipeline-plan-writer.js",
399
410
  "core/pipeline/plan-schema.js",
400
411
  "core/pipeline/prompt-context-answer.js",
@@ -760,6 +771,8 @@
760
771
  "scripts/flagship-proof-graph-v4-check.js",
761
772
  "scripts/git-precommit-fixture-check.js",
762
773
  "scripts/goal-mode-official-default-check.js",
774
+ "scripts/gpt-final-arbiter-check.js",
775
+ "scripts/gpt-final-arbiter-performance-check.js",
763
776
  "scripts/gpt-image-2-real-file-smoke.js",
764
777
  "scripts/gpt-image-2-request-validator-check.js",
765
778
  "scripts/hooks-0.134-context-parity-check.js",
@@ -789,9 +802,13 @@
789
802
  "scripts/lib/real-codex-parallel-gate.js",
790
803
  "scripts/lib/real-codex-parallel-proof-fixture.js",
791
804
  "scripts/lib/valid-png-fixture.js",
805
+ "scripts/local-collab-gpt-final-availability-check.js",
806
+ "scripts/local-collab-no-local-only-final-check.js",
807
+ "scripts/local-collab-policy-check.js",
792
808
  "scripts/loop-blocker-check.js",
793
809
  "scripts/mad-preflight-blocks-unreadable-config-check.js",
794
810
  "scripts/mad-sks-actual-executor-blackbox.js",
811
+ "scripts/mad-sks-app-ui-no-mutation-check.js",
795
812
  "scripts/mad-sks-audit-proof-check.js",
796
813
  "scripts/mad-sks-db-executor-check.js",
797
814
  "scripts/mad-sks-executor-proof-graph-check.js",
@@ -806,6 +823,7 @@
806
823
  "scripts/mad-sks-service-executor-check.js",
807
824
  "scripts/mad-sks-shell-executor-check.js",
808
825
  "scripts/mad-sks-write-guard-check.js",
826
+ "scripts/mad-sks-zellij-default-pane-worker-check.js",
809
827
  "scripts/mad-sks-zellij-launch-check.js",
810
828
  "scripts/mcp-0-134-modernization-check.js",
811
829
  "scripts/mcp-readonly-concurrency-check.js",
@@ -839,6 +857,7 @@
839
857
  "scripts/prepublish-release-check-or-fast.js",
840
858
  "scripts/priority-full-closure-check.js",
841
859
  "scripts/provider-badge-context-check.js",
860
+ "scripts/provider-context-config-toml-check.js",
842
861
  "scripts/python-tools-smoke-check.js",
843
862
  "scripts/qa-actual-route-backfill-check.js",
844
863
  "scripts/qa-backfill-route-blackbox.js",
@@ -943,6 +962,7 @@
943
962
  "scripts/zellij-spawn-on-demand-layout-check.js",
944
963
  "scripts/zellij-ui-design-check.js",
945
964
  "scripts/zellij-worker-pane-manager-check.js",
965
+ "scripts/zellij-worker-pane-manager-single-owner-check.js",
946
966
  "scripts/zellij-worker-pane-spawn-order-check.js",
947
967
  "vendor/openai-codex/latest/hooks/permission-request.command.input.schema.json",
948
968
  "vendor/openai-codex/latest/hooks/permission-request.command.output.schema.json",
@@ -80,6 +80,7 @@ export const COMMANDS = {
80
80
  run: entry('beta', 'Classify and execute a task through the SKS trust kernel', 'dist/core/commands/run-command.js', argsCommand(() => import('../core/commands/run-command.js'), 'runCommand', 'dist/core/commands/run-command.js')),
81
81
  status: entry('stable', 'Show concise active mission and trust status', 'dist/core/commands/status-command.js', argsCommand(() => import('../core/commands/status-command.js'), 'statusCommand', 'dist/core/commands/status-command.js')),
82
82
  root: entry('stable', 'Show active SKS root', 'dist/commands/root.js', directCommand(() => import('../commands/root.js'), 'dist/commands/root.js')),
83
+ update: entry('stable', 'Update the global SKS npm package', 'dist/core/commands/basic-cli.js', subcommand(() => import(basicModule), 'updateCommand', 'dist/core/commands/basic-cli.js', 'check')),
83
84
  'update-check': entry('stable', 'Check npm package freshness', 'dist/core/commands/basic-cli.js', basicArgs('updateCheckCommand')),
84
85
  wizard: entry('stable', 'Open setup wizard help', 'dist/core/commands/basic-cli.js', basicNoArgs('quickstartCommand')),
85
86
  usage: entry('stable', 'Show focused usage topic', 'dist/core/commands/basic-cli.js', basicArgs('usageCommand')),
@@ -117,6 +118,7 @@ export const COMMANDS = {
117
118
  dfix: entry('stable', 'Run DFix diagnose/plan/patch/verify loop', 'dist/core/commands/dfix-command.js', commandArgsCommand(() => import('../core/commands/dfix-command.js'), 'dfixCommand', 'dist/core/commands/dfix-command.js')),
118
119
  team: entry('beta', 'Create and observe Team missions', 'dist/core/commands/team-command.js', argsCommand(() => import('../core/commands/team-command.js'), 'team', 'dist/core/commands/team-command.js')),
119
120
  agent: entry('beta', 'Run native multi-session agent missions', 'dist/core/commands/agent-command.js', argsCommand(() => import('../core/commands/agent-command.js'), 'agentCommand', 'dist/core/commands/agent-command.js')),
121
+ 'with-local-llm': entry('beta', 'Enable or inspect local Ollama worker backend', 'dist/core/commands/local-model-command.js', argsCommand(() => import('../core/commands/local-model-command.js'), 'localModelCommand', 'dist/core/commands/local-model-command.js')),
120
122
  naruto: entry('labs', 'Run $Naruto shadow-clone swarm (up to 100 parallel sessions)', 'dist/core/commands/naruto-command.js', argsCommand(() => import('../core/commands/naruto-command.js'), 'narutoCommand', 'dist/core/commands/naruto-command.js')),
121
123
  'qa-loop': entry('beta', 'Run QA loop missions', 'dist/core/commands/qa-loop-command.js', subcommand(() => import('../core/commands/qa-loop-command.js'), 'qaLoopCommand', 'dist/core/commands/qa-loop-command.js')),
122
124
  research: entry('labs', 'Run research missions', 'dist/core/commands/research-command.js', subcommand(() => import('../core/commands/research-command.js'), 'researchCommand', 'dist/core/commands/research-command.js')),
@@ -96,12 +96,17 @@ export async function run(_command, args = []) {
96
96
  model_provider: null
97
97
  }
98
98
  }));
99
- const codexAppUi = await repairCodexAppFastUi(root, {
100
- apply: flag(args, '--fix') && flag(args, '--repair-codex-app-ui'),
101
- reportPath: `${root}/.sneakoscope/reports/codex-app-fast-ui-repair.json`
99
+ const doctorFix = flag(args, '--fix');
100
+ const explicitCodexAppUiRepair = flag(args, '--repair-codex-app-ui') || flag(args, '--yes');
101
+ const codexAppUiPlan = await repairCodexAppFastUi(root, {
102
+ apply: false,
103
+ reportPath: `${root}/.sneakoscope/reports/codex-app-fast-ui-repair-plan.json`
102
104
  }).catch((err) => ({
103
105
  schema: 'sks.codex-app-fast-ui-repair.v1',
104
106
  ok: false,
107
+ apply: false,
108
+ safe_auto_apply: false,
109
+ requires_confirmation: true,
105
110
  fast_selector: 'manual_action_required',
106
111
  provider_selector: 'ok',
107
112
  host_owned_config: 'diagnostic_failed',
@@ -109,6 +114,27 @@ export async function run(_command, args = []) {
109
114
  actions: [],
110
115
  blockers: [err?.message || String(err)]
111
116
  }));
117
+ const shouldApplyCodexAppUiRepair = doctorFix && (explicitCodexAppUiRepair ||
118
+ codexAppUiPlan.safe_auto_apply === true);
119
+ const codexAppUi = shouldApplyCodexAppUiRepair
120
+ ? await repairCodexAppFastUi(root, {
121
+ apply: true,
122
+ force: explicitCodexAppUiRepair,
123
+ reportPath: `${root}/.sneakoscope/reports/codex-app-fast-ui-repair.json`
124
+ }).catch((err) => ({
125
+ schema: 'sks.codex-app-fast-ui-repair.v1',
126
+ ok: false,
127
+ apply: true,
128
+ safe_auto_apply: false,
129
+ requires_confirmation: true,
130
+ fast_selector: 'manual_action_required',
131
+ provider_selector: 'ok',
132
+ host_owned_config: 'diagnostic_failed',
133
+ next_action: 'Review Codex App UI config manually.',
134
+ actions: [],
135
+ blockers: [err?.message || String(err)]
136
+ }))
137
+ : codexAppUiPlan;
112
138
  const zellij = await checkZellijCapability({ root, require: process.env.SKS_REQUIRE_ZELLIJ === '1' });
113
139
  const permissionProfiles = await inventoryCodexPermissionProfiles(root, { writeReport: true });
114
140
  const globalSksInstallCleanup = flag(args, '--fix') && !flag(args, '--local-only')
@@ -12,7 +12,10 @@ export function parseAgentCommandArgs(command, args = []) {
12
12
  const minimumWorkItems = Number(readOption(args, '--minimum-work-items', targetActiveSlots));
13
13
  const maxQueueExpansion = Number(readOption(args, '--max-queue-expansion', 10));
14
14
  const concurrency = Number(readOption(args, '--concurrency', Math.min(agents, 5)));
15
- const backend = String(readOption(args, '--backend', hasFlag(args, '--mock') ? 'fake' : 'codex-sdk'));
15
+ const useOllama = hasFlag(args, '--ollama') || hasFlag(args, '--local-model');
16
+ const noOllama = hasFlag(args, '--no-ollama') || hasFlag(args, '--no-local-model');
17
+ const backendExplicit = hasOption(args, '--backend');
18
+ const backend = String(readOption(args, '--backend', hasFlag(args, '--mock') ? 'fake' : useOllama && !noOllama ? 'ollama' : 'codex-sdk'));
16
19
  const route = String(readOption(args, '--route', '$Agent'));
17
20
  const mock = hasFlag(args, '--mock') || backend === 'fake';
18
21
  const real = hasFlag(args, '--real');
@@ -26,6 +29,10 @@ export function parseAgentCommandArgs(command, args = []) {
26
29
  const serviceTier = normalizeServiceTier(explicitServiceTier, null) || undefined;
27
30
  const fastMode = hasFlag(args, '--no-fast') || serviceTier === 'standard' ? false : hasFlag(args, '--fast') ? true : undefined;
28
31
  const noFast = hasFlag(args, '--no-fast');
32
+ const ollamaModel = String(readOption(args, '--ollama-model', readOption(args, '--local-model-model', '')) || '') || null;
33
+ const ollamaBaseUrl = String(readOption(args, '--ollama-base-url', readOption(args, '--local-model-base-url', '')) || '') || null;
34
+ const zellijSessionName = String(readOption(args, '--zellij-session-name', '') || '') || null;
35
+ const zellijPaneWorker = hasFlag(args, '--no-zellij-pane-worker') ? false : hasFlag(args, '--zellij-pane-worker') ? true : undefined;
29
36
  const apply = hasFlag(args, '--apply');
30
37
  const dryRun = hasFlag(args, '--dry-run') || hasFlag(args, '--dryrun');
31
38
  const drain = hasFlag(args, '--drain');
@@ -33,7 +40,7 @@ export function parseAgentCommandArgs(command, args = []) {
33
40
  const graceMs = Number(readOption(args, '--grace-ms', 750));
34
41
  const killEscalation = hasFlag(args, '--kill-escalation') || !hasFlag(args, '--no-kill-escalation');
35
42
  const codexApp = hasFlag(args, '--codex-app');
36
- const positionals = positionalArgs(rest, new Set(['--agents', '--target-active-slots', '--work-items', '--minimum-work-items', '--max-queue-expansion', '--concurrency', '--backend', '--route', '--mission', '--mission-id', '--agent', '--lane', '--stale-ms', '--grace-ms', '--profile', '--write-mode', '--max-write-agents', '--patch-entry-id', '--patch-entry', '--service-tier', '--intake', '--agent-root', '--artifact-dir', '--result-path', '--heartbeat-path', '--patch-envelope-path']));
43
+ const positionals = positionalArgs(rest, new Set(['--agents', '--target-active-slots', '--work-items', '--minimum-work-items', '--max-queue-expansion', '--concurrency', '--backend', '--route', '--mission', '--mission-id', '--agent', '--lane', '--stale-ms', '--grace-ms', '--profile', '--write-mode', '--max-write-agents', '--patch-entry-id', '--patch-entry', '--service-tier', '--zellij-session-name', '--intake', '--agent-root', '--artifact-dir', '--result-path', '--heartbeat-path', '--patch-envelope-path', '--ollama-model', '--local-model-model', '--ollama-base-url', '--local-model-base-url']));
37
44
  const missionDefault = action === 'run' || action === 'spawn' || action === 'plan' ? '' : 'latest';
38
45
  const positionalMission = action === 'run' || action === 'spawn' || action === 'plan' ? '' : (positionals[0] || '');
39
46
  const missionId = String(readOption(args, '--mission', readOption(args, '--mission-id', positionalMission || missionDefault)));
@@ -41,7 +48,7 @@ export function parseAgentCommandArgs(command, args = []) {
41
48
  const patchEntryId = String(readOption(args, '--patch-entry-id', readOption(args, '--patch-entry', '')));
42
49
  const promptPositionals = positionalMission ? positionals.slice(1) : positionals;
43
50
  const prompt = promptPositionals.join(' ').trim() || 'Native agent run';
44
- return { command, action, prompt, route, agents, targetActiveSlots, desiredWorkItemCount, minimumWorkItems, maxQueueExpansion, concurrency, backend, mock, real, readonly, profile, writeMode, applyPatches, dryRunPatches, maxWriteAgents, fastMode, serviceTier, noFast, apply, dryRun, drain, staleMs, graceMs, killEscalation, json, missionId, lane, codexApp, patchEntryId };
51
+ return { command, action, prompt, route, agents, targetActiveSlots, desiredWorkItemCount, minimumWorkItems, maxQueueExpansion, concurrency, backend, backendExplicit, mock, real, readonly, profile, writeMode, applyPatches, dryRunPatches, maxWriteAgents, fastMode, serviceTier, noFast, ollamaEnabled: useOllama && !noOllama, noOllama, ollamaModel, ollamaBaseUrl, zellijSessionName, zellijPaneWorker, apply, dryRun, drain, staleMs, graceMs, killEscalation, json, missionId, lane, codexApp, patchEntryId };
45
52
  }
46
53
  function hasFlag(args, flag) {
47
54
  return args.includes(flag);
@@ -53,6 +60,9 @@ function readOption(args, name, fallback) {
53
60
  const prefixed = args.find((arg) => String(arg).startsWith(name + '='));
54
61
  return prefixed ? prefixed.slice(name.length + 1) : fallback;
55
62
  }
63
+ function hasOption(args, name) {
64
+ return args.includes(name) || args.some((arg) => String(arg).startsWith(name + '='));
65
+ }
56
66
  function positionalArgs(args, valueFlags) {
57
67
  const out = [];
58
68
  for (let i = 0; i < args.length; i += 1) {
@@ -20,6 +20,8 @@ import { AgentPatchTransactionJournal } from './agent-patch-transaction-journal.
20
20
  import { normalizeAgentPatchEnvelope } from './agent-patch-schema.js';
21
21
  import { runFakeAgent } from './agent-runner-fake.js';
22
22
  import { runProcessAgent } from './agent-runner-process.js';
23
+ import { classifyOllamaWorkerSlice, runOllamaAgent } from './agent-runner-ollama.js';
24
+ import { resolveOllamaWorkerConfig } from './ollama-worker-config.js';
23
25
  import { validateAgentWorkerResult } from './agent-worker-pipeline.js';
24
26
  import { writeAgentCleanupReport } from './agent-cleanup.js';
25
27
  import { writeAgentTrustReport } from './agent-trust-report.js';
@@ -47,6 +49,9 @@ import { writeNativeCliSessionProof } from './native-cli-session-proof.js';
47
49
  import { writeNoSubagentScalingPolicy } from './no-subagent-scaling-policy.js';
48
50
  import { runCodexTask } from '../codex-control/codex-control-plane.js';
49
51
  import { CODEX_AGENT_WORKER_RESULT_SCHEMA_ID, codexAgentWorkerResultSchema } from '../codex-control/schemas/agent-worker-result.schema.js';
52
+ import { resolveLocalCollaborationPolicy, localCollaborationParticipated } from '../local-llm/local-collaboration-policy.js';
53
+ import { runFinalGptReviewStage } from '../pipeline/final-gpt-review-stage.js';
54
+ import { selectFinalGptPatchSource } from '../pipeline/final-gpt-patch-stage.js';
50
55
  export async function runNativeAgentOrchestrator(opts = {}) {
51
56
  const root = path.resolve(opts.root || process.cwd());
52
57
  const prompt = String(opts.prompt || 'Native agent run');
@@ -251,6 +256,8 @@ export async function runNativeAgentOrchestrator(opts = {}) {
251
256
  requestedAgents: Number(opts.agents || roster.agent_count || targetActiveSlots),
252
257
  targetActiveSlots,
253
258
  backend,
259
+ backendExplicit: opts.backendExplicit === true,
260
+ noOllama: opts.noOllama === true,
254
261
  route,
255
262
  fastModePolicy
256
263
  });
@@ -365,15 +372,51 @@ export async function runNativeAgentOrchestrator(opts = {}) {
365
372
  });
366
373
  const noSubagentScalingPolicy = await writeNoSubagentScalingPolicy(ledgerRoot, { nativeProof: nativeCliSessionProof });
367
374
  const fastModePropagation = await writeFastModePropagationProof(ledgerRoot, { policy: fastModePolicy, backend, results });
375
+ const localCollaborationPolicy = resolveLocalCollaborationPolicy();
376
+ await writeJsonAtomic(path.join(ledgerRoot, 'local-collaboration-policy.json'), localCollaborationPolicy);
377
+ const localParticipated = localCollaborationParticipated(results);
378
+ const candidatePatchEnvelopes = results.flatMap((result) => Array.isArray(result.patch_envelopes) ? result.patch_envelopes : []);
379
+ const gptFinalArbiter = localParticipated
380
+ ? await runFinalGptReviewStage({
381
+ schema: 'sks.gpt-final-arbiter-input.v1',
382
+ route,
383
+ mission_id: missionId,
384
+ local_mode: localCollaborationPolicy.mode,
385
+ local_outputs: results.map((result) => ({
386
+ worker_id: result.agent_id,
387
+ backend: result.backend_router_report?.selected_backend || result.backend || backend,
388
+ status: result.status,
389
+ summary: result.summary,
390
+ patch_envelopes: result.patch_envelopes || [],
391
+ proof: result.verification?.status || '',
392
+ blockers: result.blockers || [],
393
+ changed_files: result.changed_files || []
394
+ })),
395
+ candidate_diff: '',
396
+ candidate_patch_envelopes: candidatePatchEnvelopes,
397
+ verification_results: results.map((result) => result.verification || { status: result.status || 'unknown' }),
398
+ side_effect_report: { schema: 'sks.agent-side-effect-summary.v1', ok: true, route, mutation_owner: 'parent_agent_orchestrator' },
399
+ mutation_ledger: { parallel_write_policy: parallelWritePolicy, result_count: results.length },
400
+ rollback_plan: { verification_rollback_dag: strategyCompiled.verification_rollback_dag || null }
401
+ }, { cwd: root, mutationLedgerRoot: path.join(ledgerRoot, 'gpt-final-arbiter') })
402
+ : null;
403
+ const finalGptPatchStage = localParticipated
404
+ ? selectFinalGptPatchSource(gptFinalArbiter, candidatePatchEnvelopes)
405
+ : null;
406
+ const resultsForPatchSwarm = localParticipated && finalGptPatchStage?.ok === true && gptFinalArbiter?.result?.status === 'modified'
407
+ ? withFinalGptPatchEnvelopes(results, finalGptPatchStage.patch_envelopes)
408
+ : results;
368
409
  const patchSwarm = await runAgentPatchSwarmRuntime(root, ledgerRoot, {
369
410
  missionId,
370
411
  route,
371
412
  routeCommand,
372
413
  writeCapable,
373
- results,
414
+ results: resultsForPatchSwarm,
374
415
  parallelWritePolicy,
375
416
  verificationRollbackDag: strategyCompiled.verification_rollback_dag,
376
- dryRun: opts.dryRunPatches === true || opts.applyPatches !== true
417
+ dryRun: opts.dryRunPatches === true || opts.applyPatches !== true || (localParticipated && gptFinalArbiter?.ok !== true),
418
+ gptFinalArbiter,
419
+ finalGptPatchStage
377
420
  });
378
421
  const stale = await detectStaleAgentSessions(ledgerRoot);
379
422
  if (!stale.ok)
@@ -399,6 +442,8 @@ export async function runNativeAgentOrchestrator(opts = {}) {
399
442
  ...(nativeCliSessionProof.ok ? [] : nativeCliSessionProof.blockers),
400
443
  ...(noSubagentScalingPolicy.ok ? [] : noSubagentScalingPolicy.blockers),
401
444
  ...(fastModePropagation.ok ? [] : fastModePropagation.blockers),
445
+ ...(localParticipated && gptFinalArbiter?.ok !== true ? gptFinalArbiter?.blockers || ['gpt_final_arbiter_not_ok'] : []),
446
+ ...(localParticipated && finalGptPatchStage?.ok === false ? finalGptPatchStage.blockers || ['final_gpt_patch_stage_not_ok'] : []),
402
447
  ...(patchSwarm.ok ? [] : patchSwarm.blockers),
403
448
  ...(janitor.ok ? [] : janitor.blockers)
404
449
  ];
@@ -434,7 +479,10 @@ export async function runNativeAgentOrchestrator(opts = {}) {
434
479
  fastModePolicy,
435
480
  fastModePropagation,
436
481
  triwikiContext,
437
- selectedCoreSkill
482
+ selectedCoreSkill,
483
+ localCollaborationPolicy,
484
+ gptFinalArbiter,
485
+ finalGptPatchStage
438
486
  });
439
487
  await writeAgentCodexCockpitArtifacts(dir, { missionId, projectHash: namespace.root_hash });
440
488
  await setCurrent(root, { mission_id: missionId, mode: 'AGENT', phase: proof.ok ? 'AGENT_NATIVE_KERNEL_DONE' : 'AGENT_NATIVE_KERNEL_BLOCKED', native_agent_backend: backend, updated_at: nowIso() });
@@ -474,10 +522,30 @@ export async function runNativeAgentOrchestrator(opts = {}) {
474
522
  no_subagent_scaling_policy: noSubagentScalingPolicy,
475
523
  fast_mode_policy: fastModePolicy,
476
524
  fast_mode_propagation: fastModePropagation,
525
+ local_collaboration_policy: localCollaborationPolicy,
526
+ gpt_final_arbiter: gptFinalArbiter,
527
+ final_gpt_patch_stage: finalGptPatchStage,
477
528
  patch_swarm: patchSwarm,
478
529
  proof
479
530
  };
480
531
  }
532
+ function withFinalGptPatchEnvelopes(results, patchEnvelopes = []) {
533
+ const byAgent = new Map();
534
+ for (const envelope of patchEnvelopes) {
535
+ const agentId = String(envelope?.agent_id || 'gpt-final-arbiter');
536
+ byAgent.set(agentId, [...(byAgent.get(agentId) || []), envelope]);
537
+ }
538
+ let assigned = false;
539
+ const next = results.map((result) => {
540
+ const envelopes = byAgent.get(String(result.agent_id || '')) || [];
541
+ if (envelopes.length)
542
+ assigned = true;
543
+ return { ...result, patch_envelopes: envelopes };
544
+ });
545
+ if (!assigned && patchEnvelopes.length && next[0])
546
+ next[0] = { ...next[0], patch_envelopes: patchEnvelopes };
547
+ return next;
548
+ }
481
549
  async function legacyCodexExecBlockedRun(input) {
482
550
  const blockers = ['legacy_codex_exec_runtime_removed'];
483
551
  const ledgerRoot = path.join(input.dir, 'agents');
@@ -702,7 +770,9 @@ async function runAgentPatchSwarmRuntime(root, ledgerRoot, input) {
702
770
  verification: verificationResults.results.map((result) => result.status),
703
771
  parallelWritePolicy: input.parallelWritePolicy,
704
772
  conflictRebase,
705
- verificationRollbackDag: input.verificationRollbackDag
773
+ verificationRollbackDag: input.verificationRollbackDag,
774
+ gptFinalArbiter: input.gptFinalArbiter,
775
+ finalGptPatchStage: input.finalGptPatchStage
706
776
  };
707
777
  const initialProof = buildAgentPatchProof(proofInput);
708
778
  await writeJsonAtomic(path.join(ledgerRoot, 'agent-patch-proof.json'), initialProof);
@@ -867,8 +937,11 @@ function buildProvidedAgentRoster(input, opts = {}) {
867
937
  };
868
938
  }
869
939
  async function runAgentByBackend(backend, agent, slice, opts) {
940
+ backend = await maybeSelectOllamaBackend(backend, agent, slice, opts);
870
941
  if (backend === 'process')
871
942
  return runProcessAgent(agent, slice, opts);
943
+ if (backend === 'ollama')
944
+ return runOllamaAgent(agent, slice, opts);
872
945
  if (backend === 'codex-sdk' || backend === 'zellij') {
873
946
  const ledgerRoot = path.resolve(opts.agentRoot || opts.cwd || process.cwd());
874
947
  const workerDir = path.join(ledgerRoot, 'codex-sdk-workers', String(agent.session_id || agent.id || 'agent'), String(slice?.id || 'slice'));
@@ -932,6 +1005,21 @@ async function runAgentByBackend(backend, agent, slice, opts) {
932
1005
  }
933
1006
  return runFakeAgent(agent, slice, opts);
934
1007
  }
1008
+ async function maybeSelectOllamaBackend(backend, agent, slice, opts) {
1009
+ if (backend !== 'codex-sdk')
1010
+ return backend;
1011
+ if (opts.backendExplicit === true || opts.backend_explicit === true || opts.noOllama === true || opts.no_ollama === true)
1012
+ return backend;
1013
+ const config = await resolveOllamaWorkerConfig({
1014
+ ollamaEnabled: opts.ollamaEnabled === true || opts.ollama_enabled === true,
1015
+ model: opts.ollamaModel || opts.ollama_model || null,
1016
+ baseUrl: opts.ollamaBaseUrl || opts.ollama_base_url || null
1017
+ }).catch(() => null);
1018
+ if (!config?.ok || config.enabled !== true)
1019
+ return backend;
1020
+ const policy = classifyOllamaWorkerSlice(slice, { route: opts.route, agent });
1021
+ return policy.ok ? 'ollama' : backend;
1022
+ }
935
1023
  function buildDirectSdkWorkerPrompt(slice) {
936
1024
  const write = sdkWritePaths(slice, {});
937
1025
  return [
@@ -33,7 +33,7 @@ export const AGENT_RESULT_RUNTIME_SCHEMA = {
33
33
  persona_id: { type: 'string' },
34
34
  task_slice_id: { type: 'string' },
35
35
  status: { enum: ['done', 'blocked', 'failed'] },
36
- backend: { enum: ['fake', 'process', 'codex-sdk', 'zellij'] },
36
+ backend: { enum: ['fake', 'process', 'codex-sdk', 'zellij', 'ollama'] },
37
37
  summary: { type: 'string' },
38
38
  findings: { type: 'array', items: { type: 'string' } },
39
39
  proposed_changes: { type: 'array', items: { type: 'string' } },
@@ -91,6 +91,7 @@ export const AGENT_RESULT_RUNTIME_SCHEMA = {
91
91
  worker_process_id: { type: 'number' },
92
92
  backend_child_process_id: { type: 'number' },
93
93
  backend_sdk_thread_id: { type: 'string' },
94
+ backend_ollama_request_id: { type: 'string' },
94
95
  fast_mode: { type: 'boolean' },
95
96
  service_tier: { enum: ['fast', 'standard'] },
96
97
  lease_id: { type: 'string' },
@@ -50,6 +50,8 @@ export function buildAgentPatchProof(input = {}) {
50
50
  ...journalBlockers,
51
51
  ...rebaseBlockers,
52
52
  ...parallelGroupBlockers,
53
+ ...(input.gptFinalArbiter && input.gptFinalArbiter.ok !== true ? input.gptFinalArbiter.blockers || ['gpt_final_arbiter_not_ok'] : []),
54
+ ...(input.finalGptPatchStage && input.finalGptPatchStage.ok !== true ? input.finalGptPatchStage.blockers || ['final_gpt_patch_stage_not_ok'] : []),
53
55
  ...applyResults.flatMap((applyResult) => applyResult.ok ? [] : (applyResult.violations || ['patch_apply_failed']))
54
56
  ].map(String);
55
57
  const changedFilesByAgent = changedFilesGroupedByAgent(applyResults);
@@ -71,6 +73,9 @@ export function buildAgentPatchProof(input = {}) {
71
73
  verification_node_coverage: strictWiring.verification_node_coverage,
72
74
  rollback_node_coverage: strictWiring.rollback_node_coverage,
73
75
  parallel_patch_apply_verified: hasParallelGroup,
76
+ gpt_final_arbiter: input.gptFinalArbiter ? 'gpt-final-arbiter/gpt-final-arbiter.json' : null,
77
+ gpt_final_status: input.gptFinalArbiter?.result?.status || null,
78
+ final_patch_source: input.finalGptPatchStage?.final_patch_source || null,
74
79
  patch_conflict_count: Number(input.merge?.conflicts?.length || 0),
75
80
  serial_bottleneck_count: Number(input.merge?.serial_conflicts?.length || 0),
76
81
  changed_files_by_agent: changedFilesByAgent,
@@ -17,6 +17,7 @@ export function normalizeAgentPatchEnvelope(input) {
17
17
  ...(hasFiniteNumber(input?.worker_process_id) ? { worker_process_id: Number(input.worker_process_id) } : {}),
18
18
  ...(hasFiniteNumber(input?.backend_child_process_id) ? { backend_child_process_id: Number(input.backend_child_process_id) } : {}),
19
19
  ...(input?.backend_sdk_thread_id ? { backend_sdk_thread_id: String(input.backend_sdk_thread_id) } : {}),
20
+ ...(input?.backend_ollama_request_id ? { backend_ollama_request_id: String(input.backend_ollama_request_id) } : {}),
20
21
  ...(input?.fast_mode === undefined ? {} : { fast_mode: Boolean(input.fast_mode) }),
21
22
  ...(input?.service_tier === 'fast' || input?.service_tier === 'standard' ? { service_tier: input.service_tier } : {}),
22
23
  ...(input?.lease_id ? { lease_id: String(input.lease_id) } : {}),
@@ -50,7 +51,7 @@ export function validateAgentPatchEnvelope(envelope) {
50
51
  violations.push('operations_missing');
51
52
  if (envelope.source && !['fixture', 'model_authored', 'process_generated', 'zellij_generated'].includes(envelope.source))
52
53
  violations.push('source_invalid');
53
- if (envelope.source === 'model_authored' && !hasFiniteNumber(envelope.backend_child_process_id) && !envelope.backend_sdk_thread_id)
54
+ if (envelope.source === 'model_authored' && !hasFiniteNumber(envelope.backend_child_process_id) && !envelope.backend_sdk_thread_id && !envelope.backend_ollama_request_id)
54
55
  violations.push('model_authored_backend_proof_missing');
55
56
  if (envelope.source === 'fixture' && envelope.backend_child_process_id !== undefined)
56
57
  violations.push('fixture_backend_child_process_id_present');