@crewhaus/spec 0.1.8 → 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.
- package/dist/index.d.ts +4759 -206
- package/dist/index.js +339 -6
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -26,6 +26,24 @@ const safeName = z
|
|
|
26
26
|
* Will grow into the full catalog spec (eval, deploy) — see
|
|
27
27
|
* docs/MODULE-CATALOG.md PART A Layer F1.
|
|
28
28
|
*/
|
|
29
|
+
/**
|
|
30
|
+
* Section 28 (#43) — OPTIONAL spec-schema version stamp. Every target schema is
|
|
31
|
+
* `.strict()`, so before this field a migration that stamped `version: 1` on a
|
|
32
|
+
* spec's YAML produced a document `parseSpec` REJECTED ("Unrecognized key(s)").
|
|
33
|
+
* This additive optional field gives migrations somewhere to stamp:
|
|
34
|
+
*
|
|
35
|
+
* - ABSENT → the spec is current/unversioned (the pre-#43 world). Fully
|
|
36
|
+
* back-compat: every existing spec keeps parsing unchanged.
|
|
37
|
+
* - PRESENT → a non-negative integer the migration-engine reads as the spec's
|
|
38
|
+
* schema version (`spec.version ?? 0` in migration-engine/migration-runner).
|
|
39
|
+
*
|
|
40
|
+
* It is a spec-schema knob, NOT the IR `version` (which stays `0` — the IR's
|
|
41
|
+
* own contract version). `lower()` does not thread this field into the IR; it
|
|
42
|
+
* exists purely so the versioned-migration chain has a home. Added to EVERY
|
|
43
|
+
* member of the discriminated union because the union is `.strict()` — a field
|
|
44
|
+
* absent from a member would still be rejected on that target.
|
|
45
|
+
*/
|
|
46
|
+
const versionField = z.number().int().nonnegative().optional();
|
|
29
47
|
// Permissions block (Section 7). SECURITY: `mode: "bypass"` is intentionally
|
|
30
48
|
// absent from the enum — bypass can only enter the system via the CLI flag.
|
|
31
49
|
// Defense in depth: parse-time and runtime checks both reject it.
|
|
@@ -163,6 +181,158 @@ const toolConfigBlock = z
|
|
|
163
181
|
}
|
|
164
182
|
})
|
|
165
183
|
.optional();
|
|
184
|
+
/**
|
|
185
|
+
* Item 22 — spec-declared provider failover chain, on the agent blocks of
|
|
186
|
+
* the shapes whose emitted runtime calls `runChatLoop` with a single
|
|
187
|
+
* primary model (cli, channel, managed today).
|
|
188
|
+
*
|
|
189
|
+
* `model_fallbacks` is an ordered list of model strings tried when the
|
|
190
|
+
* primary's circuit breaker is open — each entry follows the SAME
|
|
191
|
+
* model-string grammar as `agent.model` (`claude-*`, `openai/*`,
|
|
192
|
+
* `bedrock/*`, `groq/*`, …; validated like `agent.model` at parse time,
|
|
193
|
+
* with the full grammar enforced by the model-router when resolved).
|
|
194
|
+
* Cross-provider fallbacks resolve their own credentials lazily via the
|
|
195
|
+
* normal model-router path — a fallback with a missing key warns at boot
|
|
196
|
+
* and is skipped when tried, never hard-failing the run.
|
|
197
|
+
*
|
|
198
|
+
* `circuit_breaker` tunes the per-candidate breakers. Field names mirror
|
|
199
|
+
* `CircuitBreakerOptions` in `@crewhaus/circuit-breaker` exactly
|
|
200
|
+
* (failureThreshold / windowMs / cooldownMs); package defaults (5 failures
|
|
201
|
+
* / 60s window / 30s cooldown) apply per field when omitted. Declaring
|
|
202
|
+
* `circuit_breaker` WITHOUT `model_fallbacks` is valid: the primary
|
|
203
|
+
* adapter alone gets breaker-wrapped (fail-fast on a degraded provider
|
|
204
|
+
* instead of hammering it).
|
|
205
|
+
*/
|
|
206
|
+
const modelFallbacksBlock = z.array(z.string().min(1)).min(1).optional();
|
|
207
|
+
const circuitBreakerBlock = z
|
|
208
|
+
.object({
|
|
209
|
+
/** Consecutive failures inside windowMs that trip the breaker. */
|
|
210
|
+
failureThreshold: z.number().int().positive().optional(),
|
|
211
|
+
/** Window for counting consecutive failures (ms). */
|
|
212
|
+
windowMs: z.number().int().positive().optional(),
|
|
213
|
+
/** How long the breaker stays open before allowing a probe (ms). */
|
|
214
|
+
cooldownMs: z.number().int().positive().optional(),
|
|
215
|
+
})
|
|
216
|
+
.strict()
|
|
217
|
+
.optional();
|
|
218
|
+
/**
|
|
219
|
+
* Item 26 — opt-in two-tier turn-difficulty router. When present, the runtime
|
|
220
|
+
* picks a model tier PER TURN from deterministic signals (estimated context
|
|
221
|
+
* tokens, whether tools are in play, turn index, prior-turn tool_use density):
|
|
222
|
+
* the cheap `fast` model for easy turns, the `default` model for hard ones. A
|
|
223
|
+
* fast-tier turn that FAILS re-runs on `default` (misroute recovery). Both are
|
|
224
|
+
* full model-router grammar strings. `routing` tunes the escalation thresholds
|
|
225
|
+
* (all optional — sensible defaults apply). Omitted entirely → single-model
|
|
226
|
+
* behaviour, byte-identical bundles.
|
|
227
|
+
*/
|
|
228
|
+
const modelTiersBlock = z
|
|
229
|
+
.object({
|
|
230
|
+
fast: z.string().min(1),
|
|
231
|
+
default: z.string().min(1),
|
|
232
|
+
routing: z
|
|
233
|
+
.object({
|
|
234
|
+
contextTokenThreshold: z.number().int().positive().optional(),
|
|
235
|
+
toolsToDefault: z.boolean().optional(),
|
|
236
|
+
firstTurnToDefault: z.boolean().optional(),
|
|
237
|
+
priorToolDensityThreshold: z.number().int().positive().optional(),
|
|
238
|
+
})
|
|
239
|
+
.strict()
|
|
240
|
+
.optional(),
|
|
241
|
+
})
|
|
242
|
+
.strict()
|
|
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
|
+
}
|
|
166
336
|
/**
|
|
167
337
|
* Section 17 — optional override for the model used by
|
|
168
338
|
* `compaction-autocompact` when summarising long conversations. Defaults
|
|
@@ -265,19 +435,51 @@ const failureTaxonomyEntrySchema = z
|
|
|
265
435
|
.object({
|
|
266
436
|
class: z.string().min(1),
|
|
267
437
|
pattern: z.string().min(1),
|
|
268
|
-
|
|
438
|
+
// Item 23 — `switch-model` routes the same turn onto the next provider
|
|
439
|
+
// failover candidate (pairs with `agent.model_fallbacks`; a no-op
|
|
440
|
+
// re-issue when no chain is declared). See recovery-engine +
|
|
441
|
+
// AUTOMATION-OPPORTUNITIES.md item 23.
|
|
442
|
+
recovery: z.enum(["retry", "compact", "continue", "tombstone", "switch-model", "fail"]),
|
|
269
443
|
hint: z.string().min(1).optional(),
|
|
270
444
|
})
|
|
271
445
|
.strict();
|
|
272
446
|
const failureTaxonomyBlock = z.array(failureTaxonomyEntrySchema).optional();
|
|
447
|
+
/**
|
|
448
|
+
* Item 27 — run-level spend cap with a degradation ladder. Generalizes the
|
|
449
|
+
* optimizer's `--budget-usd` to normal runs. `usd` is the dollar ceiling;
|
|
450
|
+
* when the run's accrued spend reaches it, `on_exceed` decides:
|
|
451
|
+
* - `{ action: "stop" }` — end the run cleanly before the next turn.
|
|
452
|
+
* - `{ action: "degrade", model }` — re-resolve the primary model to the
|
|
453
|
+
* cheaper `model` (one rung) and continue; a later breach on the
|
|
454
|
+
* degraded model stops the run.
|
|
455
|
+
* The check is PRE-TURN (beside compaction), so an in-flight turn always
|
|
456
|
+
* completes. Carried on the same interactive shapes as the failover chain
|
|
457
|
+
* (cli, channel, managed). `on_exceed.model` follows the agent.model
|
|
458
|
+
* grammar. Defaults to `{ action: "stop" }` when `on_exceed` is omitted.
|
|
459
|
+
*/
|
|
460
|
+
const budgetBlock = z
|
|
461
|
+
.object({
|
|
462
|
+
usd: z.number().positive(),
|
|
463
|
+
on_exceed: z
|
|
464
|
+
.discriminatedUnion("action", [
|
|
465
|
+
z.object({ action: z.literal("stop") }).strict(),
|
|
466
|
+
z.object({ action: z.literal("degrade"), model: z.string().min(1) }).strict(),
|
|
467
|
+
])
|
|
468
|
+
.default({ action: "stop" }),
|
|
469
|
+
})
|
|
470
|
+
.strict()
|
|
471
|
+
.optional();
|
|
273
472
|
/**
|
|
274
473
|
* Response-feedback block — declares that a harness collects human ratings on
|
|
275
474
|
* agent responses (thumbs/stars/scale/comment) which `crewhaus distill` turns
|
|
276
475
|
* into eval datasets + graders. Cross-cutting like security: carried on the
|
|
277
476
|
* interactive shapes that consume it (cli, channel). `channelReactions` gates
|
|
278
477
|
* codegen of Slack 👍/👎 → feedback in the channel target; `modality`/`storage`
|
|
279
|
-
* configure the capture surfaces; `autoDistill`
|
|
280
|
-
*
|
|
478
|
+
* configure the capture surfaces; `autoDistill` turns accumulated ratings into
|
|
479
|
+
* versioned `<name>-ratings` registry datasets at CLI run teardown (item 1);
|
|
480
|
+
* `exitPrompt` gates the one-keystroke REPL exit rating prompt (default on
|
|
481
|
+
* when the block is present; set `false` to keep capture surfaces without the
|
|
482
|
+
* prompt). `.strict()` so a typo'd sub-key fails the build.
|
|
281
483
|
*/
|
|
282
484
|
const feedbackBlock = z
|
|
283
485
|
.object({
|
|
@@ -286,10 +488,93 @@ const feedbackBlock = z
|
|
|
286
488
|
scale: z.object({ min: z.number().int(), max: z.number().int() }).strict().optional(),
|
|
287
489
|
storage: z.object({ location: safeName }).strict().optional(),
|
|
288
490
|
autoDistill: z.boolean().optional(),
|
|
491
|
+
exitPrompt: z.boolean().optional(),
|
|
289
492
|
channelReactions: z.boolean().optional(),
|
|
290
493
|
})
|
|
291
494
|
.strict()
|
|
292
495
|
.optional();
|
|
496
|
+
/**
|
|
497
|
+
* Feature #53 — cross-session memory block. Its mere presence wires the
|
|
498
|
+
* Remember/Recall tools into the harness (no hand-editing). The auto-*
|
|
499
|
+
* switches layer on top: `autoCapture` summarizes the session's durable
|
|
500
|
+
* outcomes into `.crewhaus/memories/<name>.jsonl` at run teardown;
|
|
501
|
+
* `autoRecall` injects the top-`recallK` relevant memories into the system
|
|
502
|
+
* prompt at session start (mirrors project-memory auto-load). Carried on the
|
|
503
|
+
* interactive shapes that run a chat loop (cli, channel, managed, research).
|
|
504
|
+
* `.strict()` so a typo'd sub-key fails the build.
|
|
505
|
+
*/
|
|
506
|
+
const memoryBlock = z
|
|
507
|
+
.object({
|
|
508
|
+
enabled: z.boolean().optional(),
|
|
509
|
+
autoCapture: z.boolean().optional(),
|
|
510
|
+
autoCaptureThreshold: z.number().int().positive().optional(),
|
|
511
|
+
autoRecall: z.boolean().optional(),
|
|
512
|
+
recallK: z.number().int().positive().max(50).optional(),
|
|
513
|
+
})
|
|
514
|
+
.strict()
|
|
515
|
+
.optional();
|
|
516
|
+
/**
|
|
517
|
+
* Ops item 37 — cross-cutting `observability` block. Today it carries one
|
|
518
|
+
* sub-block, `slo`, that declares production Service-Level Objectives + the
|
|
519
|
+
* mitigation ladder the runtime SLO monitor walks on a SUSTAINED breach.
|
|
520
|
+
*
|
|
521
|
+
* The monitor (runtime-core, env/spec-gated) folds bus events into rolling
|
|
522
|
+
* windows (reusing the alert-watchdog's accumulator + the metrics-collector's
|
|
523
|
+
* TTFT histogram) and, when a target is breached for `windowSeconds`, executes
|
|
524
|
+
* the ladder rungs in the declared order: `alert` (webhook/hook), `pause-intake`
|
|
525
|
+
* (gateway/managed 429 `budget_exceeded` path), `rollback` (auto-rollback the
|
|
526
|
+
* env pin via deployment-controller). Every rung is audit-logged.
|
|
527
|
+
*
|
|
528
|
+
* Targets are all OPTIONAL — declare only the SLOs you care about; an omitted
|
|
529
|
+
* target is never evaluated. `mitigation` defaults to `["alert"]` (observe-only
|
|
530
|
+
* is safe) so a spec that lists thresholds without a ladder still warns. Higher
|
|
531
|
+
* rungs are opt-in because they touch traffic/deploys — a spec must ask for them
|
|
532
|
+
* explicitly. `.strict()` so a typo'd sub-key fails the build.
|
|
533
|
+
*
|
|
534
|
+
* NOTE `egress_block_rate` derives from the `permission_decision` egress
|
|
535
|
+
* outcomes (no dedicated egress TraceEvent exists); `ttft_ms`/`p95_latency_ms`
|
|
536
|
+
* derive from the same per-turn/TTFT samples the alert-watchdog accumulates.
|
|
537
|
+
*/
|
|
538
|
+
const sloBlock = z
|
|
539
|
+
.object({
|
|
540
|
+
/** Fractional error rate ceiling (unrecovered errors / model calls), e.g. 0.05. */
|
|
541
|
+
error_rate: z.number().min(0).max(1).optional(),
|
|
542
|
+
/** p95 per-turn latency ceiling, milliseconds. */
|
|
543
|
+
p95_latency_ms: z.number().positive().optional(),
|
|
544
|
+
/** p95 time-to-first-token ceiling, milliseconds. */
|
|
545
|
+
ttft_ms: z.number().positive().optional(),
|
|
546
|
+
/** Cost burn ceiling, USD per hour of wall-clock. */
|
|
547
|
+
cost_per_hour_usd: z.number().positive().optional(),
|
|
548
|
+
/** Fractional egress-block rate ceiling (egress-blocked / external calls), e.g. 0.1. */
|
|
549
|
+
egress_block_rate: z.number().min(0).max(1).optional(),
|
|
550
|
+
/**
|
|
551
|
+
* Rolling window (seconds) a breach must persist before the ladder fires.
|
|
552
|
+
* A single blip never mitigates — the monitor only acts on a SUSTAINED
|
|
553
|
+
* breach across this window. Default 300s (5 min).
|
|
554
|
+
*/
|
|
555
|
+
window_seconds: z.number().int().positive().optional(),
|
|
556
|
+
/**
|
|
557
|
+
* Mitigation ladder, walked in declared order on a sustained breach. Each
|
|
558
|
+
* rung is executed at most once per session. `alert` is always safe;
|
|
559
|
+
* `pause-intake` / `rollback` touch traffic + deploys so they are opt-in.
|
|
560
|
+
*/
|
|
561
|
+
mitigation: z
|
|
562
|
+
.array(z.enum(["alert", "pause-intake", "rollback"]))
|
|
563
|
+
.nonempty()
|
|
564
|
+
.optional(),
|
|
565
|
+
})
|
|
566
|
+
.strict()
|
|
567
|
+
.refine((s) => s.error_rate !== undefined ||
|
|
568
|
+
s.p95_latency_ms !== undefined ||
|
|
569
|
+
s.ttft_ms !== undefined ||
|
|
570
|
+
s.cost_per_hour_usd !== undefined ||
|
|
571
|
+
s.egress_block_rate !== undefined, { message: "observability.slo must declare at least one target threshold" });
|
|
572
|
+
const observabilityBlock = z
|
|
573
|
+
.object({
|
|
574
|
+
slo: sloBlock.optional(),
|
|
575
|
+
})
|
|
576
|
+
.strict()
|
|
577
|
+
.optional();
|
|
293
578
|
/**
|
|
294
579
|
* Section 47 — blockchain subsystem blocks (cross-cutting). Any shape may
|
|
295
580
|
* declare any subset of `chains` / `wallets` / `contracts` /
|
|
@@ -422,6 +707,7 @@ const channelGatewayBlock = z
|
|
|
422
707
|
const cliSchema = z
|
|
423
708
|
.object({
|
|
424
709
|
name: safeName,
|
|
710
|
+
version: versionField,
|
|
425
711
|
target: z.literal("cli"),
|
|
426
712
|
agent: z
|
|
427
713
|
.object({
|
|
@@ -431,9 +717,17 @@ const cliSchema = z
|
|
|
431
717
|
// runtime default applies. Raise it for turns that emit large
|
|
432
718
|
// multi-file edits so the model isn't cut off mid-`tool_use`.
|
|
433
719
|
max_tokens: z.number().int().positive().optional(),
|
|
720
|
+
// Item 22 — provider failover chain (see modelFallbacksBlock docs).
|
|
721
|
+
model_fallbacks: modelFallbacksBlock,
|
|
722
|
+
circuit_breaker: circuitBreakerBlock,
|
|
723
|
+
// Item 26 — opt-in two-tier turn-difficulty router.
|
|
724
|
+
model_tiers: modelTiersBlock,
|
|
725
|
+
// Adaptive model routing — N-candidate pool with a selection policy.
|
|
726
|
+
model_pool: modelPoolBlock,
|
|
434
727
|
sub_agents: subAgentsBlock,
|
|
435
728
|
})
|
|
436
|
-
.strict()
|
|
729
|
+
.strict()
|
|
730
|
+
.superRefine(refineModelSelection),
|
|
437
731
|
tools: z.array(z.string().min(1)).optional(),
|
|
438
732
|
tool_config: toolConfigBlock,
|
|
439
733
|
mcp_servers: mcpServersBlock,
|
|
@@ -441,7 +735,10 @@ const cliSchema = z
|
|
|
441
735
|
compaction: compactionBlock,
|
|
442
736
|
security: securityBlock,
|
|
443
737
|
failure_taxonomy: failureTaxonomyBlock,
|
|
738
|
+
budget: budgetBlock,
|
|
444
739
|
feedback: feedbackBlock,
|
|
740
|
+
memory: memoryBlock,
|
|
741
|
+
observability: observabilityBlock,
|
|
445
742
|
cli: cliOptionsBlock,
|
|
446
743
|
chains: chainsBlock,
|
|
447
744
|
wallets: walletsBlock,
|
|
@@ -461,6 +758,7 @@ const workflowStepSchema = z
|
|
|
461
758
|
const workflowSchema = z
|
|
462
759
|
.object({
|
|
463
760
|
name: safeName,
|
|
761
|
+
version: versionField,
|
|
464
762
|
target: z.literal("workflow"),
|
|
465
763
|
model: z.string().min(1),
|
|
466
764
|
steps: z.array(workflowStepSchema).min(1),
|
|
@@ -543,14 +841,23 @@ const channelAgentSchema = z
|
|
|
543
841
|
.object({
|
|
544
842
|
model: z.string().min(1),
|
|
545
843
|
instructions: z.string().min(1),
|
|
844
|
+
// Item 22 — provider failover chain (see modelFallbacksBlock docs).
|
|
845
|
+
model_fallbacks: modelFallbacksBlock,
|
|
846
|
+
circuit_breaker: circuitBreakerBlock,
|
|
847
|
+
// Item 26 — opt-in two-tier turn-difficulty router.
|
|
848
|
+
model_tiers: modelTiersBlock,
|
|
849
|
+
// Adaptive model routing — N-candidate pool with a selection policy.
|
|
850
|
+
model_pool: modelPoolBlock,
|
|
546
851
|
tools: z.array(z.string().min(1)).optional(),
|
|
547
852
|
tool_config: toolConfigBlock,
|
|
548
853
|
sub_agents: subAgentsBlock,
|
|
549
854
|
})
|
|
550
|
-
.strict()
|
|
855
|
+
.strict()
|
|
856
|
+
.superRefine(refineModelSelection);
|
|
551
857
|
const channelSchema = z
|
|
552
858
|
.object({
|
|
553
859
|
name: safeName,
|
|
860
|
+
version: versionField,
|
|
554
861
|
target: z.literal("channel"),
|
|
555
862
|
agent: channelAgentSchema,
|
|
556
863
|
channels: channelsBlock,
|
|
@@ -559,7 +866,10 @@ const channelSchema = z
|
|
|
559
866
|
permissions: permissionsBlock,
|
|
560
867
|
compaction: compactionBlock,
|
|
561
868
|
failure_taxonomy: failureTaxonomyBlock,
|
|
869
|
+
budget: budgetBlock,
|
|
562
870
|
feedback: feedbackBlock,
|
|
871
|
+
memory: memoryBlock,
|
|
872
|
+
observability: observabilityBlock,
|
|
563
873
|
heartbeat: heartbeatBlock,
|
|
564
874
|
gateway: channelGatewayBlock,
|
|
565
875
|
chains: chainsBlock,
|
|
@@ -599,6 +909,7 @@ const graphEdgeSchema = z
|
|
|
599
909
|
const graphSchema = z
|
|
600
910
|
.object({
|
|
601
911
|
name: safeName,
|
|
912
|
+
version: versionField,
|
|
602
913
|
target: z.literal("graph"),
|
|
603
914
|
model: z.string().min(1),
|
|
604
915
|
entry: z.string().min(1),
|
|
@@ -632,17 +943,29 @@ const managedAgentSchema = z
|
|
|
632
943
|
.object({
|
|
633
944
|
model: z.string().min(1),
|
|
634
945
|
instructions: z.string().min(1),
|
|
946
|
+
// Item 22 — provider failover chain (see modelFallbacksBlock docs).
|
|
947
|
+
model_fallbacks: modelFallbacksBlock,
|
|
948
|
+
circuit_breaker: circuitBreakerBlock,
|
|
949
|
+
// Item 26 — opt-in two-tier turn-difficulty router.
|
|
950
|
+
model_tiers: modelTiersBlock,
|
|
951
|
+
// Adaptive model routing — N-candidate pool with a selection policy.
|
|
952
|
+
model_pool: modelPoolBlock,
|
|
635
953
|
})
|
|
636
|
-
.strict()
|
|
954
|
+
.strict()
|
|
955
|
+
.superRefine(refineModelSelection);
|
|
637
956
|
const managedSchema = z
|
|
638
957
|
.object({
|
|
639
958
|
name: safeName,
|
|
959
|
+
version: versionField,
|
|
640
960
|
target: z.literal("managed"),
|
|
641
961
|
agent: managedAgentSchema,
|
|
642
962
|
tenants: z.array(managedTenantSchema).min(1),
|
|
643
963
|
permissions: permissionsBlock,
|
|
644
964
|
compaction: compactionBlock,
|
|
645
965
|
failure_taxonomy: failureTaxonomyBlock,
|
|
966
|
+
budget: budgetBlock,
|
|
967
|
+
memory: memoryBlock,
|
|
968
|
+
observability: observabilityBlock,
|
|
646
969
|
})
|
|
647
970
|
.strict();
|
|
648
971
|
// Vector-store backend ids accepted in specs. Mirrors `VectorBackendId`
|
|
@@ -666,6 +989,7 @@ const pipelineDocumentSchema = z
|
|
|
666
989
|
const pipelineSchema = z
|
|
667
990
|
.object({
|
|
668
991
|
name: safeName,
|
|
992
|
+
version: versionField,
|
|
669
993
|
target: z.literal("pipeline"),
|
|
670
994
|
agent: z
|
|
671
995
|
.object({
|
|
@@ -731,6 +1055,7 @@ const crewRoutingSchema = z
|
|
|
731
1055
|
const crewSchema = z
|
|
732
1056
|
.object({
|
|
733
1057
|
name: safeName,
|
|
1058
|
+
version: versionField,
|
|
734
1059
|
target: z.literal("crew"),
|
|
735
1060
|
/** Crew-wide model fallback used by any role that omits `role.model`. */
|
|
736
1061
|
model: z.string().min(1),
|
|
@@ -765,6 +1090,7 @@ const researchRetrieveSchema = z
|
|
|
765
1090
|
const researchSchema = z
|
|
766
1091
|
.object({
|
|
767
1092
|
name: safeName,
|
|
1093
|
+
version: versionField,
|
|
768
1094
|
target: z.literal("research"),
|
|
769
1095
|
agent: z
|
|
770
1096
|
.object({
|
|
@@ -782,6 +1108,7 @@ const researchSchema = z
|
|
|
782
1108
|
permissions: permissionsBlock,
|
|
783
1109
|
compaction: compactionBlock,
|
|
784
1110
|
failure_taxonomy: failureTaxonomyBlock,
|
|
1111
|
+
memory: memoryBlock,
|
|
785
1112
|
chains: chainsBlock,
|
|
786
1113
|
wallets: walletsBlock,
|
|
787
1114
|
contracts: contractsBlock,
|
|
@@ -804,6 +1131,7 @@ const batchQueueSchema = z
|
|
|
804
1131
|
const batchSchema = z
|
|
805
1132
|
.object({
|
|
806
1133
|
name: safeName,
|
|
1134
|
+
version: versionField,
|
|
807
1135
|
target: z.literal("batch"),
|
|
808
1136
|
agent: z
|
|
809
1137
|
.object({
|
|
@@ -844,6 +1172,7 @@ const voiceTelephonySchema = z
|
|
|
844
1172
|
const voiceSchema = z
|
|
845
1173
|
.object({
|
|
846
1174
|
name: safeName,
|
|
1175
|
+
version: versionField,
|
|
847
1176
|
target: z.literal("voice"),
|
|
848
1177
|
agent: z
|
|
849
1178
|
.object({
|
|
@@ -878,6 +1207,7 @@ const browserDriverSchema = z
|
|
|
878
1207
|
const browserSchema = z
|
|
879
1208
|
.object({
|
|
880
1209
|
name: safeName,
|
|
1210
|
+
version: versionField,
|
|
881
1211
|
target: z.literal("browser"),
|
|
882
1212
|
agent: z
|
|
883
1213
|
.object({
|
|
@@ -907,6 +1237,7 @@ const browserSchema = z
|
|
|
907
1237
|
const evalSchema = z
|
|
908
1238
|
.object({
|
|
909
1239
|
name: safeName,
|
|
1240
|
+
version: versionField,
|
|
910
1241
|
target: z.literal("eval"),
|
|
911
1242
|
agent: z
|
|
912
1243
|
.object({
|
|
@@ -971,6 +1302,7 @@ const onchainTriggerSchema = z.discriminatedUnion("kind", [
|
|
|
971
1302
|
const onchainSchema = z
|
|
972
1303
|
.object({
|
|
973
1304
|
name: safeName,
|
|
1305
|
+
version: versionField,
|
|
974
1306
|
target: z.literal("onchain"),
|
|
975
1307
|
agent: z
|
|
976
1308
|
.object({
|
|
@@ -1005,6 +1337,7 @@ const onchainSchema = z
|
|
|
1005
1337
|
const onchainGameSchema = z
|
|
1006
1338
|
.object({
|
|
1007
1339
|
name: safeName,
|
|
1340
|
+
version: versionField,
|
|
1008
1341
|
target: z.literal("onchain-game"),
|
|
1009
1342
|
agent: z
|
|
1010
1343
|
.object({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crewhaus/spec",
|
|
3
|
-
"version": "0.1
|
|
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.1
|
|
18
|
+
"@crewhaus/errors": "0.2.1",
|
|
19
19
|
"yaml": "^2.6.0",
|
|
20
20
|
"zod": "^3.23.8"
|
|
21
21
|
},
|