/** * Core types for the @animus-labs/cortex package. * * These types define the public API surface for CortexAgent configuration, * context management, error classification, working tags, budget guards, * compaction, events, and model tiers. * * References: * - cortex-architecture.md * - context-manager.md * - model-tiers.md * - error-recovery.md * - working-tags.md */ import type { CortexModel } from './model-wrapper.js'; import type { ObservationalMemoryConfig, ObservationEvent, ReflectionEvent, } from './compaction/observational/types.js'; // --------------------------------------------------------------------------- // Logger // --------------------------------------------------------------------------- /** * Pluggable logger interface for Cortex diagnostics. * * Cortex never decides where logs go. The consumer provides an implementation * via CortexAgentConfig.logger. If omitted, all logging is silently discarded. * * All methods share the same signature for uniformity. The optional `data` * parameter carries structured context (token counts, server names, error * objects, etc.) that the consumer can serialize or inspect as needed. * * Compatible with `console` for quick development: `logger: console` works * because console methods accept variadic args and ignore the extra object. */ export interface CortexLogger { debug(message: string, data?: Record): void; info(message: string, data?: Record): void; warn(message: string, data?: Record): void; error(message: string, data?: Record): void; } // --------------------------------------------------------------------------- // Usage // --------------------------------------------------------------------------- /** * Token usage and cost data from a single LLM call. * Mirrors the pi-ai AssistantMessage.usage structure but is decoupled * from pi-ai's types to avoid hard runtime dependencies. */ export interface CortexUsage { /** Input (prompt) tokens. */ input: number; /** Output (completion) tokens. */ output: number; /** Cache-read tokens (tokens served from cache). */ cacheRead: number; /** Cache-write tokens (tokens written to cache). */ cacheWrite: number; /** Total tokens (input + output). */ totalTokens: number; /** Cost breakdown in USD. */ cost: { input: number; output: number; cacheRead: number; cacheWrite: number; total: number; }; /** Model identifier string (if available from the response). */ model?: string; } // --------------------------------------------------------------------------- // Session Usage // --------------------------------------------------------------------------- /** * Accumulated usage data across the lifetime of a session. * * Unlike BudgetGuard (which resets per agentic loop for enforcement), * SessionUsage accumulates across all loops for reporting and persistence. * Cortex tracks this in memory; consumers persist and restore as needed. */ export interface SessionUsage { /** Total cost in USD across all turns. */ totalCost: number; /** Total number of LLM turns across all loops. */ totalTurns: number; /** Accumulated token counts across all turns. */ tokens: { input: number; output: number; cacheRead: number; cacheWrite: number; }; } // --------------------------------------------------------------------------- // Lifecycle // --------------------------------------------------------------------------- /** * The lifecycle state of a CortexAgent instance. * * CREATED -> ACTIVE -> DESTROYED * * abort() returns the agent to ACTIVE (still usable). * destroy() transitions to DESTROYED (all resources released). */ export type CortexLifecycleState = 'created' | 'active' | 'destroyed'; // --------------------------------------------------------------------------- // Thinking / Effort Level // --------------------------------------------------------------------------- /** * Consumer-facing thinking/effort level. * * "max" maps to pi-ai/pi-agent-core's "xhigh" internally. * "off" disables extended thinking entirely. */ export type ThinkingLevel = 'off' | 'minimal' | 'low' | 'medium' | 'high' | 'max'; /** * Describes a model's thinking/reasoning capabilities. * Returned by CortexAgent.getModelThinkingCapabilities(). */ export interface ModelThinkingCapabilities { /** Whether the model supports extended thinking at all. */ supportsThinking: boolean; /** Whether the model supports the "max" (xhigh) thinking level. */ supportsMax: boolean; /** Exact thinking levels this model accepts, using Cortex's public names. */ supportedLevels: ThinkingLevel[]; } // --------------------------------------------------------------------------- // Tool Permissions // --------------------------------------------------------------------------- export type CortexToolPermissionDecision = 'allow' | 'block' | 'ask'; export interface CortexToolPermissionResult { decision: CortexToolPermissionDecision; reason?: string; } // --------------------------------------------------------------------------- // Agent Configuration // --------------------------------------------------------------------------- /** * Configuration for creating a CortexAgent instance. * * The `model` field uses CortexModel as the public boundary. * Consumers obtain these handles from ProviderManager and pass them back to * CortexAgent. Raw pi-ai model objects stay inside Cortex. */ export interface CortexAgentConfig { /** Primary model for the agentic loop, THOUGHT, REFLECT, and all consumer-facing work. */ model: CortexModel; /** * Initial application/base prompt. * Cortex composes its operational sections around this prompt. */ initialBasePrompt?: string; /** * Utility model for internal operations (WebFetch summarization, safety classifier). * - `'default'`: Cortex selects from a built-in mapping based on the primary model's provider. * - A CortexModel: explicit utility model (must be same provider as primary). * - `undefined`: same as `'default'`. */ utilityModel?: CortexModel | 'default'; /** Working directory for file operations (Bash, Read, Write, Edit, Glob, Grep). */ workingDirectory: string; /** * Callback to resolve API keys by provider name. * Throws on failure (classified as authentication error). * Returns the API key string on success. Must never return empty string. */ getApiKey?: (provider: string) => Promise; /** Ordered list of context slot names. Order defines position in the message array. */ slots?: string[]; /** Working tags configuration. */ workingTags?: { /** Whether to enable working tags. Default: true. */ enabled?: boolean; }; /** Budget guard configuration. */ budgetGuard?: { /** Maximum number of LLM turns before force-stopping the loop. Default: Infinity. */ maxTurns?: number; /** Maximum cost in USD before force-stopping the loop. Default: Infinity. */ maxCost?: number; }; /** Maximum number of concurrent sub-agents. */ maxConcurrentSubAgents?: number; /** * Tool execution strategy for assistant messages with multiple tool calls. * Defaults to sequential for deterministic permission, logging, and UI order. */ toolExecution?: 'sequential' | 'parallel'; /** WebFetch tool configuration. */ webFetch?: { /** Maximum number of web fetches per agentic loop. */ maxPerLoop?: number; }; /** Bash tool configuration. */ bash?: { /** Token threshold at which Bash auto-yields control back to the agent. */ autoYieldThreshold?: number; /** Path to the shell executable. */ shellPath?: string; }; /** * Whether the consumer is currently auto-approving tool calls. * Used by built-in tool safety gates that need stricter checks in auto mode. */ isAutoApprove?: () => boolean; /** * Disable specific built-in tools by name. * Built-in tools (Read, Write, Edit, Glob, Grep, Bash, WebFetch, TaskOutput) * are registered automatically. Use this to exclude tools the agent should not have. * SubAgent and load_skill are controlled separately via enableSubAgentTool/enableLoadSkillTool. */ disableTools?: string[]; /** * Structured permission result for a tool call. * - `allow`: proceed immediately * - `block`: deny the call * - `ask`: consumer requires approval before the call can proceed */ resolvePermission?: ( toolName: string, toolArgs: unknown, ) => Promise; /** * Initial thinking/effort level for the agentic loop. * Omit to use the pi-agent-core default (medium). * "max" maps to pi-ai/pi-agent-core's "xhigh" internally. * Consumers can use getModelThinkingCapabilities() and clampThinkingLevel() * to avoid exposing unsupported levels. */ thinkingLevel?: ThinkingLevel; /** * Limit the effective context window for compaction calculations. * Clamped to min(limit, model.contextWindow) with a floor of MINIMUM_CONTEXT_WINDOW (16K). * null or undefined = use the model's full context window. */ contextWindowLimit?: number | null; /** Compaction configuration. All layers are always active. */ compaction?: Partial; /** * Deferred tool loading. When enabled, tools marked for deferral are NOT * included in the `tools` array sent on every API turn. Instead, their * names appear in an internally-managed `_available_tools` slot, and the * agent uses the auto-registered `ToolSearch` tool to load specific tools * on demand. * * Useful when consumers connect MCP servers with many tools: schemas are * only paid for once a tool is actually needed, instead of every turn. */ deferredTools?: DeferredToolsConfig; /** * Persists oversized tool results to disk and returns the file path. * * When configured, large tool results (>25K tokens by default) are * replaced in the conversation with a bookend preview (head + tail) * plus a file reference, so the model can use the Read tool to access * the full content on demand. * * Used by both the proactive tool execution interceptor and the * reactive compaction paths (microcompaction trim, aggregate budget * enforcement). Consumer owns the storage location and cleanup. * * If both this and `compaction.microcompaction.persistResult` are set, * this top-level setting wins (and is propagated to microcompaction). */ persistResult?: PersistResultFn; /** * Per-tool token threshold overrides for the result-persistence interceptor. * * Tools not listed here use the built-in defaults (Bash: 7,500 to keep * verbose command output tight; everything else: 25,000). * * Useful for tuning specific MCP tools or custom tools whose output has * unusual size characteristics (e.g., a tool that returns large JSON payloads * where you want a smaller cap, or a research tool whose output you want to * keep more of in context). */ toolResultThresholds?: Record; /** * Consumer-set environment variables that propagate to ALL subprocesses * (Bash tool, MCP stdio servers), bypassing the security blocklist. * * Use case: macOS dock icon suppression requires DYLD_INSERT_LIBRARIES * and ANIMUS_DOCK_SUPPRESS_ADDON to propagate to child processes, but * the safe-env blocklist strips DYLD_ prefixed variables by default. * envOverrides are merged ON TOP of the sanitized environment, restoring * these specific variables. */ envOverrides?: Record; /** * Optional logger for Cortex internal diagnostics. * If omitted, all internal logging is silently discarded (no-op). * The library never decides where logs go; only the consumer does. * * Compatible with `console` for quick development: `{ logger: console }`. */ logger?: CortexLogger; /** * Optional diagnostics for investigating prompt or provider stalls. * All diagnostics are opt-in and should remain disabled in normal use. */ diagnostics?: CortexDiagnosticsConfig; /** * Stable identifier for prefix-cache routing. Passed to the provider as its * cache/session key (e.g. OpenAI's prompt_cache_key) so requests sharing a * long common prefix are routed to the same inference machine and reuse * cached KV state. Use a value that is stable across the calls that share a * prefix (e.g. one per logical session). Can also be set later via * setSessionId(). Optional. */ sessionId?: string; /** * Called before a sub-agent is spawned (foreground or background), giving * the consumer a chance to record the spawn and curate the child's starting * context. Receives the requested spawn parameters plus the generated taskId, * and may return an augmentation that overrides the child's system prompt or * tool set and injects an initial background-context slot. Purely additive: * if omitted, sub-agents spawn with the default inherited context. */ onBeforeSubAgentSpawn?: ( req: SubAgentSpawnRequest, ) => SubAgentSpawnAugmentation | void | Promise; /** * Optional consumer gate consulted before a sub-agent spawn, in addition to * the built-in concurrency limit. Return false (or { allowed: false, reason }) * to refuse the spawn, e.g. when a budget or policy ceiling has been hit. The * reason is surfaced to the model as the SubAgent tool result so it knows why * it could not delegate. Omit to always allow (subject to concurrency). */ canSpawnSubAgent?: () => boolean | { allowed: boolean; reason?: string }; } /** * Prompt watchdog diagnostics configuration. * * Emits bounded lifecycle and heartbeat logs around prompt() and abort(). * Intended for investigating freezes or hung provider calls. */ export interface PromptWatchdogDiagnosticsConfig { /** Whether prompt watchdog diagnostics are enabled. Default: false. */ enabled?: boolean; /** Heartbeat interval while a prompt is in flight. Default: 1000ms. */ heartbeatIntervalMs?: number; /** Warn if abort waits longer than this for idle. Default: 2000ms. */ abortWaitWarningMs?: number; } /** * Optional diagnostics for Cortex internals. * * These are intentionally narrow and structured so consumers can opt into * targeted investigations without turning normal debug logging into trace spam. */ export interface CortexDiagnosticsConfig { /** Prompt and abort watchdog logs for freeze investigation. */ promptWatchdog?: PromptWatchdogDiagnosticsConfig; } // --------------------------------------------------------------------------- // Deferred Tools // --------------------------------------------------------------------------- /** * Configuration for deferred tool loading. * * Reference: docs/cortex/tools/tool-search.md (TBD) */ export interface DeferredToolsConfig { /** * Master switch. When false (default), all tools are sent on every turn * exactly as before. When true, tools marked for deferral are pulled out * of the per-turn tools array and announced by name in the * `_available_tools` slot instead. */ enabled?: boolean; /** * When true (default), all MCP tools are deferred. When false, MCP tools * are sent in full like built-ins. Has no effect when `enabled` is false. */ deferMcp?: boolean; /** * Tool names that should never be deferred even if they match deferral * criteria (overrides `shouldDefer` and `deferMcp` for the named tools). * Use this to keep specific MCP tools always loaded. */ alwaysLoad?: string[]; } // --------------------------------------------------------------------------- // Context Manager // --------------------------------------------------------------------------- /** * Configuration for the ContextManager. * * Slots define the ordered list of persistent content blocks at the start * of the message array. Order determines position (first = most stable, * best prefix cache hit rate). */ export interface ContextManagerConfig { /** Ordered list of slot names. */ slots: string[]; } // --------------------------------------------------------------------------- // Error Classification // --------------------------------------------------------------------------- /** * Error categories for classifying LLM and network errors. * Checked in priority order (first match wins). */ export type ErrorCategory = | 'authentication' | 'rate_limit' | 'context_overflow' | 'server_error' | 'network' | 'cancelled' | 'unknown'; /** * Error severity levels. * - fatal: unrecoverable, stop processing (e.g., invalid API key) * - retry: transient, can be retried (e.g., rate limit, server error, network) * - recoverable: can be handled without retry (e.g., context overflow triggers compaction) */ export type ErrorSeverity = 'fatal' | 'retry' | 'recoverable'; /** * A classified error with category, severity, original message, and suggested action. */ export interface ClassifiedError { /** The error category determined by pattern matching. */ category: ErrorCategory; /** The severity level for the category. */ severity: ErrorSeverity; /** The original error message string. */ originalMessage: string; /** Human-readable suggested action, or undefined if no action is needed. */ suggestedAction?: string; } // --------------------------------------------------------------------------- // Working Tags // --------------------------------------------------------------------------- /** * Structured output from parsing working tags in agent text. * * Working tags separate internal reasoning (...) from * user-facing communication. Both remain in conversation history; the * difference is only in delivery. */ export interface AgentTextOutput { /** Text intended for the user (working tag content stripped, whitespace normalized). */ userFacing: string; /** Content from inside tags, concatenated. Null if no working tags present. */ working: string | null; /** The original unparsed text exactly as the agent produced it. */ raw: string; } // --------------------------------------------------------------------------- // Tool Results // --------------------------------------------------------------------------- /** * Structured tool result with content array and typed details. */ export interface ToolContentDetails { content: Array< | { type: 'text'; text: string } | { type: 'image'; data: string; mimeType: string } >; details: T; } // --------------------------------------------------------------------------- // Tool Execute Context // --------------------------------------------------------------------------- /** * Context passed to tool execute functions by the pi-agent-core adapter. * * Tools that want streaming support accept this as an optional second parameter. * Tools that don't need it simply ignore it (backward compatible). */ export interface ToolExecuteContext { /** Unique identifier for this tool call. */ toolCallId: string; /** Abort signal for cancellation. */ signal?: AbortSignal; /** * Callback for emitting incremental results during execution. * Pi-agent-core emits these as tool_execution_update events. * Useful for long-running tools (bash, sub-agents) that want to * stream progress to the consumer's UI. */ onUpdate?: (partialResult: ToolContentDetails) => void; } // --------------------------------------------------------------------------- // Event Payloads // --------------------------------------------------------------------------- /** * Typed payload for tool_call_start events. */ export interface ToolCallStartPayload { toolCallId: string; toolName: string; args: Record; } /** * Typed payload for tool_call_update events. */ export interface ToolCallUpdatePayload { toolCallId: string; toolName: string; args: Record; partialResult: ToolContentDetails; } /** * Typed payload for tool_call_end events. */ export interface ToolCallEndPayload { toolCallId: string; toolName: string; result: ToolContentDetails; durationMs: number; isError: boolean; error?: string; } // --------------------------------------------------------------------------- // Budget Guard // --------------------------------------------------------------------------- /** * Budget guard configuration with explicit limits. * Both default to Infinity (no enforcement). */ export interface BudgetGuardConfig { /** Maximum number of LLM turns. Default: Infinity. */ maxTurns: number; /** Maximum cost in USD. Default: Infinity. */ maxCost: number; } // --------------------------------------------------------------------------- // Compaction // --------------------------------------------------------------------------- /** * Tool category for microcompaction retention decisions. * - rereadable: agent can re-read the source (files, directories) * - non-reproducible: output may change or cost to re-fetch (web, APIs) * - ephemeral: stale quickly, trivially re-runnable (ls, git status) * - computational: small results from computations, non-reproducible without re-running */ export type ToolCategory = 'rereadable' | 'non-reproducible' | 'ephemeral' | 'computational'; /** * Microcompaction configuration: progressive tool result trimming. */ /** * Callback invoked when microcompaction persists a cleared tool result to disk. * The consumer implements the actual I/O and returns the file path. * * @param content - The full original tool result content * @param metadata - Information about the result being persisted * @returns The file path where the content was saved */ export type PersistResultFn = ( content: string, metadata: { toolName: string; category: ToolCategory; /** Present when called from the proactive tool execution interceptor. */ toolCallId?: string; /** Present when called from compaction (reactive paths). */ messageIndex?: number; }, ) => Promise; export interface MicrocompactionConfig { /** Maximum tokens for a single tool result at insertion time. Default: 50000. */ maxResultTokens: number; // --- Cache-aware gating --- /** * Minimum context utilization ratio below which L1 never trims, even when * the cache is cold. Prevents pointless work on nearly empty contexts. * Default: 0.25 (25%). */ trimFloorRatio: number; // --- Hot zone (token-offset recency window) --- /** * Absolute floor for the hot zone size (in tokens). Tool results within the * hot zone of the most recent message are never trimmed. The effective hot * zone is `max(hotZoneMinTokens, contextWindow * hotZoneRatio)`. * Default: 16000. */ hotZoneMinTokens: number; /** * Hot zone as a fraction of the context window. Wins over the floor on * very large windows. Default: 0.05 (5%). */ hotZoneRatio: number; /** * Multiplier applied to the hot zone for non-reproducible tools. When * undefined, resolves to 1.0 if a persistResult callback is configured * (full content recoverable from disk) or 1.5 if not (some buffer since * content is lost on trim). */ extendedRetentionMultiplier?: number; // --- Progressive bookend degradation --- /** Bookend size at the hot zone boundary (chars per side). Default: 2000. */ bookendMaxChars: number; /** Bookend size at the far end of the degradation span (chars per side). Default: 256. */ bookendMinChars: number; /** * The degradation span as a fraction of the context window. Bookend size * interpolates linearly from bookendMaxChars to bookendMinChars across this * span. Beyond the span, results become placeholder or clear based on tool * category. Default: 0.40 (40%). */ degradationSpanRatio: number; // --- Tool categorization --- /** Tool name to category mapping. Unregistered tools default to standard retention. */ toolCategories?: Record; // --- Persistence --- /** * Callback to persist tool results to disk before destructive trim actions * (bookend, placeholder, clear). When set, the in-context replacement * includes a file path reference the agent can Read to recover full content. * Only fires for non-reproducible and computational tools. * * Typically set at the top-level CortexAgentConfig.persistResult and * propagated here automatically. */ persistResult?: PersistResultFn; /** Maximum aggregate tokens for all tool results in a single turn. Default: 150000. */ maxAggregateTurnTokens?: number; } /** * Conversation summarization (Layer 2) configuration. */ export interface CompactionConfig { /** Context usage ratio that triggers summarization. Default: 0.70. */ threshold: number; /** Number of recent turns preserved verbatim. Default: 6. */ preserveRecentTurns: number; /** Custom summarization prompt. If provided, replaces the default prompt. */ customPrompt?: string; /** Maximum Layer 2 retry attempts before falling through to Layer 3. Default: 3. */ maxRetries?: number; /** Delay in ms between Layer 2 retry attempts. Default: 2000. */ retryDelayMs?: number; } /** * Emergency truncation (Layer 3) configuration. */ export interface FailsafeConfig { /** Context usage ratio that triggers emergency truncation. Default: 0.90. */ threshold: number; } /** * Adaptive threshold configuration for Layer 2 compaction. * * Adjusts the compaction trigger point based on how recently the user last * interacted. When the user is idle (no messages for a while), the threshold * is lowered so compaction fires earlier, reducing token costs during interval * ticks where the agent is thinking to itself. * * Three windows: * - Recent (< recentWindowMs): no threshold reduction * - Moderate (recentWindowMs .. idleWindowMs): moderateReduction applied * - Idle (> idleWindowMs): idleReduction applied * * The effective threshold is: config.compaction.threshold - reduction */ export interface AdaptiveThresholdConfig { /** Whether adaptive thresholds are enabled. Default: true. */ enabled: boolean; /** Milliseconds within which interaction is considered "recent". Default: 300000 (5 min). */ recentWindowMs: number; /** Milliseconds beyond which interaction is considered "idle". Default: 1800000 (30 min). */ idleWindowMs: number; /** Threshold reduction when interaction is recent. Default: 0.0. */ recentReduction: number; /** Threshold reduction when interaction is moderate. Default: 0.10. */ moderateReduction: number; /** Threshold reduction when interaction is idle. Default: 0.20. */ idleReduction: number; } /** * Full compaction configuration for CortexAgent. * * Supports two strategies: * - `'observational'` (default): Observer/Reflector background compression * replaces L1 threshold trimming and L2 summarization. L3 emergency * truncation remains active as a safety valve. * - `'classic'`: The existing L1 + L2 + L3 system operates unchanged. * * The failsafe (L3) is always active regardless of strategy. */ export interface CortexCompactionConfig { /** * Compaction strategy selection. * - `'observational'`: Background observer/reflector compression (default). * - `'classic'`: Traditional L1 microcompaction + L2 summarization. * @default 'observational' */ strategy?: 'observational' | 'classic'; /** Microcompaction (L1) configuration. Used when strategy is 'classic'. */ microcompaction: MicrocompactionConfig; /** Conversation summarization (L2) configuration. Used when strategy is 'classic'. */ compaction: CompactionConfig; /** Emergency truncation (L3) configuration. Always active regardless of strategy. */ failsafe: FailsafeConfig; /** Adaptive threshold configuration. Adjusts Layer 2 trigger based on interaction recency. Used when strategy is 'classic'. */ adaptive: AdaptiveThresholdConfig; /** * Observational memory configuration. Used when strategy is 'observational' * (or omitted, since observational is the default). */ observational?: Partial; } /** * Information about the compaction target passed to onBeforeCompaction. */ export interface CompactionTarget { /** Number of turns that will be summarized. */ turnsToCompact: number; /** Estimated tokens in the compaction target. */ estimatedTokens: number; } /** * Result of a compaction operation. */ export interface CompactionResult { /** Total tokens before compaction. */ tokensBefore: number; /** Total tokens after compaction. */ tokensAfter: number; /** Number of conversation turns that were compacted (summarized/removed). */ turnsCompacted: number; /** Number of conversation turns preserved after compaction. */ turnsPreserved: number; /** Token count of the generated summary. */ summaryTokens: number; /** * ISO timestamp of the oldest preserved turn, or null if no timestamp * could be determined. The consumer (backend) should use * `oldestPreservedIndex` to map back to a database timestamp when * this is null. */ oldestPreservedTimestamp: string | null; /** * Index of the oldest preserved turn in the original (pre-compaction) * conversation history. The consumer can map this index back to a * database timestamp via messages.db. Always present and accurate. */ oldestPreservedIndex: number; /** The generated summary text. */ summary: string; } /** * Info passed to onCompactionDegraded when Layer 2 failed and Layer 3 was used. */ export interface CompactionDegradedInfo { /** Number of consecutive Layer 2 failures (including this episode). */ layer2Failures: number; /** Number of turns dropped by emergency truncation. */ turnsDropped: number; } /** * Info passed to onCompactionExhausted when all compaction layers have failed. */ export interface CompactionExhaustedInfo { /** The error from the last Layer 2 attempt. */ error: Error; /** Number of consecutive Layer 2 failures. */ layer2Failures: number; } // --------------------------------------------------------------------------- // Events // --------------------------------------------------------------------------- /** * Event handlers emitted by CortexAgent during the agentic loop lifecycle. */ export interface CortexEvents { /** Fired when the full agentic loop finishes (agent_end, not turn_end). */ onLoopComplete: () => void; /** Fired before compaction starts. Awaited. Consumer should flush state. */ onBeforeCompaction: (target: CompactionTarget) => Promise; /** Fired after compaction completes. Consumer can re-seed messages, update state. */ onPostCompaction: (result: CompactionResult) => void; /** Fired when context compaction fails. */ onCompactionError: (error: Error) => void; /** Fired when Layer 2 compaction failed and Layer 3 (emergency truncation) was used as fallback. */ onCompactionDegraded: (info: CompactionDegradedInfo) => void; /** Fired when all compaction layers have failed. Consumer should take recovery action. */ onCompactionExhausted: (info: CompactionExhaustedInfo) => void; /** Fired when an error is classified during the agentic loop. */ onError: (error: ClassifiedError) => void; /** Fired at the end of each turn with parsed working tag output. */ onTurnComplete: (output: AgentTextOutput) => void; /** Fired when a sub-agent is spawned for delegated work. */ onSubAgentSpawned: (taskId: string, instructions: string, background: boolean) => void; /** Fired when a sub-agent completes successfully. */ onSubAgentCompleted: (taskId: string, result: string, status: string, usage: unknown) => void; /** Fired when a sub-agent fails. */ onSubAgentFailed: (taskId: string, error: string) => void; /** Fired when observational memory activates (messages compressed and removed). Only fires when strategy is 'observational'. */ onObservation: (event: ObservationEvent) => void; /** Fired when the reflector condenses observations. Only fires when strategy is 'observational'. */ onReflection: (event: ReflectionEvent) => void; } // --------------------------------------------------------------------------- // MCP Client // --------------------------------------------------------------------------- /** * Transport configuration for connecting to an MCP server. * Either stdio (spawn subprocess) or HTTP (connect to running server). */ export type McpTransportConfig = McpStdioConfig | McpHttpConfig; /** * Stdio transport: spawn a subprocess and communicate via stdin/stdout. */ export interface McpStdioConfig { transport: 'stdio'; /** The executable to run (e.g., 'node', '/path/to/tsx'). */ command: string; /** Command line arguments. */ args?: string[]; /** Environment variables for the subprocess. */ env?: Record; /** Working directory for the subprocess. */ cwd?: string; /** * Per-tool timeout for `tools/call`, in milliseconds. When set, this * overrides the MCP SDK default (60 seconds) and the call is rearmed on * every `notifications/progress` the server emits. Use higher values for * tools that legitimately block on external state (e.g. waiting for a * human decision). Omit to inherit the SDK default. */ toolTimeoutMs?: number; } /** * HTTP transport: connect to an already-running MCP server via Streamable HTTP. */ export interface McpHttpConfig { transport: 'http'; /** The URL of the MCP server endpoint. */ url: string; /** Optional HTTP headers (e.g., for authentication). */ headers?: Record; /** * Per-tool timeout for `tools/call`, in milliseconds. See * {@link McpStdioConfig.toolTimeoutMs} for semantics. */ toolTimeoutMs?: number; } /** * Progress event surfaced when an MCP server emits `notifications/progress` * during a long-running `tools/call`. Consumers can wire this to a UI * notification ("still waiting on connection accept...") without depending * on `@modelcontextprotocol/sdk` directly. */ export interface McpToolCallProgress { /** The MCP server that emitted the progress notification. */ serverName: string; /** The tool name (without server namespace prefix). */ toolName: string; /** Current progress value as reported by the server. Monotonically rising. */ progress: number; /** Total expected progress if the server provided one. */ total?: number; /** Optional human-readable progress message. */ message?: string; } /** * State of a single MCP server connection. */ export interface McpConnectionState { /** The server name used for namespacing tools. */ serverName: string; /** Transport configuration used for this connection. */ config: McpTransportConfig; /** Whether the connection is currently active. */ connected: boolean; /** Number of reconnect attempts since last successful connection. */ reconnectAttempts: number; /** Names of tools discovered from this server (namespaced). */ toolNames: string[]; } // --------------------------------------------------------------------------- // Model Tiers // --------------------------------------------------------------------------- /** * Default utility model mapping per provider. * Keys are provider names, values are model IDs. */ export interface UtilityModelDefaults { [provider: string]: string; } // --------------------------------------------------------------------------- // Skill System // --------------------------------------------------------------------------- /** * Configuration for registering a skill with the SkillRegistry. * The consumer provides these at startup and dynamically as plugins install/uninstall. */ export interface SkillConfig { /** Absolute path to the SKILL.md file. */ path: string; /** Where this skill came from. Used for display and debugging. */ source: string; // e.g., 'plugin:weather', 'plugin:discord', 'user', 'builtin' /** * Per-skill variables for ${VAR} substitution in the SKILL.md body. * Merged into the preprocessor variables when getSkillBody() runs. * Useful for plugin skills that reference ${PLUGIN_ROOT}. */ variables?: Record; } /** * Internal skill index entry built from parsing a SKILL.md file. */ export interface SkillEntry { /** Skill name from frontmatter (kebab-case). */ name: string; /** Skill description from frontmatter (for agent activation). */ description: string; /** Absolute path to the SKILL.md file. */ path: string; /** Absolute path to the skill directory (parent of SKILL.md). */ dir: string; /** Source identifier from SkillConfig. */ source: string; /** Full parsed YAML frontmatter (preserved for forward compatibility). */ frontmatter: Record; /** Whether the agent can auto-load this skill. Derived from disable-model-invocation. */ modelInvocable: boolean; /** Per-skill variables for ${VAR} substitution. */ variables?: Record; } /** * A loaded skill in the skill buffer (preprocessed body ready for injection). */ export interface LoadedSkill { /** The skill name. */ name: string; /** The preprocessed SKILL.md body content. */ content: string; } /** * Context object passed to skill preprocessor scripts (!{script: path}). * Cortex owns the built-in fields; the consumer provides everything else. */ export interface CortexScriptContext { /** Absolute path to the skill's directory. */ skillDir: string; /** Arguments passed to the skill (split by whitespace). */ args: string[]; /** Raw arguments string. */ rawArgs: string; /** Additional key-value pairs from !{script: path, key: value} syntax. */ scriptArgs: Record; /** Consumer-provided context fields (Cortex does not inspect these). */ [key: string]: unknown; } // --------------------------------------------------------------------------- // Sub-Agent // --------------------------------------------------------------------------- /** * Configuration for spawning a sub-agent via the SubAgent tool. */ export interface SubAgentSpawnConfig { /** What the sub-agent should do. Becomes the initial prompt. */ instructions: string; /** Tool names to make available. Default: inherits parent's tools. */ tools?: string[]; /** Custom system prompt. Default: inherits parent's system prompt. */ systemPrompt?: string; /** Maximum LLM turns. Default: inherits parent's budget guard config. */ maxTurns?: number; /** Maximum cost in USD. Default: inherits parent's budget guard config. */ maxCost?: number; /** Run asynchronously. Default: false (blocks until complete). */ background?: boolean; } /** * Describes a sub-agent about to be spawned. Passed to * CortexAgentConfig.onBeforeSubAgentSpawn so a consumer can record the spawn * and decide what background context to seed. */ export interface SubAgentSpawnRequest { /** The generated task ID for this sub-agent. */ taskId: string; /** The instructions the sub-agent will run with (its sole objective). */ instructions: string; /** Whether the sub-agent runs in the background. */ background: boolean; /** Tool names the caller requested, if any. */ requestedTools?: string[]; /** Custom system prompt the caller requested, if any. */ requestedSystemPrompt?: string; } /** * Optional augmentation returned from onBeforeSubAgentSpawn. * Every field is optional; unset fields fall back to inherited defaults. */ export interface SubAgentSpawnAugmentation { /** Override the child's system prompt. Default: inherit parent's. */ systemPrompt?: string; /** * Background context to seed into the child as an initial context slot. * This is reference material, NOT the child's objective: the child's task * is defined solely by its instructions. Rendered verbatim and ordered * before conversation history for prefix-cache stability. */ seedContext?: string; /** Override the child's tool set by name. Default: inherit parent's. */ tools?: string[]; } /** * Read-only snapshot of a running sub-agent, returned by getActiveSubAgents(). */ export interface SubAgentSnapshot { taskId: string; instructions: string; background: boolean; spawnedAt: number; status: 'running' | 'waiting-for-permission'; toolCount: number; lastToolName: string | null; lastToolSummary: string | null; lastToolStartedAt: number | null; /** Live accumulated cost of this sub-agent in USD. */ liveCostUsd: number; /** Turns used so far. */ turnsUsed: number; } /** * Result returned by a completed sub-agent. */ export interface SubAgentResult { /** The sub-agent's final text output. */ output: string; /** Completion status. */ status: 'completed' | 'failed' | 'timed_out' | 'cancelled'; /** Usage summary. */ usage: { turns: number; cost: number; durationMs: number; contextTokens: number; }; /** Summary of tool calls made by the sub-agent (extracted from conversation history on completion). */ toolCalls?: Array<{ name: string; durationMs: number; error?: string }>; } /** * Tracked sub-agent record managed by SubAgentManager. */ export interface TrackedSubAgent { /** Unique task identifier. */ taskId: string; /** The sub-agent CortexAgent instance. */ agent: unknown; // CortexAgent (avoid circular import) /** The instructions the sub-agent was spawned with. */ instructions: string; /** Whether this is a background sub-agent. */ background: boolean; /** Spawn timestamp. */ spawnedAt: number; /** Promise that resolves when the sub-agent completes. */ completion: Promise; /** Resolve function for the completion promise. */ resolve: (result: SubAgentResult) => void; // Live activity tracking (updated via EventBridge forwarding) /** Number of tool calls executed so far. */ toolCount: number; /** Name of the most recently started tool. */ lastToolName: string | null; /** Summary/args of the most recently started tool. */ lastToolSummary: string | null; /** Timestamp when the most recent tool started. */ lastToolStartedAt: number | null; /** Set while the sub-agent is blocked awaiting a permission decision. */ pendingPermission: { toolName: string; args: unknown } | null; }