@crewhaus/spec 0.2.0 → 0.2.1

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 (3) hide show
  1. package/dist/index.d.ts +2397 -106
  2. package/dist/index.js +104 -3
  3. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -241,6 +241,98 @@ const modelTiersBlock = z
241
241
  })
242
242
  .strict()
243
243
  .optional();
244
+ /**
245
+ * Adaptive model routing — an opt-in `model_pool` of user-declared candidate
246
+ * models the runtime selects among PER TURN, with a selection `policy` that can
247
+ * improve the more the harness runs. The N-candidate generalisation of
248
+ * `model_tiers` (which is the two-candidate special case), so the two are
249
+ * mutually exclusive on an agent block (enforced by a refine on each shape).
250
+ *
251
+ * The user always declares the SET; the runtime only ever picks WITHIN it —
252
+ * `agent.model` (and the optimizer's model-path exclusion) is untouched, so
253
+ * learning tunes selection policy, never the candidate roster.
254
+ *
255
+ * - `candidates` — ≥2 model-router grammar strings, each with free-form
256
+ * `tags` (e.g. `cheap`, `strong`) the heuristic routes on. Declare
257
+ * cheapest→strongest; tags override that order.
258
+ * - `policy` — `static` (first candidate), `heuristic` (deterministic
259
+ * difficulty routing, the default), or `learned` (reward-scoreboard arm
260
+ * selection that improves with usage).
261
+ * - `objective` — weights for the learned reward (quality / cost / latency);
262
+ * defaults to quality-dominant (0.7 / 0.2 / 0.1).
263
+ * - `routing` — difficulty thresholds (shared with `model_tiers`) plus the
264
+ * `strongTag`/`cheapTag` the heuristic prefers.
265
+ * - `learning` — read only for `policy: learned`: the per-arm exploration
266
+ * floor and the cost/latency reward references.
267
+ *
268
+ * Omitted entirely → single-model behaviour, byte-identical bundles.
269
+ */
270
+ const modelPoolBlock = z
271
+ .object({
272
+ candidates: z
273
+ .array(z
274
+ .object({
275
+ model: z.string().min(1),
276
+ tags: z.array(z.string().min(1)).default([]),
277
+ })
278
+ .strict())
279
+ .min(2),
280
+ policy: z.enum(["static", "heuristic", "learned"]).default("heuristic"),
281
+ objective: z
282
+ .object({
283
+ quality: z.number().min(0).optional(),
284
+ cost: z.number().min(0).optional(),
285
+ latency: z.number().min(0).optional(),
286
+ })
287
+ .strict()
288
+ .optional(),
289
+ routing: z
290
+ .object({
291
+ contextTokenThreshold: z.number().int().positive().optional(),
292
+ toolsToDefault: z.boolean().optional(),
293
+ firstTurnToDefault: z.boolean().optional(),
294
+ priorToolDensityThreshold: z.number().int().positive().optional(),
295
+ strongTag: z.string().min(1).optional(),
296
+ cheapTag: z.string().min(1).optional(),
297
+ })
298
+ .strict()
299
+ .optional(),
300
+ learning: z
301
+ .object({
302
+ minSamplesPerArm: z.number().int().positive().optional(),
303
+ costRefUsd: z.number().positive().optional(),
304
+ latencyRefMs: z.number().int().positive().optional(),
305
+ })
306
+ .strict()
307
+ .optional(),
308
+ })
309
+ .strict()
310
+ .optional();
311
+ /**
312
+ * Mutual-exclusion refine shared by every agent block that carries model
313
+ * routing: `model_pool` is the superset of both the two-tier router and the
314
+ * ordered failover chain, so declaring it alongside either is an error rather
315
+ * than an ambiguous double-route. (Per-candidate failover chains compose in a
316
+ * later release; this release keeps precedence unambiguous.)
317
+ */
318
+ function refineModelSelection(agent, ctx) {
319
+ if (agent.model_pool === undefined)
320
+ return;
321
+ if (agent.model_tiers !== undefined) {
322
+ ctx.addIssue({
323
+ code: z.ZodIssueCode.custom,
324
+ message: "agent.model_pool and agent.model_tiers are mutually exclusive — model_pool is the N-candidate generalisation of the two-tier router",
325
+ path: ["model_tiers"],
326
+ });
327
+ }
328
+ if (agent.model_fallbacks !== undefined) {
329
+ ctx.addIssue({
330
+ code: z.ZodIssueCode.custom,
331
+ message: "agent.model_pool and agent.model_fallbacks are mutually exclusive in this release — per-candidate failover chains are a future addition",
332
+ path: ["model_fallbacks"],
333
+ });
334
+ }
335
+ }
244
336
  /**
245
337
  * Section 17 — optional override for the model used by
246
338
  * `compaction-autocompact` when summarising long conversations. Defaults
@@ -630,9 +722,12 @@ const cliSchema = z
630
722
  circuit_breaker: circuitBreakerBlock,
631
723
  // Item 26 — opt-in two-tier turn-difficulty router.
632
724
  model_tiers: modelTiersBlock,
725
+ // Adaptive model routing — N-candidate pool with a selection policy.
726
+ model_pool: modelPoolBlock,
633
727
  sub_agents: subAgentsBlock,
634
728
  })
635
- .strict(),
729
+ .strict()
730
+ .superRefine(refineModelSelection),
636
731
  tools: z.array(z.string().min(1)).optional(),
637
732
  tool_config: toolConfigBlock,
638
733
  mcp_servers: mcpServersBlock,
@@ -751,11 +846,14 @@ const channelAgentSchema = z
751
846
  circuit_breaker: circuitBreakerBlock,
752
847
  // Item 26 — opt-in two-tier turn-difficulty router.
753
848
  model_tiers: modelTiersBlock,
849
+ // Adaptive model routing — N-candidate pool with a selection policy.
850
+ model_pool: modelPoolBlock,
754
851
  tools: z.array(z.string().min(1)).optional(),
755
852
  tool_config: toolConfigBlock,
756
853
  sub_agents: subAgentsBlock,
757
854
  })
758
- .strict();
855
+ .strict()
856
+ .superRefine(refineModelSelection);
759
857
  const channelSchema = z
760
858
  .object({
761
859
  name: safeName,
@@ -850,8 +948,11 @@ const managedAgentSchema = z
850
948
  circuit_breaker: circuitBreakerBlock,
851
949
  // Item 26 — opt-in two-tier turn-difficulty router.
852
950
  model_tiers: modelTiersBlock,
951
+ // Adaptive model routing — N-candidate pool with a selection policy.
952
+ model_pool: modelPoolBlock,
853
953
  })
854
- .strict();
954
+ .strict()
955
+ .superRefine(refineModelSelection);
855
956
  const managedSchema = z
856
957
  .object({
857
958
  name: safeName,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crewhaus/spec",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "type": "module",
5
5
  "description": "User-facing spec schema (Zod) + YAML parser",
6
6
  "main": "dist/index.js",
@@ -15,7 +15,7 @@
15
15
  "test": "bun test src"
16
16
  },
17
17
  "dependencies": {
18
- "@crewhaus/errors": "0.2.0",
18
+ "@crewhaus/errors": "0.2.1",
19
19
  "yaml": "^2.6.0",
20
20
  "zod": "^3.23.8"
21
21
  },