@warmdrift/kgauto-compiler 2.0.0-alpha.19 → 2.0.0-alpha.20

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.
@@ -1,6 +1,6 @@
1
- import { G as GlassboxEvent } from '../types-DWF6mPGg.mjs';
2
- export { A as AdvisoryFiredData, C as CompileDoneData, a as CompileStartData, E as ExecuteAttemptData, b as ExecuteSuccessData, F as FallbackWalkedData, c as GLASSBOX_STREAM_TTL_MS, d as GlassboxEventKind, e as GlassboxPubSub } from '../types-DWF6mPGg.mjs';
3
- import '../ir-C3P4gDt0.mjs';
1
+ import { G as GlassboxEvent } from '../types-BYj1Kl2m.mjs';
2
+ export { A as AdvisoryFiredData, C as CompileDoneData, a as CompileStartData, E as ExecuteAttemptData, b as ExecuteSuccessData, F as FallbackWalkedData, c as GLASSBOX_STREAM_TTL_MS, d as GlassboxEventKind, e as GlassboxPubSub } from '../types-BYj1Kl2m.mjs';
3
+ import '../ir-DTMbSnyE.mjs';
4
4
  import '../dialect.mjs';
5
5
 
6
6
  /**
@@ -1,6 +1,6 @@
1
- import { G as GlassboxEvent } from '../types-xeklorHU.js';
2
- export { A as AdvisoryFiredData, C as CompileDoneData, a as CompileStartData, E as ExecuteAttemptData, b as ExecuteSuccessData, F as FallbackWalkedData, c as GLASSBOX_STREAM_TTL_MS, d as GlassboxEventKind, e as GlassboxPubSub } from '../types-xeklorHU.js';
3
- import '../ir-CFHU3BUT.js';
1
+ import { G as GlassboxEvent } from '../types-CwtaDaWN.js';
2
+ export { A as AdvisoryFiredData, C as CompileDoneData, a as CompileStartData, E as ExecuteAttemptData, b as ExecuteSuccessData, F as FallbackWalkedData, c as GLASSBOX_STREAM_TTL_MS, d as GlassboxEventKind, e as GlassboxPubSub } from '../types-CwtaDaWN.js';
3
+ import '../ir-CsTU4cMB.js';
4
4
  import '../dialect.js';
5
5
 
6
6
  /**
@@ -1,5 +1,5 @@
1
- import { G as GlassboxEvent } from '../types-DWF6mPGg.mjs';
2
- import '../ir-C3P4gDt0.mjs';
1
+ import { G as GlassboxEvent } from '../types-BYj1Kl2m.mjs';
2
+ import '../ir-DTMbSnyE.mjs';
3
3
  import '../dialect.mjs';
4
4
 
5
5
  /**
@@ -1,5 +1,5 @@
1
- import { G as GlassboxEvent } from '../types-xeklorHU.js';
2
- import '../ir-CFHU3BUT.js';
1
+ import { G as GlassboxEvent } from '../types-CwtaDaWN.js';
2
+ import '../ir-CsTU4cMB.js';
3
3
  import '../dialect.js';
4
4
 
5
5
  /**
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { C as CompilePolicy, N as NormalizedResponse, A as ApiKeys, P as ProviderOverrides, a as CompiledRequest, b as PromptIR, c as CallOptions, d as CallResult, R as RecordInput, O as OracleScore, e as CompileResult, B as BestPracticeAdvisory, f as Provider } from './ir-C3P4gDt0.mjs';
2
- export { g as CallAttempt, h as CallError, i as Constraints, F as FallbackReason, H as HistoryCachePolicy, I as IntentDeclaration, M as Message, j as MutationApplied, k as NormalizedTokens, l as PromptSection, T as ToolCall, m as ToolDefinition } from './ir-C3P4gDt0.mjs';
1
+ import { C as CompilePolicy, N as NormalizedResponse, A as ApiKeys, P as ProviderOverrides, a as CompiledRequest, b as PromptIR, c as CallOptions, d as CallResult, R as RecordInput, e as RecordOutcomeInput, O as OutcomeResult, f as OracleScore, g as CompileResult, B as BestPracticeAdvisory, h as Provider } from './ir-DTMbSnyE.mjs';
2
+ export { i as CallAttempt, j as CallError, k as Constraints, F as FallbackReason, H as HistoryCachePolicy, I as IntentDeclaration, M as Message, l as MutationApplied, m as NormalizedTokens, n as OutcomeKind, o as PromptSection, T as ToolCall, p as ToolDefinition } from './ir-DTMbSnyE.mjs';
3
3
  import { ModelProfile } from './profiles.mjs';
4
4
  export { ALIASES, CacheStrategy, CliffRule, LoweringSpec, RecoveryRule, StructuredOutputCapability, SystemPromptMode, allProfiles, getProfile, profilesByProvider, tryGetProfile } from './profiles.mjs';
5
5
  import { IntentArchetypeName } from './dialect.mjs';
@@ -198,7 +198,30 @@ interface OutcomePayload {
198
198
  ttft_ms?: number;
199
199
  history_cacheable_tokens?: number;
200
200
  history_tokens_at_compile?: number;
201
+ /**
202
+ * Mirrors `ir.constraints.toolOrchestration` from compile time. NULL when
203
+ * the consumer hadn't adopted the constraint (pre-alpha.20). Powers
204
+ * per-mode model-perf queries on the brain (the L-040 parallel-tool
205
+ * cliff lumps DeepSeek sequential perf with parallel without this).
206
+ */
207
+ tool_orchestration?: 'parallel' | 'sequential' | 'either' | null;
201
208
  }
209
+ /**
210
+ * alpha.20 Entry 4: record a quality outcome for a previously-compiled call.
211
+ *
212
+ * Fires after the consumer's UX surfaces an approve/reject event (e.g., user
213
+ * clicks Approve on a hunt result). Joins to the original `compile_outcomes`
214
+ * row via outcomeId — enables per-(model, archetype) approve-rate measurement
215
+ * once N ≥ 10 outcomes accumulate.
216
+ *
217
+ * Fire-and-forget by default (matches record() semantics). Set BrainConfig.sync
218
+ * = true for runtime contexts that can't tolerate fire-and-forget teardown
219
+ * (Vercel Edge, Cloudflare Workers, AWS Lambda) — see L-086.
220
+ *
221
+ * Returns OutcomeResult with ok: false + stable reason on persistence
222
+ * failure. Never throws.
223
+ */
224
+ declare function recordOutcome(input: RecordOutcomeInput): Promise<OutcomeResult>;
202
225
 
203
226
  /**
204
227
  * Oracle contract — how an app tells the brain whether a response was good.
@@ -475,6 +498,23 @@ interface GetDefaultFallbackChainOpts {
475
498
  * legacy unfiltered behavior — preserves alpha.9 callers byte-for-byte.
476
499
  */
477
500
  reachability?: ReachabilityOpts;
501
+ /**
502
+ * alpha.20 E3: consumer-declared tool-orchestration shape. Currently
503
+ * only affects `archetype: 'hunt'`, where 'sequential' swaps the
504
+ * parallel-tool-tier-0 chain (Flash → Pro → Sonnet → Haiku) for a
505
+ * DeepSeek-tier-0 chain (V4-Pro → Flash → Sonnet) — DeepSeek's L-040
506
+ * parallel-tool cliff doesn't apply when the consumer commits to
507
+ * single-step orchestration.
508
+ *
509
+ * Other archetypes are NOT mode-aware in this release — they ship the
510
+ * same chain regardless of toolOrchestration. Future versions may
511
+ * extend mode-awareness to ask/generate/etc. when brain evidence
512
+ * supports it.
513
+ *
514
+ * Default (omitted or 'either'): parallel chain. Back-compat with all
515
+ * pre-alpha.20 callers.
516
+ */
517
+ toolOrchestration?: 'parallel' | 'sequential' | 'either';
478
518
  }
479
519
  declare function getDefaultFallbackChain(opts: GetDefaultFallbackChainOpts): string[];
480
520
  /**
@@ -718,4 +758,4 @@ declare const loadAliasesFromBrain: () => Record<string, string>;
718
758
  */
719
759
  declare function compile(ir: PromptIR, opts?: CompileOptions): CompileResult;
720
760
 
721
- export { ApiKeys, type AppOracle, type ArchetypePerfMap, BestPracticeAdvisory, type BrainConfig, type BrainQueryConfig, CallOptions, CallResult, type CompileOptions, CompilePolicy, CompileResult, CompiledRequest, type ExecuteErr, type ExecuteOk, type ExecuteOptions, type ExecuteResult, type FallbackPosture, type GetDefaultFallbackChainOpts, IntentArchetypeName, type LLMJudgeOptions, type ModelBrainRow, ModelProfile, NormalizedResponse, type OracleContext, OracleScore, type OutcomePayload, PROVIDER_ENV_KEYS, type PricingRow, type ProfileToRowOptions, PromptIR, Provider, ProviderOverrides, type ProviderReachability, type ReachabilityOpts, RecordInput, type SupportedProvider, buildLLMJudge, call, clearBrain, compile, configureBrain, countTokens, execute, getAllStarterChains, getArchetypePerfScore, getDefaultFallbackChain, getReachabilityDiagnostic, getStarterChain, isModelReachable, isProviderReachable, loadAliasesFromBrain, loadArchetypePerfFromBrain, loadChainsFromBrain, loadModelsFromBrain, loadPricingFromBrain, profileToRow, record, resetTokenizer, resolvePricingAt, resolveProviderKey, runAdvisor, setTokenizer };
761
+ export { ApiKeys, type AppOracle, type ArchetypePerfMap, BestPracticeAdvisory, type BrainConfig, type BrainQueryConfig, CallOptions, CallResult, type CompileOptions, CompilePolicy, CompileResult, CompiledRequest, type ExecuteErr, type ExecuteOk, type ExecuteOptions, type ExecuteResult, type FallbackPosture, type GetDefaultFallbackChainOpts, IntentArchetypeName, type LLMJudgeOptions, type ModelBrainRow, ModelProfile, NormalizedResponse, type OracleContext, OracleScore, type OutcomePayload, OutcomeResult, PROVIDER_ENV_KEYS, type PricingRow, type ProfileToRowOptions, PromptIR, Provider, ProviderOverrides, type ProviderReachability, type ReachabilityOpts, RecordInput, RecordOutcomeInput, type SupportedProvider, buildLLMJudge, call, clearBrain, compile, configureBrain, countTokens, execute, getAllStarterChains, getArchetypePerfScore, getDefaultFallbackChain, getReachabilityDiagnostic, getStarterChain, isModelReachable, isProviderReachable, loadAliasesFromBrain, loadArchetypePerfFromBrain, loadChainsFromBrain, loadModelsFromBrain, loadPricingFromBrain, profileToRow, record, recordOutcome, resetTokenizer, resolvePricingAt, resolveProviderKey, runAdvisor, setTokenizer };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { C as CompilePolicy, N as NormalizedResponse, A as ApiKeys, P as ProviderOverrides, a as CompiledRequest, b as PromptIR, c as CallOptions, d as CallResult, R as RecordInput, O as OracleScore, e as CompileResult, B as BestPracticeAdvisory, f as Provider } from './ir-CFHU3BUT.js';
2
- export { g as CallAttempt, h as CallError, i as Constraints, F as FallbackReason, H as HistoryCachePolicy, I as IntentDeclaration, M as Message, j as MutationApplied, k as NormalizedTokens, l as PromptSection, T as ToolCall, m as ToolDefinition } from './ir-CFHU3BUT.js';
1
+ import { C as CompilePolicy, N as NormalizedResponse, A as ApiKeys, P as ProviderOverrides, a as CompiledRequest, b as PromptIR, c as CallOptions, d as CallResult, R as RecordInput, e as RecordOutcomeInput, O as OutcomeResult, f as OracleScore, g as CompileResult, B as BestPracticeAdvisory, h as Provider } from './ir-CsTU4cMB.js';
2
+ export { i as CallAttempt, j as CallError, k as Constraints, F as FallbackReason, H as HistoryCachePolicy, I as IntentDeclaration, M as Message, l as MutationApplied, m as NormalizedTokens, n as OutcomeKind, o as PromptSection, T as ToolCall, p as ToolDefinition } from './ir-CsTU4cMB.js';
3
3
  import { ModelProfile } from './profiles.js';
4
4
  export { ALIASES, CacheStrategy, CliffRule, LoweringSpec, RecoveryRule, StructuredOutputCapability, SystemPromptMode, allProfiles, getProfile, profilesByProvider, tryGetProfile } from './profiles.js';
5
5
  import { IntentArchetypeName } from './dialect.js';
@@ -198,7 +198,30 @@ interface OutcomePayload {
198
198
  ttft_ms?: number;
199
199
  history_cacheable_tokens?: number;
200
200
  history_tokens_at_compile?: number;
201
+ /**
202
+ * Mirrors `ir.constraints.toolOrchestration` from compile time. NULL when
203
+ * the consumer hadn't adopted the constraint (pre-alpha.20). Powers
204
+ * per-mode model-perf queries on the brain (the L-040 parallel-tool
205
+ * cliff lumps DeepSeek sequential perf with parallel without this).
206
+ */
207
+ tool_orchestration?: 'parallel' | 'sequential' | 'either' | null;
201
208
  }
209
+ /**
210
+ * alpha.20 Entry 4: record a quality outcome for a previously-compiled call.
211
+ *
212
+ * Fires after the consumer's UX surfaces an approve/reject event (e.g., user
213
+ * clicks Approve on a hunt result). Joins to the original `compile_outcomes`
214
+ * row via outcomeId — enables per-(model, archetype) approve-rate measurement
215
+ * once N ≥ 10 outcomes accumulate.
216
+ *
217
+ * Fire-and-forget by default (matches record() semantics). Set BrainConfig.sync
218
+ * = true for runtime contexts that can't tolerate fire-and-forget teardown
219
+ * (Vercel Edge, Cloudflare Workers, AWS Lambda) — see L-086.
220
+ *
221
+ * Returns OutcomeResult with ok: false + stable reason on persistence
222
+ * failure. Never throws.
223
+ */
224
+ declare function recordOutcome(input: RecordOutcomeInput): Promise<OutcomeResult>;
202
225
 
203
226
  /**
204
227
  * Oracle contract — how an app tells the brain whether a response was good.
@@ -475,6 +498,23 @@ interface GetDefaultFallbackChainOpts {
475
498
  * legacy unfiltered behavior — preserves alpha.9 callers byte-for-byte.
476
499
  */
477
500
  reachability?: ReachabilityOpts;
501
+ /**
502
+ * alpha.20 E3: consumer-declared tool-orchestration shape. Currently
503
+ * only affects `archetype: 'hunt'`, where 'sequential' swaps the
504
+ * parallel-tool-tier-0 chain (Flash → Pro → Sonnet → Haiku) for a
505
+ * DeepSeek-tier-0 chain (V4-Pro → Flash → Sonnet) — DeepSeek's L-040
506
+ * parallel-tool cliff doesn't apply when the consumer commits to
507
+ * single-step orchestration.
508
+ *
509
+ * Other archetypes are NOT mode-aware in this release — they ship the
510
+ * same chain regardless of toolOrchestration. Future versions may
511
+ * extend mode-awareness to ask/generate/etc. when brain evidence
512
+ * supports it.
513
+ *
514
+ * Default (omitted or 'either'): parallel chain. Back-compat with all
515
+ * pre-alpha.20 callers.
516
+ */
517
+ toolOrchestration?: 'parallel' | 'sequential' | 'either';
478
518
  }
479
519
  declare function getDefaultFallbackChain(opts: GetDefaultFallbackChainOpts): string[];
480
520
  /**
@@ -718,4 +758,4 @@ declare const loadAliasesFromBrain: () => Record<string, string>;
718
758
  */
719
759
  declare function compile(ir: PromptIR, opts?: CompileOptions): CompileResult;
720
760
 
721
- export { ApiKeys, type AppOracle, type ArchetypePerfMap, BestPracticeAdvisory, type BrainConfig, type BrainQueryConfig, CallOptions, CallResult, type CompileOptions, CompilePolicy, CompileResult, CompiledRequest, type ExecuteErr, type ExecuteOk, type ExecuteOptions, type ExecuteResult, type FallbackPosture, type GetDefaultFallbackChainOpts, IntentArchetypeName, type LLMJudgeOptions, type ModelBrainRow, ModelProfile, NormalizedResponse, type OracleContext, OracleScore, type OutcomePayload, PROVIDER_ENV_KEYS, type PricingRow, type ProfileToRowOptions, PromptIR, Provider, ProviderOverrides, type ProviderReachability, type ReachabilityOpts, RecordInput, type SupportedProvider, buildLLMJudge, call, clearBrain, compile, configureBrain, countTokens, execute, getAllStarterChains, getArchetypePerfScore, getDefaultFallbackChain, getReachabilityDiagnostic, getStarterChain, isModelReachable, isProviderReachable, loadAliasesFromBrain, loadArchetypePerfFromBrain, loadChainsFromBrain, loadModelsFromBrain, loadPricingFromBrain, profileToRow, record, resetTokenizer, resolvePricingAt, resolveProviderKey, runAdvisor, setTokenizer };
761
+ export { ApiKeys, type AppOracle, type ArchetypePerfMap, BestPracticeAdvisory, type BrainConfig, type BrainQueryConfig, CallOptions, CallResult, type CompileOptions, CompilePolicy, CompileResult, CompiledRequest, type ExecuteErr, type ExecuteOk, type ExecuteOptions, type ExecuteResult, type FallbackPosture, type GetDefaultFallbackChainOpts, IntentArchetypeName, type LLMJudgeOptions, type ModelBrainRow, ModelProfile, NormalizedResponse, type OracleContext, OracleScore, type OutcomePayload, OutcomeResult, PROVIDER_ENV_KEYS, type PricingRow, type ProfileToRowOptions, PromptIR, Provider, ProviderOverrides, type ProviderReachability, type ReachabilityOpts, RecordInput, RecordOutcomeInput, type SupportedProvider, buildLLMJudge, call, clearBrain, compile, configureBrain, countTokens, execute, getAllStarterChains, getArchetypePerfScore, getDefaultFallbackChain, getReachabilityDiagnostic, getStarterChain, isModelReachable, isProviderReachable, loadAliasesFromBrain, loadArchetypePerfFromBrain, loadChainsFromBrain, loadModelsFromBrain, loadPricingFromBrain, profileToRow, record, recordOutcome, resetTokenizer, resolvePricingAt, resolveProviderKey, runAdvisor, setTokenizer };
package/dist/index.js CHANGED
@@ -56,6 +56,7 @@ __export(index_exports, {
56
56
  profileToRow: () => profileToRow,
57
57
  profilesByProvider: () => profilesByProvider,
58
58
  record: () => record,
59
+ recordOutcome: () => recordOutcome,
59
60
  resetTokenizer: () => resetTokenizer,
60
61
  resolvePricingAt: () => resolvePricingAt,
61
62
  resolveProviderKey: () => resolveProviderKey,
@@ -337,7 +338,11 @@ function passApplyCliffs(ir, profile, estimatedInputTokens) {
337
338
  const mutations = [];
338
339
  const hints = { qualityWarning: [] };
339
340
  let nextIR = ir;
341
+ const sequentialMode = nextIR.constraints?.toolOrchestration === "sequential";
340
342
  for (const cliff of profile.cliffs) {
343
+ if (sequentialMode && cliff.reason.includes("L-040")) {
344
+ continue;
345
+ }
341
346
  let triggered = false;
342
347
  switch (cliff.metric) {
343
348
  case "input_tokens":
@@ -2087,8 +2092,19 @@ function compile(ir, opts = {}) {
2087
2092
  cacheableTokens: lowered.diagnostics.cacheableTokens,
2088
2093
  estimatedCacheSavingsUsd: lowered.diagnostics.estimatedCacheSavingsUsd,
2089
2094
  historyCacheableTokens: lowered.diagnostics.historyCacheableTokens,
2090
- historyTokensTotal: compressed.historyTokensTotal
2095
+ historyTokensTotal: compressed.historyTokensTotal,
2096
+ // alpha.20 E3: mirror the consumer's declared mode for Glass-Box +
2097
+ // brain observability. Undefined when not declared (pre-alpha.20).
2098
+ toolOrchestration: ir.constraints?.toolOrchestration
2091
2099
  };
2100
+ if (ir.intent.archetype === "hunt" && ir.constraints?.toolOrchestration === "sequential") {
2101
+ accumulatedMutations.push({
2102
+ id: "sequential-mode-chain-selected",
2103
+ source: "tool_orchestration",
2104
+ passName: "compile",
2105
+ description: "ir.constraints.toolOrchestration='sequential' selected the DeepSeek-tier-0 hunt chain overlay (L-040 parallel-tool cliff doesn't apply at single-step granularity)."
2106
+ });
2107
+ }
2092
2108
  const advisories = runAdvisor(
2093
2109
  ir,
2094
2110
  {
@@ -2352,7 +2368,9 @@ function registerCompile(appId, archetype, ir, result) {
2352
2368
  mutationsApplied: result.mutationsApplied.map((m) => m.id),
2353
2369
  startedAt: Date.now(),
2354
2370
  historyCacheableTokens: result.diagnostics.historyCacheableTokens,
2355
- historyTokensTotal: result.diagnostics.historyTokensTotal
2371
+ historyTokensTotal: result.diagnostics.historyTokensTotal,
2372
+ // alpha.20 E3: capture consumer's declared mode for the brain payload.
2373
+ toolOrchestration: result.diagnostics.toolOrchestration
2356
2374
  });
2357
2375
  }
2358
2376
  async function record(input) {
@@ -2365,11 +2383,22 @@ async function record(input) {
2365
2383
  const config = activeConfig;
2366
2384
  const fetchFn = config.fetchImpl ?? fetch;
2367
2385
  const send = async () => {
2386
+ let outcomeId;
2368
2387
  try {
2369
2388
  const res = await fetchFn(`${config.endpoint}/outcomes`, {
2370
2389
  method: "POST",
2371
2390
  headers: {
2372
2391
  "Content-Type": "application/json",
2392
+ // alpha.20: request the inserted row back so we can JOIN advisories
2393
+ // to it via outcome_id. PostgREST returns the row when
2394
+ // `Prefer: return=representation` is set; proxies that pass the
2395
+ // header through (the recommended `const row = { ...body }` shape
2396
+ // from OutcomePayload's forward-compat rule) will surface
2397
+ // the row id. Proxies that don't (legacy / hand-rolled shapes)
2398
+ // simply produce no parseable id → secondary advisory POST is
2399
+ // skipped silently. Best-effort — primary outcome row is the
2400
+ // load-bearing write.
2401
+ Prefer: "return=representation",
2373
2402
  ...config.apiKey ? { Authorization: `Bearer ${config.apiKey}` } : {}
2374
2403
  },
2375
2404
  body: JSON.stringify(payload)
@@ -2378,6 +2407,29 @@ async function record(input) {
2378
2407
  const text = await res.text().catch(() => "<no body>");
2379
2408
  throw new Error(`brain ${res.status}: ${text}`);
2380
2409
  }
2410
+ outcomeId = await tryExtractOutcomeId(res);
2411
+ } catch (err) {
2412
+ (config.onError ?? defaultOnError2)(err);
2413
+ return;
2414
+ }
2415
+ const advisories = input.advisories;
2416
+ if (!advisories || advisories.length === 0) return;
2417
+ if (outcomeId === void 0) return;
2418
+ try {
2419
+ const advisoryPayload = advisories.map((a) => buildAdvisoryRow(outcomeId, a));
2420
+ const res = await fetchFn(`${config.endpoint}/compile_outcome_advisories`, {
2421
+ method: "POST",
2422
+ headers: {
2423
+ "Content-Type": "application/json",
2424
+ Prefer: "return=minimal",
2425
+ ...config.apiKey ? { Authorization: `Bearer ${config.apiKey}` } : {}
2426
+ },
2427
+ body: JSON.stringify(advisoryPayload)
2428
+ });
2429
+ if (!res.ok) {
2430
+ const text = await res.text().catch(() => "<no body>");
2431
+ throw new Error(`brain advisories ${res.status}: ${text}`);
2432
+ }
2381
2433
  } catch (err) {
2382
2434
  (config.onError ?? defaultOnError2)(err);
2383
2435
  }
@@ -2427,7 +2479,12 @@ function buildPayload(input, reg) {
2427
2479
  cost_usd_actual: costUsdActual,
2428
2480
  ttft_ms: input.ttftMs,
2429
2481
  history_cacheable_tokens: reg?.historyCacheableTokens,
2430
- history_tokens_at_compile: reg?.historyTokensTotal
2482
+ history_tokens_at_compile: reg?.historyTokensTotal,
2483
+ // alpha.20 E3: mirror consumer's declared tool-orchestration mode so
2484
+ // the brain can measure per-mode model perf separately (DeepSeek in
2485
+ // sequential vs parallel mode is two different stories — L-040).
2486
+ // Null when consumer hadn't adopted the constraint yet.
2487
+ tool_orchestration: reg?.toolOrchestration ?? null
2431
2488
  };
2432
2489
  }
2433
2490
  function computeCostUsd(modelId, tokensIn, tokensOut) {
@@ -2444,6 +2501,77 @@ function computeCostUsd(modelId, tokensIn, tokensOut) {
2444
2501
  const outUsd = tokensOut / 1e6 * profile.costOutputPer1m;
2445
2502
  return Math.round((inUsd + outUsd) * 1e6) / 1e6;
2446
2503
  }
2504
+ async function tryExtractOutcomeId(res) {
2505
+ try {
2506
+ const ct = res.headers?.get?.("content-type") ?? "";
2507
+ if (ct && !ct.includes("application/json")) return void 0;
2508
+ if (typeof res.json !== "function") return void 0;
2509
+ const body = await res.json();
2510
+ if (Array.isArray(body) && body.length > 0) {
2511
+ const first = body[0];
2512
+ const id = first?.id;
2513
+ if (typeof id === "number") return id;
2514
+ } else if (body && typeof body === "object") {
2515
+ const id = body.id;
2516
+ if (typeof id === "number") return id;
2517
+ }
2518
+ return void 0;
2519
+ } catch {
2520
+ return void 0;
2521
+ }
2522
+ }
2523
+ function buildAdvisoryRow(outcomeId, a) {
2524
+ return {
2525
+ outcome_id: outcomeId,
2526
+ code: a.code,
2527
+ level: a.level,
2528
+ message: a.message,
2529
+ ...a.recommendationType ? { recommendation_type: a.recommendationType } : {},
2530
+ ...a.suggestion ? { suggestion: a.suggestion } : {},
2531
+ ...a.docsUrl ? { docs_url: a.docsUrl } : {}
2532
+ };
2533
+ }
2534
+ async function recordOutcome(input) {
2535
+ if (!activeConfig) {
2536
+ return { ok: false, reason: "brain_not_configured" };
2537
+ }
2538
+ const config = activeConfig;
2539
+ const fetchFn = config.fetchImpl ?? fetch;
2540
+ const payload = {
2541
+ outcome_id: input.outcomeId,
2542
+ outcome: input.outcome,
2543
+ rating: input.rating ?? null,
2544
+ reason: input.reason ?? null,
2545
+ observed_confidence: input.observedConfidence ?? null
2546
+ };
2547
+ const send = async () => {
2548
+ try {
2549
+ const res = await fetchFn(`${config.endpoint}/compile_outcome_quality`, {
2550
+ method: "POST",
2551
+ headers: {
2552
+ "Content-Type": "application/json",
2553
+ ...config.apiKey ? { Authorization: `Bearer ${config.apiKey}` } : {}
2554
+ },
2555
+ body: JSON.stringify(payload)
2556
+ });
2557
+ if (!res.ok) {
2558
+ const text = await res.text().catch(() => "<no body>");
2559
+ const err = new Error(`brain ${res.status}: ${text}`);
2560
+ (config.onError ?? defaultOnError2)(err);
2561
+ return { ok: false, reason: "persistence_failed" };
2562
+ }
2563
+ return { ok: true };
2564
+ } catch (err) {
2565
+ (config.onError ?? defaultOnError2)(err);
2566
+ return { ok: false, reason: "persistence_failed" };
2567
+ }
2568
+ };
2569
+ if (config.sync) {
2570
+ return send();
2571
+ }
2572
+ void send();
2573
+ return { ok: true };
2574
+ }
2447
2575
 
2448
2576
  // src/ir.ts
2449
2577
  var CallError = class extends Error {
@@ -2823,7 +2951,10 @@ var STARTER_CHAINS = {
2823
2951
  ],
2824
2952
  // Parallel-tool throughput champion (Flash, L-040). Tier 1 cross-provider
2825
2953
  // Pro; tier 2 Sonnet (quality safety net for blocked-Flash case); tier 3
2826
- // Haiku (reduced tool budget — cliff at 16 fires).
2954
+ // Haiku (reduced tool budget — cliff at 16 fires). This is the
2955
+ // `toolOrchestration: 'parallel'` (default) hunt chain. The sequential
2956
+ // variant lives in STARTER_CHAINS_BY_MODE.hunt.sequential below — see
2957
+ // alpha.20 E3 / interfaces/kgauto.md `sequential-agentic-hunt-mode`.
2827
2958
  hunt: [
2828
2959
  "gemini-2.5-flash",
2829
2960
  "gemini-2.5-pro",
@@ -2847,15 +2978,37 @@ var STARTER_CHAINS = {
2847
2978
  "gemini-2.5-flash-lite"
2848
2979
  ]
2849
2980
  };
2981
+ var STARTER_CHAINS_BY_MODE = {
2982
+ hunt: {
2983
+ sequential: [
2984
+ // V4-Pro: cheap + good reasoning at single-step granularity; no
2985
+ // L-040 cliff applies when consumer commits to sequential.
2986
+ "deepseek-v4-pro",
2987
+ // V4-Flash: cheapest viable; sibling-provider fallback.
2988
+ "deepseek-v4-flash",
2989
+ // Cross-provider safety net — Sonnet handles sequential agentic loops
2990
+ // cleanly; Pro as third-provider tail when no DeepSeek key reachable.
2991
+ "claude-sonnet-4-6",
2992
+ "gemini-2.5-pro"
2993
+ ]
2994
+ }
2995
+ };
2996
+ function resolveStarterForMode(archetype, toolOrchestration, allChains) {
2997
+ if (toolOrchestration === "sequential") {
2998
+ const overlay = STARTER_CHAINS_BY_MODE[archetype]?.sequential;
2999
+ if (overlay) return [...overlay];
3000
+ }
3001
+ return allChains[archetype];
3002
+ }
2850
3003
  function getDefaultFallbackChain(opts) {
2851
- const { archetype, primary, maxDepth = 3, policy, reachability } = opts;
3004
+ const { archetype, primary, maxDepth = 3, policy, reachability, toolOrchestration } = opts;
2852
3005
  if (maxDepth < 1) {
2853
3006
  throw new Error(
2854
3007
  `getDefaultFallbackChain: maxDepth must be >= 1, got ${maxDepth}`
2855
3008
  );
2856
3009
  }
2857
3010
  const allChains = loadChainsFromBrain();
2858
- const starter = allChains[archetype];
3011
+ const starter = resolveStarterForMode(archetype, toolOrchestration, allChains);
2859
3012
  if (!starter) {
2860
3013
  throw new Error(
2861
3014
  `getDefaultFallbackChain: unknown archetype "${archetype}". Known: ${Object.keys(allChains).join(", ")}`
@@ -3832,6 +3985,7 @@ function compile2(ir, opts) {
3832
3985
  profileToRow,
3833
3986
  profilesByProvider,
3834
3987
  record,
3988
+ recordOutcome,
3835
3989
  resetTokenizer,
3836
3990
  resolvePricingAt,
3837
3991
  resolveProviderKey,
package/dist/index.mjs CHANGED
@@ -215,7 +215,11 @@ function passApplyCliffs(ir, profile, estimatedInputTokens) {
215
215
  const mutations = [];
216
216
  const hints = { qualityWarning: [] };
217
217
  let nextIR = ir;
218
+ const sequentialMode = nextIR.constraints?.toolOrchestration === "sequential";
218
219
  for (const cliff of profile.cliffs) {
220
+ if (sequentialMode && cliff.reason.includes("L-040")) {
221
+ continue;
222
+ }
219
223
  let triggered = false;
220
224
  switch (cliff.metric) {
221
225
  case "input_tokens":
@@ -891,8 +895,19 @@ function compile(ir, opts = {}) {
891
895
  cacheableTokens: lowered.diagnostics.cacheableTokens,
892
896
  estimatedCacheSavingsUsd: lowered.diagnostics.estimatedCacheSavingsUsd,
893
897
  historyCacheableTokens: lowered.diagnostics.historyCacheableTokens,
894
- historyTokensTotal: compressed.historyTokensTotal
898
+ historyTokensTotal: compressed.historyTokensTotal,
899
+ // alpha.20 E3: mirror the consumer's declared mode for Glass-Box +
900
+ // brain observability. Undefined when not declared (pre-alpha.20).
901
+ toolOrchestration: ir.constraints?.toolOrchestration
895
902
  };
903
+ if (ir.intent.archetype === "hunt" && ir.constraints?.toolOrchestration === "sequential") {
904
+ accumulatedMutations.push({
905
+ id: "sequential-mode-chain-selected",
906
+ source: "tool_orchestration",
907
+ passName: "compile",
908
+ description: "ir.constraints.toolOrchestration='sequential' selected the DeepSeek-tier-0 hunt chain overlay (L-040 parallel-tool cliff doesn't apply at single-step granularity)."
909
+ });
910
+ }
896
911
  const advisories = runAdvisor(
897
912
  ir,
898
913
  {
@@ -1156,7 +1171,9 @@ function registerCompile(appId, archetype, ir, result) {
1156
1171
  mutationsApplied: result.mutationsApplied.map((m) => m.id),
1157
1172
  startedAt: Date.now(),
1158
1173
  historyCacheableTokens: result.diagnostics.historyCacheableTokens,
1159
- historyTokensTotal: result.diagnostics.historyTokensTotal
1174
+ historyTokensTotal: result.diagnostics.historyTokensTotal,
1175
+ // alpha.20 E3: capture consumer's declared mode for the brain payload.
1176
+ toolOrchestration: result.diagnostics.toolOrchestration
1160
1177
  });
1161
1178
  }
1162
1179
  async function record(input) {
@@ -1169,11 +1186,22 @@ async function record(input) {
1169
1186
  const config = activeConfig;
1170
1187
  const fetchFn = config.fetchImpl ?? fetch;
1171
1188
  const send = async () => {
1189
+ let outcomeId;
1172
1190
  try {
1173
1191
  const res = await fetchFn(`${config.endpoint}/outcomes`, {
1174
1192
  method: "POST",
1175
1193
  headers: {
1176
1194
  "Content-Type": "application/json",
1195
+ // alpha.20: request the inserted row back so we can JOIN advisories
1196
+ // to it via outcome_id. PostgREST returns the row when
1197
+ // `Prefer: return=representation` is set; proxies that pass the
1198
+ // header through (the recommended `const row = { ...body }` shape
1199
+ // from OutcomePayload's forward-compat rule) will surface
1200
+ // the row id. Proxies that don't (legacy / hand-rolled shapes)
1201
+ // simply produce no parseable id → secondary advisory POST is
1202
+ // skipped silently. Best-effort — primary outcome row is the
1203
+ // load-bearing write.
1204
+ Prefer: "return=representation",
1177
1205
  ...config.apiKey ? { Authorization: `Bearer ${config.apiKey}` } : {}
1178
1206
  },
1179
1207
  body: JSON.stringify(payload)
@@ -1182,6 +1210,29 @@ async function record(input) {
1182
1210
  const text = await res.text().catch(() => "<no body>");
1183
1211
  throw new Error(`brain ${res.status}: ${text}`);
1184
1212
  }
1213
+ outcomeId = await tryExtractOutcomeId(res);
1214
+ } catch (err) {
1215
+ (config.onError ?? defaultOnError2)(err);
1216
+ return;
1217
+ }
1218
+ const advisories = input.advisories;
1219
+ if (!advisories || advisories.length === 0) return;
1220
+ if (outcomeId === void 0) return;
1221
+ try {
1222
+ const advisoryPayload = advisories.map((a) => buildAdvisoryRow(outcomeId, a));
1223
+ const res = await fetchFn(`${config.endpoint}/compile_outcome_advisories`, {
1224
+ method: "POST",
1225
+ headers: {
1226
+ "Content-Type": "application/json",
1227
+ Prefer: "return=minimal",
1228
+ ...config.apiKey ? { Authorization: `Bearer ${config.apiKey}` } : {}
1229
+ },
1230
+ body: JSON.stringify(advisoryPayload)
1231
+ });
1232
+ if (!res.ok) {
1233
+ const text = await res.text().catch(() => "<no body>");
1234
+ throw new Error(`brain advisories ${res.status}: ${text}`);
1235
+ }
1185
1236
  } catch (err) {
1186
1237
  (config.onError ?? defaultOnError2)(err);
1187
1238
  }
@@ -1231,7 +1282,12 @@ function buildPayload(input, reg) {
1231
1282
  cost_usd_actual: costUsdActual,
1232
1283
  ttft_ms: input.ttftMs,
1233
1284
  history_cacheable_tokens: reg?.historyCacheableTokens,
1234
- history_tokens_at_compile: reg?.historyTokensTotal
1285
+ history_tokens_at_compile: reg?.historyTokensTotal,
1286
+ // alpha.20 E3: mirror consumer's declared tool-orchestration mode so
1287
+ // the brain can measure per-mode model perf separately (DeepSeek in
1288
+ // sequential vs parallel mode is two different stories — L-040).
1289
+ // Null when consumer hadn't adopted the constraint yet.
1290
+ tool_orchestration: reg?.toolOrchestration ?? null
1235
1291
  };
1236
1292
  }
1237
1293
  function computeCostUsd(modelId, tokensIn, tokensOut) {
@@ -1248,6 +1304,77 @@ function computeCostUsd(modelId, tokensIn, tokensOut) {
1248
1304
  const outUsd = tokensOut / 1e6 * profile.costOutputPer1m;
1249
1305
  return Math.round((inUsd + outUsd) * 1e6) / 1e6;
1250
1306
  }
1307
+ async function tryExtractOutcomeId(res) {
1308
+ try {
1309
+ const ct = res.headers?.get?.("content-type") ?? "";
1310
+ if (ct && !ct.includes("application/json")) return void 0;
1311
+ if (typeof res.json !== "function") return void 0;
1312
+ const body = await res.json();
1313
+ if (Array.isArray(body) && body.length > 0) {
1314
+ const first = body[0];
1315
+ const id = first?.id;
1316
+ if (typeof id === "number") return id;
1317
+ } else if (body && typeof body === "object") {
1318
+ const id = body.id;
1319
+ if (typeof id === "number") return id;
1320
+ }
1321
+ return void 0;
1322
+ } catch {
1323
+ return void 0;
1324
+ }
1325
+ }
1326
+ function buildAdvisoryRow(outcomeId, a) {
1327
+ return {
1328
+ outcome_id: outcomeId,
1329
+ code: a.code,
1330
+ level: a.level,
1331
+ message: a.message,
1332
+ ...a.recommendationType ? { recommendation_type: a.recommendationType } : {},
1333
+ ...a.suggestion ? { suggestion: a.suggestion } : {},
1334
+ ...a.docsUrl ? { docs_url: a.docsUrl } : {}
1335
+ };
1336
+ }
1337
+ async function recordOutcome(input) {
1338
+ if (!activeConfig) {
1339
+ return { ok: false, reason: "brain_not_configured" };
1340
+ }
1341
+ const config = activeConfig;
1342
+ const fetchFn = config.fetchImpl ?? fetch;
1343
+ const payload = {
1344
+ outcome_id: input.outcomeId,
1345
+ outcome: input.outcome,
1346
+ rating: input.rating ?? null,
1347
+ reason: input.reason ?? null,
1348
+ observed_confidence: input.observedConfidence ?? null
1349
+ };
1350
+ const send = async () => {
1351
+ try {
1352
+ const res = await fetchFn(`${config.endpoint}/compile_outcome_quality`, {
1353
+ method: "POST",
1354
+ headers: {
1355
+ "Content-Type": "application/json",
1356
+ ...config.apiKey ? { Authorization: `Bearer ${config.apiKey}` } : {}
1357
+ },
1358
+ body: JSON.stringify(payload)
1359
+ });
1360
+ if (!res.ok) {
1361
+ const text = await res.text().catch(() => "<no body>");
1362
+ const err = new Error(`brain ${res.status}: ${text}`);
1363
+ (config.onError ?? defaultOnError2)(err);
1364
+ return { ok: false, reason: "persistence_failed" };
1365
+ }
1366
+ return { ok: true };
1367
+ } catch (err) {
1368
+ (config.onError ?? defaultOnError2)(err);
1369
+ return { ok: false, reason: "persistence_failed" };
1370
+ }
1371
+ };
1372
+ if (config.sync) {
1373
+ return send();
1374
+ }
1375
+ void send();
1376
+ return { ok: true };
1377
+ }
1251
1378
 
1252
1379
  // src/ir.ts
1253
1380
  var CallError = class extends Error {
@@ -1627,7 +1754,10 @@ var STARTER_CHAINS = {
1627
1754
  ],
1628
1755
  // Parallel-tool throughput champion (Flash, L-040). Tier 1 cross-provider
1629
1756
  // Pro; tier 2 Sonnet (quality safety net for blocked-Flash case); tier 3
1630
- // Haiku (reduced tool budget — cliff at 16 fires).
1757
+ // Haiku (reduced tool budget — cliff at 16 fires). This is the
1758
+ // `toolOrchestration: 'parallel'` (default) hunt chain. The sequential
1759
+ // variant lives in STARTER_CHAINS_BY_MODE.hunt.sequential below — see
1760
+ // alpha.20 E3 / interfaces/kgauto.md `sequential-agentic-hunt-mode`.
1631
1761
  hunt: [
1632
1762
  "gemini-2.5-flash",
1633
1763
  "gemini-2.5-pro",
@@ -1651,15 +1781,37 @@ var STARTER_CHAINS = {
1651
1781
  "gemini-2.5-flash-lite"
1652
1782
  ]
1653
1783
  };
1784
+ var STARTER_CHAINS_BY_MODE = {
1785
+ hunt: {
1786
+ sequential: [
1787
+ // V4-Pro: cheap + good reasoning at single-step granularity; no
1788
+ // L-040 cliff applies when consumer commits to sequential.
1789
+ "deepseek-v4-pro",
1790
+ // V4-Flash: cheapest viable; sibling-provider fallback.
1791
+ "deepseek-v4-flash",
1792
+ // Cross-provider safety net — Sonnet handles sequential agentic loops
1793
+ // cleanly; Pro as third-provider tail when no DeepSeek key reachable.
1794
+ "claude-sonnet-4-6",
1795
+ "gemini-2.5-pro"
1796
+ ]
1797
+ }
1798
+ };
1799
+ function resolveStarterForMode(archetype, toolOrchestration, allChains) {
1800
+ if (toolOrchestration === "sequential") {
1801
+ const overlay = STARTER_CHAINS_BY_MODE[archetype]?.sequential;
1802
+ if (overlay) return [...overlay];
1803
+ }
1804
+ return allChains[archetype];
1805
+ }
1654
1806
  function getDefaultFallbackChain(opts) {
1655
- const { archetype, primary, maxDepth = 3, policy, reachability } = opts;
1807
+ const { archetype, primary, maxDepth = 3, policy, reachability, toolOrchestration } = opts;
1656
1808
  if (maxDepth < 1) {
1657
1809
  throw new Error(
1658
1810
  `getDefaultFallbackChain: maxDepth must be >= 1, got ${maxDepth}`
1659
1811
  );
1660
1812
  }
1661
1813
  const allChains = loadChainsFromBrain();
1662
- const starter = allChains[archetype];
1814
+ const starter = resolveStarterForMode(archetype, toolOrchestration, allChains);
1663
1815
  if (!starter) {
1664
1816
  throw new Error(
1665
1817
  `getDefaultFallbackChain: unknown archetype "${archetype}". Known: ${Object.keys(allChains).join(", ")}`
@@ -2347,6 +2499,7 @@ export {
2347
2499
  profileToRow,
2348
2500
  profilesByProvider,
2349
2501
  record,
2502
+ recordOutcome,
2350
2503
  resetTokenizer,
2351
2504
  resolvePricingAt,
2352
2505
  resolveProviderKey,
@@ -90,6 +90,29 @@ interface Constraints {
90
90
  maxResponseWords?: number;
91
91
  /** Override target model selection — if set, compiler uses this instead of routing. */
92
92
  forceModel?: string;
93
+ /**
94
+ * alpha.20: consumer-declared tool-orchestration shape for this call.
95
+ * - 'parallel': model may fire multiple tool calls per step (current
96
+ * default behavior; the L-040 cliff applies — DeepSeek's
97
+ * `tool_count >= 1` cliff trims tools because parallel-tool throughput
98
+ * collapses to sequential semantics).
99
+ * - 'sequential': consumer commits to one tool call per step (the agentic
100
+ * loop pattern). DeepSeek V4-Flash + V4-Pro can compete cleanly in
101
+ * this mode — the L-040 cliff is silenced and the hunt chain shifts
102
+ * to a DeepSeek-tier-1 ordering.
103
+ * - 'either': consumer doesn't care; library picks the parallel chain
104
+ * (status-quo default) and may upgrade to brain-driven per-mode perf
105
+ * selection in a future release.
106
+ *
107
+ * Affects:
108
+ * - Chain composition for `archetype: 'hunt'` (see
109
+ * `getDefaultFallbackChain` and `STARTER_CHAINS_BY_MODE`).
110
+ * - L-040 cliff in `passApplyCliffs` (silent when 'sequential').
111
+ *
112
+ * Default (when undefined): equivalent to 'parallel' for back-compat
113
+ * with every pre-alpha.20 caller.
114
+ */
115
+ toolOrchestration?: 'parallel' | 'sequential' | 'either';
93
116
  }
94
117
  /**
95
118
  * Cache marker policy for the messages array (history + currentTurn).
@@ -308,6 +331,21 @@ interface BestPracticeAdvisory {
308
331
  suggestion?: string;
309
332
  /** Optional: link to docs anchor for more context. */
310
333
  docsUrl?: string;
334
+ /**
335
+ * alpha.20 — actionable category for routing/dashboard surfacing. When set,
336
+ * the brain persists this as `recommendation_type` on
337
+ * `compile_outcome_advisories` so consumers can filter "show me all
338
+ * client-side issues that are caching-fix recommendations." Optional;
339
+ * absent on legacy or uncategorized rules.
340
+ *
341
+ * - `'model-swap'` — swap to a different model fixes this
342
+ * - `'prompt-fix'` — restructure prompt (sections, tools, format)
343
+ * - `'caching-fix'` — add cache markers (system or history)
344
+ * - `'no-ai-needed'` — the call shouldn't be using an AI model
345
+ * - `'tier-down'` — current model is overkill for this archetype
346
+ * - `'architecture-change'` — the issue isn't fixable at the kgauto layer
347
+ */
348
+ recommendationType?: 'model-swap' | 'prompt-fix' | 'caching-fix' | 'no-ai-needed' | 'tier-down' | 'architecture-change';
311
349
  }
312
350
  interface CompileResult {
313
351
  /** Unique handle for this call — pass to record() to correlate the outcome. */
@@ -359,6 +397,14 @@ interface CompileResult {
359
397
  * 0 when history is empty. alpha.7.
360
398
  */
361
399
  historyTokensTotal: number;
400
+ /**
401
+ * alpha.20 E3. Consumer-declared tool-orchestration mode for this call,
402
+ * mirrored from `ir.constraints.toolOrchestration` for downstream
403
+ * observability (Glass-Box panel, brain telemetry, advisor logs).
404
+ * Undefined when the consumer hadn't adopted the constraint yet —
405
+ * treat as 'parallel' equivalent for back-compat.
406
+ */
407
+ toolOrchestration?: 'parallel' | 'sequential' | 'either';
362
408
  };
363
409
  }
364
410
  /**
@@ -634,6 +680,66 @@ interface RecordInput {
634
680
  * surfaces it. Distinct from `latencyMs` (end-to-end wall clock).
635
681
  */
636
682
  ttftMs?: number;
683
+ /**
684
+ * alpha.20 — advisories fired at compile() time. Persisted to the brain's
685
+ * `compile_outcome_advisories` sibling table via a second POST that fires
686
+ * AFTER the primary outcome insert succeeds. Best-effort: a failed
687
+ * advisory POST is logged via onError but does NOT throw or roll back the
688
+ * primary outcome row.
689
+ *
690
+ * Pass `result.advisories` from the CompileResult directly. The brain
691
+ * uses these to compute the `empty_rate_clean` comparator (rows with
692
+ * zero advisories fired) so consumers can distinguish "model is bad"
693
+ * from "client sent a bloated/uncached/malformed request."
694
+ *
695
+ * Empty array / undefined → no second POST fires.
696
+ */
697
+ advisories?: BestPracticeAdvisory[];
698
+ }
699
+ /**
700
+ * alpha.20 Entry 4: kinds of consumer-declared outcomes feeding the quality
701
+ * loop. Surfaces in `recordOutcome()` as the verdict the consumer's UX is
702
+ * forwarding to the brain.
703
+ *
704
+ * - `approved` user explicitly approved (thumbs up, "looks good", accepted)
705
+ * - `rejected` user explicitly rejected (thumbs down, "redo", discarded)
706
+ * - `partial` accepted with edits or partial use (mixed signal)
707
+ * - `engaged` user engaged with the output (copy/scroll/dwell)
708
+ * - `abandoned` user abandoned the response (closed, navigated away)
709
+ * - `unknown` verdict could not be inferred — recorded for completeness
710
+ */
711
+ type OutcomeKind = 'approved' | 'rejected' | 'partial' | 'engaged' | 'abandoned' | 'unknown';
712
+ /**
713
+ * Input to `recordOutcome()` — consumer's verdict on a previously-compiled
714
+ * call. Joins to the original `compile_outcomes` row via outcomeId,
715
+ * enabling per-(model, archetype) approve-rate measurement once N ≥ 10
716
+ * outcomes accumulate.
717
+ */
718
+ interface RecordOutcomeInput {
719
+ /** Joins to compile_outcomes.id. Returned by compile() via CompileResult.outcomeId. */
720
+ outcomeId: number | string;
721
+ /** What did the user / system do with this output? */
722
+ outcome: OutcomeKind;
723
+ /** Optional 1-5 user rating (e.g., thumbs up/down with intensity, NPS-style). */
724
+ rating?: 1 | 2 | 3 | 4 | 5;
725
+ /** Optional free-text reason (e.g., user-typed feedback, system-inferred cause). */
726
+ reason?: string;
727
+ /**
728
+ * Optional model-reported confidence at compile time (0..1). Used for
729
+ * Brier-score calibration in later phases (alpha.21+) — pair this with
730
+ * the actual `outcome` to compute calibration error.
731
+ */
732
+ observedConfidence?: number;
733
+ }
734
+ /**
735
+ * Return shape of `recordOutcome()`. Never throws — persistence failures
736
+ * surface as `ok: false` with a stable `reason` string.
737
+ */
738
+ interface OutcomeResult {
739
+ /** True when the POST landed (2xx). False when brain not configured or POST failed. */
740
+ ok: boolean;
741
+ /** Stable reason code when ok=false. One of: 'brain_not_configured' | 'persistence_failed'. */
742
+ reason?: string;
637
743
  }
638
744
 
639
- export { type ApiKeys as A, type BestPracticeAdvisory as B, type CompilePolicy as C, type FallbackReason as F, type HistoryCachePolicy as H, type IntentDeclaration as I, type Message as M, type NormalizedResponse as N, type OracleScore as O, type ProviderOverrides as P, type RecordInput as R, type ToolCall as T, type CompiledRequest as a, type PromptIR as b, type CallOptions as c, type CallResult as d, type CompileResult as e, type Provider as f, type CallAttempt as g, CallError as h, type Constraints as i, type MutationApplied as j, type NormalizedTokens as k, type PromptSection as l, type ToolDefinition as m };
745
+ export { type ApiKeys as A, type BestPracticeAdvisory as B, type CompilePolicy as C, type FallbackReason as F, type HistoryCachePolicy as H, type IntentDeclaration as I, type Message as M, type NormalizedResponse as N, type OutcomeResult as O, type ProviderOverrides as P, type RecordInput as R, type ToolCall as T, type CompiledRequest as a, type PromptIR as b, type CallOptions as c, type CallResult as d, type RecordOutcomeInput as e, type OracleScore as f, type CompileResult as g, type Provider as h, type CallAttempt as i, CallError as j, type Constraints as k, type MutationApplied as l, type NormalizedTokens as m, type OutcomeKind as n, type PromptSection as o, type ToolDefinition as p };
@@ -90,6 +90,29 @@ interface Constraints {
90
90
  maxResponseWords?: number;
91
91
  /** Override target model selection — if set, compiler uses this instead of routing. */
92
92
  forceModel?: string;
93
+ /**
94
+ * alpha.20: consumer-declared tool-orchestration shape for this call.
95
+ * - 'parallel': model may fire multiple tool calls per step (current
96
+ * default behavior; the L-040 cliff applies — DeepSeek's
97
+ * `tool_count >= 1` cliff trims tools because parallel-tool throughput
98
+ * collapses to sequential semantics).
99
+ * - 'sequential': consumer commits to one tool call per step (the agentic
100
+ * loop pattern). DeepSeek V4-Flash + V4-Pro can compete cleanly in
101
+ * this mode — the L-040 cliff is silenced and the hunt chain shifts
102
+ * to a DeepSeek-tier-1 ordering.
103
+ * - 'either': consumer doesn't care; library picks the parallel chain
104
+ * (status-quo default) and may upgrade to brain-driven per-mode perf
105
+ * selection in a future release.
106
+ *
107
+ * Affects:
108
+ * - Chain composition for `archetype: 'hunt'` (see
109
+ * `getDefaultFallbackChain` and `STARTER_CHAINS_BY_MODE`).
110
+ * - L-040 cliff in `passApplyCliffs` (silent when 'sequential').
111
+ *
112
+ * Default (when undefined): equivalent to 'parallel' for back-compat
113
+ * with every pre-alpha.20 caller.
114
+ */
115
+ toolOrchestration?: 'parallel' | 'sequential' | 'either';
93
116
  }
94
117
  /**
95
118
  * Cache marker policy for the messages array (history + currentTurn).
@@ -308,6 +331,21 @@ interface BestPracticeAdvisory {
308
331
  suggestion?: string;
309
332
  /** Optional: link to docs anchor for more context. */
310
333
  docsUrl?: string;
334
+ /**
335
+ * alpha.20 — actionable category for routing/dashboard surfacing. When set,
336
+ * the brain persists this as `recommendation_type` on
337
+ * `compile_outcome_advisories` so consumers can filter "show me all
338
+ * client-side issues that are caching-fix recommendations." Optional;
339
+ * absent on legacy or uncategorized rules.
340
+ *
341
+ * - `'model-swap'` — swap to a different model fixes this
342
+ * - `'prompt-fix'` — restructure prompt (sections, tools, format)
343
+ * - `'caching-fix'` — add cache markers (system or history)
344
+ * - `'no-ai-needed'` — the call shouldn't be using an AI model
345
+ * - `'tier-down'` — current model is overkill for this archetype
346
+ * - `'architecture-change'` — the issue isn't fixable at the kgauto layer
347
+ */
348
+ recommendationType?: 'model-swap' | 'prompt-fix' | 'caching-fix' | 'no-ai-needed' | 'tier-down' | 'architecture-change';
311
349
  }
312
350
  interface CompileResult {
313
351
  /** Unique handle for this call — pass to record() to correlate the outcome. */
@@ -359,6 +397,14 @@ interface CompileResult {
359
397
  * 0 when history is empty. alpha.7.
360
398
  */
361
399
  historyTokensTotal: number;
400
+ /**
401
+ * alpha.20 E3. Consumer-declared tool-orchestration mode for this call,
402
+ * mirrored from `ir.constraints.toolOrchestration` for downstream
403
+ * observability (Glass-Box panel, brain telemetry, advisor logs).
404
+ * Undefined when the consumer hadn't adopted the constraint yet —
405
+ * treat as 'parallel' equivalent for back-compat.
406
+ */
407
+ toolOrchestration?: 'parallel' | 'sequential' | 'either';
362
408
  };
363
409
  }
364
410
  /**
@@ -634,6 +680,66 @@ interface RecordInput {
634
680
  * surfaces it. Distinct from `latencyMs` (end-to-end wall clock).
635
681
  */
636
682
  ttftMs?: number;
683
+ /**
684
+ * alpha.20 — advisories fired at compile() time. Persisted to the brain's
685
+ * `compile_outcome_advisories` sibling table via a second POST that fires
686
+ * AFTER the primary outcome insert succeeds. Best-effort: a failed
687
+ * advisory POST is logged via onError but does NOT throw or roll back the
688
+ * primary outcome row.
689
+ *
690
+ * Pass `result.advisories` from the CompileResult directly. The brain
691
+ * uses these to compute the `empty_rate_clean` comparator (rows with
692
+ * zero advisories fired) so consumers can distinguish "model is bad"
693
+ * from "client sent a bloated/uncached/malformed request."
694
+ *
695
+ * Empty array / undefined → no second POST fires.
696
+ */
697
+ advisories?: BestPracticeAdvisory[];
698
+ }
699
+ /**
700
+ * alpha.20 Entry 4: kinds of consumer-declared outcomes feeding the quality
701
+ * loop. Surfaces in `recordOutcome()` as the verdict the consumer's UX is
702
+ * forwarding to the brain.
703
+ *
704
+ * - `approved` user explicitly approved (thumbs up, "looks good", accepted)
705
+ * - `rejected` user explicitly rejected (thumbs down, "redo", discarded)
706
+ * - `partial` accepted with edits or partial use (mixed signal)
707
+ * - `engaged` user engaged with the output (copy/scroll/dwell)
708
+ * - `abandoned` user abandoned the response (closed, navigated away)
709
+ * - `unknown` verdict could not be inferred — recorded for completeness
710
+ */
711
+ type OutcomeKind = 'approved' | 'rejected' | 'partial' | 'engaged' | 'abandoned' | 'unknown';
712
+ /**
713
+ * Input to `recordOutcome()` — consumer's verdict on a previously-compiled
714
+ * call. Joins to the original `compile_outcomes` row via outcomeId,
715
+ * enabling per-(model, archetype) approve-rate measurement once N ≥ 10
716
+ * outcomes accumulate.
717
+ */
718
+ interface RecordOutcomeInput {
719
+ /** Joins to compile_outcomes.id. Returned by compile() via CompileResult.outcomeId. */
720
+ outcomeId: number | string;
721
+ /** What did the user / system do with this output? */
722
+ outcome: OutcomeKind;
723
+ /** Optional 1-5 user rating (e.g., thumbs up/down with intensity, NPS-style). */
724
+ rating?: 1 | 2 | 3 | 4 | 5;
725
+ /** Optional free-text reason (e.g., user-typed feedback, system-inferred cause). */
726
+ reason?: string;
727
+ /**
728
+ * Optional model-reported confidence at compile time (0..1). Used for
729
+ * Brier-score calibration in later phases (alpha.21+) — pair this with
730
+ * the actual `outcome` to compute calibration error.
731
+ */
732
+ observedConfidence?: number;
733
+ }
734
+ /**
735
+ * Return shape of `recordOutcome()`. Never throws — persistence failures
736
+ * surface as `ok: false` with a stable `reason` string.
737
+ */
738
+ interface OutcomeResult {
739
+ /** True when the POST landed (2xx). False when brain not configured or POST failed. */
740
+ ok: boolean;
741
+ /** Stable reason code when ok=false. One of: 'brain_not_configured' | 'persistence_failed'. */
742
+ reason?: string;
637
743
  }
638
744
 
639
- export { type ApiKeys as A, type BestPracticeAdvisory as B, type CompilePolicy as C, type FallbackReason as F, type HistoryCachePolicy as H, type IntentDeclaration as I, type Message as M, type NormalizedResponse as N, type OracleScore as O, type ProviderOverrides as P, type RecordInput as R, type ToolCall as T, type CompiledRequest as a, type PromptIR as b, type CallOptions as c, type CallResult as d, type CompileResult as e, type Provider as f, type CallAttempt as g, CallError as h, type Constraints as i, type MutationApplied as j, type NormalizedTokens as k, type PromptSection as l, type ToolDefinition as m };
745
+ export { type ApiKeys as A, type BestPracticeAdvisory as B, type CompilePolicy as C, type FallbackReason as F, type HistoryCachePolicy as H, type IntentDeclaration as I, type Message as M, type NormalizedResponse as N, type OutcomeResult as O, type ProviderOverrides as P, type RecordInput as R, type ToolCall as T, type CompiledRequest as a, type PromptIR as b, type CallOptions as c, type CallResult as d, type RecordOutcomeInput as e, type OracleScore as f, type CompileResult as g, type Provider as h, type CallAttempt as i, CallError as j, type Constraints as k, type MutationApplied as l, type NormalizedTokens as m, type OutcomeKind as n, type PromptSection as o, type ToolDefinition as p };
@@ -1,4 +1,4 @@
1
- import { f as Provider } from './ir-C3P4gDt0.mjs';
1
+ import { h as Provider } from './ir-DTMbSnyE.mjs';
2
2
  import { IntentArchetypeName } from './dialect.mjs';
3
3
 
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { f as Provider } from './ir-CFHU3BUT.js';
1
+ import { h as Provider } from './ir-CsTU4cMB.js';
2
2
  import { IntentArchetypeName } from './dialect.js';
3
3
 
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { j as MutationApplied, B as BestPracticeAdvisory, F as FallbackReason, g as CallAttempt } from './ir-C3P4gDt0.mjs';
1
+ import { l as MutationApplied, B as BestPracticeAdvisory, F as FallbackReason, i as CallAttempt } from './ir-DTMbSnyE.mjs';
2
2
 
3
3
  /**
4
4
  * Glass-Box observability types (alpha.17).
@@ -1,4 +1,4 @@
1
- import { j as MutationApplied, B as BestPracticeAdvisory, F as FallbackReason, g as CallAttempt } from './ir-CFHU3BUT.js';
1
+ import { l as MutationApplied, B as BestPracticeAdvisory, F as FallbackReason, i as CallAttempt } from './ir-CsTU4cMB.js';
2
2
 
3
3
  /**
4
4
  * Glass-Box observability types (alpha.17).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@warmdrift/kgauto-compiler",
3
- "version": "2.0.0-alpha.19",
3
+ "version": "2.0.0-alpha.20",
4
4
  "description": "Prompt compiler + central learning brain for multi-model AI apps. Swap models without rewriting prompts.",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",