/** * Injection Engine — subflow builder. * * Pattern: Subflow Builder (returns a FlowChart mountable via * `addSubFlowChartNext`). Each subflow stands alone. * Role: Layer-3 context-engineering primitive. Sits BEFORE the * three slot subflows in any primitive (Agent, LLMCall) that * uses Injections. Evaluates every Injection's trigger once * per iteration. * * Four small, readable stages (was one monolithic `evaluate`): * 1. Gather — snapshot the turn's inputs (iteration, history size, * last tool, LLM-activated count). Observability only. * 2. Evaluate — run every trigger → `activeInjections` (the REAL output * the slot subflows read). Logic is UNCHANGED from the old * single stage: `activeInjections` is byte-identical, so the * slots are 100% unaffected. (Safety invariant.) * 3. Route — partition `activeInjections` into per-slot buckets * (`activeByslot`), mirroring how the slots filter. Pure * annotation — the slots still do their own filtering. * 4. Delta — diff this turn's buckets vs last turn's (`slotDelta`): * per slot, what activated / deactivated / stayed. The * explainability win ("tools +skill X, system-prompt * unchanged"). Reads last turn via `priorActiveByslot` * carried by the mount's input/output mappers. * * Nothing here SKIPS a slot — Route/Delta only annotate. See * docs (injection-algorithm blog) + memory agentfootprint_slot_plan_review * for why per-turn skip was deferred. * * Emits: `agentfootprint.context.evaluated` at the Evaluate stage, with * aggregate metadata. The per-slot route/delta ride visible stage * STATE (`activeByslot` / `slotDelta`) so the lens reads them from * the commit log without a new event-type contract. * * Mount with: * builder.addSubFlowChartNext( * SUBFLOW_IDS.INJECTION_ENGINE, * buildInjectionEngineSubflow({ injections }), * 'Injection Engine', * { * inputMapper: (parent) => ({ * iteration: parent.iteration, * userMessage: parent.userMessage, * history: parent.history, * lastToolResult: parent.lastToolResult, * activatedInjectionIds: parent.activatedInjectionIds ?? [], * priorActiveByslot: parent.activeByslot ?? EMPTY_ACTIVE_BY_SLOT, * }), * outputMapper: (sf) => ({ * activeInjections: sf.activeInjections, * activeByslot: sf.activeByslot, // carried so next turn's Delta can diff * }), * }, * ) */ import type { FlowChart } from 'footprintjs'; import { type ActiveInjection, type Injection, type InjectionContext } from './types.js'; export interface InjectionEngineConfig { /** * The Injection list. Frozen at build time. To change at runtime, * rebuild the agent / chart — the primitive is intentionally * declarative. */ readonly injections: readonly Injection[]; /** * The skill-graph CURSOR resolver (`graph.nextSkill`), present only when the * agent was built with `.skillGraph()`. The Evaluate stage advances the cursor * with the SAME `ctx` the triggers gate on, so trigger ↔ cursor never diverge * (the keystone). Absent → `currentSkillId` is never written (no graph routing). */ readonly nextSkill?: (ctx: InjectionContext) => string | undefined; } /** One routed entry per (active injection × slot it contributes to). */ export interface RoutedInjection { readonly id: string; readonly source: ActiveInjection['flavor']; readonly reason: string; } /** Active injections partitioned by the slot they contribute to. */ export interface ActiveBySlot { readonly systemPrompt: readonly RoutedInjection[]; readonly messages: readonly RoutedInjection[]; readonly tools: readonly RoutedInjection[]; } /** Per-slot change since last turn. */ export interface SlotDeltaEntry { readonly added: readonly string[]; readonly removed: readonly string[]; readonly kept: readonly string[]; } /** Per-slot delta across the whole context. */ export interface SlotDelta { readonly systemPrompt: SlotDeltaEntry; readonly messages: SlotDeltaEntry; readonly tools: SlotDeltaEntry; } /** Empty buckets — turn-1 prior, and the safe default for the mappers. */ export declare const EMPTY_ACTIVE_BY_SLOT: ActiveBySlot; /** * Build the Injection Engine subflow — Gather → Evaluate → Route → Delta. */ export declare function buildInjectionEngineSubflow(config: InjectionEngineConfig): FlowChart; /** Partition active injections by the slot(s) each contributes to. Mirrors * the slot subflows' own filters so this view matches what they compose. * Pure — exported for unit tests + reuse (e.g. the lens). */ export declare function routeActiveInjections(active: readonly ActiveInjection[]): ActiveBySlot; /** Diff two per-slot snapshots into a per-slot delta. Pure — exported for * unit tests + reuse. */ export declare function diffActiveBySlot(prior: ActiveBySlot, current: ActiveBySlot): SlotDelta; //# sourceMappingURL=buildInjectionEngineSubflow.d.ts.map