Repository Public
1/** Dependencies of the agent loop. Everything has a production default; tests inject fakes. */2import { getKernel, type Kernel } from '@aos/kernel';3import { catalogueEntry, getProvider, type ProviderAdapter } from '@aos/providers';4import type { Provider } from '@aos/shared';5import { TOOLS, type Tool } from '@aos/tools';67/** Tunables of the loop (CONTRACT §5). Changing one mid-run changes the logic's decisions: keep them stable per deployment. */8export interface EngineLimits {9 /** Consecutive turns without a tool call tolerated before the run ends `partial`. */10 maxNudges: number;11 /** Consecutive `max_tokens` / `pause` stops without a tool call before the run ends `partial`. */12 maxContinuations: number;13 /** Identical tool call + identical result, N times in a row ⇒ stall. */14 stallRepeat: number;15 /** Model turns without a successful write / commit / criterion change ⇒ stall. */16 stallTurns: number;17 /** Fraction of the usd / turn / wall budget at which the model gets its single final summarising turn. */18 finalTurnFraction: number;19 /** Fractions of the model's context window: operator note to wrap up, then `partial`. */20 contextWarnFraction: number;21 contextStopFraction: number;22 /** Durable back-off (ms) before re-trying a model call that failed with a retryable provider error. */23 modelRetryDelaysMs: number[];24 /** Project instruction file cap (bytes). */25 instructionsMaxBytes: number;26}2728export const DEFAULT_LIMITS: EngineLimits = {29 maxNudges: 2,30 maxContinuations: 3,31 stallRepeat: 3,32 stallTurns: 15,33 finalTurnFraction: 0.9,34 contextWarnFraction: 0.7,35 contextStopFraction: 0.9,36 modelRetryDelaysMs: [30_000, 120_000, 300_000],37 instructionsMaxBytes: 32 * 1024,38};3940export interface EngineDeps {41 /** Default: the process-wide kernel (DATABASE_URL / AOS_DATA_DIR). */42 kernel?: Kernel;43 /** Default: `getProvider` (API keys read from the environment on first use). */44 provider?: (id: Provider) => ProviderAdapter;45 /** Default: the `@aos/tools` registry. Order of this record = order presented to the model. */46 tools?: Record<string, Tool>;47 /** Context window (tokens) of a model; null = unknown ⇒ no context guard. Default: the provider catalogue. */48 contextWindow?: (provider: Provider, model: string) => number | null;49 /** Default: env AOS_WORKSPACE_ROOT. Sibling project directories under it are unreadable from the sandbox. */50 workspaceRoot?: string;51 /** Extra absolute paths the sandboxed shell must not read. Default: env AOS_SANDBOX_EXTRA_DENY_READ (comma list). */52 extraDenyRead?: string[];53 /** Deny the owner's ~/Desktop, ~/Documents, ~/Downloads to the sandbox when they exist. Default true. */54 denyOwnerFolders?: boolean;55 /** Test hook: path of the sandbox launcher (proves fail-closed behaviour). */56 sandboxExecPath?: string;57 limits?: Partial<EngineLimits>;58 maxOutputTokens?: number;59}6061export interface ResolvedDeps {62 kernel: Kernel;63 provider: (id: Provider) => ProviderAdapter;64 tools: Record<string, Tool>;65 contextWindow: (provider: Provider, model: string) => number | null;66 workspaceRoot: string | null;67 extraDenyRead: string[];68 denyOwnerFolders: boolean;69 sandboxExecPath: string | undefined;70 limits: EngineLimits;71 maxOutputTokens: number | undefined;72}7374const commaList = (v: string | undefined): string[] => (v ?? '').split(',').map((s) => s.trim()).filter((s) => s.length > 0);7576export function resolveDeps(deps: EngineDeps = {}): ResolvedDeps {77 return {78 kernel: deps.kernel ?? getKernel(),79 provider: deps.provider ?? getProvider,80 tools: deps.tools ?? TOOLS,81 contextWindow: deps.contextWindow ?? ((p, m) => catalogueEntry(p, m)?.contextTokens ?? null),82 workspaceRoot: deps.workspaceRoot ?? process.env.AOS_WORKSPACE_ROOT ?? null,83 extraDenyRead: deps.extraDenyRead ?? commaList(process.env.AOS_SANDBOX_EXTRA_DENY_READ),84 denyOwnerFolders: deps.denyOwnerFolders ?? true,85 sandboxExecPath: deps.sandboxExecPath,86 limits: { ...DEFAULT_LIMITS, ...(deps.limits ?? {}) },87 maxOutputTokens: deps.maxOutputTokens,88 };89}90