import type { LanguageModel, ModelMessage, TranscriptionModel } from 'ai'; import type { UserInputContent } from './userInput.js'; import type { Session } from '../types/session.js'; import type { SessionEventLog } from '../events/SessionEventLog.js'; import type { SessionStore } from '../session/SessionStore.js'; import type { AuditListOptions, ConversationAuditEntry } from '../audit/types.js'; import type { AgentConfig } from '../types/agentConfig.js'; import type { ChannelDriver } from '../types/channel.js'; import type { Hooks } from '../types/hooks.js'; import type { AnyTool } from '../types/effectTool.js'; import type { TurnHandle } from '../types/stream.js'; import { type InterruptRequest, type RunKind, type SignalDelivery } from './durable/types.js'; import type { ResolvedSelection } from '../types/selection.js'; import type { ConversationOutcome, ConversationOutcomeMarkedBy } from '../outcomes/types.js'; import type { DeploymentTraceContext } from '../types/trace.js'; import type { FlowGateJudgeProvider } from '../flow/evaluateGates.js'; import type { classifyHostTarget, selectHostTarget } from './select.js'; import type { AuthoringFlowDefinition } from '../flows/definition/authoring.js'; import type { FlowDefinitionsStore } from '../flows/definition/store.js'; import type { NlPredicateProvider } from '../flows/authoring/compileNlPredicate.js'; import type { RunStore } from './durable/RunStore.js'; import type { KnowledgeProviderConfig } from '../types/knowledge.js'; import type { PersistentMemoryStore } from '../memory/blocks/types.js'; import type { ExtractedValueStore } from '../memory/extract/store.js'; import { type CompactionConfig } from './compaction.js'; import type { EscalationConfig } from '../escalation/types.js'; import type { WakeOptions } from '../scheduler/index.js'; import type { HandoffInputFilter } from './handoffFilters.js'; import type { AgentSpan, AgentTrace } from '../types/trace.js'; import { type AiSdkTelemetryConfig } from '../telemetry/aiSdkOtel.js'; import { type TraceSink, type TraceStore } from '../tracing/TraceStore.js'; import { type Policy } from './policies/toolPolicy.js'; /** * What the user is told when the run hands off to a human and the app has not configured an * escalation handler to say something better. Silence is the wrong default: an escalation * that emits no text is indistinguishable from the agent having crashed. */ export declare const HANDOFF_TO_HUMAN_MESSAGE = "I'm bringing a colleague into this \u2014 they'll pick it up from here."; /** * What the user is told on any turn that arrives WHILE a run is already held for a human. * The agent does not re-run for these turns — without a live `kuralle resume` the session * would otherwise re-escalate every message regardless of topic. Distinct from * `HANDOFF_TO_HUMAN_MESSAGE`, which is the one-time notice emitted on the escalating turn. */ export declare const HELD_FOR_HUMAN_MESSAGE = "A colleague is already handling this \u2014 I'll pick it back up as soon as they're done."; export interface TracingConfig { enabled?: boolean; store?: TraceStore; sinks?: TraceSink[]; redact?: (span: AgentSpan) => AgentSpan | null; sampling?: number | ((context: { sessionId: string; input?: unknown; }) => boolean); } export interface HarnessConfig { agents: AgentConfig[]; defaultAgentId: string; /** Default channel driver for every run. `RunOptions.driver` overrides it per call. */ driver?: ChannelDriver; sessionStore?: SessionStore; defaultModel?: LanguageModel; maxHandoffs?: number; terminalHandoffTargets?: string[]; hooks?: Hooks; voiceMode?: boolean; hostClassify?: typeof classifyHostTarget; /** @deprecated Use hostClassify — test injection adapter for HostSelection stubs. */ hostSelect?: typeof selectHostTarget; tools?: Record; knowledge?: KnowledgeProviderConfig; /** Default store for `agent.memory.workingMemory` when `workingMemory.store` is omitted. */ defaultWorkingMemoryStore?: PersistentMemoryStore; /** Default store for `agent.memory.extract` when no per-agent store is configured. */ extractedValueStore?: ExtractedValueStore; /** * Optional AI SDK transcription model. When set, inbound audio file parts (voice * notes) are transcribed to text before the model turn — so voice input works on * text-only models. When omitted, audio parts pass through to audio-capable models. */ transcriptionModel?: TranscriptionModel; /** * Automatic history compaction. When set, the runtime summarizes older * messages into one system note after any turn whose history exceeds * `triggerTokens` (off the user's latency path), and force-compacts once as * the retry step after a provider context-overflow error. */ compaction?: CompactionConfig; /** * Escalation-to-human pipeline. When set, any escalation — a terminal * handoff (`handoffs: ['human']`, validator `escalate` decision, host * control) or a flow `escalate()` pause — builds an `EscalationRequest` * (state snapshot + recent messages + optional LLM handoff brief) and * invokes the handler. Resume with `runtime.resumeFromEscalation()`. */ escalation?: EscalationConfig; /** Default handoff input filter when a route does not define `filter`. */ handoffInputFilter?: HandoffInputFilter; /** * Silent handoff (default `true`). A transfer between agents reads as one * continuous assistant: the transfer is a silent control tool call, and the * target is given a continuation directive so it does not greet or * re-introduce itself. Set `false` for an explicit visible transfer (the * target follows its own instructions, e.g. "Bill here"). */ silentHandoff?: boolean; /** * Structured goal/thread tracking (G5). When enabled, a cheap control-model * pass at turn end patches `session.workingMemory.__goals` and open threads * are projected into the next turn's prompt. Default off — opt-in cost/latency. */ trackGoals?: boolean; /** * Default decision for every tool call, when the agent does not supply its own. * Omitted, tools honour `needsApproval` exactly as before. */ policy?: Policy; /** * Structured judge for flow `gates` of kind `judge`. Absent provider is an * execution error (always blocking, even when the gate is declared advisory). */ flowGateJudge?: FlowGateJudgeProvider | LanguageModel; /** Read-only observability, configured independently from durable session state. */ tracing?: TracingConfig; /** * Opt-in AI SDK OpenTelemetry (`@ai-sdk/otel`). Omitted or disabled: Kuralle does * not register an integration and model calls emit no SDK spans. Enabled: registers * once at runtime construction and passes per-call `telemetry` options where wired. */ aiSdkTelemetry?: AiSdkTelemetryConfig | boolean; /** * Durable run journal. When omitted, each session uses `SessionRunStore` over * `sessionStore`. A shared adapter (e.g. `PostgresRunStore`) is used as-is. */ runStore?: RunStore; /** Session-scoped event log for replay and cross-client attach. Omitted: live stream only. */ eventLog?: SessionEventLog; /** * Default versioned store for `addDynamicFlows` / `loadDynamicFlows` when the * call does not pass `store`. Live registration still works without one. */ flowDefinitionsStore?: FlowDefinitionsStore; } export interface RunOptions { sessionId?: string; /** The user turn: plain text, or AI SDK multimodal content (text + file/image/audio parts). */ input?: UserInputContent; selection?: ResolvedSelection; /** * Agent-initiated (proactive) turn — mutually exclusive with `input`. The * runtime appends a wake note instead of a user message and runs the normal * loop: free-conversation agents proactively re-engage; an active flow * re-prompts its current step. Schedule wakes with `createWakeJobRunner`. */ wake?: WakeOptions; userId?: string; agentId?: string; seedMessages?: ModelMessage[]; historyDelta?: ModelMessage[]; /** * Caller-supplied instructions merged into the system prompt via a run-lifetime * system note — never pushed into the model message array. */ callerInstructions?: string; driver?: ChannelDriver; /** * Address an existing run in this session. Unknown and cross-session values * fail closed. Omit to open the session's conversation run. */ runId?: string; /** * When omitted, opens the session's conversation run. `'flow'` mints a new * headless flow run (server-side id). Caller-supplied `runId` is resume-only * and wins over this field. */ kind?: RunKind; /** * Creation-only with `kind: 'flow'`. Sets `activeFlow` on the minted run. * Ignored on resume. */ flowName?: string; signalDelivery?: SignalDelivery; /** Stable key for this inbound user message; duplicate webhook retries are ignored (H2). */ idempotencyKey?: string; abortSignal?: AbortSignal; /** Immutable release identity already authorized and pinned by the deployment host. */ deployment?: DeploymentTraceContext; } export interface RunHandle { runId: string; sessionId: string; kind: RunKind; status: 'running' | 'paused' | 'finished' | 'error' | 'aborted'; activeAgentId: string; activeFlow?: string; activeNode?: string; waitingFor?: InterruptRequest; createdAt: number; updatedAt: number; } export declare class Runtime { private readonly config; private readonly agentsById; private readonly sessionStore; private readonly defaultModel?; private readonly maxHandoffs; private readonly terminalHandoffTargets; private readonly hooks?; private readonly activeTurnAborts; private readonly sessionAbortKeys; private readonly sessionMutex; private readonly runMutex; private readonly traceStore?; private readonly traceSinks; private readonly pendingTraceWrites; private readonly pendingExtractions; private readonly extractedValueStore; private readonly runStoreOverride?; private readonly leaseHolder; private readonly flowCatalogs; private readonly flowCatalogMutex; constructor(config: HarnessConfig); private runStoreFor; run(opts: RunOptions): TurnHandle; runOnce(opts: RunOptions): Promise; stream(opts: RunOptions): TurnHandle; getSession(sessionId: string): Promise; getTrace(traceId: string): Promise; listTraces(sessionId: string): Promise; getTraceStore(): TraceStore | undefined; /** The agent used when neither the caller nor persisted state names one. */ getDefaultAgentId(): string; getSessionStore(): SessionStore; getEventLog(): SessionEventLog | undefined; getRunStore(sessionId?: string): RunStore; /** * Atomically register a bundle of stored flow definitions on a live agent. * * Persistence is not transactional across rows. On failure the in-memory * catalog rolls back this bundle's registrations; members that already * persisted are best-effort archived so a later `loadDynamicFlows` does not * resurrect them. * * Reusing an existing dynamic name is rejected unless `replace: true`. */ addDynamicFlows(defs: readonly AuthoringFlowDefinition[], opts: { agentId: string; store?: FlowDefinitionsStore; replace?: boolean; compiler?: NlPredicateProvider | LanguageModel; }): Promise; /** * Drop a dynamic flow from this runtime's live catalog. * * The store row stays active; a later `loadDynamicFlows` (including boot) * will reload it unless the caller archives the name first. */ removeDynamicFlow(name: string, opts: { agentId: string; }): Promise; /** * Load `status: 'active'` versions onto the agent's live catalog. Per-row * failures are logged and skipped so one corrupt definition cannot sink boot. */ loadDynamicFlows(opts: { agentId: string; store?: FlowDefinitionsStore; }): Promise; private withFlowCatalogLock; private requireAgent; private catalogFor; private liveAgent; /** Resolves once every in-flight background extraction has settled. */ settled(): Promise; deleteSession(sessionId: string): Promise; abortSession(sessionId: string, reason?: string): void; private registerTurnAbort; private unregisterTurnAbort; replayAuditLog(sessionId: string, opts?: AuditListOptions): Promise; markOutcome(sessionId: string, outcome: ConversationOutcome, opts?: { reason?: string; markedBy?: ConversationOutcomeMarkedBy; }): Promise; private shouldTrace; private writeSpan; private settleTraceWrites; private trackBackgroundExtraction; private flushTraceSinks; /** * Compact `runState.messages` when over the configured trigger (or always, * when `force`). Persists both the run state and the session message mirror. * Returns whether compaction applied. */ private applyCompaction; /** * Context-overflow recovery: strip the failed turn's partial assistant/tool * messages (the user's own message is preserved), force one compaction, and * let the caller retry the turn once. */ private recoverFromOverflow; getRun(runId: string, sessionId?: string): Promise; getConversationLength(sessionId: string): Promise; /** * Build the escalation request, invoke the configured handler, record the * outcome on session metadata, and emit the `escalation` stream part. * No-op without `config.escalation`. Handler errors become a `failed` * outcome — escalation must never take down the turn. */ private dispatchEscalation; /** * Hand the conversation back to the bot after a human resolved an * escalation: appends a resolution note the model will see, clears any * parked flow/escalation state, and marks the run runnable again. The next * `run()` continues the conversation with full context. */ resumeFromEscalation(sessionId: string, opts?: { resolutionSummary?: string; }): Promise; } export declare function createRuntime(config: HarnessConfig): Runtime;