/** * Orchestration facade (E017 Inc 02/03/04, OK-9.3 composition contract — * research/2026-08-18-shift-fusion-orchestration-ADR.md; S2 of * research/2026-08-18-e015-research-match-integration-review.md). * * ONE entry owns the routing composition that call sites used to hand-wire: * stage classify → per-stage tier latch (OK-9.1 session stickiness) → * override rules (bypass the latch) → corroborative scorer → pins clamp → * posture default → candidate pick (never[] filtered) → RoutingEvent with * tier + source on the activity feed. * * Deliberately NOT here: the budget guard and the provider fallback chain * stay on {@link ShiftRouter} (the execution path); this facade decides WHO * serves the turn, the router executes it. No model call on the hot path * (OK-9.4). * * Precedence (OK-9.7, one line): pin → override signals (critical/compaction, * unless a ceiling pin suppresses them) → posture threshold → bandit prior → * stage default. The bandit prior today feeds cast/pair selection * ({@link FusionBandit.recommend}); tier DEFAULTS stay posture-driven until * the calibration loop (OK-9 W6) earns the right to move them — the W5 * evidence note. */ import { type ShiftInput, type Stage } from "./shift/stages.js"; import { type Tier, type TierInput } from "./shift/tier.js"; import { type TierRouteResult } from "./shift/router.js"; import { type ActivitySink } from "./shift/activity.js"; import { type CastConfig } from "./fusion/casts.js"; import { type BanditArm } from "./fusion/bandit.js"; /** The operator's cost↔quality exchange rate (OK-9.7 posture dial). */ export type ShiftPosture = "quality" | "balanced" | "saver"; /** * Hard pins — deterministic, always beat the learned layer (FU-4 discipline, * OK-9.7). `floor[stage]` is the lowest tier that stage may route at * (escalation PAST a floor is always allowed — safe direction only); * `ceiling` is the highest tier ANY stage may route at — including * critical-error/compaction overrides and the cascade move (the documented * batch/CI cost-certainty posture: the operator accepts quality loss for a * hard cost bound). `never` denies specific "provider/model" candidates. */ export interface ShiftPins { floor?: Partial>; ceiling?: Tier; never?: string[]; } export interface OrchestratorOptions { /** Working directory — the telemetry/activity root the sink writes under. */ cwd: string; /** * Cast config (the SAME `~/.openkai/config.json` casts surface — no second * model config). The resolved default cast supplies the CAPABLE member of * each stage's pair; the cheapest cast on the same provider (falling back * to any cheap-tier cast, then the capable cast itself) supplies the * EFFICIENT member. */ castConfig?: CastConfig; /** Posture dial (default: "balanced"). */ posture?: ShiftPosture; /** Hard pins (default: none). */ pins?: ShiftPins; /** * Activity sink for routing events. Events are redacted before reaching * this sink (see {@link createRedactingSink}); when omitted, no events are * emitted (headless tests). */ onActivity?: ActivitySink; } /** * The orchestrator — stateful per session: the tier latch (one stage's * decision holds until a signal flips it), the cascade budget (one * escalation per stage), and the bandit the gate outcomes teach. */ export declare class Orchestrator { private readonly posture; private readonly threshold; private readonly pins; private readonly never; private readonly sink; private readonly bandit; private readonly capableCast; private readonly efficientCast; /** The latched decision — replaced wholesale when the stage changes (stage change resets). */ private latch; /** Stages that already spent their one cascade escalation (OK-9.3 rule 2). */ private readonly escalatedStages; /** The last decision served — the arm {@link noteGateOutcome} rewards. */ private lastDecision; constructor(options: OrchestratorOptions); /** The posture's fall-open tier for a stage (quality: always capable; saver: always efficient). */ private defaultTierFor; /** * Apply the pins AFTER the scorer: the floor raises a below-floor tier; * the ceiling caps an above-ceiling tier — INCLUDING override escalations * (documented OK-9.7 cost-certainty posture). The source label is kept so * the feed shows WHAT fired; the reason records that a pin moved the tier. */ private clamp; /** * Pick the model for a stage at a tier. Candidate order: the tier cast's * stage model, then the OTHER tier's stage model (failing across tiers * before sideways within a cast), then the rest of both fallback chains. * `never[]` entries are filtered from the picks; if the denylist empties * the pool the pick FAILS OPEN with a labelled reason (the filterByModality * posture — a denied-everything config must not silently refuse the task). */ private pickModel; /** Emit the routing event with tier + source (OK-9.1 observability discipline). */ private emitDecision; /** * The tier decision for one turn. Latch semantics (OK-9.1): the latched * tier holds until an override fires (critical/compaction/tests_passed) or * the corroborative scorer crosses the posture threshold with the opposite * sign; a stage change resets the latch. */ decide(input: ShiftInput, signals: TierInput): TierRouteResult; /** * The cascade move (OK-9.3 rule 2 — verify-then-escalate, never escalate on * vibes): after the gate's retry cap, force the stage CAPABLE once and * latch it there. One escalation per stage per session — a repeat call * returns the latched escalation with the spent budget noted. A ceiling * pin suppresses the escalation (same documented posture as overrides). */ escalate(stage: Stage): TierRouteResult; /** * The compaction hook (OK-9.3 rule 3 — compaction is a free tier-switch * point): re-decide the current stage from fresh signals, BYPASSING the * latch. Returns a decision only when the tier FLIPS; a held tier returns * undefined (nothing to show on the feed). */ reevaluate(signals: TierInput): TierRouteResult | undefined; /** * The reward writeback (OK-9 W5 — gate outcome is the routing reward): * record the gate's verdict against the model that served the last * decision, per bucket. NOTE (W5 evidence): the posterior currently feeds * cast/pair selection via {@link FusionBandit.recommend}; tier defaults * stay posture-driven until calibration (W6) supports moving them. */ /** * Reward writeback. `servedModels` names the models that ACTUALLY served * the attempt — the caller (fuse) knows its panel; decide() is advisory * and its pick may never have touched the panel (E017 review: crediting * lastDecision.model trained phantom arms the panel never used). */ noteGateOutcome(outcome: "pass" | "fail", bucket: string, servedModels?: string[]): void; /** Read one bandit arm's posterior back (calibration/inspection; a copy). */ banditArm(bucket: string, modelId: string): BanditArm; } //# sourceMappingURL=orchestrate.d.ts.map