sneakoscope 10.3.3 → 10.3.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 (70) hide show
  1. package/README.md +22 -12
  2. package/config/skills-hash-ledger.v1.json +4 -1
  3. package/crates/sks-core/Cargo.lock +1 -1
  4. package/crates/sks-core/Cargo.toml +1 -1
  5. package/dist/commands/doctor.js +2 -2
  6. package/dist/config/skills-manifest.json +9 -6
  7. package/dist/core/agents/agent-effort-policy.js +15 -13
  8. package/dist/core/agents/native-worker-backend-router.js +13 -8
  9. package/dist/core/codex-lb/desktop-bridge-migration/retired-runtime-cleanup.js +1 -0
  10. package/dist/core/codex-native/core-skill-manifest.js +4 -4
  11. package/dist/core/decisions/cli.js +9 -2
  12. package/dist/core/decisions/config.js +7 -0
  13. package/dist/core/decisions/integration.js +54 -12
  14. package/dist/core/decisions/policy.js +27 -4
  15. package/dist/core/decisions/questions.js +39 -9
  16. package/dist/core/decisions/routing.js +15 -10
  17. package/dist/core/decisions/types.js +11 -25
  18. package/dist/core/doctor/current-project-guidance.js +38 -1
  19. package/dist/core/doctor/skill-legacy-surface.js +2 -1
  20. package/dist/core/hooks-runtime/hook-context.js +1 -1
  21. package/dist/core/hooks-runtime/jev-spawn-routing.js +21 -2
  22. package/dist/core/hooks-runtime/managed-guidance-preflight.js +30 -0
  23. package/dist/core/hooks-runtime/official-subagent-lifecycle.js +3 -3
  24. package/dist/core/hooks-runtime/parent-orchestration-gate.js +372 -0
  25. package/dist/core/hooks-runtime/subagent-context.js +13 -3
  26. package/dist/core/hooks-runtime/subagent-spawn-policy.js +5 -10
  27. package/dist/core/hooks-runtime.js +46 -8
  28. package/dist/core/init/skills/inventory.js +5 -1
  29. package/dist/core/init/skills.js +3 -3
  30. package/dist/core/init.js +36 -4
  31. package/dist/core/managed-assets/managed-assets-manifest.js +17 -17
  32. package/dist/core/pipeline-internals/runtime-core.js +2 -2
  33. package/dist/core/provider/model-router.js +8 -5
  34. package/dist/core/recallpulse/policy.js +2 -2
  35. package/dist/core/recallpulse.js +3 -3
  36. package/dist/core/release/gate-affected-globs.js +0 -1
  37. package/dist/core/research/mock-result.js +2 -2
  38. package/dist/core/research/research-adversarial-review.js +7 -7
  39. package/dist/core/research/research-claim-synthesizer.js +3 -3
  40. package/dist/core/research/research-falsification-runner.js +3 -3
  41. package/dist/core/research/research-super-search.js +5 -2
  42. package/dist/core/research/research-synthesis-writer.js +2 -2
  43. package/dist/core/research.js +5 -5
  44. package/dist/core/routes/dollar-manifest-lite.js +1 -1
  45. package/dist/core/routes.js +10 -10
  46. package/dist/core/runtime/task-profile.js +9 -5
  47. package/dist/core/subagents/model-policy.js +27 -45
  48. package/dist/core/subagents/model-tiers.js +153 -0
  49. package/dist/core/subagents/naruto-command-args.js +3 -3
  50. package/dist/core/subagents/naruto-help-contract.js +18 -13
  51. package/dist/core/subagents/naruto-host-credentials.js +8 -13
  52. package/dist/core/subagents/official-subagent-config.js +11 -7
  53. package/dist/core/subagents/official-subagent-preparation.js +12 -8
  54. package/dist/core/subagents/official-subagent-prompt.js +33 -33
  55. package/dist/core/subagents/official-subagent-runner.js +3 -3
  56. package/dist/core/subagents/role-model-preferences.js +26 -21
  57. package/dist/core/update/managed-permission-repair.js +462 -0
  58. package/dist/core/update/update-migration-state/retired-local-decision.js +32 -2
  59. package/dist/core/update/update-migration-state/simple-stages.js +25 -1
  60. package/dist/core/update/update-migration-state.js +63 -1
  61. package/dist/core/update-check.js +35 -8
  62. package/dist/core/version.js +1 -1
  63. package/dist/scripts/codex-native-agent-role-content-check.js +10 -5
  64. package/dist/scripts/codex-native-gate-lib.js +3 -1
  65. package/dist/scripts/current-surface-update-e2e-check.js +1 -0
  66. package/dist/scripts/mutation-callsite-coverage-check.js +2 -2
  67. package/dist/scripts/release-affected-selector-check.js +1 -2
  68. package/dist/scripts/typed-routing-gate-lib.js +3 -1
  69. package/package.json +1 -1
  70. package/release-gates.v2.json +2 -6
package/README.md CHANGED
@@ -16,7 +16,7 @@
16
16
  Sneakoscope Codex (`sks`) is an open-source trust layer for Codex CLI and ChatGPT Desktop. It coordinates bounded AI coding agents, records machine-verifiable evidence, preserves project memory, and blocks release claims that are not supported by current tests or artifacts. Search visibility outcomes are measured separately; SKS does not promise rankings or traffic.
17
17
  <!-- END SKS SEARCH VISIBILITY MARKETING -->
18
18
 
19
- Current package: **SKS 10.3.3**. Install the latest stable release from npm.
19
+ Current package: **SKS 10.3.4**. Install the latest stable release from npm.
20
20
 
21
21
  [Quick start](#install-in-one-command) · [Commands](#everyday-commands) · [SKS Center](#sks-center-macos) · [Documentation](#documentation) · [Changelog](CHANGELOG.md)
22
22
 
@@ -39,7 +39,7 @@ sks bootstrap --yes
39
39
 
40
40
  | Capability | What you get |
41
41
  | --- | --- |
42
- | Focused execution | Small tasks stay lightweight; independent work can use official Codex subagents with parent-owned integration. |
42
+ | Focused execution | Answers and tiny edits stay lightweight; implementation work runs through official Codex subagents while the parent orchestrates and integrates. |
43
43
  | Project context | TriWiki indexes repository code and supplies bounded context that can be checked against source. |
44
44
  | Verification | Tests, diagnostics, and release evidence support completion claims. Security and data-integrity checks stay in place. |
45
45
  | Native controls | SKS Center brings connections, updates, MCP servers, and diagnostics together on macOS. |
@@ -138,16 +138,26 @@ configuration, transport checks, and recovery commands.
138
138
 
139
139
  ## Naruto workflow
140
140
 
141
- SKS enforces GPT-6 Astra for every managed child and varies effort by task.
142
- The parent owns decomposition, integration,
143
- and final verification; children receive bounded tasks and do not spawn children.
144
-
145
- | Work | Managed model | Effort |
146
- | --- | --- | --- |
147
- | Tiny mechanical tasks | GPT-6 Astra | low |
148
- | Exploration, large-context reads, and direct tool operation | GPT-6 Astra | medium |
149
- | Implementation | GPT-6 Astra | high |
150
- | Review, debugging, and focused judgment | GPT-6 Astra | max |
141
+ The parent orchestrates: it decomposes the task, spawns a child for each
142
+ disjoint slice, waits, and owns integration and final verification. It does not
143
+ implement slices itself. The SKS PreToolUse hook denies parent source edits
144
+ until the first child starts and while children are still running. Children
145
+ receive bounded tasks and do not spawn children.
146
+
147
+ No model family is pinned. Every child runs the newest model of the tier its
148
+ work needs, read from the Codex models cache, so a new model family is used as
149
+ soon as Codex lists it:
150
+
151
+ | Work | Tier | Effort | Today |
152
+ | --- | --- | --- | --- |
153
+ | Tiny mechanical tasks | fast | low | `gpt-6-luna` |
154
+ | Instructed implementation | balanced | low | `gpt-6-sol` |
155
+ | Exploration, large-context reads, and direct tool operation | context | medium | `gpt-6-sol` |
156
+ | Planning, review, debugging, and focused judgment | deep | max | `gpt-6-astra` |
157
+
158
+ With Jev mode on, Jev picks the tier for every spawn, every gated parent edit,
159
+ the plan, and the context, so the parent spends no time on those choices. A
160
+ stored role-model preference on a current model wins in both modes.
151
161
 
152
162
  An active Codex task keeps the user's selected main model, effort, and service
153
163
  tier. Codex native `/goal` remains the persisted goal owner. Parallelism depends
@@ -4,6 +4,7 @@
4
4
  {
5
5
  "canonical_name": "sks",
6
6
  "trusted_sha256": [
7
+ "8aa34586ba331f0447668c2ce863fe6c9ec4f881828ebed26ca5ecc754d66840",
7
8
  "d0288d27c8bc1c89decb258c6805ed4286cd0d65371ef0eec453be233b39a2a1",
8
9
  "905fc5472d36f0780b56f0e09404f42ea20293b1a4615d2050b7db3043231dbd",
9
10
  "6d90142f3617a8ae92d9af8cb2fc18ab58a7d0070868504e1fe89e0effc305a5",
@@ -324,6 +325,7 @@
324
325
  {
325
326
  "canonical_name": "sks-naruto",
326
327
  "trusted_sha256": [
328
+ "b2eb207b2773ab1995ee6e113ba6f3b64de43389e76aef4a0f06d4845ff86223",
327
329
  "65e19a5d16f486b13541048c239e828f8039bfc6e38bafd7949d9c936fe4b5b2",
328
330
  "da43debf97f0b4a1583d8202a7306aca97ac97865accf9bca8f41d75c84a1cb9",
329
331
  "b981bea4183376c6c0204941ad6c2e78782e2cdea4f8d25cc85630c609faaf95",
@@ -331,7 +333,6 @@
331
333
  "a1219f6dbad9c8c185953125ef0b8dccf081aa01efb8e8acaa543c6214ac049c",
332
334
  "d0d5968e78ac25e1d3e588735de1ddbcf149cfb3165edebca5dec1ed7c7a5c46",
333
335
  "34222c72e4e1ead3584907f4d905444b7de820283ee7348018f110d5e6b04461",
334
- "f8e808622e222b4ad7dcb2907e5f06a890db83139ae443ec6c8c33781dfa3e37",
335
336
  "49b5e3dcc1b537b49c2cf09e5b875cb16acb05c622b22798e265e64dacd8ac90"
336
337
  ]
337
338
  },
@@ -346,6 +347,7 @@
346
347
  {
347
348
  "canonical_name": "sks-pipeline-runner",
348
349
  "trusted_sha256": [
350
+ "6f38d3387e4ab020291afe7f74713b135543e52707f5bfbffa6b3584542f45a5",
349
351
  "7749638f1e8393f3beb1f7b0c37195dd0463abdb6fd7da5d31b3d816ee91fa37",
350
352
  "fd06dfb27be41e5ffec3e3ec8c5362289f5919cf2afa9da5ab6b97e7baa4a0ac",
351
353
  "66e105773b8e042eec5d5679b97b7cb305cad8d45671034907fbfa728fc4d68a",
@@ -373,6 +375,7 @@
373
375
  {
374
376
  "canonical_name": "sks-prompt-pipeline",
375
377
  "trusted_sha256": [
378
+ "f82a103e7962e416961add515d7b3e65df89848dec1fbb9319bd49d7f1dac982",
376
379
  "93fd938dccd65d9b4d1dd140b60e1baedc627e7fcc54f9afd95eb2e44efd1143",
377
380
  "033bb114ec1fac3f8c312f698b1861ed4c2ce6f65c06f4f1a872c4b101b74b92",
378
381
  "e7c8ef56d38fdc4879c983f7d6706f26fc1cf0a60c508836bd628e815700bcc3",
@@ -259,7 +259,7 @@ dependencies = [
259
259
 
260
260
  [[package]]
261
261
  name = "sks-core"
262
- version = "10.3.3"
262
+ version = "10.3.4"
263
263
  dependencies = [
264
264
  "globset",
265
265
  "grep-matcher",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "sks-core"
3
- version = "10.3.3"
3
+ version = "10.3.4"
4
4
  edition = "2021"
5
5
 
6
6
  [dependencies]
@@ -127,7 +127,8 @@ export async function run(_command, args = [], deps = {}) {
127
127
  const doctorFix = flag(args, '--fix');
128
128
  const homeDir = path.resolve(deps.home || process.env.HOME || os.homedir());
129
129
  const globalOnly = doctorGlobalOnlySelection({ args, doctorFix, root, home: homeDir }).global_only;
130
- if (doctorFix) {
130
+ const doctorProfile = doctorProfileFromArgs(args, doctorFix);
131
+ if (doctorFix && doctorProfile !== 'migration') {
131
132
  const conflictScan = await scanHarnessConflicts(root);
132
133
  if (conflictScan.hard_block) {
133
134
  const blocked = {
@@ -151,7 +152,6 @@ export async function run(_command, args = [], deps = {}) {
151
152
  return blocked;
152
153
  }
153
154
  }
154
- const doctorProfile = doctorProfileFromArgs(args, doctorFix);
155
155
  if (!flag(args, '--json')) {
156
156
  cliUi.banner('doctor');
157
157
  cliUi.step(doctorFix ? 'repairing and validating' : 'validating');
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "schema": "sks.skills-manifest.v1",
3
- "package_version": "10.3.3",
3
+ "package_version": "10.3.4",
4
4
  "skills": [
5
5
  {
6
6
  "canonical_name": "sks",
7
7
  "type": "official",
8
- "content_sha256": "d0288d27c8bc1c89decb258c6805ed4286cd0d65371ef0eec453be233b39a2a1",
8
+ "content_sha256": "8aa34586ba331f0447668c2ce863fe6c9ec4f881828ebed26ca5ecc754d66840",
9
9
  "hash_history": [
10
+ "d0288d27c8bc1c89decb258c6805ed4286cd0d65371ef0eec453be233b39a2a1",
10
11
  "905fc5472d36f0780b56f0e09404f42ea20293b1a4615d2050b7db3043231dbd",
11
12
  "6d90142f3617a8ae92d9af8cb2fc18ab58a7d0070868504e1fe89e0effc305a5",
12
13
  "5b17aeb1484e9e2b3791bff6dcb175f30857f47e71b36eb0340e575385e8e82b"
@@ -393,15 +394,15 @@
393
394
  {
394
395
  "canonical_name": "sks-naruto",
395
396
  "type": "core",
396
- "content_sha256": "65e19a5d16f486b13541048c239e828f8039bfc6e38bafd7949d9c936fe4b5b2",
397
+ "content_sha256": "b2eb207b2773ab1995ee6e113ba6f3b64de43389e76aef4a0f06d4845ff86223",
397
398
  "hash_history": [
399
+ "65e19a5d16f486b13541048c239e828f8039bfc6e38bafd7949d9c936fe4b5b2",
398
400
  "da43debf97f0b4a1583d8202a7306aca97ac97865accf9bca8f41d75c84a1cb9",
399
401
  "b981bea4183376c6c0204941ad6c2e78782e2cdea4f8d25cc85630c609faaf95",
400
402
  "5df9c9678eb68fd79ceb998ae7a52dd54dc92336ad5169e8a01acca89f76bd60",
401
403
  "a1219f6dbad9c8c185953125ef0b8dccf081aa01efb8e8acaa543c6214ac049c",
402
404
  "d0d5968e78ac25e1d3e588735de1ddbcf149cfb3165edebca5dec1ed7c7a5c46",
403
405
  "34222c72e4e1ead3584907f4d905444b7de820283ee7348018f110d5e6b04461",
404
- "f8e808622e222b4ad7dcb2907e5f06a890db83139ae443ec6c8c33781dfa3e37",
405
406
  "49b5e3dcc1b537b49c2cf09e5b875cb16acb05c622b22798e265e64dacd8ac90"
406
407
  ],
407
408
  "deprecated_aliases": []
@@ -419,8 +420,9 @@
419
420
  {
420
421
  "canonical_name": "sks-pipeline-runner",
421
422
  "type": "official",
422
- "content_sha256": "7749638f1e8393f3beb1f7b0c37195dd0463abdb6fd7da5d31b3d816ee91fa37",
423
+ "content_sha256": "6f38d3387e4ab020291afe7f74713b135543e52707f5bfbffa6b3584542f45a5",
423
424
  "hash_history": [
425
+ "7749638f1e8393f3beb1f7b0c37195dd0463abdb6fd7da5d31b3d816ee91fa37",
424
426
  "fd06dfb27be41e5ffec3e3ec8c5362289f5919cf2afa9da5ab6b97e7baa4a0ac",
425
427
  "66e105773b8e042eec5d5679b97b7cb305cad8d45671034907fbfa728fc4d68a",
426
428
  "020ba4da2f96bcda23cee4596b1e790816780178f1917dee748ff4b289cba0e5",
@@ -452,8 +454,9 @@
452
454
  {
453
455
  "canonical_name": "sks-prompt-pipeline",
454
456
  "type": "official",
455
- "content_sha256": "93fd938dccd65d9b4d1dd140b60e1baedc627e7fcc54f9afd95eb2e44efd1143",
457
+ "content_sha256": "f82a103e7962e416961add515d7b3e65df89848dec1fbb9319bd49d7f1dac982",
456
458
  "hash_history": [
459
+ "93fd938dccd65d9b4d1dd140b60e1baedc627e7fcc54f9afd95eb2e44efd1143",
457
460
  "033bb114ec1fac3f8c312f698b1861ed4c2ce6f65c06f4f1a872c4b101b74b92",
458
461
  "e7c8ef56d38fdc4879c983f7d6706f26fc1cf0a60c508836bd628e815700bcc3",
459
462
  "480c8afffafa9d52c483a963b4b8fafdffc0a47cd6d2efd3fb6d7924c11832c5",
@@ -1,6 +1,7 @@
1
1
  import { codexModelEffortCapability } from '../codex-control/codex-model-capabilities.js';
2
2
  import { managedOfficialSubagentRoleByName } from '../managed-assets/managed-assets-manifest.js';
3
- import { ASTRA_SUBAGENT_MODEL, decideSubagentModel, subagentModelProfile } from '../subagents/model-policy.js';
3
+ import { decideSubagentModel, subagentModelProfile } from '../subagents/model-policy.js';
4
+ import { MODEL_TIERS, resolveLatestModelTiers } from '../subagents/model-tiers.js';
4
5
  export function decideAgentEffort(input = {}) {
5
6
  return decideOfficialSubagentModel(input);
6
7
  }
@@ -38,14 +39,14 @@ export function decideOfficialSubagentModel(input = {}) {
38
39
  reason: `managed_role:${managedRole.codex_name}:${profile.policy}`,
39
40
  dynamic: true,
40
41
  escalation_triggers: [
41
- 'focused review, debugging, planning, integration, security, database, research, release, or unresolved ambiguity selects Astra Max',
42
+ 'focused review, debugging, planning, integration, security, database, research, release, or unresolved ambiguity selects the deep tier',
42
43
  'incidental judgment vocabulary does not override a clearly classified implementation or context/tools slice',
43
44
  'requested model/effort profile unavailable blocks instead of silently falling back'
44
45
  ],
45
46
  downshift_triggers: [
46
- 'instructed UI, logic, backend, or native implementation selects Astra Low',
47
- 'long-context, Browser/Chrome, Computer Use, image-generation, or large search selects Astra Medium',
48
- 'tiny short-context mechanical search/typing/rename work selects Astra Low'
47
+ 'instructed UI, logic, backend, or native implementation selects the balanced tier',
48
+ 'long-context, Browser/Chrome, Computer Use, image-generation, or large search selects the context tier',
49
+ 'tiny short-context mechanical search/typing/rename work selects the balanced tier'
49
50
  ]
50
51
  };
51
52
  }
@@ -92,14 +93,14 @@ export function decideOfficialSubagentModel(input = {}) {
92
93
  reason: routed.reason,
93
94
  dynamic: true,
94
95
  escalation_triggers: [
95
- 'focused review, debugging, planning, integration, security, database, research, release, or unresolved ambiguity selects Astra Max',
96
+ 'focused review, debugging, planning, integration, security, database, research, release, or unresolved ambiguity selects the deep tier',
96
97
  'incidental judgment vocabulary does not override a clearly classified implementation or context/tools slice',
97
98
  'requested model/effort profile unavailable blocks instead of silently falling back'
98
99
  ],
99
100
  downshift_triggers: [
100
- 'instructed UI, logic, backend, or native implementation selects Astra Low',
101
- 'long-context, Browser/Chrome, Computer Use, or image-generation execution selects Astra Medium',
102
- 'tiny short-context mechanical work selects Astra Low'
101
+ 'instructed UI, logic, backend, or native implementation selects the balanced tier',
102
+ 'long-context, Browser/Chrome, Computer Use, or image-generation execution selects the context tier',
103
+ 'tiny short-context mechanical work selects the balanced tier'
103
104
  ]
104
105
  };
105
106
  }
@@ -118,21 +119,22 @@ export function buildAgentEffortPolicy(roster = {}) {
118
119
  reason: agent.reasoning_reason,
119
120
  dynamic: true
120
121
  })) : [];
122
+ const latest = resolveLatestModelTiers();
121
123
  return {
122
124
  schema: 'sks.agent-effort-policy.v1',
123
125
  policy_version: 1,
124
126
  dynamic: true,
125
127
  service_tier: 'fast',
126
128
  model_catalog_policy: 'official_subagent_four_profile_matrix',
127
- model_constraint: [ASTRA_SUBAGENT_MODEL],
128
- model_tiers: ['low', 'medium', 'high', 'max'].map((effort) => `${ASTRA_SUBAGENT_MODEL}-${effort}`),
129
+ model_constraint: [...new Set(MODEL_TIERS.map((tier) => latest.models[tier]))],
130
+ model_tiers: MODEL_TIERS.map((tier) => `${tier}:${latest.models[tier]}-${latest.efforts[tier]}`),
129
131
  allowed_efforts: ['low', 'medium', 'high', 'max'],
130
- model_effort_capability: codexModelEffortCapability({ model: ASTRA_SUBAGENT_MODEL }),
132
+ model_effort_capability: codexModelEffortCapability({ model: latest.models.deep }),
131
133
  max_agents: roster.max_agents || 20,
132
134
  agent_count: roster.agent_count || decisions.length,
133
135
  concurrency: roster.concurrency || decisions.length,
134
136
  decisions,
135
- rule: 'All child agents use GPT-6 Astra: Low for tiny short-context mechanical work and instructed implementation, Medium for reads and tool execution, and Max for focused judgment. The parent keeps its user-selected model, reasoning effort, and service tier; explicit Astra effort preferences may override role defaults.'
137
+ rule: 'Every child uses the newest model of the tier its work needs: fast for tiny mechanical work, balanced for instructed implementation, context for reads and tool execution, and deep for focused judgment. With Jev mode on, Jev picks the tier for each spawn. The parent keeps its user-selected model, reasoning effort, and service tier; stored role preferences may override role defaults.'
136
138
  };
137
139
  }
138
140
  export function reasoningProfileName(effort) {
@@ -10,7 +10,8 @@ import { leanEngineeringCompactText, leanPolicyReference } from '../lean-enginee
10
10
  import { readCodexLbModelCatalog, readLbHealth } from '../codex-lb/codex-lb-env.js';
11
11
  import { categoryForWorkerRole, isNarutoGpt56Model, modelRouteReason, routeModel } from '../provider/model-router.js';
12
12
  import { codexTimeoutClassForRoute } from '../codex-control/codex-reliability-shield.js';
13
- import { ASTRA_SUBAGENT_MODEL, decideSubagentModel } from '../subagents/model-policy.js';
13
+ import { decideSubagentModel } from '../subagents/model-policy.js';
14
+ import { latestTierModelSet } from '../subagents/model-tiers.js';
14
15
  export const NATIVE_WORKER_BACKEND_ROUTER_SCHEMA = 'sks.native-worker-backend-router.v1';
15
16
  export async function runNativeWorkerBackendRouter(input) {
16
17
  const root = path.resolve(input.agentRoot);
@@ -241,15 +242,19 @@ export async function resolveWorkerModelRouting(input, deps = {}) {
241
242
  const explicitNarutoModelInvalid = narutoOnly && Boolean(explicitModel) && !isNarutoGpt56Model(explicitModel);
242
243
  const explicitNarutoReasoningInvalid = narutoOnly && Boolean(explicitReasoningRaw) && !explicitReasoning;
243
244
  const explicitNarutoTierInvalid = narutoOnly && Boolean(explicitTierRaw) && !explicitTier;
244
- const savedAstraEffort = input.agent?.routed_model_policy === 'user_role_model_preference'
245
- && input.agent?.routed_model === ASTRA_SUBAGENT_MODEL
245
+ const currentModels = latestTierModelSet();
246
+ const savedPreferenceModel = input.agent?.routed_model_policy === 'user_role_model_preference'
247
+ && currentModels.has(String(input.agent?.routed_model || ''))
248
+ ? String(input.agent?.routed_model)
249
+ : '';
250
+ const savedAstraEffort = savedPreferenceModel
246
251
  ? normalizeModelReasoning(input.agent?.routed_model_reasoning_effort)
247
252
  : null;
248
253
  const jevModel = !explicitModel && input.agent?.routed_model_policy === 'jev_sealed_routing'
249
254
  ? String(input.agent.routed_model || '').trim()
250
255
  : '';
251
256
  const jevEffort = jevModel ? normalizeModelReasoning(input.agent?.routed_model_reasoning_effort) : null;
252
- const selectedModel = explicitModel || jevModel;
257
+ const selectedModel = explicitModel || jevModel || savedPreferenceModel;
253
258
  const taskPolicy = decideSubagentModel({ title: taskKindText, description: riskText, role: input.agent?.role });
254
259
  const routed = narutoOnly
255
260
  ? await routeModel(category, {
@@ -263,14 +268,14 @@ export async function resolveWorkerModelRouting(input, deps = {}) {
263
268
  ...(selectedModel ? { model: selectedModel } : {})
264
269
  })
265
270
  : {
266
- model: ASTRA_SUBAGENT_MODEL,
271
+ model: (explicitModel && currentModels.has(explicitModel) ? explicitModel : '') || savedPreferenceModel || taskPolicy.model,
267
272
  reasoning: explicitReasoning || savedAstraEffort || taskPolicy.modelReasoningEffort,
268
273
  serviceTier: explicitTier || input.fastModePolicy.service_tier || 'fast'
269
274
  };
270
275
  const blockers = [
271
276
  ...(narutoOnly && !lbCatalog?.ok ? (lbCatalog?.blockers || ['codex_lb_model_catalog_unavailable']) : []),
272
- ...(explicitNarutoModelInvalid ? ['naruto_worker_model_outside_gpt_5_6_family'] : []),
273
- ...(!narutoOnly && explicitModel && explicitModel !== ASTRA_SUBAGENT_MODEL ? ['subagent_model_must_be_astra'] : []),
277
+ ...(explicitNarutoModelInvalid ? ['naruto_worker_model_not_current'] : []),
278
+ ...(!narutoOnly && explicitModel && !currentModels.has(explicitModel) ? ['subagent_model_must_be_current'] : []),
274
279
  ...(explicitNarutoReasoningInvalid ? ['naruto_reasoning_override_invalid'] : []),
275
280
  ...(explicitNarutoTierInvalid ? ['naruto_service_tier_override_invalid'] : []),
276
281
  ...(narutoOnly && explicitReasoning && explicitReasoning !== routed.reasoning ? ['naruto_reasoning_override_conflicts_with_policy'] : []),
@@ -306,7 +311,7 @@ export function narutoWorkerBackendBlocker(backend, narutoRequest = true) {
306
311
  if (!narutoRequest)
307
312
  return null;
308
313
  if (backend === 'process')
309
- return 'naruto_gpt_5_6_family_only_process_backend_forbidden';
314
+ return 'naruto_process_backend_forbidden';
310
315
  return null;
311
316
  }
312
317
  function normalizeBackend(value) {
@@ -18,6 +18,7 @@ const SETTINGS_V2_KEYS = new Set([
18
18
  'schema', 'listen_host', 'listen_port', 'provider_registry', 'route_policy',
19
19
  'provider_session_pins', 'client_capability_sha256', 'allowed_origins',
20
20
  'connect_timeout_ms', 'idle_timeout_ms', 'official_passthrough',
21
+ 'auth_priority_enabled',
21
22
  ]);
22
23
  const TRANSFERABLE_V1_KEYS = [
23
24
  'listen_host', 'listen_port', 'allowed_origins', 'connect_timeout_ms', 'idle_timeout_ms',
@@ -14,13 +14,13 @@ const CORE_SKILL_DEFINITIONS = [
14
14
  display_name: 'naruto',
15
15
  route: '$Naruto',
16
16
  purpose: 'run a Codex official subagent workflow with official agent threads while parent integration remains owner.',
17
- discovery: 'Run $sks-naruto for explicit parallel official subagents.',
18
- when: 'Use when the user explicitly invokes $Naruto or the selected route requires bounded parallel delegation.',
19
- workflow: 'Run sks naruto run "<task>" [--agents N] [--max-threads N] [--json] with Codex official subagent threads only. The parent orchestrates only: decompose, assign disjoint slices, spawn, integrate, and verify. Do not implement slice work in the parent thread. The parent owns decomposition, per-wave capacity, later root-owned waves, integration, and final verification. Automatic targets begin at 4/6/8/16 by task size: bounded, explicit parallel, large-scale, then mass mechanical or exploration fan-out. After decomposition both lanes may expand to the SKS-owned 256-child ceiling when independent useful slices and real host capacity remain positive; max_threads defaults to a 256-child frame budget cap, never a target. A measured lower Codex host cap or explicit provider/API budget remains authoritative, and multi-wave scheduling reuses returned capacity. When Jev mode is on, each new child spawn is sealed by Jev to gpt-5.6-luna low, gpt-5.6-sol low, gpt-5.6-terra medium, or gpt-6-astra max; a user role preference stays authoritative, and off mode keeps gpt-6-astra. Do not choose the child model yourself while Jev mode is on. Keep the four task-class profiles and compatibility IDs; explicit Astra effort preferences, including High, remain supported when Jev mode is off or the user asked for that effort; max_depth=1 blocks nested delegation. Wait for every planned thread before final. In an active Codex App Naruto mission, commit the strict parent evidence with sks naruto parent-summary --mission <id> --stdin, then return localized Markdown without exposing the JSON.',
17
+ discovery: 'Run $sks-naruto to delegate implementation to child subagents.',
18
+ when: 'Use when the user invokes $sks-naruto or $sks-work, or the selected route is Naruto: ordinary implementation work, where the parent orchestrates and children implement.',
19
+ workflow: 'Run sks naruto run "<task>" [--agents N] [--max-threads N] [--json] with Codex official subagent threads only. The parent orchestrates only: decompose, assign disjoint slices, spawn, integrate, and verify. Do not implement slice work in the parent thread; the SKS PreToolUse gate denies parent-thread source edits until the first child thread starts and while children are still running, and .sneakoscope artifacts stay parent-writable. The parent owns decomposition, per-wave capacity, later root-owned waves, integration, and final verification. Automatic targets begin at 4/6/8/16 by task size: bounded, explicit parallel, large-scale, then mass mechanical or exploration fan-out. After decomposition both lanes may expand to the SKS-owned 256-child ceiling when independent useful slices and real host capacity remain positive; max_threads defaults to a 256-child frame budget cap, never a target. A measured lower Codex host cap or explicit provider/API budget remains authoritative, and multi-wave scheduling reuses returned capacity. Every child runs the newest model of the tier its work needs (fast, balanced, context, or deep); no model family is pinned. When Jev mode is on, Jev picks the tier for each new spawn and SKS seals it. A user role preference stays authoritative. Do not choose child models yourself. Keep the four task-class profiles and compatibility IDs; max_depth=1 blocks nested delegation. Wait for every planned thread before final. In an active Codex App Naruto mission, commit the strict parent evidence with sks naruto parent-summary --mission <id> --stdin, then return localized Markdown without exposing the JSON.',
20
20
  safety: 'Preserve user-authored content, inherit the parent permission mode, do not spawn nested subagents, do not inject the full pack or the full TriWiki context into every child, and do not fall back to another model, process runtime, custom scheduler, or worker pool. The historical Naruto process runtime is removed; stop with explicit blocker evidence when the official path is unavailable.',
21
21
  cli: 'sks naruto run "<task>" [--agents N] [--max-threads N] [--json]; sks naruto status|subagents|proof [--mission <id>] [--json]; sks naruto parent-summary --mission <id> --stdin [--json]',
22
22
  evidence: 'subagent-plan.json, subagent-events.jsonl, subagent-parent-summary.json, subagent-evidence.json, naruto-summary.json, and naruto-gate.json.',
23
- fallback: 'Return explicit official-subagent availability blockers and continue parent-owned only when the sealed task still has meaningful in-scope work; never fabricate process, PID, or subagent evidence.'
23
+ fallback: 'When official subagents are unavailable, report the explicit availability blocker; the orchestration gate releases parent edits only after its bounded denials, and that release is recorded. Never fabricate process, PID, or subagent evidence.'
24
24
  },
25
25
  {
26
26
  id: 'sks-core-qa-loop',
@@ -3,7 +3,7 @@ import path from 'node:path';
3
3
  import { printJson } from '../../cli/output.js';
4
4
  import { nowIso, writeJsonAtomic } from '../fsx.js';
5
5
  import { resolveOpenRouterApiKey } from '../providers/openrouter/openrouter-secret-store.js';
6
- import { jevEnabled, readDecisionConfig, writeDecisionConfig } from './config.js';
6
+ import { jevEnabled, readDecisionConfig, writeDecisionConfig, jevCapabilityActive } from './config.js';
7
7
  import { buildEvaluationReport } from './evaluation.js';
8
8
  import { OPENROUTER_DECISIONS_ENDPOINT, OPENROUTER_DECISIONS_MODEL, requestOpenRouterDecision } from './openrouter.js';
9
9
  import { buildDecisionBundle } from './questions.js';
@@ -152,7 +152,14 @@ export async function statusReport(env) {
152
152
  source: resolved.source,
153
153
  preview: resolved.key_preview
154
154
  },
155
- capabilities: config.capabilities,
155
+ capabilities: {
156
+ context: { ...config.capabilities.context, ready: jevCapabilityActive(config, 'context') },
157
+ plan: { ...config.capabilities.plan, ready: jevCapabilityActive(config, 'plan') },
158
+ recovery: config.capabilities.recovery
159
+ },
160
+ decision_points: jevEnabled(config)
161
+ ? ['turn_tier', 'spawn_tier', 'role_tiers', 'role_omission', 'plan', 'context', 'parent_edit_delegation']
162
+ : [],
156
163
  recovery: RECOVERY_CAPABILITY,
157
164
  nextStep,
158
165
  notes: [
@@ -70,6 +70,13 @@ export async function writeDecisionConfig(patch, env = process.env) {
70
70
  export function jevEnabled(config) {
71
71
  return config.mode === 'jev' && config.consentCloud === true;
72
72
  }
73
+ export function jevCapabilityActive(config, capability) {
74
+ if (!jevEnabled(config))
75
+ return false;
76
+ if (capability === 'recovery')
77
+ return config.capabilities.recovery.ready;
78
+ return true;
79
+ }
73
80
  function normalizeConfig(raw) {
74
81
  const fallback = defaultDecisionConfig();
75
82
  if (!isRecord(raw) || raw.schema !== DECISION_CONFIG_SCHEMA)
@@ -1,6 +1,6 @@
1
1
  import { sha256 } from '../fsx.js';
2
2
  import { resolveOpenRouterApiKey } from '../providers/openrouter/openrouter-secret-store.js';
3
- import { jevEnabled, readDecisionConfig } from './config.js';
3
+ import { jevCapabilityActive, jevEnabled, readDecisionConfig } from './config.js';
4
4
  import { requestOpenRouterDecision } from './openrouter.js';
5
5
  import { compileDecision } from './policy.js';
6
6
  import { buildDecisionBundle, validatePlanCoverage } from './questions.js';
@@ -8,7 +8,7 @@ import { applyRecoveryEffect, listProductionRecoveryCandidates } from './recover
8
8
  import { assembleRoutingSelection, buildRoutingCandidates } from './routing.js';
9
9
  import { buildDecisionReceipt } from './receipt.js';
10
10
  import { applyOptionalContextSelection, graphFileDigest, hydrateContextCandidates, sourceSnapshotDigest } from './state.js';
11
- import { DESIGN_DEFAULTS, POLICY_REVISION, UNKNOWN_USAGE, sealedRoutingModel } from './types.js';
11
+ import { DESIGN_DEFAULTS, POLICY_REVISION, UNKNOWN_USAGE } from './types.js';
12
12
  let testOverrides = null;
13
13
  const appliedEffects = new Set();
14
14
  export function setDecisionTestOverrides(overrides) {
@@ -43,16 +43,16 @@ async function decideOfficialSubagentPreparationInner(input, started) {
43
43
  overrides?.observe?.({ mode: config.mode, eligible: false, compiled: missing.compiled, receipt: missing.receipt });
44
44
  return missing;
45
45
  }
46
- const contextCandidates = config.capabilities.context.ready
46
+ const contextCandidates = jevCapabilityActive(config, 'context')
47
47
  ? await hydrateContextCandidates(input.root, input.attention, input.changedPaths || [])
48
48
  : [];
49
- const planCandidates = config.capabilities.plan.ready && input.requestedSource === 'automatic'
49
+ const planCandidates = jevCapabilityActive(config, 'plan') && input.requestedSource === 'automatic'
50
50
  ? buildAutomaticPlanCandidates(input)
51
51
  : [];
52
52
  const routingCandidates = input.requestedSource === 'automatic'
53
53
  ? buildRoutingCandidates({ roles: input.routingRoles || [] })
54
54
  : [];
55
- const recoveryCandidates = config.capabilities.recovery.ready
55
+ const recoveryCandidates = jevCapabilityActive(config, 'recovery')
56
56
  ? [...listProductionRecoveryCandidates()]
57
57
  : [];
58
58
  const optionalContext = contextCandidates.filter((row) => !row.pinned && row.excerpt);
@@ -134,7 +134,7 @@ export async function consultJevTurnModel(input) {
134
134
  const overrides = activeOverrides();
135
135
  const env = input.env || process.env;
136
136
  const config = overrides?.config ?? await readDecisionConfig(env);
137
- const none = (reason) => ({ called: false, model: null, effort: null, reason });
137
+ const none = (reason) => ({ called: false, model: null, effort: null, tier: null, reason });
138
138
  if (!jevEnabled(config))
139
139
  return none('off');
140
140
  const resolved = await resolveOpenRouterApiKey({ env });
@@ -159,14 +159,56 @@ export async function consultJevTurnModel(input) {
159
159
  ...(overrides?.fetchImpl ? { fetchImpl: overrides.fetchImpl } : {})
160
160
  });
161
161
  if (!transport.ok)
162
- return { called: true, model: null, effort: null, reason: transport.reason };
162
+ return { called: true, model: null, effort: null, tier: null, reason: transport.reason };
163
163
  const compiled = compileDecision(bundle, transport.response);
164
164
  if (compiled.kind !== 'apply')
165
- return { called: true, model: null, effort: null, reason: compiled.reason };
166
- const selected = assembleRoutingSelection(compiled.effects.flatMap((effect) => (effect.kind === 'select_routing' ? [{ roleId: effect.roleId, model: effect.model }] : [])));
165
+ return { called: true, model: null, effort: null, tier: null, reason: compiled.reason };
166
+ const selected = assembleRoutingSelection(compiled.effects.flatMap((effect) => (effect.kind === 'select_routing' ? [{ roleId: effect.roleId, tier: effect.tier }] : [])));
167
167
  const model = selected?.models[roleId] || null;
168
- const sealed = model ? sealedRoutingModel(model) : null;
169
- return { called: true, model: sealed?.id || null, effort: sealed?.effort || null, reason: sealed ? 'applied' : 'keep_baseline' };
168
+ const effort = selected?.efforts[roleId] || null;
169
+ const tier = selected?.tiers[roleId] || null;
170
+ if (!model || !effort || !tier)
171
+ return { called: true, model: null, effort: null, tier: null, reason: 'keep_baseline' };
172
+ return { called: true, model, effort, tier, reason: 'applied' };
173
+ }
174
+ export async function consultJevToolDelegation(input) {
175
+ const overrides = activeOverrides();
176
+ const env = input.env || process.env;
177
+ const config = overrides?.config ?? await readDecisionConfig(env);
178
+ const none = (reason) => ({ called: false, choice: null, reason });
179
+ if (!jevEnabled(config))
180
+ return none('off');
181
+ const resolved = await resolveOpenRouterApiKey({ env });
182
+ if (!resolved.key)
183
+ return none('missing_key');
184
+ const goal = String(input.missionGoal || '').trim();
185
+ const toolName = String(input.toolName || '').trim();
186
+ if (!goal || !toolName)
187
+ return none('empty_candidate');
188
+ const targets = input.targets.map((row) => String(row || '').trim()).filter(Boolean);
189
+ const bundle = buildDecisionBundle({
190
+ projectId: sha256(input.root).slice(0, 32),
191
+ workflowRunId: 'delegation',
192
+ workflowRevision: 'delegation',
193
+ sourceDigest: sha256(JSON.stringify([toolName, targets])).slice(0, 32),
194
+ graphDigest: null,
195
+ goal,
196
+ delegationCandidate: { toolName, targets, missionGoal: goal }
197
+ });
198
+ const transport = await requestOpenRouterDecision(bundle, {
199
+ env,
200
+ deadlineMs: DESIGN_DEFAULTS.deadlineMs,
201
+ ...(overrides?.fetchImpl ? { fetchImpl: overrides.fetchImpl } : {})
202
+ });
203
+ if (!transport.ok)
204
+ return { called: true, choice: null, reason: transport.reason };
205
+ const compiled = compileDecision(bundle, transport.response);
206
+ if (compiled.kind !== 'apply')
207
+ return { called: true, choice: null, reason: compiled.reason };
208
+ const effect = compiled.effects.find((row) => row.kind === 'select_delegation');
209
+ if (!effect || effect.kind !== 'select_delegation')
210
+ return { called: true, choice: null, reason: 'keep_baseline' };
211
+ return { called: true, choice: effect.choice, reason: 'applied' };
170
212
  }
171
213
  export function effectAlreadyConsumed(identity) {
172
214
  return appliedEffects.has(identity);
@@ -210,7 +252,7 @@ function applyEffects(attention, contextCandidates, planCandidates, compiled) {
210
252
  evidence.push(`plan:${selectedPlan.id}`);
211
253
  }
212
254
  if (effect.kind === 'select_routing') {
213
- routingEffects.push({ roleId: effect.roleId, model: effect.model });
255
+ routingEffects.push({ roleId: effect.roleId, tier: effect.tier });
214
256
  }
215
257
  if (effect.kind === 'omit_role') {
216
258
  omittedRoutingRoles.push(effect.roleId);
@@ -1,4 +1,4 @@
1
- import { CHOICE_MIN_CONFIDENCE, CHOICE_MIN_PROBABILITY, CONTEXT_DROP_NOUL_MAX, DISTRIBUTION_SUM_TOLERANCE, KEEP_BASELINE_CHOICE, NEEDS_EVIDENCE_CHOICE, POLICY_REVISION, ROUTING_RISK_NOUL_MIN, ROUTING_ROLE_OMIT_NOUL_MAX, UNKNOWN_USAGE, sealedRoutingModel } from './types.js';
1
+ import { CHOICE_MIN_CONFIDENCE, CHOICE_MIN_PROBABILITY, CONTEXT_DROP_NOUL_MAX, DISTRIBUTION_SUM_TOLERANCE, DELEGATION_CHOICES, KEEP_BASELINE_CHOICE, NEEDS_EVIDENCE_CHOICE, POLICY_REVISION, ROUTING_RISK_NOUL_MIN, ROUTING_ROLE_OMIT_NOUL_MAX, UNKNOWN_USAGE, ESCALATION_ROUTING_TIER, routingTier } from './types.js';
2
2
  export function compileDecision(bundle, response) {
3
3
  const usage = usageFromWire(response.usage);
4
4
  const decoded = decodeWireResponse(bundle, response);
@@ -33,6 +33,14 @@ export function compileDecision(bundle, response) {
33
33
  else if (!fallbackReason)
34
34
  fallbackReason = compiled.reason;
35
35
  }
36
+ const delegationBinding = Object.entries(bundle.questionBindings).find(([, binding]) => binding.kind === 'delegation');
37
+ if (delegationBinding) {
38
+ const compiled = compileDelegation(decoded.response.answers[delegationBinding[0]]);
39
+ if (compiled.kind === 'effect')
40
+ effects.push(compiled.effect);
41
+ else if (!fallbackReason)
42
+ fallbackReason = compiled.reason;
43
+ }
36
44
  if (effects.length === 0) {
37
45
  return {
38
46
  kind: 'keep_baseline',
@@ -172,7 +180,7 @@ function compileRoleRouting(bundle, answers, roleId) {
172
180
  return { kind: 'baseline', reason: 'invalid_response' };
173
181
  if (answer.choice === KEEP_BASELINE_CHOICE)
174
182
  return { kind: 'baseline', reason: 'keep_baseline_selected' };
175
- const selected = sealedRoutingModel(answer.choice);
183
+ const selected = routingTier(answer.choice);
176
184
  if (!selected)
177
185
  return { kind: 'baseline', reason: 'invalid_response' };
178
186
  const choiceQuestion = choiceId ? bundle.request.questions[choiceId] : undefined;
@@ -180,8 +188,8 @@ function compileRoleRouting(bundle, answers, roleId) {
180
188
  const uncertainty = requiredChoiceUncertainty(answer, labels);
181
189
  if (!uncertainty.ok)
182
190
  return { kind: 'baseline', reason: uncertainty.reason };
183
- const model = escalateRole(bundle, answers, roleId) ? 'gpt-6-astra' : selected.id;
184
- return { kind: 'effect', effect: { kind: 'select_routing', roleId, model } };
191
+ const tier = escalateRole(bundle, answers, roleId) ? ESCALATION_ROUTING_TIER : selected.id;
192
+ return { kind: 'effect', effect: { kind: 'select_routing', roleId, tier } };
185
193
  }
186
194
  function escalateRole(bundle, answers, roleId) {
187
195
  const difficultyId = questionIdFor(bundle, 'routing_difficulty', roleId);
@@ -264,6 +272,21 @@ function compileRecovery(bundle, answer) {
264
272
  return { kind: 'baseline', reason: uncertainty.reason };
265
273
  return { kind: 'effect', effect: { kind: 'dispatch_recovery', actionId: candidate.id } };
266
274
  }
275
+ function compileDelegation(answer) {
276
+ if (!answer)
277
+ return { kind: 'baseline', reason: 'missing_answer' };
278
+ if (answer.type !== 'choice')
279
+ return { kind: 'baseline', reason: 'invalid_response' };
280
+ if (answer.choice === KEEP_BASELINE_CHOICE)
281
+ return { kind: 'baseline', reason: 'keep_baseline_selected' };
282
+ const choice = DELEGATION_CHOICES.find((row) => row === answer.choice);
283
+ if (!choice)
284
+ return { kind: 'baseline', reason: 'invalid_response' };
285
+ const uncertainty = requiredChoiceUncertainty(answer, [...DELEGATION_CHOICES, KEEP_BASELINE_CHOICE]);
286
+ if (!uncertainty.ok)
287
+ return { kind: 'baseline', reason: uncertainty.reason };
288
+ return { kind: 'effect', effect: { kind: 'select_delegation', choice } };
289
+ }
267
290
  function canDropOptional(candidate, noul) {
268
291
  return !candidate.pinned
269
292
  && candidate.fresh