2026 03 23 Generation Pipeline Optimization
Documentation for Plans
Generation Pipeline Optimization Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Cut time-to-first-preview from ~15 min to ~3 min, reduce cost by ~11%, and make generation feel interactive via progressive module rendering.
Architecture: Four changes: (1) switch default output to CODE so SSE events carry files and activate existing S246 progressive preview, (2) defer visual JSON to background Haiku calls, (3) enable Anthropic prompt caching for the repeated EXECUTE system prompt, (4) handle partial module sets in preview app shell synthesis.
Tech Stack: NestJS (backend-vitto-api), Angular 20 (vitto-studio, platform-preview), Anthropic Claude API (prompt caching)
Spec: docs/superpowers/specs/2026-03-23-generation-pipeline-optimization-design.md
Task 1: Switch Output Mode to CODE
The keystone change. Three backend endpoints and one frontend caller default to VISUAL or BOTH. Switching to CODE activates the existing progressive preview path (S246) and removes visual from the critical path.
Files:
Modify:
APIs/backend-vitto-api/src/app/module-generation/module-generation.controller.ts:102,191,230Modify:
libs/libs-structural/vitto-studio/src/lib/services/studio-vitto-bridge.service.ts:69Step 1: Change
POST generatedefault (line 102)
// Before:
outputMode: (dto.outputMode as ModuleGenOutputMode) || ModuleGenOutputMode.VISUAL,
// After:
outputMode: (dto.outputMode as ModuleGenOutputMode) || ModuleGenOutputMode.CODE,
- Step 2: Change
GET startdefault (line 191)
// Before:
outputMode: (outputMode as ModuleGenOutputMode) || ModuleGenOutputMode.VISUAL,
// After:
outputMode: (outputMode as ModuleGenOutputMode) || ModuleGenOutputMode.CODE,
- Step 3: Change
POST generate-singledefault (line 230)
// Before:
outputMode: (dto.outputMode as ModuleGenOutputMode) || ModuleGenOutputMode.VISUAL,
// After:
outputMode: (dto.outputMode as ModuleGenOutputMode) || ModuleGenOutputMode.CODE,
- Step 4: Change frontend caller default
In studio-vitto-bridge.service.ts:69:
// Before:
generateModule(description: string, spaceId: string, universeId: string, outputMode: 'visual' | 'code' | 'both' = 'both'): Observable<VittoSSEEvent> {
// After:
generateModule(description: string, spaceId: string, universeId: string, outputMode: 'visual' | 'code' | 'both' = 'code'): Observable<VittoSSEEvent> {
- Step 5: Verify no other callers override to 'visual'
Run: grep -r "outputMode.*visual\|outputMode.*VISUAL" libs/ apps/ --include="*.ts" -l
Check workflow-state.service.ts:82 — this is a type definition, not a caller. If any caller hardcodes 'visual', change it to 'code'.
- Step 6: Verify compilation
Run: npx tsc --noEmit --project APIs/backend-vitto-api/tsconfig.json 2>&1 | head -5
Run: npx tsc --noEmit --project libs/libs-structural/vitto-studio/tsconfig.lib.json 2>&1 | head -5
Expected: No new errors.
Task 2: Deferred Visual Background Job
After all CODE modules complete, fire background Haiku calls to generate visual JSON. Non-blocking — uses Promise.allSettled with per-module error logging.
Files:
Modify:
APIs/backend-vitto-api/src/app/module-generation/phases/execute.phase.tsStep 1: Add deferred visual generation method
Add at the end of the ExecutePhaseService class, before the closing brace:
/**
* S257: Deferred visual generation — runs in background after CODE completes.
* Uses Haiku for cost efficiency. Non-blocking, fire-and-forget with logging.
*/
private async deferredVisualGeneration(
executionPlan: ModuleGenPlanItem[],
moduleSpecs: ModuleSpec[],
sseSubject: Subject<ModuleGenSSEEvent>,
analyzeResult: ModuleGenAnalyzeResult,
): Promise<void> {
const normalizeForMatch = (s: string) => s.toLowerCase().replace(/[\s\-_]+/g, '');
const results = await Promise.allSettled(
executionPlan.map(async (plan) => {
const moduleSpec = moduleSpecs.find(
s => normalizeForMatch(s.name) === normalizeForMatch(plan.moduleName),
) || { name: plan.moduleName, description: plan.description || '', entities: [], features: [] };
const moduleEntities = analyzeResult?.coreEntities?.filter(
(e: any) => normalizeForMatch(e.module || '') === normalizeForMatch(plan.moduleName),
) || analyzeResult?.coreEntities || [];
try {
const result = await this.generateVisual(plan, moduleSpec, moduleEntities, sseSubject, '', '', '');
this.logger.log(`Deferred visual for ${plan.moduleName}: success`);
return result;
} catch (err) {
this.logger.warn(`Deferred visual for ${plan.moduleName} failed (non-fatal): ${(err as Error).message}`);
return null;
}
}),
);
const succeeded = results.filter(r => r.status === 'fulfilled' && r.value).length;
this.logger.log(`Deferred visual generation: ${succeeded}/${executionPlan.length} modules completed`);
}
- Step 2: Wire deferred visual into the execute method
In execute(), find the return statement that builds ModuleGenExecuteResult (around line 260). Before the return, add:
// S257: Fire deferred visual generation in background (non-blocking)
if (outputMode === ModuleGenOutputMode.CODE) {
this.deferredVisualGeneration(executionPlan, moduleSpecs, sseSubject, analyzeResult)
.catch(err => this.logger.error(`Deferred visual background job failed: ${(err as Error).message}`));
}
Note: analyzeResult and moduleSpecs need to be passed into execute() or captured from the method's existing parameters. Check the method signature — if these aren't available, extract them from the plan parameter or the decompose result that the executor passes in.
- Step 3: Make generateVisual use Haiku when called for deferred work
In the generateVisual() method (around line 292), the model is currently set via this.providerFactory.getForPhase('EXECUTE'). For deferred visual, we want Haiku. Add an optional parameter:
// Add optional parameter to generateVisual signature:
private async generateVisual(
plan: ModuleGenPlanItem,
moduleSpec: ModuleSpec,
moduleEntities: any[],
sseSubject: Subject<ModuleGenSSEEvent>,
previousModules: string,
playbookStrategy: string,
masteryContext: string,
overrideModel?: string, // S257: Allow Haiku override for deferred calls
): Promise<{ module: GeneratedModule; tokens: { input: number; output: number } }> {
Then in the model selection inside generateVisual():
// Before:
const { provider, model: phaseModel } = this.providerFactory.getForPhase('EXECUTE');
// After:
const { provider, model: defaultModel } = this.providerFactory.getForPhase('EXECUTE');
const phaseModel = overrideModel || defaultModel;
And in deferredVisualGeneration, pass the Haiku model:
const result = await this.generateVisual(plan, moduleSpec, moduleEntities, sseSubject, '', '', '', 'claude-haiku-4-5-20251001');
- Step 4: Verify compilation
Run: npx tsc --noEmit --project APIs/backend-vitto-api/tsconfig.json 2>&1 | head -10
Expected: No new errors.
Task 3: Prompt Caching for EXECUTE System Prompts
The implement.prompt.md system prompt (~10K tokens) is identical for every module. Enable Anthropic's prompt caching to get 90% discount on modules 2-5.
Files:
Modify:
APIs/backend-vitto-api/src/app/llm/llm.types.ts:77-87Modify:
APIs/backend-vitto-api/src/app/llm/providers/claude.provider.ts:45-54Modify:
APIs/backend-vitto-api/src/app/module-generation/phases/execute.phase.ts(EXECUTE calls)Step 1: Add
cacheSystemPromptto SendOptions
In llm.types.ts, add to the SendOptions interface:
export interface SendOptions {
/** Override the provider's default model for this call */
model?: string;
maxTokens?: number;
temperature?: number;
topP?: number;
stopSequences?: string[];
systemPrompt?: string;
/** Session 197: Per-request timeout override (ms). For long code generation calls. */
timeout?: number;
/** S257: Enable Anthropic prompt caching on the system prompt. Requires ≥1024 tokens. */
cacheSystemPrompt?: boolean;
}
- Step 2: Refactor claude.provider.ts to support content block system prompts
In claude.provider.ts, change the system parameter construction (around line 45-54):
const params: Anthropic.MessageCreateParamsNonStreaming = {
model: options?.model || this.config.defaultModel,
max_tokens: options?.maxTokens || 4096,
messages: anthropicMessages,
stream: false,
// S257: Support content-block system prompts for prompt caching
...(options?.systemPrompt && {
system: options.cacheSystemPrompt
? [{ type: 'text' as const, text: options.systemPrompt, cache_control: { type: 'ephemeral' as const } }]
: options.systemPrompt,
}),
...(options?.temperature !== undefined && { temperature: options.temperature }),
...(anthropicTools && { tools: anthropicTools }),
...(options?.stopSequences && { stop_sequences: options.stopSequences }),
};
- Step 3: Enable caching in EXECUTE code generation calls
In execute.phase.ts, in the generateCode() method (around line 432-441), add cacheSystemPrompt:
const sendOptions: SendOptions = {
systemPrompt,
model: phaseModel,
maxTokens: 32768,
temperature: 0.1,
timeout: 600_000,
cacheSystemPrompt: true, // S257: Cache the implement.prompt.md system prompt
};
- Step 4: Verify compilation
Run: npx tsc --noEmit --project APIs/backend-vitto-api/tsconfig.json 2>&1 | head -10
Expected: No new errors.
Task 4: Progressive Preview — Partial App Shell Synthesis
When the first module's files arrive via SSE, the preview needs to compile and render them. The synthesizeAppShell() in bundler-filesystem.service.ts currently builds routes from ALL detected modules. It needs to handle partial sets during progressive generation.
Files:
Modify:
libs/libs-structural/platform-preview/src/lib/services/bundler-filesystem.service.tsStep 1: Identify the synthesizeAppShell method
Read bundler-filesystem.service.ts around the synthesizeAppShell() method and the app.routes.ts synthesis section. Understand how module routes are currently built.
- Step 2: Update app.routes.ts synthesis for partial modules
In the section that generates app.routes.ts (around line 288-307 based on exploration), the routes are built from all detected modules. Add a fallback catch-all route for unresolved lazy imports:
// After the module route entries, add:
const routeEntries = detectedModules.map(m => /* existing route generation */);
// S257: Catch-all route for modules still generating
routeEntries.push(` { path: '**', loadComponent: () => import('./generating.component').then(m => m.GeneratingComponent) }`);
- Step 3: Create a minimal GeneratingComponent placeholder
Add a placeholder component to the synthesized files that shows "Generating..." for routes that don't exist yet:
// In synthesizeAppShell(), add to the files map:
files['/generating.component.ts'] = `
import { Component } from '@angular/core';
@Component({
selector: 'app-generating',
standalone: true,
template: \`
<div style="display:flex;align-items:center;justify-content:center;height:60vh;color:var(--ux-text-muted,#71717a);">
<div style="text-align:center;">
<div style="font-size:2rem;margin-bottom:8px;">⏳</div>
<p style="font-size:1rem;">Generating module...</p>
</div>
</div>
\`,
})
export class GeneratingComponent {}
`;
- Step 4: Ensure recompilation triggers on VFS file writes
Check how AiFileWriterService.receiveGeneratedFiles() connects to the preview compilation. The handleModuleGenerated() in studio-generation-listener.service.ts:293 calls this.fileWriter.receiveGeneratedFiles(generatedFiles, true). Verify the signal chain:
receiveGeneratedFiles()writes files to VFS- VFS state change should trigger a recompile in
BundlerService
If there's no automatic trigger, add one: after writing files in handleModuleGenerated, explicitly call the preview's compile method. This may require injecting BundlerService or emitting an event that the preview panel listens for.
Read AiFileWriterService to understand the file writing flow and identify where to hook the recompile trigger.
- Step 5: Verify compilation
Run: npx tsc --noEmit --project libs/libs-structural/platform-preview/tsconfig.lib.json 2>&1 | head -5
Expected: No new errors.
Task 5: Final Verification & Log Decisions
- Step 1: Compile all modified libraries
npx tsc --noEmit --project APIs/backend-vitto-api/tsconfig.json
npx tsc --noEmit --project libs/libs-structural/platform-preview/tsconfig.lib.json
npx tsc --noEmit --project libs/libs-structural/vitto-studio/tsconfig.lib.json
- Step 2: Log MCP decisions
Log decisions for each change via log_decision():
Change A: CODE-first output mode (architecture)
Change B: Deferred visual to Haiku background (implementation)
Change C: Prompt caching for EXECUTE (implementation)
Change D: Progressive preview partial shell (implementation)
Step 3: Verify with
intelligence({ action: "reflect" })
Reflect on the changes with taskSummary, outcome, and lessonsLearned.
NOT in This Plan (Deferred)
Pipeline overlap (PLAN → EXECUTE streaming) — HIGH risk, requires executor/checkpoint redesign. Separate session. See spec Change C for details.