@harness-engineering/orchestrator 0.11.2 → 0.13.0

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 CHANGED
@@ -1,11 +1,12 @@
1
1
  import * as _harness_engineering_types from '@harness-engineering/types';
2
- import { Issue, AgentEvent, WorkflowConfig, TokenUsage, ConcernSignal, ScopeTier, EscalationConfig, IssueRoutingDecision, Result, WorkflowDefinition, BackendDef, RoutingConfig, WorkspaceConfig, HooksConfig, AgentBackend, SessionStartParams, AgentSession, AgentError, TurnParams, TurnResult, CheckScriptDefinition, OutputRetentionConfig, RoutingDecision, RoutingUseCase, ContainerConfig, SecretConfig, AgentConfig, LocalModelStatus, CustomTaskDefinition, MaintenanceConfig, TokenScope, AuthToken, AuthTokenPublic, IndexedFileKind, SessionSearchResult, ReindexStats, SessionSummarizationConfig, SessionSummary, SessionSummaryMeta, SessionsConfig, GatewayEvent, NotificationEnvelope, NotificationDeliveryResult, NotificationSinkConfig, NotificationsConfig } from '@harness-engineering/types';
3
- import { IssueTrackerClient, Issue as Issue$1, TrackerConfig, eventSourcing, CacheMetricsRecorder, ArchiveHooks, SkillProposal, ProposalGateFinding, SkillKind } from '@harness-engineering/core';
2
+ import { Issue, WorkflowExecutionPlan, StageRun, AgentEvent, WorkflowConfig, TokenUsage, ConcernSignal, ScopeTier, EscalationConfig, IssueRoutingDecision, Result, WorkflowDefinition, BackendDef, RoutingConfig, RoutingDecision, RoutingUseCase, ContainerConfig, SecretConfig, AgentBackend, RoutingRequest, CapabilityTier, WorkspaceConfig, HooksConfig, SessionStartParams, AgentSession, AgentError, TurnParams, TurnResult, CheckScriptDefinition, OutputRetentionConfig, BackendCapabilityRegistry, RoutingPolicy, ComplexityVerdict, RoutingTelemetry, RoutingStatus, RoutingError, PrivacyClass, BackendCapabilities, AgentConfig, LocalModelStatus, CustomTaskDefinition, MaintenanceConfig, TokenScope, AuthToken, AuthTokenPublic, IndexedFileKind, SessionSearchResult, ReindexStats, SessionSummarizationConfig, SessionSummary, SessionSummaryMeta, SessionsConfig, GatewayEvent, NotificationEnvelope, NotificationDeliveryResult, NotificationSinkConfig, NotificationsConfig } from '@harness-engineering/types';
3
+ import { IssueTrackerClient, Issue as Issue$1, CacheMetricsRecorder, TrackerConfig, eventSourcing, ArchiveHooks, SkillProposal, ProposalGateFinding, SkillKind } from '@harness-engineering/core';
4
4
  import { EnrichedSpec, ComplexityScore, SimulationResult, IntelligencePipeline, WeightedRecommendation, AnalysisProvider } from '@harness-engineering/intelligence';
5
5
  import { GraphStore } from '@harness-engineering/graph';
6
6
  import { execFile } from 'node:child_process';
7
7
  import { EventEmitter } from 'node:events';
8
- import { PoolStateProvider, SchedulerTimerHandle } from '@harness-engineering/local-models';
8
+ import { PoolStateProvider, SchedulerTimerHandle, DiscoverCandidatesOptions, DiscoverCandidatesResult } from '@harness-engineering/local-models';
9
+ export { discoverCandidates } from '@harness-engineering/local-models';
9
10
  import { z } from 'zod';
10
11
 
11
12
  /**
@@ -44,6 +45,22 @@ interface RunningEntry {
44
45
  startedAt: string;
45
46
  phase: RunAttemptPhase;
46
47
  session: LiveSession | null;
48
+ /**
49
+ * AMR Phase 4 (D10/SC16): the tier the AdaptiveRouter resolved this dispatch
50
+ * at (`decision.tierRequired`), captured so a later quality outcome can feed
51
+ * `AdaptiveRouter.recordOutcome(issue.id, lastRoutedTier, ok)`. Absent when
52
+ * AMR is off (no policy) or the dispatch used an override.
53
+ */
54
+ lastRoutedTier?: _harness_engineering_types.CapabilityTier;
55
+ /** split-routing: the plan this unit is running staged, if any. Engine-owned. */
56
+ workflow?: WorkflowExecutionPlan;
57
+ /** split-routing: the stage index currently executing. Engine-owned. */
58
+ currentStageIndex?: number;
59
+ /**
60
+ * split-routing: per-stage runs. The per-stage session/abort live HERE
61
+ * (C1), never in the issue-level `session` field above.
62
+ */
63
+ stageRuns?: StageRun[];
47
64
  }
48
65
  /**
49
66
  * Entry in the retry queue.
@@ -1000,107 +1017,545 @@ declare function discoverSkillCatalog(projectRoot: string): SkillCatalogEntry[];
1000
1017
  declare function discoverSkillCatalogNames(projectRoot: string): string[];
1001
1018
 
1002
1019
  /**
1003
- * Adapter for using a markdown roadmap file as an issue tracker.
1020
+ * split-routing D5/D12/D13 the result of the doubly-opt-in match.
1004
1021
  *
1005
- * This adapter parses a standard Harness roadmap file, extracts features,
1006
- * and maps them to the internal Issue model using deterministic hashing
1007
- * for identifiers.
1022
+ * `workflowFor` is the SINGLE matching authority: it returns BOTH the execution
1023
+ * `plan` AND the matched decl's optional `stageDeadlineMs` (D12) so `dispatchIssue`
1024
+ * never re-runs the prefix+labels+`>= 2` predicate to recover the deadline. One
1025
+ * match, one source of truth — no drift between the predicate and a second matcher.
1008
1026
  */
1009
- declare class RoadmapTrackerAdapter implements IssueTrackerClient {
1010
- private config;
1027
+ interface WorkflowMatch {
1028
+ /** The execution plan for the matched >= 2-stage decl. */
1029
+ plan: WorkflowExecutionPlan;
1030
+ /** The matched decl's per-stage deadline override (D12), if declared. */
1031
+ stageDeadlineMs?: number;
1032
+ }
1033
+ /**
1034
+ * split-routing D5/D13 — the doubly-opt-in gate as a PURE matcher.
1035
+ *
1036
+ * Selects a `WorkflowMatch` for `issue` iff `config.workflows` declares a
1037
+ * matching decl with `stages.length >= 2`. Match grain (v1): an
1038
+ * `identifierPrefix` and/or `labels`; both, when declared, must hold. The FIRST
1039
+ * matching decl wins. The returned `WorkflowMatch` carries the matched decl's
1040
+ * `stageDeadlineMs` (D12) so the caller reads it from HERE rather than
1041
+ * re-matching — this function is the single match authority.
1042
+ *
1043
+ * D13: a matching 1-stage decl returns `undefined` — the unit takes the
1044
+ * single-agent path (a 1-stage "workflow" is not staged). A 0-stage decl never
1045
+ * reaches here (rejected at config validation).
1046
+ *
1047
+ * SC4 — this matcher has ZERO side effects: it never touches the workspace,
1048
+ * claims, routes, logs, or mutates state. When it returns `undefined`
1049
+ * `dispatchIssue` falls through to the byte-identical single-agent path. Keep it
1050
+ * cheap and pure so calling it on every dispatch cannot change non-workflow
1051
+ * behavior.
1052
+ */
1053
+ declare function workflowFor(issue: Issue, config: WorkflowConfig): WorkflowMatch | undefined;
1054
+
1055
+ interface RoutingDecisionBusFilter {
1056
+ skillName?: string;
1057
+ mode?: string;
1058
+ backendName?: string;
1059
+ limit?: number;
1060
+ }
1061
+ interface RoutingDecisionBusOptions {
1062
+ /** Default 500. Bound on the in-memory ring buffer. */
1063
+ capacity?: number;
1011
1064
  /**
1012
- * Creates a new RoadmapTrackerAdapter.
1013
- *
1014
- * @param config - The tracker configuration including the file path
1065
+ * Logger for the structured `routing-decision` line (O1) and for
1066
+ * one-off warn() when a subscriber throws (S6). When omitted, the
1067
+ * bus silently swallows subscriber errors (test-mode default).
1015
1068
  */
1016
- constructor(config: TrackerConfig);
1069
+ logger?: StructuredLogger;
1070
+ }
1071
+ /**
1072
+ * Spec B Phase 4 (D8): in-process bus + ring buffer for
1073
+ * {@link RoutingDecision} events. One emit() per
1074
+ * {@link BackendRouter.resolve} call; subscribers receive the
1075
+ * decision synchronously after the ring buffer is updated.
1076
+ *
1077
+ * Subscriber errors are isolated (caught + logged, never thrown
1078
+ * back to the emitter) so a misbehaving subscriber cannot block a
1079
+ * dispatch. (S6)
1080
+ *
1081
+ * Capacity-bound (default 500) via Array.shift() — acceptable for
1082
+ * v1 (see plan C4); switch to circular indexing if 24h dispatch
1083
+ * volume ever pushes 10K+ records/min.
1084
+ */
1085
+ declare class RoutingDecisionBus {
1086
+ private readonly ringBuffer;
1087
+ private readonly listeners;
1088
+ private readonly capacity;
1089
+ private readonly logger;
1090
+ constructor(opts?: RoutingDecisionBusOptions);
1091
+ emit(decision: RoutingDecision): void;
1092
+ recent(filter?: RoutingDecisionBusFilter): RoutingDecision[];
1093
+ subscribe(listener: (d: RoutingDecision) => void): () => void;
1017
1094
  /**
1018
- * Fetches all issues that are in an "active" state according to the config.
1095
+ * Spec B Phase 5 (review-S2 fix): release all subscriber references so
1096
+ * teardown can complete without anchoring closures. Called from
1097
+ * `Orchestrator.stop()` before nulling the bus reference. The bus
1098
+ * remains usable after clear — `subscribe()` works as normal.
1019
1099
  */
1020
- fetchCandidateIssues(): Promise<Result<Issue$1[], Error>>;
1100
+ clearListeners(): void;
1101
+ }
1102
+
1103
+ interface BackendRouterOptions {
1104
+ backends: Record<string, BackendDef>;
1105
+ routing: RoutingConfig;
1021
1106
  /**
1022
- * Fetches issues that match any of the given state names.
1023
- *
1024
- * @param stateNames - List of statuses to filter by
1107
+ * Spec B Phase 4 (D8): when present, every resolve() emits its
1108
+ * decision onto the bus. The bus owns the structured log line + ring
1109
+ * buffer; the router stays a pure resolution function.
1025
1110
  */
1026
- fetchIssuesByStates(stateNames: string[]): Promise<Result<Issue$1[], Error>>;
1111
+ decisionBus?: RoutingDecisionBus;
1112
+ }
1113
+ /**
1114
+ * BackendRouter (Spec B Phase 1)
1115
+ *
1116
+ * Owns the lookup from a {@link RoutingUseCase} (a discriminated query
1117
+ * — tier, intelligence layer, maintenance, chat, isolation, **skill**,
1118
+ * **mode**) to a {@link RoutingDecision} naming a chosen backend and
1119
+ * the full resolution path that produced it.
1120
+ *
1121
+ * Resolution order (D2): invocation override -> per-skill -> per-mode
1122
+ * -> existing per-tier/intelligence/isolation/maintenance/chat ->
1123
+ * `routing.default`. Within each source, fallback chain entries are
1124
+ * tried in declared order; first existing backend wins. Unknown
1125
+ * entries are recorded with `outcome: 'unknown-backend'` and the walk
1126
+ * continues.
1127
+ *
1128
+ * Construction-time validation guarantees every name referenced by
1129
+ * `routing` is present in `backends` so the static-config case can
1130
+ * never produce a runtime exhaustion throw. The runtime throw at the
1131
+ * end of `resolve()` is a safety net for future dynamic-backends
1132
+ * scenarios where a chain entry can become unknown post-construction.
1133
+ */
1134
+ declare class BackendRouter {
1135
+ private readonly backends;
1136
+ private readonly routing;
1137
+ private readonly decisionBus;
1138
+ constructor(opts: BackendRouterOptions);
1027
1139
  /**
1028
- * Transitions a roadmap feature into the first configured terminal state
1029
- * and rewrites the markdown file. Idempotent: if the feature is already
1030
- * in a terminal state, this is a no-op.
1140
+ * Resolve a {@link RoutingUseCase} to a {@link RoutingDecision}.
1031
1141
  *
1032
- * The orchestrator calls this after a successful agent exit so the feature
1033
- * is no longer returned by `fetchCandidateIssues` on the next tick — and,
1034
- * critically, after an orchestrator restart.
1142
+ * @param useCase the routing query
1143
+ * @param opts.invocationOverride if set and the named backend exists,
1144
+ * beats all other sources (D7 — the `--backend <name>` escape hatch)
1035
1145
  */
1036
- markIssueComplete(issueId: string): Promise<Result<void, Error>>;
1146
+ resolve(useCase: RoutingUseCase, opts?: {
1147
+ invocationOverride?: string;
1148
+ }): RoutingDecision;
1037
1149
  /**
1038
- * Claims an issue by transitioning its status to "in-progress" and
1039
- * writing the orchestratorId into the assignee field. Idempotent if
1040
- * already claimed by the same orchestratorId.
1150
+ * Returns the {@link BackendDef} reference for the resolved name.
1151
+ * Identity-equal to the entry in `backends` (no copy) so callers
1152
+ * relying on reference equality (SC21) continue to work.
1041
1153
  */
1042
- claimIssue(issueId: string, orchestratorId: string): Promise<Result<void, Error>>;
1154
+ resolveDefinition(useCase: RoutingUseCase, opts?: {
1155
+ invocationOverride?: string;
1156
+ }): BackendDef;
1043
1157
  /**
1044
- * Releases a claimed issue by transitioning back to the first active
1045
- * state and clearing the assignee field.
1158
+ * Spec B Phase 4 (closes P1-IMP-2): a single resolve() + def lookup
1159
+ * for callers that need both. Replaces the previous pattern of
1160
+ * `resolveDefinition(useCase) + resolve(useCase)` which produced two
1161
+ * RoutingDecision emissions per dispatch — doubling routing-decision
1162
+ * log volume now that Phase 4 emits.
1163
+ *
1164
+ * Identity-equal `BackendDef` (no copy) so callers relying on
1165
+ * reference equality (SC21) continue to work.
1046
1166
  */
1047
- releaseIssue(issueId: string): Promise<Result<void, Error>>;
1167
+ resolveDecisionAndDef(useCase: RoutingUseCase, opts?: {
1168
+ invocationOverride?: string;
1169
+ }): {
1170
+ decision: RoutingDecision;
1171
+ def: BackendDef;
1172
+ };
1048
1173
  /**
1049
- * Resolve the roadmap store anchored on the configured aggregate file path.
1050
- * The shard backend is chosen when a sibling `roadmap.d/` exists next to the
1051
- * file; otherwise the monolith file is read/written whole. Callers guard
1052
- * `this.config.filePath` before invoking.
1174
+ * The pre-Spec-B resolution helper: returns the configured
1175
+ * {@link RoutingValue} for tier/intelligence/isolation/maintenance/chat
1176
+ * use cases (or `undefined` for skill/mode use cases, which are owned
1177
+ * by the per-skill / per-mode steps in {@link resolve}). Returning
1178
+ * `undefined` lets the caller fall through to `routing.default`.
1053
1179
  */
1054
- private resolveStore;
1055
- /** Load the roadmap via the store (sharded or monolith), returning a Result. */
1056
- private loadRoadmap;
1057
- private findFeatureById;
1180
+ private resolveExistingUseCase;
1181
+ private validateReferences;
1182
+ }
1183
+
1184
+ /**
1185
+ * Options for `OrchestratorBackendFactory`.
1186
+ *
1187
+ * `sandboxPolicy` and `container`/`secrets` mirror the orchestrator's own
1188
+ * agent-config fields. `getResolverModelFor` is a registration hook the
1189
+ * orchestrator calls to bind each `local`/`pi` backend to its
1190
+ * `LocalModelResolver` (so multi-resolver array-fallback works without
1191
+ * leaking resolver lifetimes into the factory).
1192
+ */
1193
+ /**
1194
+ * Runtime-feedback callbacks a `local`/`pi` backend fires as turns complete.
1195
+ * See {@link OrchestratorBackendFactoryOptions.getModelUsageHooksFor}.
1196
+ */
1197
+ interface LocalModelUsageHooks {
1198
+ /** Fired with the resolved model after a successful turn (LRU + breaker clear). */
1199
+ onModelUsed?: (model: string) => void;
1200
+ /** Fired with the resolved model after a failed turn (breaker increment). */
1201
+ onModelFailed?: (model: string) => void;
1202
+ }
1203
+ interface OrchestratorBackendFactoryOptions {
1204
+ backends: Record<string, BackendDef>;
1205
+ routing: RoutingConfig;
1206
+ sandboxPolicy: 'none' | 'docker';
1207
+ container?: ContainerConfig;
1208
+ secrets?: SecretConfig;
1058
1209
  /**
1059
- * Fetches full issue details for a list of identifiers.
1210
+ * Hook for resolver injection. Invoked per `local`/`pi` backend at
1211
+ * `forUseCase()` time with the backend's name. When the hook returns a
1212
+ * function, the factory rebuilds the local/pi instance using that
1213
+ * function as `getModel` (overriding the head-of-array placeholder
1214
+ * baked into `createBackend`). Returning `undefined` means "no
1215
+ * resolver registered for this name" — the placeholder stays in place.
1060
1216
  *
1061
- * @param issueIds - List of issue IDs to fetch
1217
+ * This indirection keeps the factory ignorant of `LocalModelResolver`'s
1218
+ * existence and lifecycle while still letting it produce backends that
1219
+ * route through the resolver Map.
1062
1220
  */
1063
- fetchIssueStatesByIds(issueIds: string[]): Promise<Result<Map<string, Issue$1>, Error>>;
1221
+ getResolverModelFor?: (backendName: string, useCase: RoutingUseCase) => (() => string | null) | undefined;
1064
1222
  /**
1065
- * Maps a raw RoadmapFeature from the parser to the unified Issue model.
1223
+ * Consumption Phase 3 (T11): per-`local`/`pi` backend hook returning the
1224
+ * runtime-feedback callbacks bound to that backend's resolver + pool. The
1225
+ * factory forwards them to the constructed `LocalBackend` so a completed turn
1226
+ * stamps `lastUsedAt` (LRU) + clears the circuit breaker, and a failed turn
1227
+ * feeds the breaker. Returning `undefined` means "no feedback wiring" (the
1228
+ * backend runs exactly as before). Kept parallel to `getResolverModelFor` so
1229
+ * the factory stays ignorant of resolver/pool lifecycles.
1066
1230
  */
1067
- private mapFeatureToIssue;
1231
+ getModelUsageHooksFor?: (backendName: string) => LocalModelUsageHooks | undefined;
1068
1232
  /**
1069
- * Generates a deterministic, URL-safe identifier for a feature name.
1233
+ * Phase 5: prompt-cache recorder forwarded to Anthropic-capable backends.
1234
+ * Other backends accept-but-ignore. Shared across dispatches so the
1235
+ * `/api/v1/telemetry/cache/stats` endpoint sees the full rolling window.
1070
1236
  */
1071
- private generateId;
1237
+ cacheMetrics?: CacheMetricsRecorder;
1238
+ /**
1239
+ * Spec B Phase 4 (D8): forwarded to the underlying BackendRouter so
1240
+ * every resolve() during forUseCase / resolveName emits.
1241
+ */
1242
+ decisionBus?: RoutingDecisionBus;
1072
1243
  }
1073
-
1074
1244
  /**
1075
- * Interface for a Linear GraphQL tool extension — a thin authenticated client
1076
- * over Linear's GraphQL API (https://linear.app/developers/graphql).
1245
+ * High-level factory wrapping `BackendRouter` + `createBackend` plus
1246
+ * orchestrator-side concerns (sandbox wrapping, resolver binding).
1247
+ *
1248
+ * Spec 2 SC22-SC25: every `forUseCase(useCase)` call returns a fresh
1249
+ * `AgentBackend` whose class matches the routed `BackendDef.type`.
1250
+ * `local`/`pi` defs are bound to their per-name resolver before being
1251
+ * returned, and the result is wrapped in `ContainerBackend` when
1252
+ * sandboxPolicy is 'docker'.
1077
1253
  */
1078
- interface LinearGraphQLExtension {
1254
+ declare class OrchestratorBackendFactory {
1255
+ private readonly router;
1256
+ private readonly opts;
1257
+ constructor(opts: OrchestratorBackendFactoryOptions);
1079
1258
  /**
1080
- * Execute a GraphQL operation. Resolves to `Ok(data)` (the `data` object of
1081
- * the GraphQL envelope) on success, or `Err` on a transport failure, a non-2xx
1082
- * HTTP status, or a GraphQL `errors` array.
1259
+ * Resolve `useCase` to a backend name, materialize a fresh
1260
+ * `AgentBackend`, optionally rebind its model resolver, and apply
1261
+ * sandbox wrapping. Idempotent across calls (no caching) — the AgentRunner
1262
+ * holds the per-dispatch reference and discards it when the run ends.
1083
1263
  */
1084
- query(query: string, variables?: Record<string, unknown>): Promise<Result<unknown, Error>>;
1085
- }
1086
- interface LinearGraphQLClientOptions {
1087
1264
  /**
1088
- * Linear authentication token. A personal API key is sent verbatim in the
1089
- * `Authorization` header; an OAuth access token must be passed already prefixed
1090
- * with `Bearer ` (Linear's two supported schemes).
1265
+ * Resolve `useCase` to its routed backend name, exposing the
1266
+ * router lookup without materializing a backend. Used by callers
1267
+ * (e.g., the orchestrator's dispatch site) that need to label
1268
+ * telemetry with the routed name BEFORE constructing the backend.
1269
+ *
1270
+ * Spec 2 P2-I2: previously the orchestrator labelled `LiveSession`
1271
+ * + `StreamRecorder` with the legacy `agent.backend` field, which
1272
+ * is `undefined` for pure-modern configs. Threading the routed name
1273
+ * through dispatch eliminates that gap.
1091
1274
  */
1092
- apiKey: string;
1093
- /** Override the GraphQL endpoint (e.g. a proxy). Defaults to Linear's API. */
1094
- endpoint?: string;
1095
- /** Injectable fetch for tests; defaults to the global `fetch`. */
1096
- fetchFn?: typeof fetch;
1097
- }
1098
- /**
1099
- * Real Linear GraphQL client. Replaces {@link LinearGraphQLStub}, which only
1100
- * logged the query and returned an empty object. POSTs the operation to Linear's
1101
- * GraphQL endpoint with the API key, and normalizes the three failure modes
1102
- * (transport throw, non-2xx HTTP, GraphQL `errors`) into a single `Err`.
1103
- */
1275
+ resolveName(useCase: RoutingUseCase, opts?: {
1276
+ invocationOverride?: string;
1277
+ }): string;
1278
+ /**
1279
+ * Spec B Phase 1: expose the underlying router for callers that need
1280
+ * it directly (e.g., {@link buildIntelligencePipeline} for the
1281
+ * I1 SEL/PESL comparison fix). Read-only access; consumers must not
1282
+ * mutate router state.
1283
+ */
1284
+ getRouter(): BackendRouter;
1285
+ forUseCase(useCase: RoutingUseCase, opts?: {
1286
+ invocationOverride?: string;
1287
+ }): AgentBackend;
1288
+ /**
1289
+ * Rebuild a `local`/`pi` backend with a resolver-bound `getModel`,
1290
+ * mirroring `createBackend`'s local/pi branches but substituting the
1291
+ * head-of-array placeholder with the orchestrator-owned resolver.
1292
+ */
1293
+ private buildLocalLikeWithResolver;
1294
+ /**
1295
+ * Apply ContainerBackend wrapping (PFC-3). Pulls the runtime + secret
1296
+ * backend per call so each dispatch sees a fresh container handle map
1297
+ * (ContainerBackend keeps its own per-instance Map<sessionId, handle>).
1298
+ */
1299
+ private wrapInContainer;
1300
+ }
1301
+
1302
+ /**
1303
+ * Narrow surface the stage-execution engine needs. The Orchestrator will
1304
+ * implement this in Phase 4 when dispatchIssue wires the branch; the engine
1305
+ * must NOT import orchestrator.ts (layer cycle). Phase 1 tests inject a fake.
1306
+ */
1307
+ interface WorkflowEngineContext {
1308
+ recorder: StreamRecorder;
1309
+ logger: StructuredLogger;
1310
+ /** issueId of the coherence unit — used for recorder keys + terminal transitions. */
1311
+ issueId: string;
1312
+ identifier: string;
1313
+ externalId: string | null;
1314
+ workspacePath: string;
1315
+ /**
1316
+ * D12 (SC7): per-stage wall-clock deadline in ms. If a stage does not finish
1317
+ * within it, the engine fires the per-stage abort (running the runner's
1318
+ * `finally { stopSession }` via `gen.return()`) and treats the stage as
1319
+ * `passed:false` → feeds the D8 retry/terminal path (never an unbounded hang).
1320
+ * Absent ⇒ the engine default (`DEFAULT_STAGE_DEADLINE_MS`). Per-stage/config
1321
+ * override is Phase 4.
1322
+ */
1323
+ stageDeadlineMs?: number;
1324
+ /** Build a runner for a stage's routed backend. Phase 1: single stubbed backend. */
1325
+ makeRunner(backend: AgentBackend): {
1326
+ runSession: (issue: unknown, ws: string, prompt: string) => AsyncGenerator<AgentEvent, {
1327
+ sessionId: string;
1328
+ success: boolean;
1329
+ usage: {
1330
+ inputTokens: number;
1331
+ outputTokens: number;
1332
+ totalTokens: number;
1333
+ };
1334
+ }, void>;
1335
+ };
1336
+ /** Phase 1 stub: resolve the single backend for a stage (Phase 2 replaces with route()). */
1337
+ resolveStageBackend(step: WorkflowExecutionPlan['stages'][number]): AgentBackend;
1338
+ /**
1339
+ * split-routing 4b: render the REAL per-stage prompt (issue + stage role +
1340
+ * prior-stage outputs), replacing the Phase-1 bare skill-name stub. Bound by
1341
+ * `buildWorkflowContext` via a `PromptRenderer` (never imports orchestrator).
1342
+ * `priorOutputs` maps each prior stage's `produces` label → its captured output.
1343
+ * Optional so fake contexts that don't exercise prompt content fall back to the
1344
+ * skill name (byte-identical to the old stub).
1345
+ *
1346
+ * CONTRACT: must be non-blocking (CPU-bound). It runs BEFORE the per-stage
1347
+ * deadline timer is armed, so an I/O-bound renderer that hangs would stall the
1348
+ * stage with no wall-clock bound. The shipped renderer is synchronous LiquidJS.
1349
+ */
1350
+ renderStagePrompt?(step: WorkflowExecutionPlan['stages'][number], index: number, priorOutputs: Record<string, string>): string | Promise<string>;
1351
+ /**
1352
+ * split-routing Phase 2: per-stage adaptive router. Present ⇒ each stage is
1353
+ * routed via `route(buildStageRequest(...))`; ABSENT ⇒ identity fallback via
1354
+ * `resolveStageBackend` (no `routing.policy`). Narrow surface only (route +
1355
+ * recordOutcome) so the engine stays off the orchestrator import cycle.
1356
+ *
1357
+ * `route()` returns `{ decision }` only — the engine derives the backend name
1358
+ * from `decision.backendName` (a `BackendDef` carries no `name`; the name is
1359
+ * the router map key), so `def` is deliberately not part of this surface.
1360
+ */
1361
+ adaptiveRouter?: {
1362
+ route(req: RoutingRequest): Promise<{
1363
+ decision: RoutingDecision;
1364
+ }>;
1365
+ recordOutcome(coherenceUnit: string, tier: CapabilityTier, ok: boolean): void;
1366
+ };
1367
+ /** Terminal success — exactly one running/claimed delete + one lane persist (D6). */
1368
+ emitWorkflowSuccess(unit: string, runs: StageRun[]): Promise<void>;
1369
+ /** Terminal failure/safety-net — exactly one running/claimed delete + one lane persist (D6/I1). */
1370
+ finalizeWorkflowTerminal(unit: string, runs: StageRun[], failingStep?: WorkflowExecutionPlan['stages'][number], err?: unknown): Promise<void>;
1371
+ }
1372
+
1373
+ /**
1374
+ * split-routing Phase 4 — the narrow adaptive-router surface the workflow engine
1375
+ * consumes (`route` + `recordOutcome`). Structurally identical to
1376
+ * `WorkflowEngineContext.adaptiveRouter` and to the real `AdaptiveRouter`'s public
1377
+ * methods; typed here so the dep bag never depends on the concrete router class
1378
+ * (and never on `orchestrator.ts`).
1379
+ */
1380
+ interface WorkflowRouterDep {
1381
+ route(req: RoutingRequest): Promise<{
1382
+ decision: RoutingDecision;
1383
+ }>;
1384
+ recordOutcome(coherenceUnit: string, tier: CapabilityTier, ok: boolean): void;
1385
+ }
1386
+ /**
1387
+ * The dependency bag `buildWorkflowContext` composes the real
1388
+ * `WorkflowEngineContext` from. It is passed IN by `dispatchIssue` (Task 8) rather
1389
+ * than read off `this`, so this module imports only same-layer siblings
1390
+ * (`agent/runner`, `agent/orchestrator-backend-factory` types, `core/stream-recorder`)
1391
+ * and NEVER `orchestrator.ts` — the layer-cycle guard the context surface exists
1392
+ * for (concern #3). The two terminal seams are supplied as `settleSuccess` /
1393
+ * `settleTerminal` callbacks the orchestrator binds to its private methods (Task 7).
1394
+ */
1395
+ interface BuildWorkflowContextDeps {
1396
+ recorder: StreamRecorder;
1397
+ logger: StructuredLogger;
1398
+ /** The dispatch unit; issueId/identifier/externalId flow from here. */
1399
+ issue: Issue;
1400
+ /** The unit's ONE worktree (from the single `ensureWorkspace` in dispatchIssue). */
1401
+ workspacePath: string;
1402
+ /** `this.config.agent.maxTurns` — the per-stage runner turn cap. */
1403
+ maxTurns: number;
1404
+ /**
1405
+ * The orchestrator's backend factory. Used to materialize the real routed
1406
+ * backend for a stage: `forUseCase(useCase, { invocationOverride: name })`
1407
+ * mirrors the AMR dispatch swap (orchestrator.ts:2062-2064). `null` when
1408
+ * migration failed (legacy-only config) — then the identity fallback uses the
1409
+ * name-only `routingDefault` backend.
1410
+ */
1411
+ backendFactory: OrchestratorBackendFactory | null;
1412
+ /**
1413
+ * The real `AdaptiveRouter` (narrowed) when `routing.policy` is set
1414
+ * (orchestrator.ts:698), else `null` ⇒ the engine takes the identity fallback
1415
+ * (D5). Only `route`/`recordOutcome` are consumed.
1416
+ */
1417
+ adaptiveRouter: WorkflowRouterDep | null;
1418
+ /** `this.config.agent.routing?.default` — the identity-fallback backend name. */
1419
+ routingDefault: string | undefined;
1420
+ /** D12 override; absent ⇒ engine default DEFAULT_STAGE_DEADLINE_MS. */
1421
+ stageDeadlineMs?: number;
1422
+ /** Terminal-success seam (Task 6/7): bound to `settleWorkflowSuccess`. */
1423
+ settleSuccess: (unit: string, runs: StageRun[]) => Promise<void>;
1424
+ /** Terminal-failure/safety-net seam (Task 6/7): bound to `settleWorkflowTerminal`. */
1425
+ settleTerminal: (unit: string, runs: StageRun[], failingStep?: WorkflowExecutionPlan['stages'][number], err?: unknown) => Promise<void>;
1426
+ }
1427
+ /**
1428
+ * split-routing Phase 4 — compose the REAL `WorkflowEngineContext` (minus the two
1429
+ * terminal seams, which Task 6 completes) from orchestrator machinery.
1430
+ *
1431
+ * Seam → real code:
1432
+ * - `recorder`/`logger`/`issueId`/`identifier`/`externalId`/`workspacePath` → the deps.
1433
+ * - `stageDeadlineMs` → `deps.stageDeadlineMs` (conditional; engine defaults when absent).
1434
+ * - `makeRunner(backend)` → `new AgentRunner(realBackend, { maxTurns })`. The engine
1435
+ * passes a name-only backend for the routed path (execute-workflow.ts:320); we
1436
+ * re-materialize the real backend from that name via
1437
+ * `backendFactory.forUseCase(useCase, { invocationOverride: backend.name })`
1438
+ * (same swap as orchestrator.ts:2062-2064). Its `runSession` returns a
1439
+ * `TurnResult` (runner.ts:112) — the seam is already typed against it
1440
+ * (carry-forward #9), confirmed self-documenting by the explicit annotation below.
1441
+ * - `resolveStageBackend(step)` → identity fallback: `backendFactory.forUseCase(useCase)`
1442
+ * when the factory exists, else a name-only backend from `routingDefault`.
1443
+ * - `adaptiveRouter` → present iff `deps.adaptiveRouter !== null` (D5).
1444
+ *
1445
+ * The two TERMINAL seams (`emitWorkflowSuccess` / `finalizeWorkflowTerminal`,
1446
+ * SC5) are thin forwarders to the orchestrator's `settleSuccess` / `settleTerminal`
1447
+ * callbacks. They are NOT routed through `emitWorkerExit`/`handleWorkerExit`
1448
+ * (which fire the ISSUE-keyed `finishRecording(attempt)` + `recordAmrOutcome` — a
1449
+ * DOUBLE-fire, since the engine already ran PER-STAGE recorders + per-stage
1450
+ * `recordOutcome`). The reducer-reproduction (running.delete → completed.set →
1451
+ * claimed.delete → cleanWorkspace → persist → emit for success; and the
1452
+ * finalizeRoutingTerminal + needs-human + cleanWorkspace sequence for terminal)
1453
+ * lives in the orchestrator's private methods (Task 7) the callbacks bind to.
1454
+ */
1455
+ declare function buildWorkflowContext(deps: BuildWorkflowContextDeps): WorkflowEngineContext;
1456
+
1457
+ /**
1458
+ * Adapter for using a markdown roadmap file as an issue tracker.
1459
+ *
1460
+ * This adapter parses a standard Harness roadmap file, extracts features,
1461
+ * and maps them to the internal Issue model using deterministic hashing
1462
+ * for identifiers.
1463
+ */
1464
+ declare class RoadmapTrackerAdapter implements IssueTrackerClient {
1465
+ private config;
1466
+ /**
1467
+ * Creates a new RoadmapTrackerAdapter.
1468
+ *
1469
+ * @param config - The tracker configuration including the file path
1470
+ */
1471
+ constructor(config: TrackerConfig);
1472
+ /**
1473
+ * Fetches all issues that are in an "active" state according to the config.
1474
+ */
1475
+ fetchCandidateIssues(): Promise<Result<Issue$1[], Error>>;
1476
+ /**
1477
+ * Fetches issues that match any of the given state names.
1478
+ *
1479
+ * @param stateNames - List of statuses to filter by
1480
+ */
1481
+ fetchIssuesByStates(stateNames: string[]): Promise<Result<Issue$1[], Error>>;
1482
+ /**
1483
+ * Transitions a roadmap feature into the first configured terminal state
1484
+ * and rewrites the markdown file. Idempotent: if the feature is already
1485
+ * in a terminal state, this is a no-op.
1486
+ *
1487
+ * The orchestrator calls this after a successful agent exit so the feature
1488
+ * is no longer returned by `fetchCandidateIssues` on the next tick — and,
1489
+ * critically, after an orchestrator restart.
1490
+ */
1491
+ markIssueComplete(issueId: string): Promise<Result<void, Error>>;
1492
+ /**
1493
+ * Claims an issue by transitioning its status to "in-progress" and
1494
+ * writing the orchestratorId into the assignee field. Idempotent if
1495
+ * already claimed by the same orchestratorId.
1496
+ */
1497
+ claimIssue(issueId: string, orchestratorId: string): Promise<Result<void, Error>>;
1498
+ /**
1499
+ * Releases a claimed issue by transitioning back to the first active
1500
+ * state and clearing the assignee field.
1501
+ */
1502
+ releaseIssue(issueId: string): Promise<Result<void, Error>>;
1503
+ /**
1504
+ * Resolve the roadmap store anchored on the configured aggregate file path.
1505
+ * The shard backend is chosen when a sibling `roadmap.d/` exists next to the
1506
+ * file; otherwise the monolith file is read/written whole. Callers guard
1507
+ * `this.config.filePath` before invoking.
1508
+ */
1509
+ private resolveStore;
1510
+ /** Load the roadmap via the store (sharded or monolith), returning a Result. */
1511
+ private loadRoadmap;
1512
+ private findFeatureById;
1513
+ /**
1514
+ * Fetches full issue details for a list of identifiers.
1515
+ *
1516
+ * @param issueIds - List of issue IDs to fetch
1517
+ */
1518
+ fetchIssueStatesByIds(issueIds: string[]): Promise<Result<Map<string, Issue$1>, Error>>;
1519
+ /**
1520
+ * Maps a raw RoadmapFeature from the parser to the unified Issue model.
1521
+ */
1522
+ private mapFeatureToIssue;
1523
+ /**
1524
+ * Generates a deterministic, URL-safe identifier for a feature name.
1525
+ */
1526
+ private generateId;
1527
+ }
1528
+
1529
+ /**
1530
+ * Interface for a Linear GraphQL tool extension — a thin authenticated client
1531
+ * over Linear's GraphQL API (https://linear.app/developers/graphql).
1532
+ */
1533
+ interface LinearGraphQLExtension {
1534
+ /**
1535
+ * Execute a GraphQL operation. Resolves to `Ok(data)` (the `data` object of
1536
+ * the GraphQL envelope) on success, or `Err` on a transport failure, a non-2xx
1537
+ * HTTP status, or a GraphQL `errors` array.
1538
+ */
1539
+ query(query: string, variables?: Record<string, unknown>): Promise<Result<unknown, Error>>;
1540
+ }
1541
+ interface LinearGraphQLClientOptions {
1542
+ /**
1543
+ * Linear authentication token. A personal API key is sent verbatim in the
1544
+ * `Authorization` header; an OAuth access token must be passed already prefixed
1545
+ * with `Bearer ` (Linear's two supported schemes).
1546
+ */
1547
+ apiKey: string;
1548
+ /** Override the GraphQL endpoint (e.g. a proxy). Defaults to Linear's API. */
1549
+ endpoint?: string;
1550
+ /** Injectable fetch for tests; defaults to the global `fetch`. */
1551
+ fetchFn?: typeof fetch;
1552
+ }
1553
+ /**
1554
+ * Real Linear GraphQL client. Replaces {@link LinearGraphQLStub}, which only
1555
+ * logged the query and returned an empty object. POSTs the operation to Linear's
1556
+ * GraphQL endpoint with the API key, and normalizes the three failure modes
1557
+ * (transport throw, non-2xx HTTP, GraphQL `errors`) into a single `Err`.
1558
+ */
1104
1559
  declare class LinearGraphQLClient implements LinearGraphQLExtension {
1105
1560
  private readonly apiKey;
1106
1561
  private readonly endpoint;
@@ -1123,6 +1578,24 @@ declare class LinearGraphQLStub implements LinearGraphQLExtension {
1123
1578
  query(query: string, _variables?: Record<string, unknown>): Promise<Result<unknown, Error>>;
1124
1579
  }
1125
1580
 
1581
+ /**
1582
+ * AMR 4c: a single-agent quality-verdict source (ADR 0069). The escalation
1583
+ * mechanism + seam are complete; this supplies the *sound* signal that was
1584
+ * missing — a BASELINE-RELATIVE scan of the diff the agent introduced. Only the
1585
+ * ADDED lines are scanned, so a pre-existing pattern never counts against the
1586
+ * agent. This is sound (not approximate) because every security rule is
1587
+ * single-line, so per-added-line matching yields exactly the introduced findings.
1588
+ */
1589
+ /** One hunk of added lines from a unified diff, with its true start line. */
1590
+ interface IntroducedHunk {
1591
+ /** Repo-relative path of the changed file. */
1592
+ file: string;
1593
+ /** The added lines (leading `+` stripped), joined with `\n`. */
1594
+ addedContent: string;
1595
+ /** New-file line number the first added line sits at (for accurate findings). */
1596
+ startLine: number;
1597
+ }
1598
+
1126
1599
  /**
1127
1600
  * Structured event emitted when {@link WorkspaceManager.resolveBaseRef}
1128
1601
  * falls back past `origin/HEAD` and `origin/main`/`origin/master` to a
@@ -1162,6 +1635,27 @@ declare class WorkspaceManager {
1162
1635
  * Resolves the full path for an issue's workspace.
1163
1636
  */
1164
1637
  resolvePath(identifier: string): string;
1638
+ /**
1639
+ * AMR 4c: the lines the dispatched agent INTRODUCED in its worktree, as
1640
+ * per-hunk added-line blocks — the sound input for a baseline-relative quality
1641
+ * scan (pre-existing content is excluded by construction). Diffs the working
1642
+ * tree (so both committed AND uncommitted agent work is captured) against the
1643
+ * MERGE-BASE of the worktree HEAD and the base ref — merge-base, not the base
1644
+ * ref itself, so a base branch that advanced mid-dispatch never attributes
1645
+ * other merges to this agent. The seeded handoff overlay (`seedPaths`) is
1646
+ * excluded — those files are not the agent's work.
1647
+ */
1648
+ getIntroducedDiff(identifier: string): Promise<IntroducedHunk[]>;
1649
+ /**
1650
+ * AMR 4c v2: the SAME introduced change as {@link getIntroducedDiff}, but as the
1651
+ * RAW unified diff text (default context, NOT `--unified=0`) — the input an LLM
1652
+ * spec-satisfaction eval needs to judge whether the diff satisfies the spec.
1653
+ * Merge-base relative for the same reason (a base branch that advanced
1654
+ * mid-dispatch never attributes other merges here), and the seeded handoff
1655
+ * overlay is excluded via git `:(exclude)` pathspecs so the judge never reads
1656
+ * pre-seeded proposal/roadmap content as the agent's work.
1657
+ */
1658
+ getIntroducedDiffText(identifier: string): Promise<string>;
1165
1659
  /**
1166
1660
  * Discovers the git repository root from the workspace root directory.
1167
1661
  */
@@ -1473,133 +1967,235 @@ interface MaintenanceStatus {
1473
1967
  history: RunResult[];
1474
1968
  }
1475
1969
 
1476
- interface RoutingDecisionBusFilter {
1477
- skillName?: string;
1478
- mode?: string;
1479
- backendName?: string;
1480
- limit?: number;
1481
- }
1482
- interface RoutingDecisionBusOptions {
1483
- /** Default 500. Bound on the in-memory ring buffer. */
1484
- capacity?: number;
1485
- /**
1486
- * Logger for the structured `routing-decision` line (O1) and for
1487
- * one-off warn() when a subscriber throws (S6). When omitted, the
1488
- * bus silently swallows subscriber errors (test-mode default).
1489
- */
1490
- logger?: StructuredLogger;
1491
- }
1492
1970
  /**
1493
- * Spec B Phase 4 (D8): in-process bus + ring buffer for
1494
- * {@link RoutingDecision} events. One emit() per
1495
- * {@link BackendRouter.resolve} call; subscribers receive the
1496
- * decision synchronously after the ring buffer is updated.
1497
- *
1498
- * Subscriber errors are isolated (caught + logged, never thrown
1499
- * back to the emitter) so a misbehaving subscriber cannot block a
1500
- * dispatch. (S6)
1501
- *
1502
- * Capacity-bound (default 500) via Array.shift() — acceptable for
1503
- * v1 (see plan C4); switch to circular indexing if 24h dispatch
1504
- * volume ever pushes 10K+ records/min.
1971
+ * D10 vertical escalation. Per-`coherenceUnit` quality-failure counter that
1972
+ * raises the unit's floor tier one step on the Nth consecutive QUALITY failure
1973
+ * (never transport that is the shipped per-model breaker). Monotonic +
1974
+ * `strong`-capped cannot loop or thrash. Naming mirrors
1975
+ * `LocalModelResolver.recordSuccess/recordFailure` (local-model-resolver.ts:460/474).
1505
1976
  */
1506
- declare class RoutingDecisionBus {
1507
- private readonly ringBuffer;
1508
- private readonly listeners;
1509
- private readonly capacity;
1510
- private readonly logger;
1511
- constructor(opts?: RoutingDecisionBusOptions);
1512
- emit(decision: RoutingDecision): void;
1513
- recent(filter?: RoutingDecisionBusFilter): RoutingDecision[];
1514
- subscribe(listener: (d: RoutingDecision) => void): () => void;
1977
+ declare class EscalationState {
1978
+ private readonly threshold;
1979
+ private readonly units;
1980
+ constructor(threshold?: number);
1981
+ /**
1982
+ * The current escalation floor for a unit — the minimum tier `route()` must
1983
+ * resolve at. Defaults to `'fast'` (no-op floor) for an unknown/absent unit,
1984
+ * so an un-escalated request derives its tier normally.
1985
+ */
1986
+ floorFor(coherenceUnit?: string): CapabilityTier;
1987
+ /** D10 mechanism flag: has this unit ever climbed a tier? (Phase 6 reads this for Tier-A disqualification.) */
1988
+ isEscalated(coherenceUnit?: string): boolean;
1989
+ /**
1990
+ * AMR observability: the coherence units that have climbed ABOVE the `fast`
1991
+ * floor, with their current floor tier. Operator status only (read-only) — an
1992
+ * empty list means nothing has escalated. Units still at `fast` are omitted.
1993
+ */
1994
+ climbedUnits(): Array<{
1995
+ coherenceUnit: string;
1996
+ floor: CapabilityTier;
1997
+ }>;
1515
1998
  /**
1516
- * Spec B Phase 5 (review-S2 fix): release all subscriber references so
1517
- * teardown can complete without anchoring closures. Called from
1518
- * `Orchestrator.stop()` before nulling the bus reference. The bus
1519
- * remains usable after clear `subscribe()` works as normal.
1999
+ * SC16: a QUALITY failure at `tier` increments this unit's counter; on the
2000
+ * Nth (threshold) consecutive failure the floor climbs one step (fast→standard
2001
+ * →strong), the count resets, and `escalated` latches true. `strong` is the
2002
+ * ceiling: a threshold-crossing failure already at `strong` returns
2003
+ * 'exhausted' (router emits routing:escalation-exhausted). `ok` clears the
2004
+ * in-progress count but leaves the raised floor (monotonic per D10).
2005
+ *
2006
+ * `_tier` (the tier the outcome occurred at) is ADVISORY/UNUSED today: the climb
2007
+ * is driven entirely off the unit's own `state.floorTier`, not off this argument.
2008
+ * It is kept in the signature so the call-site contract stays stable for a future
2009
+ * refinement that guards against out-of-order (stale-tier) reports. (Note: this is
2010
+ * NOT positionally mirroring `LocalModelResolver.recordFailure`, which keys on a
2011
+ * model identifier and takes no tier — the earlier comment claiming that shape was
2012
+ * misleading.)
1520
2013
  */
1521
- clearListeners(): void;
2014
+ recordOutcome(coherenceUnit: string, _tier: CapabilityTier, ok: boolean): 'ok' | 'escalated' | 'exhausted';
1522
2015
  }
1523
2016
 
1524
- interface BackendRouterOptions {
1525
- backends: Record<string, BackendDef>;
1526
- routing: RoutingConfig;
2017
+ interface AdaptiveRouterDeps {
2018
+ router: BackendRouter;
2019
+ registry: BackendCapabilityRegistry;
2020
+ policy: RoutingPolicy;
2021
+ /**
2022
+ * Complexity classifier. Accepts a sync verdict-provider (Phase-3 conservative
2023
+ * stub) OR an async classifier (D4 live cascade); `route()` awaits either shape.
2024
+ * A rejection/throw is caught and degraded to `{moderate, low}` (never blocks
2025
+ * dispatch — Failure modes / D4).
2026
+ */
2027
+ classify: (req: RoutingRequest) => ComplexityVerdict | Promise<ComplexityVerdict>;
2028
+ /** name → provider type, derived from agent.backends (Task 6). REQUIRED when policy.allowedProviders is set. */
2029
+ providerOf?: (name: string) => BackendDef['type'] | undefined;
2030
+ /** Injected spend snapshot (D8/S1-001). Defaults to 0-spend. */
2031
+ budgetState?: () => {
2032
+ spentUsd: number;
2033
+ };
1527
2034
  /**
1528
- * Spec B Phase 4 (D8): when present, every resolve() emits its
1529
- * decision onto the bus. The bus owns the structured log line + ring
1530
- * buffer; the router stays a pure resolution function.
2035
+ * D10 vertical escalation state. When present, `route()` uses
2036
+ * `escalation.floorFor(req.coherenceUnit)` as the tier floor (a climbed unit
2037
+ * resolves higher); when absent, the floor is the no-op `'fast'`.
2038
+ */
2039
+ escalation?: EscalationState;
2040
+ /**
2041
+ * D10 steward-escalation seam. Invoked with the `coherenceUnit` when
2042
+ * `recordOutcome` returns `'exhausted'` (floor already `strong`, re-crossed the
2043
+ * threshold). The orchestrator binds this to `routingDecisionBus`/logger to emit
2044
+ * `routing:escalation-exhausted`.
2045
+ */
2046
+ onExhausted?: (coherenceUnit: string) => void;
2047
+ /**
2048
+ * SC9: enriched-decision bus. The shipped `resolveDecisionAndDef` emits the
2049
+ * BASE decision inside the D2-frozen router (no `complexity`/`tierRequired`/
2050
+ * `estCostUsd`). When `AdaptiveRouter` owns live dispatch it emits the
2051
+ * ENRICHED decision here so a subscriber actually receives the AMR telemetry.
2052
+ * Absent ⇒ no enriched emit (and when AMR is off the router isn't constructed,
2053
+ * so nothing changes on the bus — SC8/SC17 preserved).
1531
2054
  */
1532
2055
  decisionBus?: RoutingDecisionBus;
1533
2056
  }
1534
2057
  /**
1535
- * BackendRouter (Spec B Phase 1)
1536
- *
1537
- * Owns the lookup from a {@link RoutingUseCase} (a discriminated query
1538
- * — tier, intelligence layer, maintenance, chat, isolation, **skill**,
1539
- * **mode**) to a {@link RoutingDecision} naming a chosen backend and
1540
- * the full resolution path that produced it.
1541
- *
1542
- * Resolution order (D2): invocation override -> per-skill -> per-mode
1543
- * -> existing per-tier/intelligence/isolation/maintenance/chat ->
1544
- * `routing.default`. Within each source, fallback chain entries are
1545
- * tried in declared order; first existing backend wins. Unknown
1546
- * entries are recorded with `outcome: 'unknown-backend'` and the walk
1547
- * continues.
2058
+ * AMR Phase 3 (D2): wraps never modifies — the shipped {@link BackendRouter}.
1548
2059
  *
1549
- * Construction-time validation guarantees every name referenced by
1550
- * `routing` is present in `backends` so the static-config case can
1551
- * never produce a runtime exhaustion throw. The runtime throw at the
1552
- * end of `resolve()` is a safety net for future dynamic-backends
1553
- * scenarios where a chain entry can become unknown post-construction.
2060
+ * `route()` classifies the request, derives the required {@link CapabilityTier}
2061
+ * via `deriveRequiredTier`, picks the cheapest qualifying backend
2062
+ * (`selectCheapestQualifying`), and delegates the actual resolution to
2063
+ * `router.resolveDecisionAndDef` enriching the returned {@link RoutingDecision}
2064
+ * with `complexity`/`tierRequired`/`estCostUsd` (SC9). The shipped router's
2065
+ * identity/default chain still owns the final backend lookup, so when tier
2066
+ * selection abstains (`undefined`) dispatch falls through unchanged.
1554
2067
  */
1555
- declare class BackendRouter {
1556
- private readonly backends;
1557
- private readonly routing;
1558
- private readonly decisionBus;
1559
- constructor(opts: BackendRouterOptions);
2068
+ declare class AdaptiveRouter {
2069
+ private readonly deps;
2070
+ /**
2071
+ * D8 live spend accumulator. Monotonic sum of `estCostUsd` over every decision
2072
+ * this router has made. `route()` reads it (via the `budgetState` default)
2073
+ * BEFORE deriving a tier, so `deriveRequiredTier`'s budget clamp actually fires
2074
+ * as spend accrues — previously `budgetState` was an un-wired `{ spentUsd: 0 }`
2075
+ * stub, so the clamp was dead.
2076
+ *
2077
+ * Semantics (deliberately modest — do NOT read this as a hard ceiling):
2078
+ * - **Soft, lagging cap.** The clamp reads spend accrued from PRIOR dispatches;
2079
+ * a burst of concurrent dispatches all read a stale-low total before any of
2080
+ * them accrues, so a burst can overshoot `capUsd` before the clamp engages.
2081
+ * This is a degrade *signal*, not an admission gate.
2082
+ * - **Single-step degrade.** `deriveRequiredTier` lowers the tier by exactly ONE
2083
+ * step under budget pressure and never below the D5 privacy veto floor — it
2084
+ * does not throttle progressively toward `fast`.
2085
+ * - **Monotonic** (NOT the bounded `projectTelemetry` ring-sum): a long run must
2086
+ * not evict early spend and un-clamp. Preserved across `setPolicy` (instance
2087
+ * persists) — note a *lowered* `capUsd` then clamps immediately and, since the
2088
+ * total never decreases, irreversibly for the rest of the run.
2089
+ * An injected `deps.budgetState` overrides this for the clamp (DI/tests).
2090
+ */
2091
+ private spentUsd;
2092
+ constructor(deps: AdaptiveRouterDeps);
2093
+ /**
2094
+ * Construct an `AdaptiveRouter` from an orchestrator's `agent.backends`
2095
+ * (plus optional LMLM pool). Builds the capability registry via
2096
+ * `buildCapabilityRegistry` and — crucially — derives `providerOf`
2097
+ * UNCONDITIONALLY from `agent.backends` (name → `def.type`).
2098
+ *
2099
+ * Phase-1 finding (capability-registry.ts:60-66): passing an allowlist to
2100
+ * `selectCheapestQualifying` WITHOUT a `providerOf` fail-closes EVERY request
2101
+ * (silent DoS), because every candidate's provider reads back as `undefined`.
2102
+ * Deriving `providerOf` here means enabling the (Phase-5) allowlist branch can
2103
+ * never accidentally deny-all.
2104
+ */
2105
+ static fromConfig(args: {
2106
+ router: BackendRouter;
2107
+ backends: Record<string, BackendDef>;
2108
+ pool?: PoolStateProvider;
2109
+ policy: RoutingPolicy;
2110
+ classify: (req: RoutingRequest) => ComplexityVerdict | Promise<ComplexityVerdict>;
2111
+ budgetState?: () => {
2112
+ spentUsd: number;
2113
+ };
2114
+ /** D10: injected escalation state; defaults to a fresh one seeded from `policy.escalationThreshold`. */
2115
+ escalation?: EscalationState;
2116
+ /** D10: steward-escalation seam bound by the orchestrator to the decision bus/logger. */
2117
+ onExhausted?: (coherenceUnit: string) => void;
2118
+ /** SC9: enriched-decision bus (the same instance dispatch emits base decisions onto). */
2119
+ decisionBus?: RoutingDecisionBus;
2120
+ }): AdaptiveRouter;
2121
+ /**
2122
+ * AMR Phase 5 (D1): hot-swap the routing policy in place. Every policy field
2123
+ * takes effect on the NEXT dispatch EXCEPT `escalationThreshold` (see note) —
2124
+ * the capability registry and `providerOf` are derived from `agent.backends`,
2125
+ * NOT from `policy`, so a policy edit leaves them untouched; `route()` /
2126
+ * `buildConstraints` / `deriveRequiredTier` all read `this.deps.policy` fresh,
2127
+ * so the swapped-in tier matrix / privacy floor / allowlist / budget all apply.
2128
+ * The live {@link EscalationState} is preserved by reference: a unit's climbed
2129
+ * floor survives the swap (a policy edit must not reset accumulated escalation).
2130
+ * The monotonic spend accumulator also persists — so a swap that LOWERS `capUsd`
2131
+ * clamps immediately and irreversibly for the rest of the run (see `spentUsd`).
2132
+ *
2133
+ * Threshold note: the `EscalationState` was seeded with the ORIGINAL policy's
2134
+ * `escalationThreshold` and is intentionally NOT re-seeded here — re-seeding
2135
+ * would conflate "raise the bar" with "reset progress". Preserving climbed
2136
+ * floors is the invariant (SC1); a live threshold change is out of scope (D1).
2137
+ */
2138
+ setPolicy(policy: RoutingPolicy): void;
1560
2139
  /**
1561
- * Resolve a {@link RoutingUseCase} to a {@link RoutingDecision}.
2140
+ * AMR Phase 5 (D2): project the ENRICHED routing decisions in the bus ring
2141
+ * buffer into the Shuttle telemetry wire shape ({@link RoutingTelemetry}).
1562
2142
  *
1563
- * @param useCase the routing query
1564
- * @param opts.invocationOverride if set and the named backend exists,
1565
- * beats all other sources (D7 the `--backend <name>` escape hatch)
2143
+ * Non-destructive this backs the idempotent `GET /api/v1/routing/telemetry`,
2144
+ * so it reads (never clears) the ring; the ring self-evicts FIFO at capacity
2145
+ * and Shuttle dedups by `decisionTs`. `spentUsd` is therefore a sum over the
2146
+ * RETAINED ring (telemetry, not billing-of-record).
2147
+ *
2148
+ * Filters to ENRICHED decisions only — those `AdaptiveRouter.route()` emitted,
2149
+ * carrying `estCostUsd`+`tierRequired`. The SAME bus also carries the BASE emit
2150
+ * from `BackendRouter.resolveDecisionAndDef` (neither field), so an unfiltered
2151
+ * projection would double-count rows and under-fill `spentUsd`. Returns an
2152
+ * empty payload when no bus is wired or no enriched decisions exist.
1566
2153
  */
1567
- resolve(useCase: RoutingUseCase, opts?: {
1568
- invocationOverride?: string;
1569
- }): RoutingDecision;
2154
+ projectTelemetry(): RoutingTelemetry;
1570
2155
  /**
1571
- * Returns the {@link BackendDef} reference for the resolved name.
1572
- * Identity-equal to the entry in `backends` (no copy) so callers
1573
- * relying on reference equality (SC21) continue to work.
2156
+ * D8: the EFFECTIVE spend total the budget clamp reads — the injected
2157
+ * `budgetState` when present, otherwise the router's own monotonic accumulator.
2158
+ * Returning the effective value (not blindly the internal tally) means an
2159
+ * operator reading this always sees the number that actually drove routing.
1574
2160
  */
1575
- resolveDefinition(useCase: RoutingUseCase, opts?: {
1576
- invocationOverride?: string;
1577
- }): BackendDef;
2161
+ getSpentUsd(): number;
1578
2162
  /**
1579
- * Spec B Phase 4 (closes P1-IMP-2): a single resolve() + def lookup
1580
- * for callers that need both. Replaces the previous pattern of
1581
- * `resolveDefinition(useCase) + resolve(useCase)` which produced two
1582
- * RoutingDecision emissions per dispatch doubling routing-decision
1583
- * log volume now that Phase 4 emits.
1584
- *
1585
- * Identity-equal `BackendDef` (no copy) so callers relying on
1586
- * reference equality (SC21) continue to work.
2163
+ * AMR observability: the live operator status — budget spend-vs-cap (using the
2164
+ * monotonic accumulator that drives the clamp, NOT the telemetry ring sum),
2165
+ * the coherence units that have climbed their escalation floor, and the active
2166
+ * provider allowlist. Called only on a live router, so `active` is always true.
1587
2167
  */
1588
- resolveDecisionAndDef(useCase: RoutingUseCase, opts?: {
1589
- invocationOverride?: string;
1590
- }): {
2168
+ getStatus(): RoutingStatus;
2169
+ route(req: RoutingRequest): Promise<{
1591
2170
  decision: RoutingDecision;
1592
2171
  def: BackendDef;
1593
- };
2172
+ }>;
1594
2173
  /**
1595
- * The pre-Spec-B resolution helper: returns the configured
1596
- * {@link RoutingValue} for tier/intelligence/isolation/maintenance/chat
1597
- * use cases (or `undefined` for skill/mode use cases, which are owned
1598
- * by the per-skill / per-mode steps in {@link resolve}). Returning
1599
- * `undefined` lets the caller fall through to `routing.default`.
2174
+ * D4 fail-safe: await the (possibly async) classifier; if it rejects or throws,
2175
+ * degrade to a conservative `{ level:'moderate', confidence:'low' }` verdict.
2176
+ * Classification NEVER blocks dispatch a classifier failure/timeout must not
2177
+ * propagate out of `route()`.
1600
2178
  */
1601
- private resolveExistingUseCase;
1602
- private validateReferences;
2179
+ private classifySafe;
2180
+ /**
2181
+ * D10 outcome feedback (mirrors LocalModelResolver.recordSuccess/recordFailure).
2182
+ * Only QUALITY failures are reported here; transport/inference errors go to the
2183
+ * shipped per-model breaker and must NOT reach this path (they never
2184
+ * double-count). A quality failure that re-crosses the threshold while the floor
2185
+ * is already `strong` returns `'exhausted'` from EscalationState, at which point
2186
+ * the injected `onExhausted` seam emits `routing:escalation-exhausted` for
2187
+ * steward escalation. No-op when no EscalationState is injected.
2188
+ */
2189
+ recordOutcome(coherenceUnit: string, tier: CapabilityTier, ok: boolean): void;
2190
+ private selectTarget;
2191
+ /**
2192
+ * Translate the request's declared constraints into {@link SelectConstraints}.
2193
+ * `policy.privacyFloor`, the provider allowlist (AMR Phase 5, D3), and the
2194
+ * per-request capability needs are threaded in. `providerOf` is derived
2195
+ * unconditionally in `fromConfig`, so the allowlist can never silently
2196
+ * fail-close every request (Phase-1 finding, capability-registry.ts:60-66).
2197
+ */
2198
+ private buildConstraints;
1603
2199
  }
1604
2200
 
1605
2201
  /**
@@ -1668,6 +2264,13 @@ declare class Orchestrator extends EventEmitter {
1668
2264
  * construction time. Eliminating this fallback is autopilot Phase 4+.
1669
2265
  */
1670
2266
  private backendFactory;
2267
+ /**
2268
+ * AMR Phase 3 (D11): opt-in adaptive router. Constructed ONLY when
2269
+ * `agent.routing.policy` is present and non-empty; `null` otherwise so
2270
+ * dispatch stays byte-identical on the shipped `BackendRouter`
2271
+ * (SC8/SC17/SC19). Exposed for tests via {@link getAdaptiveRouter}.
2272
+ */
2273
+ private adaptiveRouter;
1671
2274
  /**
1672
2275
  * Spec B Phase 4 (D8): per-orchestrator in-process bus for
1673
2276
  * `RoutingDecision` events. Constructed alongside backendFactory when
@@ -1698,6 +2301,13 @@ declare class Orchestrator extends EventEmitter {
1698
2301
  * so this map is the single source of truth post-migration.
1699
2302
  */
1700
2303
  private localResolvers;
2304
+ /**
2305
+ * Consumption Phase 1 (T2): bus listener that debounce-refreshes every local
2306
+ * resolver when a `local-models:pool` mutation fires, so a just-installed or
2307
+ * swapped model becomes usable within the refresh window instead of waiting up
2308
+ * to `probeIntervalMs` for the next poll. Held for removal in {@link stop}.
2309
+ */
2310
+ private poolRefreshListener;
1701
2311
  /** Phase 4 (D5): pool-state port shared by all local/pi resolvers. Null when LMLM disabled. */
1702
2312
  private poolStateProvider;
1703
2313
  private poolStateStore;
@@ -1735,6 +2345,10 @@ declare class Orchestrator extends EventEmitter {
1735
2345
  * see the Phase 2 candidate-parser gap noted on `startRefreshScheduler`.
1736
2346
  */
1737
2347
  private modelRecommender;
2348
+ /** Live HF candidate discovery (injectable for tests so startup makes no network calls). */
2349
+ private readonly discoverCandidatesFn;
2350
+ /** Snapshot of the last candidate seeding, surfaced to the refresh route. */
2351
+ private candidateSourceState;
1738
2352
  /** Test seam: injected timer/clock for the scheduler so no real 24h timer runs. */
1739
2353
  private readonly schedulerTimerOverride;
1740
2354
  /**
@@ -1756,6 +2370,15 @@ declare class Orchestrator extends EventEmitter {
1756
2370
  */
1757
2371
  private localModelStatusUnsubscribes;
1758
2372
  private pipeline;
2373
+ /**
2374
+ * AMR live-classifier provider (final-review finding #2). The complexity
2375
+ * cascade may spend a fast-tier LLM tie-break; it borrows the SEL-layer
2376
+ * AnalysisProvider. Built lazily on first classify (the AdaptiveRouter is
2377
+ * constructed before start(), so this cannot be eager); `null` means "no
2378
+ * provider — cascade stays fully offline / static-only". `undefined` means
2379
+ * "not yet resolved".
2380
+ */
2381
+ private complexityProvider;
1759
2382
  private analysisArchive;
1760
2383
  private graphStore;
1761
2384
  private claimManager;
@@ -1813,6 +2436,8 @@ declare class Orchestrator extends EventEmitter {
1813
2436
  now?: () => number;
1814
2437
  random?: () => number;
1815
2438
  };
2439
+ /** Live-candidate-discovery seam: tests inject a fake so startup makes no HF calls. */
2440
+ discoverCandidates?: (opts: DiscoverCandidatesOptions) => Promise<DiscoverCandidatesResult>;
1816
2441
  });
1817
2442
  /**
1818
2443
  * Phase 5: construct the OTLP/HTTP trace exporter and wire telemetry fanout.
@@ -1868,6 +2493,19 @@ declare class Orchestrator extends EventEmitter {
1868
2493
  */
1869
2494
  private initMaintenance;
1870
2495
  private createIntelligencePipeline;
2496
+ /**
2497
+ * AMR live-classifier provider resolution (final-review finding #2). The
2498
+ * complexity cascade's OPTIONAL fast-tier tie-break borrows the SEL-layer
2499
+ * AnalysisProvider (the same one intelligence enrichment uses). Resolved and
2500
+ * memoized on first classify because the AdaptiveRouter is constructed BEFORE
2501
+ * start(), so the provider cannot be resolved eagerly.
2502
+ *
2503
+ * Returns `undefined` when no provider is available (intelligence disabled, no
2504
+ * backendFactory, or the layer resolves to nothing) — the cascade then stays
2505
+ * fully offline and returns the static verdict (never throws). A build failure
2506
+ * degrades the same way: static-only, never blocks dispatch (D4).
2507
+ */
2508
+ private resolveComplexityProvider;
1871
2509
  /**
1872
2510
  * Lazily initializes the ClaimManager if it hasn't been created yet.
1873
2511
  * Called from both start() and asyncTick() to avoid duplicating the init block.
@@ -1954,10 +2592,136 @@ declare class Orchestrator extends EventEmitter {
1954
2592
  private processAgentEvent;
1955
2593
  private awaitRateLimitClearance;
1956
2594
  private runAgentInBackgroundTask;
2595
+ /**
2596
+ * AMR 4c (ADR 0069): the sound single-agent quality-verdict feeder. On a normal
2597
+ * exit, when AMR is active, run a BASELINE-RELATIVE security scan of the lines
2598
+ * the agent INTRODUCED (only added lines — a pre-existing pattern never counts);
2599
+ * a NEW error-severity finding → `'quality-fail'`, which climbs the coherence
2600
+ * unit's escalation floor. Success stays `neutral` (returns `undefined`) — a
2601
+ * premature `'quality-pass'` would clear accumulating failures (ADR 0069).
2602
+ *
2603
+ * Fully guarded: any error (git/scan/parse) → `undefined` → `neutral`, so this
2604
+ * NEVER breaks completion. No-op (zero cost) when AMR is off, keeping the
2605
+ * dispatch path byte-identical. Staged workflows use their own per-stage gate
2606
+ * feeder; this is the single-agent equivalent.
2607
+ */
2608
+ private deriveSingleAgentQualityVerdict;
2609
+ /**
2610
+ * AMR 4c v2: opt-in LLM spec-satisfaction verdict. Gated on
2611
+ * `routing.policy.acceptanceEval.enabled` (HEAVY — one model call + latency per
2612
+ * exit, so separate from the always-on cheap security scan), a present spec, and
2613
+ * an available analysis provider. Runs the shared `OutcomeEvaluator` over the
2614
+ * introduced diff vs the spec's judgment section, reusing the SEL-layer provider
2615
+ * the live classifier already builds; a BLOCKING verdict (high-confidence
2616
+ * NOT_SATISFIED) → `'quality-fail'`. Conservative + fully guarded: no
2617
+ * spec / no provider / empty diff / any error → `undefined` (neutral). The mapper
2618
+ * only ever emits the negative, so success can never become a premature
2619
+ * `'quality-pass'`. An absent GraphStore falls back to an ephemeral one — the
2620
+ * evaluator's `execution_outcome` persistence is best-effort and never blocks.
2621
+ */
2622
+ private deriveAcceptanceEvalVerdict;
1957
2623
  /**
1958
2624
  * Informs the state machine that an agent worker has exited.
1959
2625
  */
1960
2626
  private emitWorkerExit;
2627
+ /**
2628
+ * AMR Phase 4 (D10/SC16): feed a dispatch outcome into vertical escalation.
2629
+ * No-op unless an AdaptiveRouter is live (policy present) AND this dispatch was
2630
+ * AMR-routed (a `lastRoutedTier` was captured). Transport outcomes never reach
2631
+ * `recordOutcome` — the shipped per-model breaker owns those, so the two signals
2632
+ * never double-count. The coherence unit is the issue id (D6 issue-grain pinning).
2633
+ */
2634
+ private recordAmrOutcome;
2635
+ /**
2636
+ * AMR steward-escalation seam (D10, findings item 1 + 2). Queues a `needs-human`
2637
+ * interaction for a coherence unit whose routing hard-failed — either the vertical
2638
+ * escalation exhausted the `strong` ceiling (`escalation-exhausted`) or the
2639
+ * fail-closed selector left no compliant backend (`privacy-no-match`). Both ride
2640
+ * the SAME `needs-human` mechanism as every other escalation. The `RoutingError`
2641
+ * code disambiguates the two on the steward's channel. The coherence unit is the
2642
+ * issue id (D6 issue-grain pinning); title/description are recovered from running
2643
+ * state when still present. Fire-and-forget + `.catch` — a queue write must never
2644
+ * block or throw out of the dispatch/outcome path.
2645
+ */
2646
+ private escalateRoutingToHuman;
2647
+ /**
2648
+ * AMR dispatch-boundary routing-failure handler (finding #3 + live-wiring
2649
+ * review blocker). When `AdaptiveRouter.route()` throws a fail-closed
2650
+ * `PrivacyNoMatch` (`RoutingError` code `'privacy-no-match'`) at dispatch, that
2651
+ * distinct signal MUST NOT be swallowed by the generic transport/dispatch-error
2652
+ * path (S4-001): it is not a runner/transport failure, so it must never be
2653
+ * recorded as one or feed the vertical escalation breaker. Instead it emits a
2654
+ * DISTINCT `routing:no-tier-match` steward escalation (needs-human, same
2655
+ * mechanism as `onExhausted`) carrying the coherence unit + reason.
2656
+ *
2657
+ * It is ALSO deterministic — the `privacyFloor`/allowlist that emptied the
2658
+ * candidate set is config-driven, so re-dispatch would throw the SAME
2659
+ * `PrivacyNoMatch`. Therefore this path is TERMINAL: it drives the unit to the
2660
+ * `canceled` lane and removes it from `running`/`claimed` directly, rather than
2661
+ * routing through `emitWorkerExit('error')` (whose state-machine error branch
2662
+ * enqueues a retry whenever the retry budget is not yet exhausted — which would
2663
+ * re-dispatch, re-fail closed, and re-escalate up to `maxRetries` times). No
2664
+ * retry is scheduled, no transport outcome is recorded, and exactly one
2665
+ * needs-human escalation is queued. Fail-closed is preserved — `route()` already
2666
+ * refused to pick a non-compliant backend, and returning `true` here stops the
2667
+ * caller from falling through to any further routing.
2668
+ *
2669
+ * Returns `true` when the boundary CLAIMED the error (`privacy-no-match` or the
2670
+ * hard-cap `budget-exhausted`), so the caller returns without ANY
2671
+ * `emitWorkerExit`. Returns `false` for any other error (including
2672
+ * `escalation-exhausted`, which the `onExhausted` seam owns) so the generic
2673
+ * dispatch-error path runs unchanged.
2674
+ */
2675
+ private handleRoutingFailure;
2676
+ /**
2677
+ * AMR live-wiring review blocker: terminally retire a unit whose dispatch failed
2678
+ * closed (`privacy-no-match`). Mirrors the terminal side of a worker exit —
2679
+ * remove the unit from `running` and release its `claimed` slot — then persist
2680
+ * the terminal `canceled` lane (`abandon`), matching how retry-exhausted
2681
+ * escalations settle. Crucially it does NOT run the state-machine `worker_exit`
2682
+ * reducer, so no `scheduleRetry` effect is emitted. Best-effort lane persistence
2683
+ * (`persistLaneSafe` never throws). No transport/escalation outcome is recorded —
2684
+ * that stays the sole job of the single `routing:no-tier-match` escalation already
2685
+ * queued by `escalateRoutingToHuman`.
2686
+ */
2687
+ private finalizeRoutingTerminal;
2688
+ /**
2689
+ * split-routing Phase 4 (D6/SC5) — terminal SUCCESS settle for a workflow unit.
2690
+ * The real `WorkflowEngineContext.emitWorkflowSuccess` forwards here (bound via
2691
+ * the context's `settleSuccess` dep in `dispatchIssue`). It reproduces the
2692
+ * `worker_exit`/`reason==='normal'` reducer BY HAND (state-machine.ts:457,467-474):
2693
+ * `running.delete` → `completed.set(now)` → `claimed.delete` → `cleanWorkspace`
2694
+ * effect → then persists the terminal `success` lane and emits one state change.
2695
+ *
2696
+ * It deliberately does NOT route through `emitWorkerExit`/`handleWorkerExit`
2697
+ * (completion/handler.ts): that fires the ISSUE-keyed `finishRecording(issueId,
2698
+ * attempt)` + `recordAmrOutcome`, but the engine already ran PER-STAGE recorders
2699
+ * (`stageAttemptKey(index, attempt)`) and per-stage `recordOutcome`. Going through
2700
+ * the worker-exit path would (a) finish a recording never started at the
2701
+ * issue-attempt key and (b) double-feed the escalation state. This is the ONE
2702
+ * hand-reproduced reducer sequence in Phase 4; the `worker_exit` reducer itself
2703
+ * stays untouched (Task 12 pins it) so the two remain in sync.
2704
+ *
2705
+ * `runs` are the per-stage records (best-effort telemetry; the per-stage cost is
2706
+ * already attributable via the recorders). Never throws — a success settle must
2707
+ * complete the single terminal transition (D6).
2708
+ */
2709
+ private settleWorkflowSuccess;
2710
+ /**
2711
+ * split-routing Phase 4 (D6/I1/SC5) — terminal FAILURE/safety-net settle for a
2712
+ * workflow unit. The real context's `finalizeWorkflowTerminal` forwards here
2713
+ * (bound via `settleTerminal`). Composed from the `finalizeRoutingTerminal`
2714
+ * pattern (`running.delete` + `claimed.delete` + `persistLaneSafe('abandon')`,
2715
+ * orchestrator.ts:2388-2394) PLUS a single `needs-human` escalation
2716
+ * (escalateRoutingToHuman-style queue push, :2301-2316) PLUS `cleanWorkspace`
2717
+ * (S5). It must NEVER rethrow — the engine's `catch` calls it on the I1 safety
2718
+ * net, so a throw here would defeat the single-exit guarantee.
2719
+ *
2720
+ * It is NOT a verbatim `finalizeRoutingTerminal` call (that lacks the needs-human
2721
+ * + cleanWorkspace the Phase-3 terminal contract pinned). Exactly one needs-human
2722
+ * per terminal transition.
2723
+ */
2724
+ private settleWorkflowTerminal;
1961
2725
  /**
1962
2726
  * Hermes Phase 3: wire in-process notification sinks against the
1963
2727
  * orchestrator's event bus (`this`). A misconfigured sink (unknown kind,
@@ -1993,6 +2757,14 @@ declare class Orchestrator extends EventEmitter {
1993
2757
  * `store.load()`.
1994
2758
  */
1995
2759
  private initModelPool;
2760
+ /**
2761
+ * Resume installs interrupted by a restart. A proposal left `installing` had
2762
+ * its background `ollama pull` cut short when the orchestrator went down; the
2763
+ * pull is idempotent (ollama resumes from cached blobs), so we re-drive it.
2764
+ * Fire-and-forget with its own error isolation — a resumed multi-GB download
2765
+ * must not block startup, and a re-drive failure only logs.
2766
+ */
2767
+ private redriveInterruptedInstalls;
1996
2768
  /**
1997
2769
  * LMLM Phase 7 wiring: apply the operator's configured pool bounds (disk
1998
2770
  * budget + org/family allowlist) from `localModels.pool` to the live pool.
@@ -2015,6 +2787,17 @@ declare class Orchestrator extends EventEmitter {
2015
2787
  * breadth is the only piece deferred to the Phase 2 recommender.
2016
2788
  */
2017
2789
  private startRefreshScheduler;
2790
+ /** (Re)build the recommender over `candidates` and record the seeding source. */
2791
+ private seedRecommender;
2792
+ /**
2793
+ * Refresh ranking candidates live from HuggingFace, merge the curated
2794
+ * `ollamaName`/`family` tags from the frozen snapshot (so results stay
2795
+ * installable — decision A), and re-seed the recommender. Fail-closed: on any
2796
+ * error or an empty installable result, the current candidates stand. Runs a
2797
+ * `forceRefresh` tick so recommendations + proposals reflect the fresh set.
2798
+ * Used by both the startup background refresh and the operator "Refresh" button.
2799
+ */
2800
+ private refreshCandidatesLive;
2018
2801
  /** Resolve the hardware profile for a refresh tick (operator override wins). */
2019
2802
  private detectLmlmHardware;
2020
2803
  /** Map the on-disk model-proposal queue to F7 dedup pairs (open→pending, rejected→rejected). */
@@ -2041,6 +2824,51 @@ declare class Orchestrator extends EventEmitter {
2041
2824
  * single-backend config bypassed agent.backends synthesis.
2042
2825
  */
2043
2826
  getRoutingDecisionBus(): RoutingDecisionBus | null;
2827
+ /**
2828
+ * AMR Phase 3 (D11): the opt-in adaptive router, or `null` when no
2829
+ * `routing.policy` is configured (the default-off path). Exposed for the
2830
+ * SC8/SC17/SC19 default-off proof: `null` here means dispatch stays on the
2831
+ * shipped `BackendRouter`, byte-identical, with no classify()/telemetry.
2832
+ */
2833
+ getAdaptiveRouter(): AdaptiveRouter | null;
2834
+ /**
2835
+ * AMR Phase 3 (D11) / Phase 5 (D1): construct the opt-in AdaptiveRouter for a
2836
+ * policy. Extracted from the constructor so runtime ingestion
2837
+ * (`ingestRoutingPolicy`) builds a router IDENTICAL to the constructor's —
2838
+ * same live classify seam, strong-cap escalation-exhaustion hard-fail-to-human
2839
+ * (D10), and enriched-decision bus (SC9). Precondition: the routing subsystem
2840
+ * exists (`backendFactory` + `agent.backends` present); callers guard.
2841
+ */
2842
+ private buildAdaptiveRouter;
2843
+ /**
2844
+ * AMR Phase 5 (D1/D5): ingest a routing policy pushed at runtime by the
2845
+ * Shuttle control plane (`PUT /api/v1/routing/policy`). Hot-swaps the live
2846
+ * router:
2847
+ * - empty policy (`{}` / no activating fields) → `adaptiveRouter = null`
2848
+ * (default-off restored, D5 — byte-identical dispatch resumes);
2849
+ * - an existing router → `setPolicy` (preserves the accumulated
2850
+ * `EscalationState` climbed floors — a policy edit must not reset them);
2851
+ * - no router yet → construct one from the pushed policy.
2852
+ *
2853
+ * The field-swap is atomic between `await`s (single-threaded): a dispatch that
2854
+ * already captured the router finishes on it; the next dispatch sees the new
2855
+ * policy. No-op-safe when the routing subsystem is absent (`backendFactory`
2856
+ * null) — the caller (`PUT` handler) reports 503 in that case, so this path is
2857
+ * reached only when routing is available.
2858
+ */
2859
+ ingestRoutingPolicy(policy: RoutingPolicy): void;
2860
+ /**
2861
+ * AMR Phase 5 (D2): project the live router's enriched decision ring into the
2862
+ * Shuttle telemetry wire shape (`GET /api/v1/routing/telemetry`). Returns an
2863
+ * empty payload when routing is off (no router) — a safe, idempotent read.
2864
+ */
2865
+ getRoutingTelemetry(): RoutingTelemetry;
2866
+ /**
2867
+ * AMR observability: the live operator status (budget spend-vs-cap, escalated
2868
+ * units, allowlist), or an inactive payload when AMR is off. Backs
2869
+ * `GET /api/v1/routing/status`.
2870
+ */
2871
+ getRoutingStatus(): RoutingStatus;
2044
2872
  /**
2045
2873
  * Spec B Phase 5: live BackendRouter for HTTP routes. The orchestrator
2046
2874
  * dispatch path uses the factory-owned router directly; observability
@@ -2079,102 +2907,70 @@ declare function launchTUI(orchestrator: Orchestrator): {
2079
2907
  };
2080
2908
 
2081
2909
  /**
2082
- * Options for `OrchestratorBackendFactory`.
2910
+ * Fail-closed signal: privacy floor / allowlist emptied the candidate set (S4-001).
2911
+ * Distinguishable from a tier/cost-only exclusion, which returns `undefined`.
2083
2912
  *
2084
- * `sandboxPolicy` and `container`/`secrets` mirror the orchestrator's own
2085
- * agent-config fields. `getResolverModelFor` is a registration hook the
2086
- * orchestrator calls to bind each `local`/`pi` backend to its
2087
- * `LocalModelResolver` (so multi-resolver array-fallback works without
2088
- * leaking resolver lifetimes into the factory).
2913
+ * Finding #4 — ONE typed error family: `PrivacyNoMatch` stays orchestrator-local
2914
+ * (it is thrown deep in the selector, a layer below the exported types package) but
2915
+ * EXTENDS the exported `RoutingError`, carrying its canonical `'privacy-no-match'`
2916
+ * code. The dispatch boundary can therefore catch it as either `PrivacyNoMatch`
2917
+ * (ergonomic `instanceof`) or `RoutingError` (code-narrowed), and the sibling
2918
+ * `escalation-exhausted` code is emitted via `RoutingError` too — no divergent
2919
+ * surfaces, no dead exported error type. `code` narrows to the literal here.
2089
2920
  */
2090
- interface OrchestratorBackendFactoryOptions {
2091
- backends: Record<string, BackendDef>;
2092
- routing: RoutingConfig;
2093
- sandboxPolicy: 'none' | 'docker';
2094
- container?: ContainerConfig;
2095
- secrets?: SecretConfig;
2096
- /**
2097
- * Hook for resolver injection. Invoked per `local`/`pi` backend at
2098
- * `forUseCase()` time with the backend's name. When the hook returns a
2099
- * function, the factory rebuilds the local/pi instance using that
2100
- * function as `getModel` (overriding the head-of-array placeholder
2101
- * baked into `createBackend`). Returning `undefined` means "no
2102
- * resolver registered for this name" — the placeholder stays in place.
2103
- *
2104
- * This indirection keeps the factory ignorant of `LocalModelResolver`'s
2105
- * existence and lifecycle while still letting it produce backends that
2106
- * route through the resolver Map.
2107
- */
2108
- getResolverModelFor?: (backendName: string) => (() => string | null) | undefined;
2109
- /**
2110
- * Phase 5: prompt-cache recorder forwarded to Anthropic-capable backends.
2111
- * Other backends accept-but-ignore. Shared across dispatches so the
2112
- * `/api/v1/telemetry/cache/stats` endpoint sees the full rolling window.
2113
- */
2114
- cacheMetrics?: CacheMetricsRecorder;
2115
- /**
2116
- * Spec B Phase 4 (D8): forwarded to the underlying BackendRouter so
2117
- * every resolve() during forUseCase / resolveName emits.
2118
- */
2119
- decisionBus?: RoutingDecisionBus;
2921
+ declare class PrivacyNoMatch extends RoutingError {
2922
+ readonly code: "privacy-no-match";
2923
+ constructor(message: string);
2924
+ }
2925
+ interface SelectConstraints {
2926
+ privacyFloor?: PrivacyClass;
2927
+ /** Present ⇒ only these providers allowed. Absent ⇒ all allowed. Empty array ⇒ none. */
2928
+ allowed?: BackendDef['type'][];
2929
+ needsVision?: boolean;
2930
+ needsToolUse?: boolean;
2931
+ minContextTokens?: number;
2120
2932
  }
2121
2933
  /**
2122
- * High-level factory wrapping `BackendRouter` + `createBackend` plus
2123
- * orchestrator-side concerns (sandbox wrapping, resolver binding).
2934
+ * D1 core: filter the registry to backends with tier ≥ requiredTier, privacyClass
2935
+ * at least as strong as the floor, provider in the allowlist, and capabilities ⊇
2936
+ * required (vision/toolUse/minContextTokens); sort by costPer1kTokens ascending;
2937
+ * return the cheapest.
2124
2938
  *
2125
- * Spec 2 SC22-SC25: every `forUseCase(useCase)` call returns a fresh
2126
- * `AgentBackend` whose class matches the routed `BackendDef.type`.
2127
- * `local`/`pi` defs are bound to their per-name resolver before being
2128
- * returned, and the result is wrapped in `ContainerBackend` when
2129
- * sandboxPolicy is 'docker'.
2939
+ * Fail-closed (S4-001): if a privacy-floor OR allowlist constraint empties the set,
2940
+ * throw PrivacyNoMatch (the item must surface to the steward — never fall through
2941
+ * to identity routing at a non-compliant backend). A tier/cost-only exclusion is
2942
+ * best-effort: return undefined so the caller can fall back to the shipped router's
2943
+ * identity/default chain. No `if (local)` anywhere.
2130
2944
  */
2131
- declare class OrchestratorBackendFactory {
2132
- private readonly router;
2133
- private readonly opts;
2134
- constructor(opts: OrchestratorBackendFactoryOptions);
2135
- /**
2136
- * Resolve `useCase` to a backend name, materialize a fresh
2137
- * `AgentBackend`, optionally rebind its model resolver, and apply
2138
- * sandbox wrapping. Idempotent across calls (no caching) the AgentRunner
2139
- * holds the per-dispatch reference and discards it when the run ends.
2140
- */
2141
- /**
2142
- * Resolve `useCase` to its routed backend name, exposing the
2143
- * router lookup without materializing a backend. Used by callers
2144
- * (e.g., the orchestrator's dispatch site) that need to label
2145
- * telemetry with the routed name BEFORE constructing the backend.
2146
- *
2147
- * Spec 2 P2-I2: previously the orchestrator labelled `LiveSession`
2148
- * + `StreamRecorder` with the legacy `agent.backend` field, which
2149
- * is `undefined` for pure-modern configs. Threading the routed name
2150
- * through dispatch eliminates that gap.
2151
- */
2152
- resolveName(useCase: RoutingUseCase, opts?: {
2153
- invocationOverride?: string;
2154
- }): string;
2155
- /**
2156
- * Spec B Phase 1: expose the underlying router for callers that need
2157
- * it directly (e.g., {@link buildIntelligencePipeline} for the
2158
- * I1 SEL/PESL comparison fix). Read-only access; consumers must not
2159
- * mutate router state.
2160
- */
2161
- getRouter(): BackendRouter;
2162
- forUseCase(useCase: RoutingUseCase, opts?: {
2163
- invocationOverride?: string;
2164
- }): AgentBackend;
2165
- /**
2166
- * Rebuild a `local`/`pi` backend with a resolver-bound `getModel`,
2167
- * mirroring `createBackend`'s local/pi branches but substituting the
2168
- * head-of-array placeholder with the orchestrator-owned resolver.
2169
- */
2170
- private buildLocalLikeWithResolver;
2171
- /**
2172
- * Apply ContainerBackend wrapping (PFC-3). Pulls the runtime + secret
2173
- * backend per call so each dispatch sees a fresh container handle map
2174
- * (ContainerBackend keeps its own per-instance Map<sessionId, handle>).
2175
- */
2176
- private wrapInContainer;
2177
- }
2945
+ declare function selectCheapestQualifying(registry: BackendCapabilityRegistry, requiredTier: CapabilityTier, constraints: SelectConstraints,
2946
+ /**
2947
+ * Provider lookup by name, required to enforce a `constraints.allowed` allowlist.
2948
+ * If `allowed` is set but `providerOf` is absent (or returns `undefined` for an
2949
+ * entry), that entry cannot be admitted → excluded; an allowlist with no
2950
+ * `providerOf` therefore excludes ALL candidates and fails closed
2951
+ * (`PrivacyNoMatch`). Phase 3 (`AdaptiveRouter`) MUST derive this from
2952
+ * `agent.backends` whenever it sets an allowlist. Absent `allowed` ⇒ unused.
2953
+ */
2954
+ providerOf?: (name: string) => BackendDef['type'] | undefined): {
2955
+ name: string;
2956
+ capabilities: BackendCapabilities;
2957
+ } | undefined;
2958
+ /** Default capability block derived for an LMLM pool candidate that carries no
2959
+ * explicit capabilities. On-device strongest privacy, zero marginal cost.
2960
+ * Seed values (tunable in later phases); a candidate is thus visible to tier
2961
+ * selection (spec "Failure modes": a backend with NO capabilities is invisible,
2962
+ * but a pool candidate is always given a derived block so it can win on cost). */
2963
+ declare function defaultPoolCapabilities(): BackendCapabilities;
2964
+ /**
2965
+ * Build the tier-selection registry (name → capabilities) from configured
2966
+ * `agent.backends` (their `capabilities` blocks, when present) merged with LMLM
2967
+ * pool candidates (each given a derived on-device/zero-cost block). A configured
2968
+ * backend WITHOUT a `capabilities` block is omitted — invisible to tier selection,
2969
+ * reachable only via identity routing (spec "Failure modes"). No LMLM code changes.
2970
+ */
2971
+ declare function buildCapabilityRegistry(backends: Record<string, BackendDef>, pool?: PoolStateProvider): BackendCapabilityRegistry;
2972
+
2973
+ declare function estimateCost(def: BackendDef, _req: RoutingRequest): number;
2178
2974
 
2179
2975
  /**
2180
2976
  * Result of running `migrateAgentConfig`.
@@ -2451,6 +3247,26 @@ interface LocalModelResolverOptions {
2451
3247
  poolState?: PoolStateProvider;
2452
3248
  /** Probe cadence in ms; default 30_000, minimum 1_000. */
2453
3249
  probeIntervalMs?: number;
3250
+ /** Debounce window for event-driven {@link LocalModelResolver.refresh}; default 250ms. */
3251
+ refreshDebounceMs?: number;
3252
+ /**
3253
+ * Consumption Phase 5 (T19/T20): called with the newly-resolved model name
3254
+ * whenever the resolver's composite selection changes (and is non-null), so
3255
+ * the orchestrator can warm it into VRAM before the next dispatch. Naturally
3256
+ * debounced — it fires only on an actual selection change, not every probe.
3257
+ * Best-effort: a throwing hook is swallowed. Injected in tests; wired to
3258
+ * {@link defaultWarmModel} in production.
3259
+ */
3260
+ warmModel?: (ollamaName: string) => void;
3261
+ /**
3262
+ * Consumption Phase 3 (T10): consecutive inference failures before a model is
3263
+ * deprioritized by the circuit breaker. Default 3.
3264
+ */
3265
+ breakerThreshold?: number;
3266
+ /** Cooldown (ms) after which a tripped model is eligible again. Default 60_000. */
3267
+ breakerCooldownMs?: number;
3268
+ /** Injectable clock (ms since epoch) for the circuit-breaker cooldown; default `Date.now`. Test seam. */
3269
+ now?: () => number;
2454
3270
  /**
2455
3271
  * Per-request timeout for the default fetch implementation, in ms.
2456
3272
  * Default: 5_000. Ignored when a custom `fetchModels` is provided
@@ -2482,8 +3298,21 @@ declare class LocalModelResolver {
2482
3298
  private readonly poolState?;
2483
3299
  private readonly probeIntervalMs;
2484
3300
  private readonly fetchModels;
3301
+ private readonly warmModel?;
2485
3302
  private readonly logger;
2486
3303
  private timer;
3304
+ /** Debounce handle for event-driven {@link refresh}; coalesces a burst into one probe. */
3305
+ private refreshTimer;
3306
+ /** Test seam for the refresh debounce delay (0 in tests → next-tick probe). */
3307
+ private readonly refreshDebounceMs;
3308
+ /** Consumption Phase 3 (T10): circuit-breaker config + per-model failure/trip state. */
3309
+ private readonly breakerThreshold;
3310
+ private readonly breakerCooldownMs;
3311
+ private readonly now;
3312
+ /** Consecutive inference failures per model since its last success. */
3313
+ private consecutiveFailures;
3314
+ /** Epoch-ms at which a tripped model becomes eligible again (cooldown expiry). */
3315
+ private trippedUntil;
2487
3316
  private listeners;
2488
3317
  /**
2489
3318
  * Tracks an in-flight probe so concurrent invocations (interval tick while a
@@ -2502,7 +3331,15 @@ declare class LocalModelResolver {
2502
3331
  private warnings;
2503
3332
  private available;
2504
3333
  constructor(opts: LocalModelResolverOptions);
2505
- resolveModel(): string | null;
3334
+ /**
3335
+ * The model to dispatch to. With no `useCase`, returns the cached composite
3336
+ * resolution from the last probe (byte-identical to the pre-Phase-4 resolver).
3337
+ * With a `useCase`, orders the (pool-derived) candidates by that use-case's
3338
+ * task profile and picks the best loaded, breaker-healthy one — so a
3339
+ * coding-tagged dispatch prefers the coding specialist. A `general`-mapped
3340
+ * use-case returns the cached composite resolution unchanged.
3341
+ */
3342
+ resolveModel(useCase?: RoutingUseCase): string | null;
2506
3343
  getStatus(): LocalModelStatus;
2507
3344
  /**
2508
3345
  * Effective candidate list. With a poolState port present the list derives
@@ -2513,6 +3350,41 @@ declare class LocalModelResolver {
2513
3350
  onStatusChange(handler: (status: LocalModelStatus) => void): () => void;
2514
3351
  probe(): Promise<LocalModelStatus>;
2515
3352
  private runProbe;
3353
+ /** Best-effort warm-hook invocation — a throwing warm never breaks a probe. */
3354
+ private warm;
3355
+ /**
3356
+ * Consumption Phase 3 (T10): pick the resolved model from the candidates that
3357
+ * are actually loaded, preferring ones whose circuit breaker is not tripped.
3358
+ * Candidate order (score-desc from the pool) is preserved within each tier, so
3359
+ * a tripped top pick sinks below a healthy lower pick but still resolves as a
3360
+ * last resort when every loaded candidate is tripped (better a flaky model than
3361
+ * none). Returns null when no candidate is loaded.
3362
+ */
3363
+ private selectMatch;
3364
+ /**
3365
+ * Whether `model`'s circuit breaker is currently tripped. Lazily clears the
3366
+ * trip once its cooldown has elapsed so the model becomes eligible again on the
3367
+ * next resolution without needing a separate timer.
3368
+ */
3369
+ private isTripped;
3370
+ /**
3371
+ * Record a successful inference on `model`: clears its failure count and any
3372
+ * active trip so a recovered model is immediately re-preferred.
3373
+ */
3374
+ recordSuccess(model: string): void;
3375
+ /**
3376
+ * Record a failed inference on `model`. On the Nth consecutive failure the
3377
+ * breaker trips (deprioritizing the model for `breakerCooldownMs`) and a
3378
+ * re-probe is scheduled so the resolver rolls to a healthy alternative.
3379
+ */
3380
+ recordFailure(model: string): void;
3381
+ /**
3382
+ * Event-driven refresh: schedule a debounced re-probe. Called when a
3383
+ * `local-models:pool` mutation fires so a just-installed/swapped model becomes
3384
+ * usable within a debounce window instead of waiting up to `probeIntervalMs`.
3385
+ * A burst of frames coalesces into one probe (the timer is only armed once).
3386
+ */
3387
+ refresh(): void;
2516
3388
  start(): Promise<void>;
2517
3389
  stop(): void;
2518
3390
  private snapshotForDiff;
@@ -3663,4 +4535,4 @@ declare function emitProposalCreated(bus: EventEmitter, proposal: SkillProposal)
3663
4535
  declare function emitProposalApproved(bus: EventEmitter, proposal: SkillProposal): void;
3664
4536
  declare function emitProposalRejected(bus: EventEmitter, proposal: SkillProposal): void;
3665
4537
 
3666
- export { type AgentDispatchResult, type AgentDispatcher, type AgentDispatcherDeps, type AgentUpdateEvent, AnalysisArchive, type AnalysisRecord, type ApplyEventResult, type ArtifactPresence, type AttemptStats, BUILT_IN_TASKS, BackendDefSchema, type BackendResolver, BackendRouter, type BackendRouterOptions, type BaseRefFallbackEvent, type BuildArchiveHooksOptions, type CheckCommandResult, type CheckCommandRunner, type CheckFailureClassification, type CheckFailureKind, CheckScriptRunner, ClaimManager, type ClaimManagerConfig, type CleanWorkspaceEffect, type CommandExecResult, type CommandExecutor, type CreateTokenInput, type CreateTokenResult, type CustomTaskValidationError, type DispatchEffect, type EmitLogEffect, type EscalateEffect, type ExecFileAsyncFn, type ExecFileError, type ExecFileFn$1 as ExecFileFn, type FromConfigOptions, GateNotReadyError, type GateResult, GateRunError, type HarnessSpawn, type Highlight, type HighlightsInfo, type IndexedDoc, InteractionQueue, LinearGraphQLClient, type LinearGraphQLClientOptions, type LinearGraphQLExtension, LinearGraphQLStub, type LiveSession, LocalModelResolver, type LocalModelResolverOptions, MAINTENANCE_CHECK_MAX_BUFFER, MAINTENANCE_CHECK_TIMEOUT_MS, MAX_ATTEMPTS, MaintenanceReporter, type MaintenanceReporterOptions, type MigrationResult, MockBackend, type NotificationSink, type NotificationSinkDeliverInput, ORCHESTRATOR_IDENTITY_FILE, Orchestrator, OrchestratorBackendFactory, type OrchestratorBackendFactoryOptions, type OrchestratorContext, type OrchestratorEvent, type OrchestratorState, PRDetector, type PRDetectorLogger, type PRLifecycleManager, type PendingInteraction, type PersistedOutputEntry, PromotionError, type PromotionResult, PromptRenderer, type ProposalApprovedData, type ProposalCreatedData, type ProposalRejectedData, type PublishedIndex, type QueueInsertInput, type QueueRow, type QueueStats, RETRY_DELAYS_MS, type RateLimitSnapshot as RateLimitComputeSnapshot, type RateLimitConfig, type RateLimitSnapshot$1 as RateLimitSnapshot, type RegistryEntry, type ReleaseClaimEffect, type ResolverLogger, type RetryEntry, type RetryFiredEvent, RoadmapTrackerAdapter, RoutingConfigSchema, RoutingValueSchema, type RunAttemptPhase, type RunHarnessCheckOptions, type RunMode, type RunOrigin, type RunResult, type RunningEntry, type ScheduleRetryEffect, type SearchOptions, type SideEffect, SinkConfigError, SinkRegistry, type SkillCatalogEntry, SlackSink, type SlackSinkOptions, SqliteSearchIndex, type StallDetectedEvent, type StopEffect, type StreamManifest, StreamRecorder, type SummarizeContext, type SummarizeResult, type SyncMainOptions, type SyncMainResult, type SyncSkipReason, type TaskDefinition, TaskOutputStore, TaskRunner, type TaskRunnerOptions, type TaskSelectionFilter, type TaskType, type TickEvent, TokenStore, type TokenTotals, type TriageConfig, type TriageDecision, type TriageSignals, type TriageSkill, type UpdateTokensEffect, type ValidateWorkflowConfigOptions, type ValidatedWorkflowConfig, WebhookQueue, type WorkerExitEvent, WorkflowLoader, WorkspaceHooks, WorkspaceManager, type WorkspaceManagerOptions, applyEvent, artifactPresenceFromIssue, buildArchiveHooks, calculateRetryDelay, canDispatch, classifyCheckExecutionFailure, computeRateLimitDelay, createAgentDispatcher, createBackend, createEmptyState, crossFieldRoutingIssues, defaultFetchModels, deriveSeedPaths, detectScopeTier, discoverSkillCatalog, discoverSkillCatalogNames, emitProposalApproved, emitProposalCreated, emitProposalRejected, explicitFindingsCount, extractHighlights, extractTitlePrefix, getAvailableSlots, getDefaultConfig, getPerStateCount, indexSessionDirectory, isCheckTimeoutError, isEligible, isSummaryEnabled, launchTUI, loadPublishedIndex, makeBackendResolver, migrateAgentConfig, normalizeFts5Query, normalizeHarnessCommand, normalizeLocalModel, openSearchIndex, promote, reconcile, recoverFindingsCount, reindexFromArchive, renderAnalysisComment, renderLlmSummaryMarkdown, renderPRComment, resolveEscalationConfig, resolveOrchestratorId, routeIssue, routingWarnings, runGate, runHarnessCheck, savePublishedIndex, searchIndexPath, selectCandidates, selectTasks, sortCandidates, summarizeArchivedSession, syncMain, triageIssue, truncateForBudget, validateCustomTasks, validateWorkflowConfig, wireNotificationSinks, wrapAsEnvelope };
4538
+ export { AdaptiveRouter, type AdaptiveRouterDeps, type AgentDispatchResult, type AgentDispatcher, type AgentDispatcherDeps, type AgentUpdateEvent, AnalysisArchive, type AnalysisRecord, type ApplyEventResult, type ArtifactPresence, type AttemptStats, BUILT_IN_TASKS, BackendDefSchema, type BackendResolver, BackendRouter, type BackendRouterOptions, type BaseRefFallbackEvent, type BuildArchiveHooksOptions, type BuildWorkflowContextDeps, type CheckCommandResult, type CheckCommandRunner, type CheckFailureClassification, type CheckFailureKind, CheckScriptRunner, ClaimManager, type ClaimManagerConfig, type CleanWorkspaceEffect, type CommandExecResult, type CommandExecutor, type CreateTokenInput, type CreateTokenResult, type CustomTaskValidationError, type DispatchEffect, type EmitLogEffect, type EscalateEffect, EscalationState, type ExecFileAsyncFn, type ExecFileError, type ExecFileFn$1 as ExecFileFn, type FromConfigOptions, GateNotReadyError, type GateResult, GateRunError, type HarnessSpawn, type Highlight, type HighlightsInfo, type IndexedDoc, InteractionQueue, LinearGraphQLClient, type LinearGraphQLClientOptions, type LinearGraphQLExtension, LinearGraphQLStub, type LiveSession, LocalModelResolver, type LocalModelResolverOptions, MAINTENANCE_CHECK_MAX_BUFFER, MAINTENANCE_CHECK_TIMEOUT_MS, MAX_ATTEMPTS, MaintenanceReporter, type MaintenanceReporterOptions, type MigrationResult, MockBackend, type NotificationSink, type NotificationSinkDeliverInput, ORCHESTRATOR_IDENTITY_FILE, Orchestrator, OrchestratorBackendFactory, type OrchestratorBackendFactoryOptions, type OrchestratorContext, type OrchestratorEvent, type OrchestratorState, PRDetector, type PRDetectorLogger, type PRLifecycleManager, type PendingInteraction, type PersistedOutputEntry, PrivacyNoMatch, PromotionError, type PromotionResult, PromptRenderer, type ProposalApprovedData, type ProposalCreatedData, type ProposalRejectedData, type PublishedIndex, type QueueInsertInput, type QueueRow, type QueueStats, RETRY_DELAYS_MS, type RateLimitSnapshot as RateLimitComputeSnapshot, type RateLimitConfig, type RateLimitSnapshot$1 as RateLimitSnapshot, type RegistryEntry, type ReleaseClaimEffect, type ResolverLogger, type RetryEntry, type RetryFiredEvent, RoadmapTrackerAdapter, RoutingConfigSchema, RoutingValueSchema, type RunAttemptPhase, type RunHarnessCheckOptions, type RunMode, type RunOrigin, type RunResult, type RunningEntry, type ScheduleRetryEffect, type SearchOptions, type SelectConstraints, type SideEffect, SinkConfigError, SinkRegistry, type SkillCatalogEntry, SlackSink, type SlackSinkOptions, SqliteSearchIndex, type StallDetectedEvent, type StopEffect, type StreamManifest, StreamRecorder, type SummarizeContext, type SummarizeResult, type SyncMainOptions, type SyncMainResult, type SyncSkipReason, type TaskDefinition, TaskOutputStore, TaskRunner, type TaskRunnerOptions, type TaskSelectionFilter, type TaskType, type TickEvent, TokenStore, type TokenTotals, type TriageConfig, type TriageDecision, type TriageSignals, type TriageSkill, type UpdateTokensEffect, type ValidateWorkflowConfigOptions, type ValidatedWorkflowConfig, WebhookQueue, type WorkerExitEvent, WorkflowLoader, type WorkflowRouterDep, WorkspaceHooks, WorkspaceManager, type WorkspaceManagerOptions, applyEvent, artifactPresenceFromIssue, buildArchiveHooks, buildCapabilityRegistry, buildWorkflowContext, calculateRetryDelay, canDispatch, classifyCheckExecutionFailure, computeRateLimitDelay, createAgentDispatcher, createBackend, createEmptyState, crossFieldRoutingIssues, defaultFetchModels, defaultPoolCapabilities, deriveSeedPaths, detectScopeTier, discoverSkillCatalog, discoverSkillCatalogNames, emitProposalApproved, emitProposalCreated, emitProposalRejected, estimateCost, explicitFindingsCount, extractHighlights, extractTitlePrefix, getAvailableSlots, getDefaultConfig, getPerStateCount, indexSessionDirectory, isCheckTimeoutError, isEligible, isSummaryEnabled, launchTUI, loadPublishedIndex, makeBackendResolver, migrateAgentConfig, normalizeFts5Query, normalizeHarnessCommand, normalizeLocalModel, openSearchIndex, promote, reconcile, recoverFindingsCount, reindexFromArchive, renderAnalysisComment, renderLlmSummaryMarkdown, renderPRComment, resolveEscalationConfig, resolveOrchestratorId, routeIssue, routingWarnings, runGate, runHarnessCheck, savePublishedIndex, searchIndexPath, selectCandidates, selectCheapestQualifying, selectTasks, sortCandidates, summarizeArchivedSession, syncMain, triageIssue, truncateForBudget, validateCustomTasks, validateWorkflowConfig, wireNotificationSinks, workflowFor, wrapAsEnvelope };