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:
Modify:
APIs/backend-vitto-api/src/app/mind/mind.service.tsModify:
APIs/backend-vitto-api/src/app/engine/mind.controller.tsModify:
libs/libs-ai/vitto-core/src/lib/services/vitto-api.service.tsModify:
libs/libs-ai/vitto-hub/src/lib/components/memory-manager/memory-manager.component.tsModify:
libs/libs-ai/vitto-hub/src/lib/components/memory-manager/memory-manager.component.htmlStep 1: Add
askMemory()to MindService
In mind.service.ts, add a method that:
- Calls
this.memoryService.search(query, userId, 5)(or equivalent) to get top memory matches - If matches found, uses
ProviderFactoryService.getProvider('anthropic').sendMessage()with Haiku model to synthesize a 2-3 sentence answer from the matched memories - Returns
{ answer: string; sources: Array<{ id: string; summary: string; type: string }> } - 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().
- Step 2: Add route to MindController
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).
- Step 3: Add
askMemory()to VittoApiService (frontend)
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.
- Step 4: Add search UI to memory-manager component
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).
- Step 5: Test and commit
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:
Modify:
libs/libs-ai/vitto-hub/src/lib/components/brain-interactive/vitto-brain-interactive.component.tsModify:
libs/libs-ai/vitto-hub/src/lib/components/brain-interactive/vitto-brain-interactive.component.scssStep 1: Subscribe to conscience SSE in brain-interactive component
In vitto-brain-interactive.component.ts:
- Inject
VittoConscienceService - Add signal:
readonly activeRegions = signal<Set<string>>(new Set()) - In
ngOnInitor 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);
}
- Step 2: Add CSS pulse animation
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; }
}
- Step 3: Wire
[class.region--active]in template
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)
- Step 4: Test and commit
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:
Modify:
libs/libs-ai/vitto-hub/src/lib/components/introspection/vitto-introspection.component.htmlModify:
libs/libs-ai/vitto-hub/src/lib/components/introspection/vitto-introspection.component.tsStep 1: Add RouterLink import
In vitto-introspection.component.ts, add RouterLink to imports array:
import { RouterLink } from '@angular/router';
// In @Component imports:
imports: [...existingImports, RouterLink],
- Step 2: Add drill-down links in template
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; }
}
- Step 3: Test and commit
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:
Modify:
APIs/backend-ai-api/src/app/agent-process/agent-process.controller.ts(or wherever the execute endpoints live)Create:
APIs/backend-vitto-api/src/app/engine/agent-process-compat.controller.tsModify:
APIs/backend-vitto-api/src/app/engine/engine.module.tsModify:
libs/libs-ai/ai-agents-training-center/src/lib/services/agent-executor/agent-executor.service.tsStep 1: Add deprecation warnings on backend-ai-api
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.
- Step 2: Create compatibility controller on backend-vitto-api
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);
}
}
- Step 3: Register controller in EngineModule
In engine.module.ts, add AgentProcessCompatController to controllers array:
import { AgentProcessCompatController } from './agent-process-compat.controller';
// In @Module({ controllers: [...] })
controllers: [VittoController, MindController, AgentProcessCompatController],
- Step 4: Switch frontend AgentExecutorService URL
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.
- Step 5: Test and commit
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:
Create:
APIs/backend-vitto-api/src/app/engine/match-phase.service.tsCreate:
APIs/backend-vitto-api/src/app/engine/validate-phase.service.tsModify:
APIs/backend-vitto-api/src/app/engine/sub-agent.service.tsModify:
APIs/backend-vitto-api/src/app/engine/engine.module.tsModify:
APIs/backend-vitto-api/src/app/mind/schemas/sub-agent-definition.schema.tsStep 1: Create MatchPhaseService
Extract MATCH logic from agent-process.service.ts (lines 438-476). The MATCH phase:
- Takes a prompt + learning context (patterns, decisions)
- Finds similar patterns from the intelligence pyramid
- Returns matched patterns + confidence score
// 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;
}
}
}
- Step 2: Create ValidatePhaseService
Extract VALIDATE logic from agent-process.service.ts (lines 512-542). The VALIDATE phase:
- Takes generated output text
- Runs basic quality checks (syntax, best practices)
- Returns pass/fail + score
// 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 };
}
}
- Step 3: Add
enableValidationto SubAgentDefinition schema
In sub-agent-definition.schema.ts, add field:
@Prop({ default: false })
enableValidation: boolean;
- Step 4: Wire optional phases in SubAgentService.executeTask()
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
}
- Step 5: Register services in EngineModule and commit
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:
Modify:
APIs/backend-vitto-api/src/app/engine/sub-agent.service.tsModify:
APIs/backend-vitto-api/src/app/connectivity/backend-ai-client.service.tsStep 1: Add logDecision call to SubAgentService.executeTask()
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
}
- Step 2: Test and commit
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 |