2026 03 12 S209 Remaining Gaps

Documentation for Plans

S209 Remaining Gaps — Implementation Plan

For agentic workers: REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Close all remaining S209 plan items (2.4-2.6 Jarvis UX + 3.1-3.3 Execution Consolidation) and wire the results into the live system.

Architecture: Six tasks split into two groups. Group A (Jarvis UX: tasks 1-3) adds user-facing intelligence features on backend-vitto-api + frontend. Group B (Execution Consolidation: tasks 4-6) migrates the 8-phase AgentProcessService pipeline from backend-ai-api to backend-vitto-api, making Vitto the sole executor.

Tech Stack: NestJS (backend-vitto-api, backend-ai-api), Angular 20+ (standalone, signals, OnPush), MongoDB (vitto-mind, ai-agents), RxJS Observables (no Promises in frontend).

Spec: docs/superpowers/specs/2026-03-12-vitto-intelligence-v2-design.md Sections 6 + 7 S209 Plan: docs/english/Plans/S209-vitto-jarvis-level-agent-ux.plan.md items 2.4-2.6, 3.1-3.3

Branch: session-210.2 (or current active session branch) Commit prefix: [S210.3] or current sub-session tag Git protocol: Stage specific files — NEVER git add .


File Structure

Group A: Jarvis UX Features

File Action Task
APIs/backend-vitto-api/src/app/mind/mind.service.ts Modify T1: Add askMemory() method
APIs/backend-vitto-api/src/app/engine/mind.controller.ts Modify T1: Add GET /mind/memory/ask route
libs/libs-ai/vitto-core/src/lib/services/vitto-api.service.ts Modify T1: Add askMemory() frontend method
libs/libs-ai/vitto-hub/src/lib/components/memory-manager/memory-manager.component.ts Modify T1: Add search bar + result card
libs/libs-ai/vitto-hub/src/lib/components/memory-manager/memory-manager.component.html Modify T1: Search UI
libs/libs-ai/vitto-hub/src/lib/components/brain-interactive/vitto-brain-interactive.component.ts Modify T2: Subscribe to conscience SSE, pulse regions
libs/libs-ai/vitto-hub/src/lib/components/brain-interactive/vitto-brain-interactive.component.scss Modify T2: Add pulse animation CSS
libs/libs-ai/vitto-hub/src/lib/components/introspection/vitto-introspection.component.html Modify T3: Add routerLink drill-down
libs/libs-ai/vitto-hub/src/lib/components/introspection/vitto-introspection.component.ts Modify T3: Import RouterLink

Group B: Execution Consolidation

File Action Task
APIs/backend-ai-api/src/app/agent-process/services/agent-process.service.ts Modify T4: Add deprecation warnings
APIs/backend-ai-api/src/app/agent-process/agent-process.controller.ts Modify T4: Log deprecation on execute
APIs/backend-vitto-api/src/app/engine/agent-process-compat.controller.ts Create T4: Compatibility controller
APIs/backend-vitto-api/src/app/engine/engine.module.ts Modify T4: Register new controller
libs/libs-ai/ai-agents-training-center/src/lib/services/agent-executor/agent-executor.service.ts Modify T4: Switch AI_API_URL → VITTO_API_URL
APIs/backend-vitto-api/src/app/engine/match-phase.service.ts Create T5: Extract MATCH logic
APIs/backend-vitto-api/src/app/engine/validate-phase.service.ts Create T5: Extract VALIDATE logic
APIs/backend-vitto-api/src/app/engine/sub-agent.service.ts Modify T5: Wire optional phases
APIs/backend-vitto-api/src/app/mind/schemas/sub-agent-definition.schema.ts Modify T5: Add enableValidation field
APIs/backend-vitto-api/src/app/connectivity/backend-ai-client.service.ts Modify T6: Add logDecision() call in execution path

Chunk 1: Jarvis UX Features

Task 1: "Ask Vitto" Memory Query (S209 item 2.4)

Files:

In mind.service.ts, add a method that:

  1. Calls this.memoryService.search(query, userId, 5) (or equivalent) to get top memory matches
  2. If matches found, uses ProviderFactoryService.getProvider('anthropic').sendMessage() with Haiku model to synthesize a 2-3 sentence answer from the matched memories
  3. Returns { answer: string; sources: Array<{ id: string; summary: string; type: string }> }
  4. If no matches or LLM unavailable: returns { answer: 'I don\'t have memories about that yet.', sources: [] }
async askMemory(query: string, userId: string): Promise<{ answer: string; sources: Array<{ id: string; summary: string; type: string }> }> {
  const empty = { answer: "I don't have memories about that yet.", sources: [] };
  if (!query || query.length < 3) return empty;

  // Search memories using existing search infrastructure
  const memories = await this.memoryService.search(query, userId, 5).catch(() => []);
  if (!memories?.length) return empty;

  const sources = memories.map((m: any) => ({
    id: m._id?.toString() || m.id,
    summary: m.summary || m.content?.substring(0, 100) || '',
    type: m.type || 'general',
  }));

  // Synthesize answer using cheap model
  try {
    const provider = this.providerFactory?.getDefault();
    if (!provider) return { answer: sources.map(s => s.summary).join('. '), sources };

    const memoryContext = memories.map((m: any, i: number) =>
      `[${i + 1}] ${m.summary || m.content?.substring(0, 200) || 'No content'}`,
    ).join('\n');

    const response = await provider.sendMessage(
      [{ role: 'user', content: `Based on these memories, answer the question concisely (2-3 sentences):\n\nMemories:\n${memoryContext}\n\nQuestion: ${query}` }],
      [],
      { model: 'claude-haiku-4-5-20251001', maxTokens: 256, temperature: 0.3 },
    );

    return { answer: response.content || empty.answer, sources };
  } catch {
    return { answer: sources.map(s => s.summary).join('. '), sources };
  }
}

Note: Check MindService constructor — if ProviderFactoryService is not injected, add it as @Optional().

In mind.controller.ts, add:

@Public()
@Get('memory/ask')
async askMemory(
  @Query('query') query: string,
  @Query('userId') userId: string,
) {
  if (!query) throw new HttpException('query is required', HttpStatus.BAD_REQUEST);
  if (!userId) throw new HttpException('userId is required', HttpStatus.BAD_REQUEST);
  const result = await this.mindService.askMemory(query, userId);
  return { success: true, ...result };
}

Note: MindController currently only injects SubAgentService. Add MindService to constructor. Check that MindService is exported from MindModule and available to EngineModule (which hosts MindController).

In vitto-api.service.ts (canonical: libs/libs-ai/vitto-core/src/lib/services/vitto-api.service.ts), add near the memory methods:

/** S209: Ask Vitto what he remembers — synthesized answer from memory search */
askMemory(query: string, userId: string): Observable<ApiResponse> {
  return this.http.get<ApiResponse>(this.url(`../mind/memory/ask`), {
    params: { query, userId },
  }).pipe(
    catchError(() => of({ success: false, answer: 'Could not reach Vitto.', sources: [] })),
  );
}

Note: The url() helper prepends ${vittoUrl}/engine/. Since MindController is at /mind/, use ${vittoUrl}/mind/memory/ask directly. Check how other mind routes are called — may need a separate mindUrl() helper or use the raw vittoUrl.

In memory-manager.component.ts, add:

// Ask Vitto state
readonly askQuery = signal('');
readonly askAnswer = signal<{ answer: string; sources: Array<{ id: string; summary: string; type: string }> } | null>(null);
readonly askLoading = signal(false);

askVitto(): void {
  const query = this.askQuery();
  if (!query || query.length < 3) return;
  this.askLoading.set(true);
  this.askAnswer.set(null);

  this.api.askMemory(query, this.userId()).pipe(
    takeUntilDestroyed(this.destroyRef),
  ).subscribe({
    next: (res: any) => {
      this.askLoading.set(false);
      this.askAnswer.set({ answer: res.answer || '', sources: res.sources || [] });
    },
    error: () => {
      this.askLoading.set(false);
      this.askAnswer.set({ answer: 'Could not reach Vitto.', sources: [] });
    },
  });
}

In memory-manager.component.html, add above the memory list:

<!-- Ask Vitto -->
<div class="ask-vitto-bar">
  <input type="text" class="search-input" placeholder="Ask Vitto what he remembers..."
    [ngModel]="askQuery()" (ngModelChange)="askQuery.set($event)"
    (keyup.enter)="askVitto()" />
  <button class="btn btn-primary btn-sm" (click)="askVitto()" [disabled]="askLoading() || askQuery().length < 3">
    {{ askLoading() ? 'Thinking...' : 'Ask' }}
  </button>
</div>

@if (askAnswer(); as a) {
  <div class="ask-result-card">
    <p class="ask-answer">{{ a.answer }}</p>
    @if (a.sources.length) {
      <div class="ask-sources">
        @for (src of a.sources; track src.id) {
          <span class="ask-source-chip">{{ src.summary.substring(0, 40) }}</span>
        }
      </div>
    }
  </div>
}

Add matching SCSS styles (.ask-vitto-bar, .ask-result-card, .ask-answer, .ask-sources, .ask-source-chip).

Run: Manual test via UI — navigate to Vitto > Memory Manager, type "authentication", hit Ask. Expected: Synthesized answer appears with source chips.

git add APIs/backend-vitto-api/src/app/mind/mind.service.ts \
  APIs/backend-vitto-api/src/app/engine/mind.controller.ts \
  libs/libs-ai/vitto-core/src/lib/services/vitto-api.service.ts \
  libs/libs-ai/vitto-hub/src/lib/components/memory-manager/
git commit -m "[S210.3] feat: Ask Vitto memory query — synthesized answers from memory search"

Task 2: Brain Real-Time Activity Overlay (S209 item 2.5)

Files:

In vitto-brain-interactive.component.ts:

  1. Inject VittoConscienceService
  2. Add signal: readonly activeRegions = signal<Set<string>>(new Set())
  3. In ngOnInit or constructor, start watching entries:
private readonly conscience = inject(VittoConscienceService);
readonly activeRegions = signal<Set<string>>(new Set());

private readonly CATEGORY_TO_REGION: Record<string, string> = {
  memory: 'memory',
  skill: 'skills',
  context: 'context',
  trust: 'trust',
  dossier: 'identity',
  personality: 'soul',
};

private lastProcessedCount = 0;

ngOnInit(): void {
  // ... existing init ...

  // Watch for new conscience entries and pulse regions
  setInterval(() => {
    const entries = this.conscience.entries();
    if (entries.length === this.lastProcessedCount) return;

    const newEntries = entries.slice(this.lastProcessedCount);
    this.lastProcessedCount = entries.length;

    for (const entry of newEntries) {
      const region = this.CATEGORY_TO_REGION[entry.category];
      if (region) this.pulseRegion(region);
    }
  }, 500);
}

private pulseRegion(region: string): void {
  this.activeRegions.update(set => {
    const next = new Set(set);
    next.add(region);
    return next;
  });

  // Auto-remove after 2s
  setTimeout(() => {
    this.activeRegions.update(set => {
      const next = new Set(set);
      next.delete(region);
      return next;
    });
  }, 2000);
}

isRegionActive(region: string): boolean {
  return this.activeRegions().has(region);
}

In the .scss file, add:

.region--active {
  animation: region-pulse 2s ease-in-out;
}

@keyframes region-pulse {
  0% { filter: drop-shadow(0 0 0px transparent); opacity: 1; }
  30% { filter: drop-shadow(0 0 12px var(--ux-primary, #6366f1)); opacity: 1; }
  100% { filter: drop-shadow(0 0 0px transparent); opacity: 1; }
}

In the .html file, for each SVG region/card that represents a brain area, add:

[class.region--active]="isRegionActive('memory')"

(Repeat for each region: memory, skills, context, trust, identity, soul)

Test: Open brain visualization, send a chat message from another tab. Brain regions should pulse as conscience events fire.

git add libs/libs-ai/vitto-hub/src/lib/components/brain-interactive/
git commit -m "[S210.3] feat: brain real-time activity overlay — pulse regions on conscience events"

Task 3: Introspection Drill-Down (S209 item 2.6)

Files:

In vitto-introspection.component.ts, add RouterLink to imports array:

import { RouterLink } from '@angular/router';

// In @Component imports:
imports: [...existingImports, RouterLink],

In vitto-introspection.component.html, find where entries render their narrative text. Add conditional links:

<!-- After the narrative text span -->
@if (entry.details?.taskId) {
  <a class="drill-link" [routerLink]="['/vitto/queue']" [queryParams]="{ taskId: entry.details.taskId }">
    View Task
  </a>
}
@if (entry.userId && entry.userId !== 'system') {
  <a class="drill-link" [routerLink]="['/vitto/dossier', entry.userId]">
    View User
  </a>
}

Add .drill-link style in the SCSS:

.drill-link {
  font-size: var(--ux-text-2xs);
  color: var(--ux-primary);
  text-decoration: none;
  margin-left: var(--ux-spacing-sm);
  &:hover { text-decoration: underline; }
}

Test: Open Introspection view, trigger a task. Entries with taskId should show "View Task" link. Click navigates to queue with taskId param.

git add libs/libs-ai/vitto-hub/src/lib/components/introspection/
git commit -m "[S210.3] feat: introspection drill-down — link entries to tasks and users"

Chunk 2: Execution Consolidation

Task 4: Deprecate AgentProcessService + Create Compat Controller (S209 item 3.1)

Files:

Find the execute endpoints in the agent-process controller on backend-ai-api. Add Logger.warn to each:

this.logger.warn('DEPRECATED: POST /agent-process/execute — use VITTO_API_URL/engine/agent-process/execute instead');

Do NOT remove the endpoints — they stay alive for backward compat.

Create APIs/backend-vitto-api/src/app/engine/agent-process-compat.controller.ts:

import { Controller, Post, Body, Get, Param, Query, HttpException, HttpStatus } from '@nestjs/common';
import { Public } from '@libs-api/auth-core';
import { SubAgentService } from './sub-agent.service';

@Controller('engine/agent-process')
export class AgentProcessCompatController {
  constructor(private readonly subAgent: SubAgentService) {}

  @Public()
  @Post('execute')
  async execute(@Body() body: {
    agentId: string;
    prompt: string;
    context?: Record<string, unknown>;
    options?: { maxIterations?: number; maxTokens?: number };
  }) {
    if (!body.agentId || !body.prompt) {
      throw new HttpException('agentId and prompt are required', HttpStatus.BAD_REQUEST);
    }

    // Map agentId to a sub-agent definition ID
    // AgentProcessService used agentId like 'module-generator', 'widget-creator'
    // SubAgentService uses MongoDB _id. Find by name or seeded agent ID.
    const definitions = await this.subAgent.listDefinitions({ search: body.agentId });
    const def = definitions[0];
    if (!def) {
      throw new HttpException(`No sub-agent definition found for '${body.agentId}'`, HttpStatus.NOT_FOUND);
    }

    const result = await this.subAgent.executeTask(def._id.toString(), body.prompt, {
      maxIterations: body.options?.maxIterations,
      maxTokens: body.options?.maxTokens,
    });

    // Map to AgentProcessService response shape for backward compat
    return {
      success: result.success,
      processId: `compat-${Date.now()}`,
      result: {
        output: result.result,
        phases: [{ name: 'EXECUTE', status: result.success ? 'completed' : 'failed' }],
      },
      metrics: {
        iterations: result.iterations,
        inputTokens: result.inputTokens,
        outputTokens: result.outputTokens,
        durationMs: result.durationMs,
      },
    };
  }

  @Public()
  @Post('execute-sync')
  async executeSync(@Body() body: {
    agentId: string;
    prompt: string;
    context?: Record<string, unknown>;
  }) {
    // Same as execute — SubAgentService.executeTask is already synchronous
    return this.execute(body as any);
  }
}

In engine.module.ts, add AgentProcessCompatController to controllers array:

import { AgentProcessCompatController } from './agent-process-compat.controller';

// In @Module({ controllers: [...] })
controllers: [VittoController, MindController, AgentProcessCompatController],

In libs/libs-ai/ai-agents-training-center/src/lib/services/agent-executor/agent-executor.service.ts:

Change the URL token injection from AI_API_URL to VITTO_API_URL:

// Before:
private readonly aiApiUrl = inject(AI_API_URL);
// After:
private readonly aiApiUrl = inject(VITTO_API_URL);

Update the import:

// Before:
import { AI_API_URL } from '@libs-foundational/api-config';
// After:
import { VITTO_API_URL } from '@libs-foundational/api-config';

The base URL pattern ${this.aiApiUrl}/agent-process becomes ${this.aiApiUrl}/engine/agent-process. Verify the service's URL construction and adjust if needed.

Test: Open Training Center, try executing an agent. It should now hit backend-vitto-api instead of backend-ai-api. Verify: Old endpoint on ai-api still works but logs deprecation warning.

git add APIs/backend-ai-api/src/app/agent-process/ \
  APIs/backend-vitto-api/src/app/engine/agent-process-compat.controller.ts \
  APIs/backend-vitto-api/src/app/engine/engine.module.ts \
  libs/libs-ai/ai-agents-training-center/src/lib/services/agent-executor/
git commit -m "[S210.3] feat: deprecate AgentProcessService execute — route to Vitto compat controller"

Task 5: Move MATCH/VALIDATE Logic to Vitto (S209 item 3.2)

Files:

Extract MATCH logic from agent-process.service.ts (lines 438-476). The MATCH phase:

// APIs/backend-vitto-api/src/app/engine/match-phase.service.ts
import { Injectable, Logger, Optional } from '@nestjs/common';
import { BackendAiClientService } from '../connectivity/backend-ai-client.service';

export interface MatchResult {
  patternsMatched: Array<{ name: string; description: string; successRate: number }>;
  decisionsMatched: Array<{ question: string; chosen: string; confidence: number }>;
  bestMatch: string | null;
  matchConfidence: number;
}

@Injectable()
export class MatchPhaseService {
  private readonly logger = new Logger('MatchPhase');

  constructor(@Optional() private readonly aiClient?: BackendAiClientService) {}

  async match(prompt: string, taskType?: string): Promise<MatchResult> {
    const empty: MatchResult = { patternsMatched: [], decisionsMatched: [], bestMatch: null, matchConfidence: 0 };
    if (!this.aiClient) return empty;

    try {
      const [intelligence, similar] = await Promise.all([
        this.aiClient.getLearningIntelligence(prompt).catch(() => null),
        this.aiClient.findSimilarDecisions(prompt, 3).catch(() => []),
      ]);

      const patternsMatched = intelligence?.relevantPatterns || [];
      const decisionsMatched = (similar || []).map((d: any) => ({
        question: d.question, chosen: d.chosen, confidence: d.confidence,
      }));

      const bestMatch = patternsMatched[0]?.name || decisionsMatched[0]?.chosen || null;
      const matchConfidence = patternsMatched[0]?.successRate || decisionsMatched[0]?.confidence || 0;

      this.logger.debug(`Matched ${patternsMatched.length} patterns, ${decisionsMatched.length} decisions for "${prompt.substring(0, 60)}"`);
      return { patternsMatched, decisionsMatched, bestMatch, matchConfidence };
    } catch {
      return empty;
    }
  }
}

Extract VALIDATE logic from agent-process.service.ts (lines 512-542). The VALIDATE phase:

// APIs/backend-vitto-api/src/app/engine/validate-phase.service.ts
import { Injectable, Logger } from '@nestjs/common';

export interface ValidationCheck {
  name: string;
  passed: boolean;
  severity: 'error' | 'warning' | 'info';
  message?: string;
}

export interface ValidateResult {
  checks: ValidationCheck[];
  allPassed: boolean;
  overallScore: number;
  warnings: string[];
}

@Injectable()
export class ValidatePhaseService {
  private readonly logger = new Logger('ValidatePhase');

  validate(output: string, agentType?: string): ValidateResult {
    const checks: ValidationCheck[] = [];
    const warnings: string[] = [];

    // Check 1: Output not empty
    checks.push({
      name: 'non-empty-output',
      passed: !!output && output.trim().length > 10,
      severity: 'error',
      message: output?.trim().length <= 10 ? 'Output is empty or too short' : undefined,
    });

    // Check 2: No obvious error patterns in output
    const errorPatterns = ['undefined', 'null', 'NaN', 'Error:', 'FATAL'];
    const hasErrors = errorPatterns.some(p => output.includes(p));
    checks.push({
      name: 'no-error-patterns',
      passed: !hasErrors,
      severity: 'warning',
      message: hasErrors ? 'Output contains error-like patterns' : undefined,
    });

    // Check 3: Reasonable length (not truncated, not exploded)
    const reasonable = output.length > 50 && output.length < 100000;
    checks.push({
      name: 'reasonable-length',
      passed: reasonable,
      severity: 'warning',
      message: !reasonable ? `Output length ${output.length} is outside expected range` : undefined,
    });

    // Agent-type-specific checks
    if (agentType === 'module-generator' || agentType === 'coder') {
      // Code output should not have NgModules (Angular 20 convention)
      const hasNgModule = output.toLowerCase().includes('@ngmodule');
      if (hasNgModule) {
        checks.push({ name: 'no-ngmodule', passed: false, severity: 'warning', message: 'Uses NgModules — should be standalone' });
        warnings.push('Code uses NgModules instead of standalone components');
      }
    }

    const passed = checks.filter(c => c.passed).length;
    const total = checks.length;
    const allPassed = checks.every(c => c.passed || c.severity !== 'error');
    const overallScore = total > 0 ? Math.round((passed / total) * 100) : 100;

    this.logger.debug(`Validation: ${passed}/${total} checks passed (${overallScore}%)`);
    return { checks, allPassed, overallScore, warnings };
  }
}

In sub-agent-definition.schema.ts, add field:

@Prop({ default: false })
enableValidation: boolean;

In sub-agent.service.ts, inject the new services and use them in executeTask():

// Add to constructor (both @Optional)
@Optional() private readonly matchPhase?: MatchPhaseService,
@Optional() private readonly validatePhase?: ValidatePhaseService,

// In executeTask(), after getting the final result:
// Optional MATCH phase (before LLM call, enrich system prompt)
if (this.matchPhase) {
  const matchResult = await this.matchPhase.match(userPrompt, def.role);
  if (matchResult.bestMatch) {
    // Prepend match context to system prompt
    systemPrompt += `\n\n## Matched Pattern\nA similar past approach: ${matchResult.bestMatch} (${Math.round(matchResult.matchConfidence * 100)}% confidence)`;
  }
}

// Optional VALIDATE phase (after LLM returns result, before recording outcome)
if (def.enableValidation && this.validatePhase && finalResult) {
  const validation = this.validatePhase.validate(finalResult, def.role);
  if (!validation.allPassed) {
    this.logger.warn(`[${agent.agentId}] Validation: ${validation.overallScore}% — ${validation.warnings.join(', ')}`);
  }
  // Adjust quality score based on validation
  const qualityMultiplier = validation.overallScore / 100;
  // Use in recordOutcome call below
}

In engine.module.ts, add to providers:

import { MatchPhaseService } from './match-phase.service';
import { ValidatePhaseService } from './validate-phase.service';

// In providers array:
MatchPhaseService, ValidatePhaseService,
git add APIs/backend-vitto-api/src/app/engine/match-phase.service.ts \
  APIs/backend-vitto-api/src/app/engine/validate-phase.service.ts \
  APIs/backend-vitto-api/src/app/engine/sub-agent.service.ts \
  APIs/backend-vitto-api/src/app/engine/engine.module.ts \
  APIs/backend-vitto-api/src/app/mind/schemas/sub-agent-definition.schema.ts
git commit -m "[S210.3] feat: extract MATCH/VALIDATE phases into Vitto — optional per-definition"

Task 6: Unify Learning Pipeline (S209 item 3.3)

Files:

After the existing recordOutcome() call in executeTask(), add:

// After recordOutcome (existing):
await this.recordOutcome(definitionId, { ... }).catch(...);

// S209.3.3: Also log to ai-agents DB for unified learning pipeline
if (this.aiClient) {
  this.aiClient.logDecision({
    type: 'implementation',
    question: `Sub-agent "${def.name}" executed: ${userPrompt.substring(0, 100)}`,
    chosen: finalResult.substring(0, 500),
    reasoning: `Agent ${def.name} (${def.role}) completed in ${costState.iterations} iterations, ${costState.inputTokens + costState.outputTokens} tokens`,
    confidence: taskSuccess ? 0.8 : 0.3,
  }).catch(() => {}); // Non-blocking
}

Run existing tests:

npx jest --config APIs/backend-vitto-api/jest.config.ts --testPathPattern="session-209-sub-agent" --no-cache

Expected: All 24 tests pass (logDecision mock already exists on mockAiClient).

git add APIs/backend-vitto-api/src/app/engine/sub-agent.service.ts
git commit -m "[S210.3] feat: unify learning pipeline — sub-agent outcomes recorded in ai-agents DB"

Final Verification

After all 6 tasks:

# Run all relevant test suites
npx jest --config APIs/backend-vitto-api/jest.config.ts --testPathPattern="session-209" --no-cache
npx jest --config APIs/backend-vitto-api/jest.config.ts --testPathPattern="sub-agent-persistent" --no-cache
npx jest --config APIs/backend-ai-api/jest.config.ts --testPathPattern="training-pipeline" --no-cache

All should pass with 0 regressions.


What This Plan Does NOT Cover (separate plans needed)

These are the v2 spec Sections 1-5 — the larger intelligence subsystems:

Item V2 Spec Section Separate Plan
Confidence-tiered skill injection Section 1 Skills Closed Loop plan
Anti-pattern mining + injection Section 1 Skills Closed Loop plan
Outcome feedback to skills Section 1 Skills Closed Loop plan
ProactiveDispatcher + multi-channel Section 2 Proactive Intelligence plan
Dossier new fields + bidirectional sync Section 3 Dossier Enhancement plan
Golden Path behavioral tests (28 scenarios) Section 4 Golden Path Tests plan
Per-feature budget enforcement Section 5 Cost Improvements plan