Skip to content

Agent-Nuvira: Core Agent Execution Engine — Modular Architecture

Design Document v1.0 | August 2026

🎨 Visual diagrams available: See ARCHITECTURE_DIAGRAMS.md for Mermaid-rendered versions of all diagrams below (Module Architecture, Extensibility System, Safe Execution Layer, Data Flow, and Observability Bus) with a color legend. These render natively on GitHub.


1. Design Principles

Principle Meaning
Separation of concerns Each module owns one capability. No module crosses into another's domain.
Plugin architecture Every module defines a clear interface. New implementations can be swapped in without touching other modules.
Observability by default Every module emits structured events. The execution engine can be introspected at any point.
Safe by construction Code execution is sandboxed. File changes are validated before write. All external calls are guarded.

2. High-Level Module Architecture

┌─────────────────────────────────────────────────────────────────────┐
│                        Execution Engine                              │
│    (Orchestrator — dependency-aware task scheduler)                  │
└───┬───────┬───────┬───────┬───────┬───────┬───────┬───────┬─────────┘
    │       │       │       │       │       │       │       │
    ▼       ▼       ▼       ▼       ▼       ▼       ▼       ▼
┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────────┐
│ Plan │ │ Inspect│ │ Edit │ │ Test │ │Recover│ │Verify │ │Report│ │Ext.     │
│Module│ │Module │ │Module │ │Module│ │Module │ │Module │ │Module│ │Integ.   │
└──┬───┘ └──┬───┘ └──┬───┘ └──┬───┘ └──┬───┘ └──┬───┘ └──┬───┘ └──────────┘
   │        │        │        │        │        │        │
   └────────┴────────┴────────┴────────┴────────┴────────┘
                              │
                              ▼
                    ┌─────────────────────┐
                    │   Shared Context     │
                    │   Bus (Event Store)  │
                    └─────────────────────┘

Key insight: The engine is not a linear pipeline — it's a dependency graph. Modules are scheduled by the orchestrator based on their declared dependencies, not by position in a list.


3. Module Specifications

3.1 Plan Module

Purpose: Decompose a user goal into an ordered, dependency-aware execution plan.

Interface:

interface PlanModule {
  createPlan(params: {
    goal: string;
    projectFileTree?: string;
    memoryContext?: string;
    mcpTools?: McpToolEntry[];
    modelRouting?: Record<string, string>;
  }): Promise<ExecutionPlan>;
}

interface ExecutionPlan {
  steps: PlanStep[];
  estimatedComplexity: 'simple' | 'moderate' | 'complex';
  suggestedProvider?: string;
}

interface PlanStep {
  id: string;
  description: string;
  module: 'inspect' | 'edit' | 'test' | 'verify' | 'execute' | 'integrate';
  config?: Record<string, unknown>;
  dependsOn: string[];
}

Dependency validation: The orchestrator rejects plans with dependency cycles, dangling references (a step depends on a non-existent step ID), or orphaned steps (steps that no other step depends on and that the goal doesn't reference). Plans are validated before any module execution begins.

Extensibility: New planners can be registered without modifying the orchestrator. A custom planner might use a local model for simple tasks and a frontier model for complex ones.

Observability: Every createPlan call emits: - plan:started — goal + project tree hash - plan:step-created — step ID + module type - plan:completed — step count + complexity score


3.2 Inspect Module (Repository Inspection)

Purpose: Scan the codebase to discover relevant files, extract structural context, and identify dependencies.

Interface:

interface InspectModule {
  inspect(params: {
    goal: string;
    workingDirectory: string;
    taskSteps: PlanStep[];
    maxFiles?: number;
  }): Promise<InspectionResult>;

  /**
   * Synchronous fallback — called by `inspect()` internally when the LLM
   * call fails or times out. Scans files by keyword matching against the
   * goal string. Returns file paths matching `InspectionResult.relevantPaths`.
   */
  scanByKeywords(goal: string, dir: string): string[];
}

interface InspectionResult {
  artifacts: Artifact[];
  fileTree: string;
  relevantPaths: string[];
  stats: {
    totalFiles: number;
    inspectedFiles: number;
    errors: number;
    llmFallbackUsed: boolean;
  };
}

Safe execution: - Respects .gitignore patterns to avoid inspecting sensitive files - Caps read size per file (default: 100KB) to prevent context overflow - Binary file detection — skips images, archives, and compiled binaries

Extensibility: Language-specific inspectors (e.g., TypeScriptInspector, PythonInspector) can register custom logic for extracting module dependencies, class hierarchies, and test-file mappings.

Observability: Emits: - inspect:scanning — directory being traversed - inspect:file-found — path + extension + size - inspect:llm-classify — LLM used for relevance classification - inspect:completed — artifact count + total bytes


3.3 Edit Module

Purpose: Generate and apply code changes with validation before writing to disk.

Interface:

interface EditModule {
  edit(params: {
    goal: string;
    artifacts: Artifact[];
    instructions: string;
    existingCode?: string;
    language?: string;
  }, callLLM: LLMCallFn): Promise<EditResult>;

  dryRun(params: EditParams): Promise<EditPreview>;
}

interface EditResult {
  preview: EditPreview;       // Includes all preview fields + validation
  appliedCount: number;        // Number of changes actually written
  skippedChanges: FileChange[];// Changes that were in the preview but not applied
}

interface EditPreview {
  changes: FileChange[];
  warnings: string[];
  estimatedImpact: string[];
  validation: {
    syntaxValid: boolean;
    astIssues: string[];
    securityWarnings: string[];
  };
}

Safe execution: - Pre-write validation: Every edit is validated for syntax errors (via AST engine) before disk write - Dry-run mode: EditModule.dryRun() returns preview without touching disk - Atomic writes: Files are written to a temp path and atomically renamed on success - Rollback capability: Original content is preserved in context for undo

Extensibility: - Language-specific formatters auto-run after write (Prettier for JS/TS, go fmt for Go, ruff for Python) - Custom edit strategies can be registered (e.g., RegexEditStrategy, PatchEditStrategy)

Observability: Emits: - edit:generating — file path + language - edit:validating — syntax check result - edit:written — file path + bytes written - edit:skipped — reason (no changes, validation failed)


3.4 Test Module

Purpose: Execute tests in an isolated sandbox and capture results.

Interface:

interface TestModule {
  runTests(params: {
    workingDirectory: string;
    testCommand?: string;
    framework?: 'vitest' | 'jest' | 'pytest' | 'go-test' | 'auto-detect';
    useDockerSandbox?: boolean;
    timeoutMs?: number;
  }): Promise<TestResult>;

  getAvailableFrameworks(): Promise<DetectedFramework[]>;
}

interface TestResult {
  success: boolean;
  passed: number;
  failed: number;
  total: number;
  failures: TestFailure[];
  durationMs: number;
  sandboxPath?: string;
  log?: string;
}

interface TestFailure {
  name: string;
  file: string;
  line?: number;
  message: string;
  output?: string;
}

Safe execution: - Tests run in an isolated temp directory (local) or Docker container (sandboxed) - Resource limits: 512MB memory, 1 CPU core, 30s timeout default - Network access restricted in sandbox mode to prevent exfiltration - Container auto-destroyed after execution (configurable retention)

Extensibility: Framework detectors auto-configure the test command. Custom runners can be registered for non-standard test frameworks.

Observability: Emits: - test:started — framework detected + sandbox type - test:failure — per-test failure with stack trace - test:completed — pass/fail/total + duration - test:sandbox-created — container ID (if Docker)


3.5 Recover Module (Error Recovery)

Purpose: Diagnose failures and apply targeted repair strategies with configurable retry budgets.

Interface:

interface RecoverModule {
  repair(params: {
    taskId: string;
    failure: AgentFailure;
    context: AgentContext;
    callLLM: LLMCallFn;
    executeAgent: (ctx: AgentContext, llm: LLMCallFn) => Promise<AgentResult>;
    budget: RepairBudget;
    fallbackModels?: string[];
  }): Promise<RepairResult>;

  classifyError(error: string): ErrorClassification;
}

interface RepairBudget {
  maxAttempts: number;
  currentAttempt: number;
  exhaustedModels: string[];
}

interface RepairResult {
  success: boolean;
  attempts: RepairAttempt[];
  finalError?: string;
  switchedModel?: string;
}

interface RepairAttempt {
  attempt: number;
  strategy: RepairStrategy;
  outcome: 'success' | 'failed' | 'skipped';
  durationMs: number;
  error?: string;
}

type RepairStrategy =
  | { type: 'retry-same' }
  | { type: 'rephrase-prompt'; newPrompt: string }
  | { type: 'switch-model'; model: string }
  | { type: 'simplify-goal'; simplifiedGoal: string }
  | { type: 'split-task'; subTasks: string[] }
  | { type: 'bypass'; reason: string };

Safe execution: - RepairBudget prevents infinite retry loops (configurable cap, default: 3) - Model switching respects provider rate limits via circuit breaker - bypass strategy allows graceful degradation when recovery is impossible - Sensitive error details are sanitized in logs

Extensibility: Custom repair strategies can be registered (e.g., CacheWarmStrategy for cold-start issues, RetryWithBackoffStrategy for transient failures).

Observability: Emits: - recover:classified — error category + strategy selected - recover:attempt — attempt number + strategy type - recover:model-switch — old model → new model (if applicable) - recover:budget-exhausted — final state after all attempts - recover:result — success/failure + total duration


3.6 Verify Module

Purpose: Validate that changes meet quality standards before proceeding.

Interface:

interface VerifyModule {
  verify(params: {
    changes: FileChange[];
    artifacts: Artifact[];
    goal: string;
    testResults?: TestResult;
    runResults?: RunResult;
    strictness: 'low' | 'medium' | 'high';
  }, callLLM: LLMCallFn): Promise<VerificationResult>;
}

interface VerificationResult {
  passed: boolean;
  checks: VerificationCheck[];
  overallScore: number; // 0.0 – 1.0
  blockers: string[];
  suggestions: string[];
}

type VerificationCheck = {
  type: 'syntax' | 'tests' | 'security' | 'goal-alignment' | 'code-quality';
  passed: boolean;
  details: string;
  severity: 'blocking' | 'warning' | 'info';
};

Safe execution: - Security scan runs as a blocking check — injection patterns or secrets block verification - Goal-alignment check uses LLM to verify the changes actually address the user's goal - Strictness levels control whether warnings are treated as failures (high) or advisory (low)

Extensibility: Custom verification checks can be registered (e.g., LintCheck, CoverageCheck, LicenseCheck, AccessibilityCheck).

Observability: Emits: - verify:starting — number of checks to run - verify:check — check type + pass/fail - verify:completed — score + blocker count


3.7 Report Module

Purpose: Produce structured summaries of what happened, what changed, and what's next.

Interface:

interface ReportModule {
  generate(params: {
    goal: string;
    agentResults: AgentResultSummary[];
    fileChanges: FileChange[];
    testResults?: TestResult;
    runOutput?: string;
    verificationResult?: VerificationResult;
    durationMs: number;
    memoryTrajectoryId?: string;
    reviewId?: string;
  }): Promise<ExecutionReport>;

  format(report: ExecutionReport, format: 'text' | 'json' | 'markdown' | 'github-annotation'): string;
}

interface ExecutionReport {
  success: boolean;
  summary: string;
  details: {
    goal: string;
    tasksCompleted: number;
    tasksTotal: number;
    duration: string;
    agentBreakdown: Array<{ agent: string; status: 'passed' | 'failed' | 'skipped'; summary: string }>;
    fileChanges: Array<{ path: string; status: string }>;
    testSummary?: string;
    verificationScore?: number;
    error?: string;
  };
  followUp?: {
    suggestedActions: string[];
    confidence: 'high' | 'medium' | 'low';
  };
}

Extensibility: Custom formatters can be registered (e.g., GitHubActionsFormatter for CI annotations, SlackFormatter for notifications, HTMLFormatter for dashboards).

Observability: This is the terminal node — it consumes observability events and produces the final human-readable output. Every report has a unique ID and can be re-generated from stored trajectory data. Emits: - report:generated — report ID + format


4. Cross-Cutting Concerns

4.1 Extensibility System

┌──────────────────────────────────────────────────────────┐
│                   Module Registry                          │
│                                                           │
│  registerModule(name: string, factory: ModuleFactory)     │
│  getModule<T>(name: string): T                            │
│  listModules(type: ModuleType): ModuleMetadata[]          │
│                                                           │
│  Built-in: Plan | Inspect | Edit | Test | Recover         │
│            | Verify | Report                              │
│                                                           │
│  Custom:   CustomPlan | CustomVerify | ...                │
└──────────────────────────────────────────────────────────┘

Any module can be replaced at the orchestrator level:

const engine = new ExecutionEngine();
engine.modules.register('plan', new MyCustomPlanner());

17-Agent Registry — the engine-level module system above composes with the 17 registered agents in src/agents/module-registry.ts, which the orchestrator uses to build task plans:

# Agent type Role
1 planner Decompose goal into an ordered, dependency-aware task plan
2 context-gatherer Scan the codebase, discover relevant files (wraps InspectModule)
3 writer Generate and apply code changes (wraps EditModule incl. Tier-0 routing)
4 reviewer Code review, bug detection, style checks (wraps VerifyModule)
5 runner Sandboxed command execution
6 tester Sandboxed test execution (wraps TestModule)
7 debugger Iterative test-fix loop (wraps RecoverModule)
8 git Commit, branch, PR generation
9 gitlab Full GitLab REST API — MRs, issues, pipelines
10 package Dependency management / package publishing
11 github-release Tag + release creation, npm publish
12 security Prompt-injection + secret/PII scanning
13 skill-runner Inject compiled skills into the execution plan
14 mcp Invoke external tools via Model Context Protocol
15 pr-review Inline code review on open PRs
16 issue-triage Issue classification, prioritization, labeling
17 branch-automation Git hooks, auto-branch workflows, CI diagnosis

Every agent is registered with registry.register(name, factory, metadata); plugins and SDK users can register additional agents or override built-ins (registerOrOverride), which is how custom agent roles enter the pipeline.

4.2 Observability Bus

Every module emits structured events on a shared EventBus:

interface EventBus {
  on(event: string, handler: EventHandler): void;
  emit(event: string, data: unknown): void;
  getHistory(filter?: EventFilter): EventRecord[];
}

// Built-in consumers:
// 1. LoggerConsumer — writes events to console (--verbose flag)
// 2. DAGConsumer — pushes events to web dashboard DAG visualization
// 3. TelemetryConsumer — aggregates metrics for performance monitoring
// 4. DebugConsumer — dumps full event history on failure for debugging

This enables: - Real-time DAG visualization in the web dashboard - Post-mortem debugging from event history replay - Performance analytics — slowest module, most retried step, etc. - CI/CD annotations — structured output for GitHub Actions

4.3 Safe Execution Layer

┌──────────────────────────────┐
│      Safe Execution Layer     │
│                               │
│  File Operations:             │
│  ├─ Atomic writes (temp→atom) │
│  ├─ Rollback snapshot         │
│  ├─ Max file size guard       │
│  └─ .gitignore compliance     │
│                               │
│  Code Execution:              │
│  ├─ Docker sandbox isolation  │
│  ├─ Resource limits (CPU/mem) │
│  ├─ Network restrictions      │
│  └─ Timeout enforcement       │
│                               │
│  LLM Calls:                   │
│  ├─ Injection guardrail       │
│  ├─ Retry with backoff        │
│  ├─ Circuit breaker           │
│  └─ Content length cap        │
└──────────────────────────────┘

4.4 Data Flow

User Goal
    │
    ▼
┌──────────────┐     ┌──────────────────┐
│   PlanModule  │────▶│  ExecutionPlan    │
│  (decompose)  │     │  (step[] + deps)  │
└──────────────┘     └────────┬─────────┘
                              │
                    Orchestrator resolves dependencies
                    and schedules parallel execution
                              │
         ┌────────────────────┼────────────────────┐
         ▼                    ▼                    ▼
   ┌──────────┐        ┌──────────┐         ┌──────────┐
   │Inspect   │        │  Edit    │         │  Test    │
   │Module    │        │  Module  │         │  Module  │
   └────┬─────┘        └────┬─────┘         └────┬─────┘
        │                   │                    │
        ▼                   ▼                    ▼
   Artifacts           FileChanges          TestResult
        │                   │                    │
        └───────────────────┴────────────────────┘
                            │
                            ▼
                     ┌──────────────┐
                     │ RecoverModule │◄── On failure, retry
                     │ (error-repair)│     with strategies
                     └──────┬───────┘
                            │ (success)
                            ▼
                     ┌──────────────┐
                     │ VerifyModule │
                     │ (validate)   │
                     └──────┬───────┘
                            │
                            ▼
                     ┌──────────────┐
                     │ ReportModule │
                     │ (summarize)  │
                     └──────────────┘

Data flow with retrieval (token-efficient context): before an LLM call, the context assembler may route the gathered context through the vector store — chunk → embed → top-k retrieval — and the reduced context feeds the EditModule / planner. The retrieval layer sits between the Inspect/Context-Gatherer stage and the model call, and is fully transparent to the pipeline above (see §4.5).

4.5 Vector Store — Pluggable Backend Tiers (FAISS-backed)

The memory/retrieval subsystem (src/memory/) exposes a pluggable vector-store backend with three tiers, auto-selected per machine in priority order:

Priority Backend Implementation Notes
1 faiss-native @faiss-node/native real FAISS (FaissIndex FLAT_IP, L2-normalized → cosine) Fastest; activates only when the native addon builds and passes a load-time smoke test (v1.49.1 fixed the silent-fallback bug)
2 faiss-ivf Pure-JS IVF-flat ANN (no native deps) Fast approximate search; default when native is unavailable
3 json Exact flat cosine over vectors-<ns>.json Original behavior; always works; zero data migration
┌─────────────────────────────────────────────────────────────┐
│                 VectorStore (facade)                         │
│  createFaissBackend() → lazily-resolved backend             │
└────────────────────────┬────────────────────────────────────┘
                         │
        ┌────────────────┼────────────────┐
        ▼                ▼                ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ faiss-native │ │  faiss-ivf   │ │    json      │
│ (FAISS addon)│ │ (pure-JS IVF)│ │ (exact flat) │
└──────────────┘ └──────────────┘ └──────────────┘

Key behaviors: - Auto-selection — resolution tries faiss-native first; any load/smoke-test failure falls back to faiss-ivf, then json (same entry format, no migration). routing.vectorBackend / BUFF_VECTOR_BACKEND override the priority. - Backend diagnostics — buff memory backend prints the active backend and why it was chosen; --check runs a native-FAISS availability probe with install guidance; checkNativeFaiss() is exported from the package API. - Namespaced indexes — each namespace has its own file (vectors-<ns>.json) so repo retrieval chunks never pollute memory/history vectors. - Retrieval integration — gathered contexts are chunked (~512 tokens, paragraph-aware), embedded locally (bge-small-en-v1.5, 384-dim), reduced to top-k relevant chunks, and the reduced context is sent to the model — small contexts pass through untouched, and any retrieval failure fails over to the full context (graceful degradation). - Benchmark-validated — 2,000-vector corpus: exact JSON recall@5 ≥ 0.99, IVF recall@5 ≥ 0.9 / recall@1 ≥ 0.8.


5. Current State vs. Target Architecture

Aspect Current (v1.50.0) Target
Module boundaries 17-agent ModuleRegistry with plugin-based loading (v1.18.0+) Registry-driven loading for all modules, incl. custom plugins
Error recovery ErrorRepairEngine class called from specific agent failure paths RecoverModule as first-class pipeline stage with configurable budgets
Verification Implicit (ReviewerAgent checks code) Explicit VerifyModule with check pipeline
Reporting Hardcoded text summary in Orchestrator Pluggable ReportModule with multiple output formats
Observability Optional --verbose flag + DAG module loaded on demand Built-in EventBus with pre-registered consumers
Safe execution Docker sandbox, AST validation, injection guardrail — all separate Unified SafeExecutionLayer wrapping all operations
Planner Single PlannerAgent class with hardcoded prompt Pluggable planners — local for simple, frontier for complex
Inspect ContextGathererAgent with LLM + keyword fallback InspectModule with language-specific file matchers

6. Migration Path

Phase Changes Impact
Phase 1 Extract RecoverModule interface from ErrorRepairEngine No breaking changes — class becomes module
Phase 2 Build ModuleRegistry and refactor createAgent() to use registry Agents can now be registered by plugins
Phase 3 Build EventBus and wire all modules to emit events Full observability without performance overhead
Phase 4 Extract ReportModule from Orchestrator's buildResult() using EventBus events Multiple output formats without touching engine
Phase 5 Build InspectModule wrapper around ContextGathererAgent Language-specific inspectors added
Phase 6 Extract VerifyModule from ReviewerAgent + security scan Verification becomes an explicit pipeline stage

Each phase is backwards-compatible — the old classes continue working until the new module system is ready. Phase 3 (EventBus) is deliberately placed before Phase 4 (ReportModule) so that extracted modules can immediately emit typed events from day one.