/** * Orchestrator — The central coordinator of the multi-agent system. * * Responsibilities: * 1. Accept a user goal and optionally a provider/model config * 2. Create a ContextVault (shared context bus) * 3. Build the project file tree and inject it for the Planner * 4. Optionally retrieve memory context from past similar trajectories * 5. Run the PlannerAgent to produce an execution plan * 6. Execute tasks sequentially, respecting dependencies * 7. Spawn the appropriate agent for each task * 8. Apply file changes to disk * 9. Execute runner commands and capture output * 10. Optionally store the trajectory in memory * 11. Synthesize and return the final result * * Called by the `agent-nuvira execute` CLI command. */ import { ConfigManager } from '../config/manager.js'; import type { TaskStep } from './agent.js'; import { type ModuleRegistry } from './module-registry.js'; import type { EventBus } from '../observability/event-bus.js'; import { type ReportModule } from './report-module.js'; import { type TaskIntent } from '../learning/auto-router.js'; /** Configuration for an orchestration session */ export interface OrchestratorOptions { /** Inference provider type (default: from configManager) */ provider?: string; /** Model override (default: from provider config) */ model?: string; /** Whether to write files to disk (false = dry-run) */ dryRun?: boolean; /** Enable verbose logging */ verbose?: boolean; /** Agent-specific model overrides */ agentModels?: Partial>; /** Enable persistent memory (trajectory storage and retrieval) */ useMemory?: boolean; /** Auto-create a review bundle instead of applying changes directly */ reviewMode?: boolean; /** Auto-route each agent to its recommended model from the ModelRouter */ autoRouteModels?: boolean; /** * Opt-in to the interactive rate-limit prompt (wait / switch / skip / abort). * Default: false — rate limits are handled fully automatically (silent wait * for transient hints, silent auto-switch to another provider when the * current one is exhausted). Also settable via `routing.askOnRateLimit` in * .buffconfig.json. */ askOnRateLimit?: boolean; /** * C3: NLU task-intent hint (router TaskIntent vocabulary) from the parsed * goal. Seeded into the planner routing decision's taskProfile.intent so the * orchestrator's strategy switch + the router's task-type see the SAME * vocabulary every action command derives from the shared NLU parser. * Safety flags (requiresVerification / escalationTarget) stay text-derived. */ taskIntentHint?: TaskIntent; /** * D1: agent-driven recall context (continue/resume goals). Recalled project * state (sessions/facts/checkpoint) is prepended to the planner's memory * block so the planner sees prior work before planning. */ recallContext?: string; /** * Session 20 — RequestContract acceptance criteria for this goal. Seeded * into vault metadata so the verification pass (the reviewer follow-up) * checks the changes against the contract, not just the loose goal * (Decision 3: spec→verify). Optional — the pipeline verifies normally * when absent. */ acceptanceCriteria?: string[]; /** * Enable automatic MCP server discovery and tool injection. * Set to false to skip MCP auto-connect for a specific pipeline. * Default: true */ enableMcp?: boolean; /** Pre-built task plan to use instead of calling the PlannerAgent (for workflow templates) */ prefillPlan?: TaskStep[]; /** * Maximum context tokens before the ContextPruner triggers pruning. * Default: 128000 (suitable for Llama-3, Groq, OpenRouter). * Set higher for Gemini (1000000) or lower for smaller models. */ contextLimit?: number; /** * Context pruning aggressiveness. * - 'soft' (default): keeps last 10 conversation messages * - 'medium': keeps last 5 * - 'aggressive': keeps last 2 */ contextPruneMode?: 'soft' | 'medium' | 'aggressive'; /** * Run runner commands and tests inside a Docker sandbox container. * Requires Docker to be installed and running. */ /** * Maximum number of auto-repair attempts per task when an agent fails. * Default: 3. Set to 0 to disable auto-repair. */ maxRepairs?: number; /** * Auto error-repair mode. * - 'auto' (default): automatically repair repairable errors without asking * - 'prompt': ask for user approval before applying repair strategies * - 'off': disable auto-repair entirely */ repairMode?: 'auto' | 'prompt' | 'off'; /** * Fallback models to try when switching during error-repair. * Example: ['groq/llama-3.3-70b', 'gemini/gemini-2.0-flash'] */ repairFallbackModels?: string[]; useDockerSandbox?: boolean; /** * When true, skip all tester and debugger tasks in the pipeline. * Useful when you only want to generate code without running tests. */ skipTests?: boolean; /** * Optional spinner reference from the CLI caller. * When set, the orchestrator stops the spinner before showing interactive * rate-limit prompts and restarts it after the user responds. */ spinner?: { stop(): void; start(text?: string): void; }; /** * Save a checkpoint after every task batch so the pipeline can be resumed * later with `--resume` (or a fresh run of the same goal). Checkpoints live * in ~/.buff/memory/checkpoints/ and let a crash / quota kill / token expiry * mid-pipeline continue from the first pending step instead of restarting. * Default: false. Implied true when resumeCheckpointId is set. */ checkpoint?: boolean; /** * Resume a previously saved pipeline from a checkpoint id (or the auto id * for goal + cwd). Completed steps are skipped; execution continues from the * first pending step with its dependencies satisfied. */ resumeCheckpointId?: string; /** * True when the user explicitly asked to RESUME (bare `--resume` with no id * included). Lets the orchestrator warn when no checkpoint matches the auto * id (e.g. a reworded goal) instead of silently starting a fresh pipeline. */ resumeRequested?: boolean; /** * Enable resilient auto-routing: every LLM call auto-routes on ANY failure * (not just rate-limit), tries ALL ranked candidates (no 3-candidate cap), * and tracks failures across the session AND across pipelines (persisted to * disk). Default: true when auto-routing is active. */ resilientRouting?: boolean; } /** The final result of an orchestration session */ export interface OrchestrationResult { /** Overall success */ success: boolean; /** The original user goal */ goal: string; /** Summary of what was accomplished */ summary: string; /** Number of tasks completed vs total */ tasksCompleted: number; tasksTotal: number; /** Detailed results from each agent */ agentResults: Array<{ agent: string; success: boolean; summary: string; }>; /** File change summary */ fileChanges: string; /** Runner output (from executed commands) */ runOutput?: string; /** Error message if failed */ error?: string; /** Memory trajectory ID if stored */ trajectoryId?: string; /** Review bundle ID if review mode was enabled */ reviewId?: string; /** Execution telemetry — attempts, repair activity, dependency installs */ stats?: ExecutionStats; /** The execution plan (lightweight — descriptions only); used by the * self-improvement loop to capture failed runs into episodic memory. */ taskPlan?: TaskStep[]; } /** * Telemetry about how the pipeline executed — used by the evaluation * framework to measure reliability, recovery behavior, and token efficiency. */ export interface ExecutionStats { /** Total LLM calls made across all agents */ llmCalls: number; /** Estimated input tokens */ inputTokens: number; /** Estimated output tokens */ outputTokens: number; /** Total repair attempts triggered by the ErrorRepairEngine */ repairAttempts: number; /** Count of 'alternative-approach' repair strategies executed */ alternativeApproaches: number; /** Tasks that failed on first attempt but succeeded after repair */ recoveredFailures: number; /** Total task failures (before repair) */ taskFailures: number; /** Whether the runner auto-installed dependencies */ dependencyInstallAttempted: boolean; /** Whether the dependency install succeeded */ dependencyInstallSucceeded: boolean; /** Number of file changes that were rolled back (reverted to original) */ rollbackCount: number; } export declare class Orchestrator { private configManager; /** The module registry used for agent lookups */ private moduleRegistry; /** The event bus for emitting observability events */ private eventBus; /** The report module for generating structured execution reports */ private reportModule; /** Optional routing decision overrides keyed by agent type */ private routingDecisionOverrides; /** * The ROUTED complexity per task (keyed by task id), recorded when the * auto-routed LLM is created. The router may escalate complexity itself at * resolve time (escalationApplied) — so the FAILED call may have actually * run at a higher tier than the raw task.complexity label. Repair escalation * must climb from the ROUTED tier, or it can land back on the same tier that * just failed (only re-rolling provider/model, not reasoning capacity). */ private routedComplexities; /** * The provider×model each task was actually ROUTED to (by task id), * recorded when the auto-routed LLM is created. The repair path compares * this against the ESCALATED decision to detect a no-op escalation (the * "stronger model" resolves to the SAME provider×model — only a weak * model is available) and degrade to lenient file-change parsing instead * of re-prompting the same weak model until the repair budget dies. */ private routedProviderModelByTask; /** * The provider×model each task's ESCALATED repair resolved to (by task * id), recorded by createEscalatedLLM. Compared against the routed baseline * in isNoOpEscalation so a repair that lands back on the same weak model is * detected without re-resolving the decision (which has side effects). */ private escalatedProviderModelByTask; /** * The user's weak-model decision for THIS pipeline, latched after the first * prompt (routing.promptOnWeakModel) so a multi-task pipeline asks ONCE, * not once per task. null = not asked yet; 'continue'/'wait'/'abort' = the * user's choice. Silent mode never prompts — this stays null and the * pipeline always takes the weak-model path. */ private weakModelChoice; /** * Latched one-shot cold-start registry probe: fired once per Orchestrator * instance when auto routing is active on an empty registry (see * maybeFireColdStartProbe). A long dev-mode session only pays for it once. */ private coldStartProbeFired; /** * P0 reasoning trace: the id of the trace for the CURRENT pipeline (set in * execute(), ended in its finally). All LLM calls made while this is set are * recorded as steps so `buff trace replay ` and the dashboard can show * exactly which agent × model × prompt produced each result. */ private activeTraceId; /** * Per-pipeline failure session: the state recordActionFailure mutates when * a per-task LLM call fails, and resolveAutoRoutingDecision CONSULTS it * before every task (M0.3) — a provider that failed earlier in this pipeline * (auth = rest of pipeline, rate-limit/transient = cooldown) never wins a * subsequent task; the decision sinks to the best-ranked non-excluded * provider. The registry/quota/breaker write-throughs it composes are also * read by the router before every task (parked providers sink below healthy * ones), so both mechanisms agree. */ private readonly failureSession; /** Execution telemetry accumulator for the current pipeline */ private stats; constructor(configManager?: ConfigManager, moduleRegistry?: ModuleRegistry, eventBus?: EventBus, reportModule?: ReportModule); /** * Execute a multi-agent pipeline for the given goal. * * Wraps the pipeline in a try/finally so MCP server connections are torn down * on EVERY exit path. Early returns (e.g. planner failure) previously skipped * the cleanup at the end of the method, leaking the spawned MCP subprocesses * and keeping the CLI process alive long after the pipeline finished. */ execute(goal: string, options?: OrchestratorOptions): Promise; /** The actual pipeline body — wrapped by execute() with a K1 runId. */ private executeCorrelated; /** Internal pipeline implementation (see execute()). */ private executePipeline; /** * Session 46 — weak-model pre-flight warning (extracted for testability). * * When auto routing resolves to a LOCAL model with a low learned score * (score < 0.5), no verified cloud provider was available at decision * time. Warn BEFORE the pipeline burns minutes on a model that is likely * to fail complex tasks. Warning only — the user keeps control. */ private maybeWarnWeakLocalModel; /** * Pre-flight project inspection — deterministic, always-on, no LLM calls. * * Scans the working directory for the project type (manifest files), counts * source + test files, and reads the git state. The readable digest is: * - Stored in the vault as `projectInspection` so the Planner builds a plan * that REUSES the existing codebase (no rework) and keeps backward * integrity (existing tests are taken into account). * - Emitted on the event bus so the CLI board / dashboard can show the * user what was found before planning starts. */ private runProjectInspection; /** Count source/test files and top-level source directories (no LLM). */ private countSourceFiles; /** Read the git branch and uncommitted-change count. Returns null if not a repo. */ private gitState; private createLLMProvider; private runAgent; /** * Create the onRateLimit callback. * * Rate-limit recovery is FULLY AUTOMATIC by default (decision #26): the * pipeline silently waits out transient hits (short reset hints) and silently * auto-switches to another provider when the current one is exhausted or * rate-limiting repeatedly — the user is never interrupted, and the build * continues on whichever provider is healthy. The interactive prompt * (wait / switch / skip / abort) is opt-in via `routing.askOnRateLimit: true` * in .buffconfig.json and only ever appears on a real TTY. * * Returns undefined only for dry-run (no LLM calls happen anyway), so even * non-interactive runs (CI, pipes) get silent auto-switch instead of grinding * the same exhausted provider. */ private createRateLimitHandler; private executeSingleTask; private getExecutionStrategy; /** * Create an LLM call function routed by the AutoModelRouter for a task. * Uses the task description for complexity analysis and resolves the best * provider/model per agent type. */ /** * M2.5: estimate the REAL prompt payload for a task — goal + task * description + the workspace context files the agent will receive (sized by * stat, the same chars→tokens heuristic as estimateTokens, without reading * file contents). Passed as contextHintTokens so the context-fit signal * differentiates per-task in multi-agent pipelines the way it does for chat's * growing conversation history. Best-effort: any stat failure contributes 0 * (the router still falls back to the task-description estimate). */ private estimateTaskPayloadTokens; private resolveAutoRoutingDecision; private createAutoRoutedLLM; /** * Detect a NO-OP model escalation for a task: the "stronger model" the * repair engine would escalate to resolves to the SAME provider×model as * the one that just failed. This happens when every stronger candidate is * unavailable/blocked (e.g. only a weak local model is configured) — * re-prompting "a stronger model" then just repeats the identical failure * until the repair budget dies. The caller degrades instead: lenient * parsing for the writer, a clear warning, and a bounded repair budget. * * Returns true when escalation would be a no-op (or the routed baseline is * unknown — treat as no-op to stay safe), false when a genuinely different * provider×model exists for escalation. */ private isNoOpEscalation; /** * Whether a stronger candidate is in a SHORT cooldown that will recover * soon — the only honest basis for offering "wait and retry". Checks the * session exclusions (rate-limit / transient cooldowns) and the shared * circuit breaker for any provider other than the weak one with a recovery * time within MAX_WEAK_WAIT_MS. Returns the wait ms (or null when no * stronger candidate is coming back soon — 'wait' is then not offered). */ private weakModelWaitAvailableMs; /** * Build an ESCALATED planner LLM for repair attempts (assessment P0). * * The Auto router picks the cheapest ADEQUATE model per task. When that * model fails to plan (garbage JSON, example regurgitation), re-resolving at * the NEXT complexity level forces the router to rank reasoning capacity * higher — the repair then runs on a genuinely stronger model instead of * re-prompting the same weak one that already failed. Uses a fresh decision * (not the latched planner override) so the escalation actually applies. */ private createEscalatedPlannerLLM; /** * Re-route a task's repair at the NEXT complexity level so the Auto router * picks a STRONGER model than the one that just failed (assessment P0). * * Used by BOTH the planner repair path and the per-task agent repair path * (writer/debugger/security/tester...): re-prompting the same weak model * that already failed just repeats the failure until the repair budget * dies. The escalation carries the stronger decision's routing snapshot * into the reasoning trace so repairs are fully auditable. */ /** * Resolve the escalated (next-complexity) routing decision for a task. * Extracted so the repair path can inspect the decision ONCE (detect a * no-op escalation) and then build the escalated LLM from it — avoiding a * double resolveAutoRoutingDecision (which has side effects: routing * history + audit write-through). */ private resolveEscalatedDecision; private createEscalatedLLM; /** Next rung on the complexity ladder (critical is the top). */ private escalateComplexity; private createAutoRoutedLLMFromDecision; /** * Create a RESILIENT auto-routed callLLM that auto-routes on ANY failure. * * Unlike createAutoRoutedLLM (which binds to ONE provider and only failovers * on rate-limit), this proxy: * 1. Routes to the auto-router's best candidate initially * 2. On ANY failure (not just rate-limit), re-routes to the next candidate * 3. Tries ALL ranked candidates (no 3-candidate cap) * 4. Tracks failures across the entire session AND persists to disk * 5. Tools/sub-agents use it transparently * * Use this when you want maximum resilience — the caller never sees errors * unless ALL providers are exhausted. */ private createResilientAutoRoutedLLM; /** * One-shot background model-registry refresh for a COLD registry. * * Fired when auto routing is active and the registry has no verified * providers: probes listModels + spot-checks the configured providers so the * pipeline's later tasks route on REAL health data (the dedicated model- * health agent's job, started on demand instead of waiting for `buff models * watch`). Latched per instance — a long dev-mode session only pays once. * Fire-and-forget: never awaited, never blocks, never throws. */ private maybeFireColdStartProbe; private applyRoutingPlanAdjustments; /** * Run the ContextPruner on the vault context. * Only prunes when the context exceeds the configured threshold. * Logs details in verbose mode. */ private pruneContext; private applyFileChanges; private buildResult; } //# sourceMappingURL=orchestrator.d.ts.map