CLI Internals
Architecture
Layer 2: Orchestrator

Layer 2: Orchestrator

The Intelligent Coordinator

The Orchestrator is the central brain of the CLI. It coordinates the entire execution, resolving dependencies, managing VFS lifecycle, and ensuring blueprints execute in the correct order.


Purpose & Responsibilities

The Orchestrator Layer sits between the CLI command interface and the blueprint execution engine.

Core Responsibilities:

  1. Dependency Resolution - Order modules using topological sort
  2. Blueprint Loading - Load blueprints from marketplace
  3. Configuration Merging - Merge defaults with user parameters
  4. VFS Lifecycle Management - Create and manage VFS per blueprint
  5. Execution Coordination - Call preprocessor β†’ executor for each blueprint
  6. Error Aggregation - Collect and report errors across all blueprints

Implementation

File Location

src/agents/orchestrator-agent.ts

Class Structure

export class OrchestratorAgent {
  private projectManager: ProjectManager;
  private blueprintLoader: BlueprintLoader;
  private blueprintPreprocessor: BlueprintPreprocessor;
  private blueprintExecutor: BlueprintExecutor;
  private dependencyResolver: DependencyResolver;
  
  constructor(projectManager: ProjectManager) {
    this.projectManager = projectManager;
    this.blueprintLoader = new BlueprintLoader();
    this.blueprintPreprocessor = new BlueprintPreprocessor();
    this.blueprintExecutor = new BlueprintExecutor(
      projectManager.getProjectRoot()
    );
    this.dependencyResolver = new DependencyResolver();
  }
  
  async executeRecipe(genome: Genome): Promise<ExecutionResult> {
    // Main orchestration logic
  }
}

Execution Flow

Step 1: Dependency Resolution

Implementation:

// Build dependency graph from modules
const dependencyGraph = new DependencyGraph();
 
for (const module of genome.modules) {
  // Load blueprint to get prerequisites
  const blueprint = await this.blueprintLoader.load(module.id);
  
  // Add to graph
  dependencyGraph.addNode(module.id);
  
  // Add edges for prerequisites
  blueprint.requires?.forEach(prereq => {
    dependencyGraph.addEdge(prereq, module.id);
  });
}
 
// Topological sort
const executionOrder = dependencyGraph.topologicalSort();
 
// Result: Adapters β†’ Connectors β†’ Features

Critical design: Topological sort automatically ensures adapters run before connectors.

Example order:

1. framework/nextjs       (no prerequisites)
2. database/drizzle       (no prerequisites)
3. ui/shadcn              (requires: framework/nextjs)
4. connector/drizzle-nextjs  (requires: framework/nextjs, database/drizzle)

Step 2: Module Classification

The Orchestrator classifies modules by type:

function classifyModule(moduleId: string): ModuleType {
  if (moduleId.startsWith('framework/')) return 'framework';
  if (moduleId.startsWith('adapter/')) return 'adapter';
  if (moduleId.startsWith('connector/')) return 'connector';
  if (moduleId.startsWith('feature/')) return 'feature';
  
  // Fallback: infer from category
  const category = extractCategoryFromId(moduleId);
  return category;
}

Why classify?: Different module types may have different execution strategies (future enhancement).


Step 3: Configuration Merging

For each module, merge blueprint defaults with user parameters:

function mergeConfiguration(
  module: GenomeModule,
  blueprint: Blueprint
): MergedConfiguration {
  return {
    module: {
      id: module.id,
      version: module.version || 'latest',
      defaults: blueprint.defaults || {},
      parameters: deepmerge(
        blueprint.defaults || {},
        module.parameters || {}
      )
    },
    project: this.projectManager.getContext(),
    activeFeatures: Object.keys(module.features || {})
      .filter(key => module.features[key] === true)
  };
}

Example:

// Blueprint defaults:
{ 
  typescript: true,
  tailwind: false,
  appRouter: true
}
 
// User parameters:
{
  tailwind: true  // Override
}
 
// Merged result:
{
  typescript: true,   // Default
  tailwind: true,     // User override
  appRouter: true     // Default
}

Step 4: Blueprint Execution Loop

The core orchestration loop:

async executeRecipe(genome: Genome, verbose: boolean, logger: EnhancedLogger) {
  // Resolve execution order
  const executionOrder = await this.resolveExecutionOrder(genome.modules);
  
  const results: BlueprintResult[] = [];
  
  // Execute each blueprint in order
  for (const module of executionOrder) {
    logger.startPhase(`Executing ${module.id}`);
    
    try {
      // 1. Load blueprint from marketplace
      const blueprint = await this.blueprintLoader.load(module.id);
      
      // 2. Merge configuration
      const mergedConfig = this.mergeConfiguration(module, blueprint);
      
      // 3. Create dedicated VFS for this blueprint
      const vfs = new VirtualFileSystem(
        `blueprint-${blueprint.id}`,
        this.projectManager.getProjectRoot()
      );
      
      // 4. Preprocess blueprint (dynamic β†’ static)
      const preprocessResult = await this.blueprintPreprocessor.processBlueprint(
        blueprint,
        mergedConfig
      );
      
      if (!preprocessResult.success) {
        throw new Error(`Preprocessing failed: ${preprocessResult.error}`);
      }
      
      // 5. Execute blueprint actions
      const execResult = await this.blueprintExecutor.executeActions(
        preprocessResult.actions,
        mergedConfig,
        vfs
      );
      
      if (!execResult.success) {
        throw new Error(`Execution failed: ${execResult.errors.join(', ')}`);
      }
      
      // 6. Flush VFS to disk (atomic commit)
      await vfs.flushToDisk();
      
      logger.completePhase(`βœ… ${module.id} completed`);
      results.push({ success: true, module: module.id });
      
    } catch (error) {
      // Fail-fast: Stop execution on first error
      logger.error(`❌ ${module.id} failed: ${error.message}`);
      
      return {
        success: false,
        errors: [`Module ${module.id}: ${error.message}`],
        completedModules: results
      };
    }
  }
  
  return {
    success: true,
    completedModules: results
  };
}

Key Design Decisions

1. Per-Blueprint VFS

Decision: Create new VFS for each blueprint

// NOT this (shared VFS):
const vfs = new VirtualFileSystem(...);
for (const blueprint of blueprints) {
  await execute(blueprint, vfs);  // Shared VFS
}
 
// But THIS (per-blueprint VFS):
for (const blueprint of blueprints) {
  const vfs = new VirtualFileSystem(...);  // New VFS
  await execute(blueprint, vfs);
  await vfs.flushToDisk();  // Commit per blueprint
}

Rationale:

  • βœ… Isolation: Blueprint A can't corrupt Blueprint B's changes
  • βœ… Atomicity: Each blueprint is an atomic unit
  • βœ… Pre-population: Each VFS pre-loaded with only files that blueprint needs
  • βœ… Rollback: Blueprint fails β†’ VFS discarded, no disk corruption

Trade-off: Memory overhead (multiple VFS instances), but safety worth it.

2. Fail-Fast Execution

Decision: Stop on first blueprint failure

if (!execResult.success) {
  // Immediately return, don't continue
  return { success: false, errors: [...] };
}

Rationale:

  • βœ… Prevents cascading failures: Later blueprints might depend on earlier ones
  • βœ… Clear error reporting: One error at a time
  • βœ… No partial projects: Project is complete or not generated

Trade-off: Can't do "best effort" generation, but better UX.

3. Topological Sort for Execution Order

Decision: Use graph-based dependency resolution

const graph = new DependencyGraph();
 
// Add nodes and edges
modules.forEach(m => {
  graph.addNode(m.id);
  m.requires.forEach(req => graph.addEdge(req, m.id));
});
 
// Sort
const order = graph.topologicalSort();

Ensures:

  • Framework installed before UI library
  • Database installed before database connector
  • Auth installed before auth connector

Rationale: Explicit > implicit. Don't rely on module order in genome.


Error Handling

Error Types Handled

try {
  await executeBlueprint(...);
} catch (error) {
  if (error instanceof PrerequisiteError) {
    // Missing dependency
    logger.error(`Missing prerequisite: ${error.prerequisite}`);
    suggestInstallation(error.prerequisite);
  } else if (error instanceof BlueprintError) {
    // Blueprint execution failed
    logger.error(`Blueprint failed: ${error.message}`);
    showBlueprintContext(error);
  } else {
    // Unknown error
    logger.error(`Unexpected error: ${error.message}`);
  }
}

Error Recovery

Current: No recovery, fail-fast

Future consideration: Checkpoint system

// Proposed:
const checkpoint = createCheckpoint();
for (const blueprint of blueprints) {
  try {
    await execute(blueprint);
    checkpoint.save(blueprint.id);
  } catch (error) {
    checkpoint.rollbackTo(lastSuccessful);
    throw error;
  }
}

Integration with Other Systems

With CLI Command Layer

// src/commands/new.ts calls orchestrator
const orchestrator = new OrchestratorAgent(projectManager);
const result = await orchestrator.executeRecipe(genome, verbose, logger);

With Blueprint Preprocessor

// Orchestrator calls preprocessor for each blueprint
const preprocessResult = await this.blueprintPreprocessor.processBlueprint(
  blueprintModule,
  mergedConfig
);

With Blueprint Executor

// Orchestrator calls executor with preprocessed actions
const execResult = await this.blueprintExecutor.executeActions(
  preprocessResult.actions,
  mergedConfig,
  vfs
);

Logging & Progress Tracking

Phase-Based Logging

logger.startPhase('Validating Genome');
// ... validation
logger.completePhase('βœ… Validated');
 
logger.startPhase('Resolving Dependencies');
// ... resolution
logger.completePhase(`βœ… ${modules.length} modules ordered`);
 
logger.startPhase('Executing Blueprints');
for (const module of modules) {
  logger.logProgress(`[${i}/${total}] ${module.id}`);
  // ... execution
}
logger.completePhase('βœ… All blueprints executed');

Output:

πŸ” Validating Genome...
   βœ… Completed in 45ms

πŸ“‹ Resolving Dependencies...
   βœ… 8 modules ordered

πŸ—οΈ Executing Blueprints...
   [1/8] framework/nextjs
   βœ… framework/nextjs
   [2/8] database/drizzle
   βœ… database/drizzle
   ...

Future Enhancements

1. Parallel Execution

Current: Sequential Proposed: Parallel execution for independent blueprints

// Group blueprints by dependency level
const levels = dependencyGraph.getLevels();
 
// Execute each level in parallel
for (const level of levels) {
  await Promise.all(
    level.map(blueprint => executeBlueprint(blueprint))
  );
}

Benefit: 2-3x faster for large genomes

2. Incremental Execution

Current: All-or-nothing Proposed: Resume from checkpoint

const completed = loadCheckpoint();
const remaining = modules.filter(m => !completed.includes(m.id));
 
for (const module of remaining) {
  await execute(module);
  saveCheckpoint(module.id);
}

Benefit: User can resume after fixing errors

3. Dry-Run Mode Enhancement

Current: Basic preview Proposed: Full simulation with VFS

// Execute in dry-run mode
const result = await executeWithDryRun(genome);
 
// Show what would be created
result.files.forEach(file => console.log(`Would create: ${file}`));
result.modifications.forEach(mod => console.log(`Would modify: ${mod}`));

Next Steps

The Orchestrator is the conductor. Understanding it is key to understanding the CLI.