import { SmolConfig } from "smoltalk"; import type { DebuggerState } from "../../debugger/debuggerState.js"; import type { LogLevel } from "../../logger.js"; import type { TrustedModules } from "../../compiler/trustedModules.js"; import { SimpleMachine } from "../../simplemachine/index.js"; import { StatelogClient, StatelogConfig, type RootLog } from "../../statelogClient.js"; import type { CoverageCollector } from "../coverageCollector.js"; import type { AbortCause } from "../errors.js"; import type { Clock } from "../clock.js"; import { type Host } from "../../host/host.js"; import type { PolicyDirs } from "../policy.js"; import { InvocationUsageMeter } from "../invocationUsage.js"; import type { AgencyCallbacks } from "../hooks.js"; import type { InterruptResponse } from "../interrupts.js"; import { LLMClient } from "../llmClient.js"; import { MemoryManager } from "../memory/manager.js"; import type { MemoryConfig } from "../memory/types.js"; import { GlobalStore } from "../state/globalStore.js"; import { StateStack } from "../state/stateStack.js"; import type { TraceConfig } from "../trace/types.js"; import type { HandlerEntry, HandlerFn, RunHandlerFn } from "../types.js"; import type { ResolvedInvocation } from "../invocationOptions.js"; import type { Checkpoint } from "./checkpointStore.js"; import { CheckpointStore } from "./checkpointStore.js"; import { PendingPromiseStore } from "./pendingPromiseStore.js"; import { type OpenWait } from "../waits.js"; /** See `RuntimeContext.debugSlot`. `waits` is what the module's runs are * waiting on right now, kept with or without a debugger (`waits.ts`). */ export type DebugSlot = { state: DebuggerState | null; waits: OpenWait[]; }; export type PendingArgOverrides = { moduleId: string | null; scopeName: string | null; values: Record; }; export declare class RuntimeContext { stateStack: StateStack; globals: GlobalStore; checkpoints: CheckpointStore; callbacks: AgencyCallbacks; onStreamLock: boolean; handlers: HandlerEntry[]; /** The platform this run is on: files, the terminal, environment * variables, the clock, and the rest. See docs/dev/runtime/host.md. The * default host of the platform when the caller passed none; a run can * carry its own through `InvocationOptions.host`. NOT serialized — * reconstructed per run, like handlers. * * DRIFT WARNING: this and the other non-serialized runtime fields * (handlers, callbacks, checkpoints, locks, …) are set by the constructor * AND, separately, by `createExecutionContext`, which builds the execution * context via `Object.create` and copies fields by hand. A field added * here but forgotten there is silently `undefined` at run time with no * compile error — that is the bug the `clock` copy fixed. If you add a * non-serialized field, copy it in `createExecutionContext` too. */ host: Host; /** The directories a policy's `dir` patterns stand for, resolved once * here so the matcher reads no file while an interrupt is answered. * NOT serialized; copied in `createExecutionContext` like `host`. */ policyDirs: PolicyDirs; /** The time source for guards: the host's clock. Real by default; a * FakeClock only when a test opts in. */ get clock(): Clock; locks: Record>; lockOwners: Record; lockWaiters: Record; lockReleasers: Record void>; pendingPromises: PendingPromiseStore; graph: SimpleMachine; _skipNextCheckpoint: boolean; /** Called before any checkpoint is built, from whichever stack. An * `llm()` call in progress registers one that copies its live thread * into its frame, so a checkpoint built while its tools run (the * debugger's rolling checkpoint inside a tool) carries the replies of * the tools that have already finished. */ beforeCheckpoint: Array<() => void>; /** Set by an external pause signal. The runner honours it at the next * step boundary by stamping a checkpoint and throwing PauseSignal. */ pauseRequested: boolean; _pendingArgOverrides?: PendingArgOverrides; _restoreCount: number; _toolCallDepth: number; /** The debugger, held in an object every execution context of this * module shares by reference: `createExecutionContext` copies the * holder, not its contents, so a debugger installed while a run is * going, or between two legs of a resume, is seen by every live and * future leg at its next step. Read and written through * `debuggerState`. */ debugSlot: DebugSlot; /** When set, `input()` calls this instead of opening its own readline on * stdin. Installed by hosts that own the terminal for this execution: * the REPL routes questions through its readline, and the * `_installSlowInput` test seam simulates a slow human. Per-execution on * purpose (never a process global): two runs in one process must not see * each other's override. Never serialized — checkpoints capture the * StateStack and GlobalStore, not the context. The TUI debugger sets * it through `InvocationOptions.inputOverride` on the node call and on * every resume and rewind it makes. */ inputOverride?: (prompt: string) => Promise; private traceWriter; statelogClient: RootLog; /** * The logger for posts made outside any run: the run's start and end * events and errors reported after the run's frame has ended. It is the * client itself, so it nests under the root span stack and redacts with * the top-level globals. Code inside a run logs through `run.log`. */ get rootLog(): RootLog; /** * The client itself with its span methods, for the code that owns the * root span stack: the `agentRun` span around a whole run, the graph * engine's span around each node, the node boundary that closes an * `abortUnwind` span after the run's own logger is gone, and the code * that makes a run's logger out of the client. All of it runs at the root * of a run, outside every fork branch. */ get rootLogWithSpans(): StatelogClient; smoltalkDefaults: Partial; /** Max characters of a single tool result fed back to the LLM (the * full result is still returned to Agency code). `undefined` falls * back to the runtime default in `runPrompt`. Baked in from * `agency.json` `client.maxToolResultChars` at compile time. */ maxToolResultChars?: number; /** Warn when one tool's JSON schema exceeds this many characters. * `undefined` falls back to the runtime default in `runPrompt`; `0` * disables the check. Baked in from `agency.json` * `client.maxToolSchemaChars` at compile time. */ maxToolSchemaChars?: number; /** Tool names already reported by the oversized-schema check, so the * warning fires once per run instead of once per LLM call. */ warnedToolSchemas?: Record; /** Paths to provider modules to load at startup, baked in from * `agency.json` `client.providerModules` at compile time and merged * with `AGENCY_PROVIDER_MODULES` at runtime by `loadProviderModules`. * Defaults to `[]`. */ providerModules: string[]; /** The `sandbox.trustedModules` entries from `agency.json`, baked in at * compile time as written (paths may be relative). Only the root reads * them; a subprocess takes its list from `SubprocessRunInfo`. See * `effectiveTrustedModules` in `lib/compiler/trustedModules.ts`. Defaults to `[]`. */ trustedModules: TrustedModules[]; private _llmClient; private _interruptResponses; get llmClient(): LLMClient; setLLMClient(client: LLMClient): void; setInterruptResponses(responses: Record): void; getInterruptResponse(interruptId: string): InterruptResponse | undefined; dirname: string; /** Callbacks registered at module top-level (via `_callback` during * `__initializeGlobals`, when no real caller frame exists yet). Persist for * the whole run. */ topLevelCallbacks: Array<{ name: string; fn: any; }>; abortController: AbortController; traceConfig: TraceConfig; runId: string | null; /** Subprocess nesting depth of THIS process: 0 in the root, seeded from * the run/resume instruction in subprocesses. Surfaced to Agency via the * std::run gate interrupt's `depth` payload and to TS helpers via * `agency.ctx().subprocessDepth`. */ subprocessDepth: number; verbose: boolean; /** * Log threshold used by ad-hoc subsystem loggers (memory, etc.). * Plumbed in from `AgencyConfig.logLevel` so users can crank it up to * `"debug"` when investigating issues without recompiling. The logger * itself is stateless — consumers call `createLogger(execCtx.logLevel)` * on demand rather than carrying a long-lived Logger instance, which * lets each subsystem add its own prefix without coordinating an * instance pool. */ logLevel: LogLevel; getStaticVars?: () => Record; coverageCollector: CoverageCollector | null; private statelogConfig; maxRestores: number; /** Ceiling on logical function-call nesting depth before the runaway- * recursion guard trips. See lib/runtime/callDepth.ts. */ maxCallDepth: number; failurePropagation: "off" | "warn" | "on"; /** Resolved root budget (dollars of LLM spend / wall-clock ms) sourced from * the `budget` config field — baked for local runs, override-supplied for * served agents. Read by installRootBudget, where a CLI flag (env var) wins. * Undefined means "no config budget"; the guard installer then relies on the * env vars alone. */ budget?: { maxCost?: number; maxTimeMs?: number; }; /** Fresh per-invocation usage meter for the serve cost seam. One per * execution context, so each `/node` / `/function` / `/resume` leg meters * only its own newly-incurred spend. Deliberately NOT serialized and NOT * restored from a checkpoint — a resume leg must start at zero, never * inherit the checkpoint-carried cumulative cost. Set both here (for a * directly-`new`'d context) and explicitly in `createExecutionContext` * (which bypasses the constructor via Object.create). */ invocationUsage: InvocationUsageMeter; jsonMemoryConfig?: MemoryConfig; private memoryManagerCache; constructor(args: { statelogConfig: StatelogConfig; smoltalkDefaults: Partial; maxToolResultChars?: number; maxToolSchemaChars?: number; providerModules?: string[]; trustedModules?: TrustedModules[]; dirname: string; maxRestores?: number; maxCallDepth?: number; failurePropagation?: "off" | "warn" | "on"; budget?: { maxCost?: number; maxTimeMs?: number; }; traceConfig?: TraceConfig; verbose?: boolean; memory?: MemoryConfig; /** Threshold for ad-hoc subsystem loggers. Optional so existing * test/runtime constructors keep working; defaults to "info" to * match the established no-debug-by-default behavior. */ logLevel?: LogLevel; /** The host to run on. Omitted in production, where the platform's * default host applies. */ host?: Host; /** Test-only override of the host's clock. Omitted in production, * where the default host decides (the AGENCY_FAKE_CLOCK env var or the * real clock). */ clock?: Clock; }); getRunId(): string; /** The statelog sink identity a subprocess should inherit — see * `withParentStatelog` in ipc.ts. Narrow accessor so the full (private) * statelog config stays encapsulated. */ /** The effective remote telemetry sink a spawned subprocess should inherit, so * a child traces to the SAME destination/credential as this context — * including a per-invocation override. Only the allow-listed telemetry fields * are exposed (never client/provider config). */ getStatelogSink(): { observability: boolean; host?: string; apiKey?: string; projectId?: string; requestTimeoutMs?: number; metadata?: StatelogConfig["metadata"]; logFile?: string; }; createExecutionContext(invocation: ResolvedInvocation): Promise>; /** * Resolve the active `MemoryManager` for the current stateStack. * * Single rule: top of `stateStack.other.memoryFrames` (accessed via * `activeMemoryFrame()`) wins. No fallback to a "default" manager — * the JSON config gets seeded as the bottom frame at execCtx * creation, so if JSON was set there will always be a frame and * this never returns `undefined` for JSON-only setups. * * The one back-compat seam: a checkpoint written before * `memoryFrames` existed (or one taken right after a user called * `disableMemory()` on the JSON-seeded bottom frame) has no frames. * If `jsonMemoryConfig` is set we re-seed it as a courtesy so old * traces resume as before. If the user explicitly popped, they * call `enableMemory(...)` again to turn it back on. * * Managers are cached per execCtx keyed by `configKey` so * push/pop/push of the same dir reuses one instance — important * for the "pop back to A returns A's manager" semantics. */ getActiveMemoryManager(branchStack: StateStack): MemoryManager | undefined; /** Iterate every cached memory manager. Used at shutdown so a * branch that opened a side store doesn't lose its writes. */ getAllCachedMemoryManagers(): MemoryManager[]; /** Register a handler. `liveGuardIds` is REQUIRED and each caller * must decide it explicitly (an empty array is NOT neutral — it * means "hide every guard from this handler", which is correct only * for handlers registered before any guard can exist, like the * --policy handler and top-level init wrappers). Agency handle * blocks capture it in Runner.handle; TS callers capture at call * time in withPushedHandler. See HandlerEntry. */ pushHandler(fn: HandlerFn, liveGuardIds: string[]): void; /** Register a handler function in the shape the chain calls: the run it * runs under, then the interrupt. Handler functions the compiler wrote * already have this shape. */ pushRunHandler(fn: RunHandlerFn, liveGuardIds: string[]): void; popHandler(): void; enterToolCall(): void; exitToolCall(): void; isInsideToolCall(): boolean; get aborted(): boolean; /** * Branch-aware cancellation check. Returns true if either: * - the global ctx is aborted (e.g., user pressed Ctrl-C), OR * - the given branch stack's per-branch abort signal has fired (e.g., * this branch is a race loser). * * Pass the local stateStack at any call site that lives inside a fork/race * branch so the check sees that branch's signal. Without a stack arg, this * is equivalent to `ctx.aborted`. */ isCancelled(stack?: StateStack): boolean; /** * Branch-aware AbortSignal for HTTP/fetch/streaming calls. Returns a * composite signal that fires on either global ctx abort OR the given * branch stack's abort. Pass to smoltalk's `abortSignal` so per-branch * cancellation actually tears down in-flight network requests. * * If no stack is given (or the stack has no branch signal), returns the * global ctx signal — same behavior as before. */ getAbortSignal(stack?: StateStack): AbortSignal; /** * Cancel the whole execution. `cause` records WHY: a terminal * `userKill` (TS `cancel()` / an external abort — the default) vs a * recoverable `userInterrupt` (the REPL's Esc, which hands control back * but keeps the session). The cause rides on the AgencyCancelledError * so downstream boundaries can read it via `readCause`. Leaving the * error itself as the abort reason keeps `signal.reason` an Error. */ cancel(reason?: string, cause?: AbortCause): void; /** Throw the cancel reason `cancel()` recorded, if the run is cancelled. * The runner calls this at a step boundary so cancel wins over a pause. */ throwIfCancelled(): void; /** * Swap in a fresh AbortController after a user-initiated cancel so the * NEXT unit of work isn't immediately aborted. The REPL calls this once * it has caught the AgencyCancelledError from a cancelled turn and is * about to accept the next prompt. Safe only when nothing is in flight * (the cancelled turn has fully unwound) — otherwise an in-flight call * would keep a reference to the old, now-orphaned signal. */ resetCancel(): void; forkStack(): StateStack; /** Sever references held by an execution context so GC can reclaim them. */ cleanup(): void; /** Get the most recent result-entry checkpoint for the current function. */ getResultCheckpoint(): Checkpoint | undefined; restoreState(checkpoint: Checkpoint): void; /** @deprecated Use checkpoints.create() instead */ stateToJSON(): { stack: import("../state/stateStack.js").StateStackJSON; globals: import("../state/globalStore.js").GlobalStoreJSON; }; toJSON(): { stateStack: import("../state/stateStack.js").StateStackJSON; callbacks: string[]; onStreamLock: boolean; graph: { nodes: string[]; edges: Record; config: { debug: { log?: boolean; logData?: boolean; } | undefined; }; }; statelogClient: string; smoltalkDefaults: string; dirname: string; }; getSmoltalkConfig(...configs: Partial[]): Partial; pauseTraceWriter(): Promise; closeTraceWriter(): Promise; writeCheckpointToTraceWriter(checkpoint: Checkpoint): Promise; writeStaticStateToTrace(values: Record): Promise; get debuggerState(): DebuggerState | null; set debuggerState(state: DebuggerState | null); hasDebugger(): boolean; hasTraceWriter(): boolean; }