run() that never throwsEvery model invocation in Alembic — every adapter, every council member, every swarm worker, every funnel tier — flows through a single function shape: an async call that never throws and returns a value discriminated on ok. This is the most important invariant in the codebase. The fusion package you met in lessons 7–13 plugs into this waist; understanding it is understanding why a flaky network or a 429 can't crash a run. Source: packages/contracts/src/model.ts + packages/adapters/src/adapter-core.ts, governed by ADR-0009.
The contract is a discriminated union on a literal ok field. There is no throw in the type — failure is a value, not a control-flow event:
// packages/contracts/src/model.ts — the two arms (condensed) type ModelRunSuccess = { // ok: literal(true) is the discriminant ok: true; adapterId: string; durationMs: number; modelId: string; text: string; usage?: TokenUsage; costUsd?: number; raw?: unknown; }; type ModelRunFailure = { // ok: literal(false) ok: false; adapterId: string; durationMs: number; modelId: string; error: { code: string; message: string; retryable: boolean }; }; type ModelRunResult = z.discriminatedUnion('ok', [Success, Failure]);
The caller writes if (result.ok) and TypeScript narrows the type. Crucially the failure arm carries a retryable boolean — the call site doesn't have to guess whether a 429 is worth retrying; the result tells it. The interface itself documents the law: run(input): Promise<ModelRunResult> // NEVER throws (invariant).
A comment saying "never throws" is worthless if one adapter forgets. So the invariant is structural: every adapter implements only an inner attempt(), and a single shared spine — runWithGuards — wraps it with four layers, in order:
// packages/adapters/src/adapter-core.ts:118-147 — the canonical spine (condensed) export const runWithGuards = async (adapterId, attempt, input, runtime = {}) => { const validation = validate(adapterId, input); // ① Zod at the boundary if (!validation.ok) return validation.result; // bad input ⇒ Failure, not throw try { return await withRetry( // ③ backoff on retryable results () => guardedAttempt(adapterId, attempt, input, runtime.breaker, logger), // ② policy, { clock, logger, random: runtime.random, signal: input.signal }, ); } catch (cause) { // withRetry only rejects if guardedAttempt rejects, which it never does; // this is a final safety net to preserve the invariant. return failureFromThrown({ adapterId, input, durationMs: 0 }, cause); // ④ } };
Layer ② is where the circuit-breaker learns: guardedAttempt calls breaker?.recordSuccess() on an ok result and breaker?.recordFailure() otherwise, and reports retry: true only when result.error.retryable (adapter-core.ts:102-110). The outer catch at ④ is logically unreachable — it exists solely so the type-level promise ("never throws") holds even if a future refactor breaks an inner assumption. That is defense in depth at the most load-bearing seam in the system.
run(): Promise<X> what it might throw, so callers either over-catch or forget. A discriminated union makes every failure path visible and exhaustive: the compiler forces you to handle ok: false. Across a system that fans one request to dozens of models in parallel, this is the difference between "one provider's 500 degrades that lane" and "one provider's 500 takes down the run."A second, lighter discriminated union — also on ok, with value/error arms — exists for non-model fallible work: file IO, parsing, subprocess wrapping. It deliberately mirrors the model waist so both read identically at call sites:
// packages/contracts/src/result.ts — the lightweight sibling type Result<T, E = Error> = | { readonly ok: true; readonly value: T } | { readonly ok: false; readonly error: E }; // helpers: ok(v), err(e), isOk, isErr, mapResult, tryCatch, tryCatchAsync
This is the Result<T, Error> you saw returned by every @alembic/hermes subsystem in lessons 7–13 — load(), transcribe(), webSearch() all return it. The repo rule is explicit: "library code returns Result rather than throwing" (CLAUDE.md). Two unions, one philosophy: fallibility is a value, validated at the boundary, never a surprise.
attempt() throws a raw TypeError deep in its body. What does the caller of run() observe?catch at ④ would convert it. The union is total — run() resolves to a ModelRunResult, never rejects.ModelRunFailure carry a retryable: boolean rather than letting the caller decide?guardedAttempt returns retry: true exactly when result.error.retryable holds, and withRetry backs off on that flag. Classifying retryability at the source keeps the policy uniform and prevents drift across call sites.try/catch in runWithGuards is described as "a final safety net." Why keep code that is logically unreachable?withRetry only rejects if guardedAttempt rejects, which it never does — so today the outer catch can't fire. It stays because the cost is one try and the downside of the invariant breaking is catastrophic. The comment in source says exactly this.ModelRunFailure with a code, a message, and a retryable flag; nothing is hidden. What's eliminated is the invisible control-flow jump, not the information.ModelRunResult is the rich model-call contract (usage, cost, duration, adapter id); Result<T, E> is the minimal fallible-op contract. They share the ok discriminant on purpose so call sites read identically, but they carry different payloads for different jobs.