2026 03 19 Vitto Jarvis Implementation
Documentation for Plans
Vitto Jarvis — 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: Implement the remaining 9 features (S238 scope) from the Vitto Jarvis Master Spec to evolve Vitto into a Jarvis-level super agent.
Architecture: 6 phases, dependency-ordered. Phase 0 (Activity Card System) is blocking — all other phases depend on it for chat visibility. Phases 1-4 can parallelize after Phase 0. Phase 5 (Desktop) is independent. Phase 6 (Soul) is last. Each phase produces working, testable features.
Tech Stack: Angular 20+ (standalone, OnPush, signals), NestJS (backend-vitto-api), MongoDB/Mongoose, BullMQ, SSE streaming, Tauri v2 (desktop).
Spec: docs/superpowers/specs/2026-03-19-vitto-jarvis-master-spec.md
API Boundaries (CRITICAL — do not violate):
backend-api(port 3000) = user data, OAuth tokensbackend-ai-api(port 3001) = intelligence, decisions, embeddingsbackend-vitto-api(port 3003) = execution, tools, memory, orchestration
File Map
Phase 0: Activity Card System
| Action | File | Responsibility |
|---|---|---|
| MODIFY | libs/libs-ai/vitto-chat/src/lib/services/vitto-chat-streaming.service.ts |
Extend StreamEvent union, route activity_card events |
| CREATE | libs/libs-ai/vitto-chat/src/lib/components/activity-cards/activity-card-host.component.ts |
Dynamic card component host |
| CREATE | libs/libs-ai/vitto-chat/src/lib/components/activity-cards/activity-card-registry.ts |
Maps cardType → component class |
| CREATE | libs/libs-ai/vitto-chat/src/lib/components/activity-cards/cards/reasoning-trace-card.component.ts |
Reasoning hypothesis/observation card |
| CREATE | libs/libs-ai/vitto-chat/src/lib/components/activity-cards/cards/background-task-card.component.ts |
Chain progress with cancel/pause |
| CREATE | libs/libs-ai/vitto-chat/src/lib/components/activity-cards/cards/proactive-summary-card.component.ts |
Idle findings summary |
| CREATE | libs/libs-ai/vitto-chat/src/lib/components/activity-cards/cards/sub-agent-approval-card.component.ts |
Approve/deny elevated tools |
| CREATE | libs/libs-ai/vitto-chat/src/lib/components/activity-cards/cards/oauth-connect-card.component.ts |
OAuth connect button |
| CREATE | libs/libs-ai/vitto-chat/src/lib/components/activity-cards/cards/skill-usage-card.component.ts |
Skill used with confidence |
| CREATE | libs/libs-ai/vitto-chat/src/lib/components/activity-cards/cards/persona-adapted-card.component.ts |
Persona slider change with undo |
| CREATE | libs/libs-ai/vitto-chat/src/lib/components/activity-cards/cards/evolution-milestone-card.component.ts |
Stage transition |
| MODIFY | libs/libs-ai/vitto-chat/src/lib/components/message/unified-message/unified-message.component.ts |
Render ActivityCardHostComponent for activity entries |
| MODIFY | libs/libs-ai/vitto-chat/src/lib/components/message/unified-message/unified-message.component.html |
Template slot for activity cards |
Phase 1: Reasoning Layer
| Action | File | Responsibility |
|---|---|---|
| CREATE | APIs/backend-vitto-api/src/app/engine/reasoning.service.ts |
Hypothesis extraction, observation linking, trace persistence |
| MODIFY | APIs/backend-vitto-api/src/app/engine/agent-loop.service.ts |
Inject ReasoningService, enrich tool.starting/tool.called events |
| MODIFY | APIs/backend-vitto-api/src/app/engine/engine.module.ts |
Register ReasoningService |
| MODIFY | APIs/backend-vitto-api/src/app/mind/schemas/execution-trace.schema.ts |
Add reasoningTrace field |
Phase 2: Background Task Chains
| Action | File | Responsibility |
|---|---|---|
| CREATE | APIs/backend-vitto-api/src/app/engine/background-task.service.ts |
Decompose, chain, cancel, pause |
| CREATE | APIs/backend-vitto-api/src/app/engine/schemas/chain-checkpoint.schema.ts |
MongoDB schema for chain state |
| CREATE | APIs/backend-vitto-api/src/app/engine/background-task.controller.ts |
REST endpoints for cancel/pause/status |
| MODIFY | APIs/backend-vitto-api/src/app/engine/vitto-engine.service.ts |
Route complex tasks to BackgroundTaskService |
| MODIFY | APIs/backend-vitto-api/src/app/engine/engine.module.ts |
Register service, schema, controller |
Phase 3: Self-Improvement
| Action | File | Responsibility |
|---|---|---|
| MODIFY | APIs/backend-vitto-api/src/app/engine/skill-manager.service.ts |
Add confidenceTier, outcomeHistory, graduation logic |
| MODIFY | APIs/backend-vitto-api/src/app/engine/phase-executor.service.ts |
Record real outcome per skill after VERIFY |
| MODIFY | APIs/backend-vitto-api/src/app/eval/eval-runner.service.ts |
Add evalVersion, per-category scoring |
| MODIFY | APIs/backend-vitto-api/src/app/engine/dossier-persona-adapter.service.ts |
Enable auto-apply for low-risk sliders |
Phase 4: External Reach
| Action | File | Responsibility |
|---|---|---|
| MODIFY | APIs/backend-vitto-api/src/app/engine/sub-agent.service.ts |
Autonomy gating before elevated tool execution |
| MODIFY | APIs/backend-vitto-api/src/app/tools/plugins/oauth.plugin.ts |
Emit oauth.connect_required SSE on auth failure |
Phase 5: Desktop Daemon
| Action | File | Responsibility |
|---|---|---|
| MODIFY | libs/libs-ai/vitto-core/ (heartbeat service) |
Native idle detection, notification dispatch |
| MODIFY | apps/vitto-desktop/src-tauri/src/lib.rs |
Version in tray menu |
| MODIFY | apps/vitto-desktop/src-tauri/Cargo.toml |
Bump to 0.4.0 |
Phase 6: Soul & Identity
| Action | File | Responsibility |
|---|---|---|
| MODIFY | libs/libs-ai/vitto-hub/src/lib/components/brain-visualization/ |
Wire to live data APIs |
| MODIFY | libs/libs-anim/vitto-avatar/src/lib/services/vitto-mood.service.ts |
Stage overlay for evolution |
Phase 0: Activity Card System
This phase is BLOCKING. Every other phase depends on it for chat visibility. Build this first.
Task 0.1: Activity Card Registry & Host Component
Files:
Create:
libs/libs-ai/vitto-chat/src/lib/components/activity-cards/activity-card-registry.tsCreate:
libs/libs-ai/vitto-chat/src/lib/components/activity-cards/activity-card-host.component.tsStep 1: Create the card type registry
// activity-card-registry.ts
import { Type } from '@angular/core';
export type ActivityCardType =
| 'reasoning-trace'
| 'background-task'
| 'proactive-summary'
| 'sub-agent-approval'
| 'oauth-connect'
| 'skill-usage'
| 'persona-adapted'
| 'evolution-milestone'
| 'memory-recall'; // Reserved for S239 — card component built when S239 delivers
export interface ActivityCardData {
cardType: ActivityCardType;
data: Record<string, unknown>;
interactive: boolean;
timestamp: number;
}
// Registry populated lazily by each card component
const CARD_REGISTRY = new Map<ActivityCardType, Type<unknown>>();
export function registerActivityCard(type: ActivityCardType, component: Type<unknown>): void {
CARD_REGISTRY.set(type, component);
}
export function getActivityCardComponent(type: ActivityCardType): Type<unknown> | undefined {
return CARD_REGISTRY.get(type);
}
- Step 2: Create the host component
// activity-card-host.component.ts
import { Component, input, computed, ChangeDetectionStrategy } from '@angular/core';
import { NgComponentOutlet } from '@angular/common';
import { ActivityCardData, getActivityCardComponent } from './activity-card-registry';
@Component({
selector: 'lib-activity-card-host',
standalone: true,
imports: [NgComponentOutlet],
template: `
@if (cardComponent()) {
<div class="activity-card-wrapper" [class.interactive]="cardData().interactive">
<ng-container
[ngComponentOutlet]="cardComponent()"
[ngComponentOutletInputs]="{ data: cardData().data }" />
</div>
}
`,
styleUrl: './activity-card-host.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ActivityCardHostComponent {
readonly cardData = input.required<ActivityCardData>();
readonly cardComponent = computed(() => {
return getActivityCardComponent(this.cardData().cardType);
});
}
- Step 3: Create minimal SCSS
// activity-card-host.component.scss
.activity-card-wrapper {
margin: 8px 0;
border-radius: 8px;
overflow: hidden;
&.interactive {
border: 1px solid var(--border-color, rgba(255, 255, 255, 0.1));
}
}
- Step 4: Verify it compiles
Run: yarn build gograb-space --skip-nx-cache 2>&1 | head -20
Expected: 0 errors (component is created but not yet wired)
- Step 5: Commit
git add libs/libs-ai/vitto-chat/src/lib/components/activity-cards/
git commit -m "[S238.5] feat: add activity card registry and host component"
Task 0.2: First Card — Reasoning Trace Card
Files:
Create:
libs/libs-ai/vitto-chat/src/lib/components/activity-cards/cards/reasoning-trace-card.component.tsCreate:
libs/libs-ai/vitto-chat/src/lib/components/activity-cards/cards/reasoning-trace-card.component.htmlCreate:
libs/libs-ai/vitto-chat/src/lib/components/activity-cards/cards/reasoning-trace-card.component.scssStep 1: Create the reasoning trace card component
// reasoning-trace-card.component.ts
import { Component, input, signal, ChangeDetectionStrategy } from '@angular/core';
import { DecimalPipe } from '@angular/common';
import { registerActivityCard } from '../activity-card-registry';
interface ReasoningStep {
toolName: string;
hypothesis: string;
observation: string;
confidence: number;
success: boolean;
}
@Component({
selector: 'lib-reasoning-trace-card',
standalone: true,
imports: [DecimalPipe],
templateUrl: './reasoning-trace-card.component.html',
styleUrl: './reasoning-trace-card.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ReasoningTraceCardComponent {
readonly data = input.required<{
hypothesis: string;
steps: ReasoningStep[];
conclusion?: string;
overallConfidence?: number;
}>();
readonly expanded = signal(false);
toggle(): void {
this.expanded.update(v => !v);
}
}
// Self-register
registerActivityCard('reasoning-trace', ReasoningTraceCardComponent);
- Step 2: Create template
<!-- reasoning-trace-card.component.html -->
<div class="reasoning-card" (click)="toggle()">
<div class="reasoning-header">
<span class="reasoning-icon">🧠</span>
<span class="reasoning-title">Reasoning</span>
<span class="reasoning-chevron" [class.expanded]="expanded()">▸</span>
</div>
@if (expanded()) {
<div class="reasoning-body">
<div class="hypothesis">{{ data().hypothesis }}</div>
@for (step of data().steps; track step.toolName) {
<div class="reasoning-step" [class.success]="step.success" [class.failure]="!step.success">
<span class="step-icon">{{ step.success ? '✓' : '✗' }}</span>
<span class="step-tool">{{ step.toolName }}</span>
<div class="step-observation">{{ step.observation }}</div>
</div>
}
@if (data().conclusion) {
<div class="conclusion">
<strong>Conclusion:</strong> {{ data().conclusion }}
@if (data().overallConfidence) {
<span class="confidence">({{ (data().overallConfidence! * 100) | number:'1.0-0' }}%)</span>
}
</div>
}
</div>
}
</div>
- Step 3: Create SCSS
// reasoning-trace-card.component.scss
.reasoning-card {
background: var(--surface-secondary, rgba(255, 255, 255, 0.03));
border-radius: 6px;
padding: 8px 12px;
cursor: pointer;
font-size: 13px;
}
.reasoning-header {
display: flex;
align-items: center;
gap: 6px;
opacity: 0.7;
}
.reasoning-chevron {
margin-left: auto;
transition: transform 0.15s;
&.expanded { transform: rotate(90deg); }
}
.reasoning-body {
margin-top: 8px;
padding-top: 8px;
border-top: 1px solid var(--border-color, rgba(255, 255, 255, 0.06));
}
.hypothesis {
font-style: italic;
margin-bottom: 8px;
opacity: 0.8;
}
.reasoning-step {
display: flex;
flex-wrap: wrap;
gap: 4px;
padding: 4px 0;
&.success .step-icon { color: var(--success-color, #4caf50); }
&.failure .step-icon { color: var(--error-color, #f44336); }
}
.step-observation {
width: 100%;
padding-left: 20px;
opacity: 0.6;
font-size: 12px;
}
.conclusion {
margin-top: 8px;
padding-top: 8px;
border-top: 1px solid var(--border-color, rgba(255, 255, 255, 0.06));
}
.confidence {
opacity: 0.6;
margin-left: 4px;
}
- Step 4: Verify it compiles
Run: yarn build gograb-space --skip-nx-cache 2>&1 | head -20
Expected: 0 errors
- Step 5: Commit
git add libs/libs-ai/vitto-chat/src/lib/components/activity-cards/cards/reasoning-trace-card*
git commit -m "[S238.5] feat: add reasoning trace activity card component"
Task 0.3: Remaining 7 Card Components
Files:
- Create:
libs/libs-ai/vitto-chat/src/lib/components/activity-cards/cards/background-task-card.component.ts(+html, +scss) - Create:
libs/libs-ai/vitto-chat/src/lib/components/activity-cards/cards/proactive-summary-card.component.ts(+html, +scss) - Create:
libs/libs-ai/vitto-chat/src/lib/components/activity-cards/cards/sub-agent-approval-card.component.ts(+html, +scss) - Create:
libs/libs-ai/vitto-chat/src/lib/components/activity-cards/cards/oauth-connect-card.component.ts(+html, +scss) - Create:
libs/libs-ai/vitto-chat/src/lib/components/activity-cards/cards/skill-usage-card.component.ts(+html, +scss) - Create:
libs/libs-ai/vitto-chat/src/lib/components/activity-cards/cards/persona-adapted-card.component.ts(+html, +scss) - Create:
libs/libs-ai/vitto-chat/src/lib/components/activity-cards/cards/evolution-milestone-card.component.ts(+html, +scss)
Each card follows the same pattern as ReasoningTraceCardComponent: standalone, OnPush, signal inputs, self-registers via registerActivityCard().
- Step 1: Create all 7 card components
Follow the pattern from Task 0.2. Each card has:
- A
datainput signal with typed interface - Self-registration via
registerActivityCard('<type>', Component) - Collapsible UI (collapsed by default for non-interactive, expanded for interactive)
- Interactive cards (
sub-agent-approval,oauth-connect,persona-adapted,background-task) emit actions via output signals
Key interfaces per card:
background-task-card:{ chainId, steps: { index, name, status, cost }[], totalCost }proactive-summary-card:{ items: { icon, text, action }[] }sub-agent-approval-card:{ agentName, role, toolName, args, taskId }oauth-connect-card:{ service, authUrl, scopes }skill-usage-card:{ skillName, tier, successRate, usageCount }persona-adapted-card:{ trait, oldValue, newValue, reason }evolution-milestone-card:{ stage, previousStage, trigger, maturityScore }Step 2: Verify all compile
Run: yarn build gograb-space --skip-nx-cache 2>&1 | head -20
Expected: 0 errors
- Step 3: Commit
git add libs/libs-ai/vitto-chat/src/lib/components/activity-cards/cards/
git commit -m "[S238.5] feat: add 7 activity card components (background, proactive, approval, oauth, skill, persona, evolution)"
Task 0.4: Wire Activity Cards into Streaming Service
Files:
Modify:
libs/libs-ai/vitto-chat/src/lib/services/vitto-chat-streaming.service.ts(lines 16-25, 161-185)Modify:
libs/libs-ai/vitto-chat/src/lib/components/message/unified-message/unified-message.component.tsModify:
libs/libs-ai/vitto-chat/src/lib/components/message/unified-message/unified-message.component.htmlStep 1: Extend StreamEvent type union
In vitto-chat-streaming.service.ts, find the StreamEvent type (lines 16-25). Add 'activity_card' to the union.
- Step 2: Add activity card handling to handleStreamEvent()
In handleStreamEvent() (lines 161-185), add a case for SSE events that should render as activity cards. Map backend event types to ActivityCardData:
// After existing event handling
if (eventType === 'activity_card' || this.isActivityCardEvent(eventType)) {
const cardData = this.mapToActivityCard(eventType, eventData);
if (cardData) {
this.activityCards.update(cards => [...cards, cardData]);
}
}
Add helper method:
private isActivityCardEvent(type: string): boolean {
return type.startsWith('conscience.') ||
type.startsWith('background.') ||
type === 'subvitto.approval_required';
}
private mapToActivityCard(type: string, data: unknown): ActivityCardData | null {
const mapping: Record<string, ActivityCardType> = {
'conscience.skill_used': 'skill-usage',
'conscience.persona_adapted': 'persona-adapted',
'conscience.evolution.visual': 'evolution-milestone',
'conscience.proactive_summary': 'proactive-summary',
'background.step_completed': 'background-task',
'background.chain_status': 'background-task',
'subvitto.approval_required': 'sub-agent-approval',
};
const cardType = mapping[type];
if (!cardType) return null;
return { cardType, data: data as Record<string, unknown>, interactive: this.isInteractive(cardType), timestamp: Date.now() };
}
- Step 3: Add
activityCardssignal to streaming service
readonly activityCards = signal<ActivityCardData[]>([]);
Reset on new message stream start.
- Step 4: Wire ActivityCardHostComponent into unified-message
In unified-message.component.ts, import ActivityCardHostComponent. In the template, after tool result cards, render activity cards:
@for (card of activityCards(); track card.timestamp) {
<lib-activity-card-host [cardData]="card" />
}
The activityCards for each message come from the streaming service, filtered by message/task ID.
- Step 5: Import all card components in the activity cards barrel
Create libs/libs-ai/vitto-chat/src/lib/components/activity-cards/index.ts that imports all 8 card components (triggering their self-registration).
- Step 6: Verify it compiles and renders
Run: yarn build gograb-space --skip-nx-cache 2>&1 | head -20
Expected: 0 errors
Manual test: Start yarn serve:space, open chat, send a message. No cards should appear yet (backend doesn't emit activity events yet), but the system should not break existing functionality.
- Step 7: Commit
git add libs/libs-ai/vitto-chat/src/lib/services/vitto-chat-streaming.service.ts
git add libs/libs-ai/vitto-chat/src/lib/components/message/unified-message/
git add libs/libs-ai/vitto-chat/src/lib/components/activity-cards/index.ts
git commit -m "[S238.5] feat: wire activity card system into streaming service and message rendering"
Phase 1: Reasoning Layer
Task 1.1: ReasoningService Backend
Files:
Create:
APIs/backend-vitto-api/src/app/engine/reasoning.service.tsModify:
APIs/backend-vitto-api/src/app/engine/engine.module.ts(line ~112, providers array)Modify:
APIs/backend-vitto-api/src/app/mind/schemas/execution-trace.schema.ts(lines 10-109)Step 1: Add reasoningTrace to ExecutionTrace schema
In execution-trace.schema.ts, add after the existing fields (before the class closing):
@Prop({ type: [{
hypothesis: String,
toolName: String,
observation: String,
confidence: Number,
success: Boolean,
timestamp: Date,
}], default: [] })
reasoningTrace: Array<{
hypothesis: string;
toolName: string;
observation: string;
confidence: number;
success: boolean;
timestamp: Date;
}>;
- Step 2: Create ReasoningService
// reasoning.service.ts
import { Injectable, Logger } from '@nestjs/common';
interface ReasoningEntry {
hypothesis: string;
toolName: string;
observation: string;
confidence: number;
success: boolean;
timestamp: Date;
}
@Injectable()
export class ReasoningService {
private readonly logger = new Logger(ReasoningService.name);
/**
* Extract hypothesis from LLM response text using heuristics.
* V1: regex-based extraction. Future: structured output from model.
*/
extractHypothesis(llmResponseText: string, toolName: string): string {
// Look for intent patterns in the LLM text preceding the tool call
const patterns = [
/(?:I'll|I will|Let me|I need to|I should|Going to)\s+(.{10,100}?)(?:\.|$)/i,
/(?:To |In order to )\s*(.{10,80}?)(?:,|\.)/i,
/(?:First|Next|Now)\s*,?\s*(.{10,80}?)(?:\.|$)/i,
];
for (const pattern of patterns) {
const match = llmResponseText.match(pattern);
if (match?.[1]) return match[1].trim();
}
// Fallback: describe what the tool does
return `Using ${toolName}`;
}
/**
* Create observation from tool result.
*/
createObservation(toolName: string, success: boolean, outputPreview: string): string {
if (!success) return `${toolName} failed`;
if (!outputPreview || outputPreview.length < 5) return `${toolName} completed`;
// Truncate long outputs
return outputPreview.length > 150
? outputPreview.substring(0, 147) + '...'
: outputPreview;
}
/**
* Enrich a tool.starting SSE event with reasoning metadata.
*/
enrichToolStarting(
eventData: Record<string, unknown>,
llmResponseText: string,
): Record<string, unknown> {
const toolName = (eventData['tool'] as string) || 'unknown';
const hypothesis = this.extractHypothesis(llmResponseText, toolName);
return {
...eventData,
reasoning: { hypothesis },
};
}
/**
* Enrich a tool.called SSE event with observation.
*/
enrichToolCalled(
eventData: Record<string, unknown>,
hypothesis: string,
): { enrichedData: Record<string, unknown>; entry: ReasoningEntry } {
const toolName = (eventData['tool'] as string) || 'unknown';
const success = (eventData['success'] as boolean) ?? true;
const outputPreview = (eventData['outputPreview'] as string) || '';
const observation = this.createObservation(toolName, success, outputPreview);
// Simple confidence: 0.9 for success, 0.3 for failure
const confidence = success ? 0.9 : 0.3;
const entry: ReasoningEntry = {
hypothesis,
toolName,
observation,
confidence,
success,
timestamp: new Date(),
};
return {
enrichedData: {
...eventData,
reasoning: { hypothesis, observation, confidence },
},
entry,
};
}
}
- Step 3: Register in engine.module.ts
In engine.module.ts, add ReasoningService to the providers array.
- Step 4: Verify it compiles
Run: cd APIs/backend-vitto-api && npx tsc --noEmit 2>&1 | head -20
Expected: 0 errors
- Step 5: Commit
git add APIs/backend-vitto-api/src/app/engine/reasoning.service.ts
git add APIs/backend-vitto-api/src/app/engine/engine.module.ts
git add APIs/backend-vitto-api/src/app/mind/schemas/execution-trace.schema.ts
git commit -m "[S238.5] feat: add ReasoningService with hypothesis extraction and observation linking"
Task 1.2: Wire Reasoning into Agent Loop
Files:
Modify:
APIs/backend-vitto-api/src/app/engine/agent-loop.service.ts(lines 503-515 tool.starting, lines 566-582 tool.called)Step 1: Inject ReasoningService into AgentLoopService
Add to constructor: private readonly reasoning: ReasoningService
- Step 2: Enrich tool.starting events
At lines ~503-515 (parallel tools path) and ~618-630 (single tool path), before emitting tool.starting, capture the last LLM response text and call reasoning.enrichToolStarting():
// Before the existing sseSubject.next({ type: 'tool.starting', ... })
const lastLlmText = messages[messages.length - 1]?.content || '';
const enrichedData = this.reasoning.enrichToolStarting(
{ tool: toolCall.name, iteration, inputPreview, category },
typeof lastLlmText === 'string' ? lastLlmText : '',
);
sseSubject.next({ type: 'tool.starting', data: enrichedData });
Store the hypothesis for the tool.called enrichment:
const currentHypothesis = enrichedData.reasoning?.hypothesis || '';
- Step 3: Enrich tool.called events and collect trace
At lines ~566-582 and ~652-668, when emitting tool.called, enrich with observation:
const { enrichedData, entry } = this.reasoning.enrichToolCalled(
{ tool: toolCall.name, iteration, durationMs, success, inputPreview, outputPreview, category },
currentHypothesis,
);
sseSubject.next({ type: 'tool.called', data: enrichedData });
// Collect for persistence
reasoningTrace.push(entry);
Initialize const reasoningTrace: ReasoningEntry[] = [] at the start of run().
- Step 4: Persist reasoning trace with execution trace
At the end of run(), where execution trace is saved, include the reasoningTrace array. Find where MindService.recordExecution() or similar is called and pass the trace.
- Step 5: Verify backend compiles
Run: cd APIs/backend-vitto-api && npx tsc --noEmit 2>&1 | head -20
Expected: 0 errors
- Step 6: Manual test
Start yarn serve:backend-vitto. Open chat. Send a message that triggers tool use (e.g., "What modules exist in this space?"). Check SSE stream — tool.starting events should now have a reasoning.hypothesis field.
- Step 7: Commit
git add APIs/backend-vitto-api/src/app/engine/agent-loop.service.ts
git commit -m "[S238.5] feat: wire reasoning service into agent loop SSE events"
Phase 2: Background Task Chains
Task 2.1: Chain Checkpoint Schema & BackgroundTaskService
Files:
Create:
APIs/backend-vitto-api/src/app/engine/schemas/chain-checkpoint.schema.tsCreate:
APIs/backend-vitto-api/src/app/engine/background-task.service.tsModify:
APIs/backend-vitto-api/src/app/engine/engine.module.tsStep 1: Create chain checkpoint schema
// chain-checkpoint.schema.ts
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document } from 'mongoose';
@Schema({ collection: 'chain-checkpoints', timestamps: true })
export class ChainCheckpoint extends Document {
@Prop({ required: true, index: true }) chainId: string;
@Prop({ required: true }) stepIndex: number;
@Prop({ required: true }) stepName: string;
@Prop({ type: String, enum: ['pending', 'running', 'completed', 'failed', 'cancelled'], default: 'pending' })
status: string;
@Prop({ type: Object }) output: Record<string, unknown>;
@Prop({ type: Number, default: 0 }) costUsd: number;
@Prop() startedAt: Date;
@Prop() completedAt: Date;
@Prop() taskId: string; // Links to the VittoTask that executed this step
@Prop({ type: String }) errorMessage: string;
}
export const ChainCheckpointSchema = SchemaFactory.createForClass(ChainCheckpoint);
ChainCheckpointSchema.index({ chainId: 1, stepIndex: 1 }, { unique: true });
- Step 2: Create BackgroundTaskService
// background-task.service.ts
import { Injectable, Logger } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { ChainCheckpoint } from './schemas/chain-checkpoint.schema';
export interface ChainTemplate {
name: string;
steps: Array<{ name: string; systemPrompt: string; modelHint: 'cheap' | 'balanced' | 'powerful' }>;
}
const TEMPLATES: Record<string, ChainTemplate> = {
RESEARCH: {
name: 'Research & Report',
steps: [
{ name: 'Web research', systemPrompt: 'Search the web for relevant information about: {task}. Collect key findings.', modelHint: 'cheap' },
{ name: 'Analyze findings', systemPrompt: 'Analyze the research findings and identify key themes, patterns, and insights. Previous findings: {prevOutput}', modelHint: 'balanced' },
{ name: 'Draft report', systemPrompt: 'Write a comprehensive report based on the analysis. Analysis: {prevOutput}', modelHint: 'balanced' },
{ name: 'Format & deliver', systemPrompt: 'Format the report for readability. Add sections, bullet points, and a summary. Draft: {prevOutput}', modelHint: 'cheap' },
],
},
CODE_REVIEW: {
name: 'Code Review',
steps: [
{ name: 'Read code', systemPrompt: 'Read and understand the code files related to: {task}', modelHint: 'cheap' },
{ name: 'Analyze patterns', systemPrompt: 'Analyze code quality, patterns, and potential issues. Code context: {prevOutput}', modelHint: 'balanced' },
{ name: 'Write review', systemPrompt: 'Write a structured code review with specific suggestions. Analysis: {prevOutput}', modelHint: 'balanced' },
],
},
GENERATE: {
name: 'Generate & Verify',
steps: [
{ name: 'Plan', systemPrompt: 'Plan the implementation for: {task}. Define the approach and files to create.', modelHint: 'balanced' },
{ name: 'Scaffold', systemPrompt: 'Create the file structure and boilerplate. Plan: {prevOutput}', modelHint: 'balanced' },
{ name: 'Implement', systemPrompt: 'Fill in the implementation details. Scaffold: {prevOutput}', modelHint: 'powerful' },
{ name: 'Verify', systemPrompt: 'Review and verify the implementation. Fix any issues. Implementation: {prevOutput}', modelHint: 'cheap' },
],
},
};
@Injectable()
export class BackgroundTaskService {
private readonly logger = new Logger(BackgroundTaskService.name);
constructor(
@InjectModel(ChainCheckpoint.name) private readonly checkpointModel: Model<ChainCheckpoint>,
) {}
/**
* Match a task description to a template.
* Returns null if no template matches (task runs normally).
*/
matchTemplate(taskDescription: string): ChainTemplate | null {
const lower = taskDescription.toLowerCase();
if (lower.includes('research') || lower.includes('investigate') || lower.includes('analyze and report'))
return TEMPLATES.RESEARCH;
if (lower.includes('review') && (lower.includes('code') || lower.includes('pr') || lower.includes('module')))
return TEMPLATES.CODE_REVIEW;
if (lower.includes('generate') || lower.includes('create') || lower.includes('build'))
return TEMPLATES.GENERATE;
return null;
}
async createChain(chainId: string, template: ChainTemplate): Promise<void> {
const checkpoints = template.steps.map((step, index) => ({
chainId,
stepIndex: index,
stepName: step.name,
status: index === 0 ? 'pending' : 'pending',
}));
await this.checkpointModel.insertMany(checkpoints);
}
async getChainStatus(chainId: string): Promise<ChainCheckpoint[]> {
return this.checkpointModel.find({ chainId }).sort({ stepIndex: 1 }).lean();
}
async updateStep(chainId: string, stepIndex: number, update: Partial<ChainCheckpoint>): Promise<void> {
await this.checkpointModel.updateOne({ chainId, stepIndex }, { $set: update });
}
async cancelChain(chainId: string): Promise<void> {
await this.checkpointModel.updateMany(
{ chainId, status: { $in: ['pending', 'running'] } },
{ $set: { status: 'cancelled' } },
);
}
async pauseChain(chainId: string): Promise<{ pausedAfterStep: number }> {
// Mark all pending steps as cancelled (will be re-created on resume)
const running = await this.checkpointModel.findOne({ chainId, status: 'running' }).lean();
const pauseAfter = running?.stepIndex ?? -1;
await this.checkpointModel.updateMany(
{ chainId, status: 'pending', stepIndex: { $gt: pauseAfter } },
{ $set: { status: 'cancelled' } },
);
return { pausedAfterStep: pauseAfter };
}
}
- Step 3: Register in engine.module.ts
Add BackgroundTaskService to providers. Add MongooseModule.forFeature([{ name: ChainCheckpoint.name, schema: ChainCheckpointSchema }]) to imports.
- Step 4: Verify it compiles
Run: cd APIs/backend-vitto-api && npx tsc --noEmit 2>&1 | head -20
Expected: 0 errors
- Step 5: Commit
git add APIs/backend-vitto-api/src/app/engine/schemas/chain-checkpoint.schema.ts
git add APIs/backend-vitto-api/src/app/engine/background-task.service.ts
git add APIs/backend-vitto-api/src/app/engine/engine.module.ts
git commit -m "[S238.5] feat: add BackgroundTaskService with chain checkpoints and templates"
Task 2.2: Background Task Controller & Engine Routing
Files:
Create:
APIs/backend-vitto-api/src/app/engine/background-task.controller.tsModify:
APIs/backend-vitto-api/src/app/engine/vitto-engine.service.tsStep 1: Create controller
// background-task.controller.ts
import { Controller, Get, Post, Param, UseGuards } from '@nestjs/common';
import { BackgroundTaskService } from './background-task.service';
@Controller('engine/background')
export class BackgroundTaskController {
constructor(private readonly backgroundTask: BackgroundTaskService) {}
@Get(':chainId/status')
async getStatus(@Param('chainId') chainId: string) {
return this.backgroundTask.getChainStatus(chainId);
}
@Post(':chainId/cancel')
async cancel(@Param('chainId') chainId: string) {
await this.backgroundTask.cancelChain(chainId);
return { success: true };
}
@Post(':chainId/pause')
async pause(@Param('chainId') chainId: string) {
return this.backgroundTask.pauseChain(chainId);
}
@Post(':chainId/resume')
async resume(@Param('chainId') chainId: string) {
return this.backgroundTask.resumeChain(chainId);
}
}
- Step 2: Wire routing in VittoEngineService
In vitto-engine.service.ts, find processTaskWorkflow(). Before the call to this.phaseExecutor.execute(task, sseSubject), add background task detection:
// Check if task should be decomposed into background chain
if (task.classification?.complexity === 'complex') {
const template = this.backgroundTask.matchTemplate(task.description || task.message);
if (template) {
// Route to chain execution instead of direct execution
return this.executeAsChain(task, template, sseSubject);
}
}
// Otherwise: existing flow
await this.phaseExecutor.execute(task, sseSubject);
Add executeAsChain() method that creates checkpoints, iterates through steps, calls phaseExecutor.execute() per step with the step's system prompt injected, and emits background.step_completed SSE events.
- Step 3: Register controller in engine.module.ts
Add BackgroundTaskController to controllers array.
- Step 4: Verify it compiles
Run: cd APIs/backend-vitto-api && npx tsc --noEmit 2>&1 | head -20
Expected: 0 errors
- Step 5: Commit
git add APIs/backend-vitto-api/src/app/engine/background-task.controller.ts
git add APIs/backend-vitto-api/src/app/engine/vitto-engine.service.ts
git add APIs/backend-vitto-api/src/app/engine/engine.module.ts
git commit -m "[S238.5] feat: add background task controller and engine routing for chain execution"
Phase 3: Self-Improvement
Task 3.1: Skill Confidence Tiers
Files:
Modify:
APIs/backend-vitto-api/src/app/engine/skill-manager.service.ts(schema lines 28-149, recordOutcome lines 709-722)Modify:
APIs/backend-vitto-api/src/app/engine/phase-executor.service.ts(line 611)Step 1: Extend VittoSkill schema
In skill-manager.service.ts, find the VittoSkill schema class (lines 28-149). Add these fields:
@Prop({ type: String, enum: ['experimental', 'validated', 'trusted'], default: 'experimental' })
confidenceTier: string;
@Prop({ type: [{ outcome: String, taskId: String, timestamp: Date }], default: [] })
outcomeHistory: Array<{ outcome: string; taskId: string; timestamp: Date }>;
@Prop({ type: Number, default: 0 }) failureCount: number;
@Prop({ type: Number, default: 0.5 }) rollingSuccessRate: number;
@Prop({ type: Date }) warmStartExpiresAt: Date;
- Step 2: Update recordOutcome() method
Replace the existing recordOutcome() at lines 709-722:
async recordOutcome(skillName: string, outcome: 'success' | 'failure' | 'partial', taskId?: string): Promise<void> {
try {
const update: Record<string, unknown> = {
$inc: { usageCount: 1 },
$set: { lastUsed: new Date() },
};
if (outcome === 'success') {
(update.$inc as Record<string, number>).successCount = 1;
} else if (outcome === 'failure') {
(update.$inc as Record<string, number>).failureCount = 1;
}
if (taskId) {
update.$push = {
outcomeHistory: {
$each: [{ outcome, taskId, timestamp: new Date() }],
$slice: -50, // Keep last 50
},
};
}
const skill = await this.skillModel.findOneAndUpdate(
{ name: skillName },
update,
{ new: true },
);
if (!skill) return;
// Recalculate rolling success rate
const total = (skill.successCount || 0) + (skill.failureCount || 0);
const rate = total > 0 ? (skill.successCount || 0) / total : 0.5;
// Determine tier
let newTier = skill.confidenceTier || 'experimental';
const inWarmStart = skill.warmStartExpiresAt && new Date() < skill.warmStartExpiresAt;
if (total >= 25 && rate >= 0.9) newTier = 'trusted';
else if (total >= 10 && rate >= 0.8) newTier = 'validated';
else if (total >= 5 && rate < 0.3 && !inWarmStart) newTier = 'experimental';
await this.skillModel.updateOne(
{ _id: skill._id },
{ $set: { rollingSuccessRate: rate, confidenceTier: newTier } },
);
if (newTier !== skill.confidenceTier) {
this.logger.log(`Skill "${skillName}" graduated: ${skill.confidenceTier} → ${newTier} (rate: ${(rate * 100).toFixed(0)}%)`);
}
} catch (err) {
this.logger.warn(`recordOutcome failed for "${skillName}": ${err.message}`);
}
}
- Step 3: Set warm start on skill creation
Find the skill creation method (likely createSkill()). After creating the skill, set warmStartExpiresAt:
// After successful creation
await this.skillModel.updateOne(
{ _id: created._id },
{ $set: { warmStartExpiresAt: new Date(Date.now() + 14 * 24 * 60 * 60 * 1000) } },
);
- Step 4: Fix phase-executor to pass real outcome
In phase-executor.service.ts, find line 611 where recordOutcome is called as this.skillManager.recordOutcome(matchedSkill.name, true). Change to pass the actual VERIFY phase result:
const outcome = verifyResult?.passed ? 'success' : 'failure';
await this.skillManager.recordOutcome(matchedSkill.name, outcome, task.taskId);
If verifyResult is not available at that point, use the task's final outcome status.
- Step 5: Update skill retrieval to weight by tier
Find the skill retrieval logic (likely in skill-manager.service.ts method that returns skills for context injection, or in context-assembly.service.ts). Update to weight and sort by confidence tier:
// When retrieving skills for context injection:
const skills = await this.getRelevantSkills(taskDescription);
const weighted = skills.map(s => {
const inWarmStart = s.warmStartExpiresAt && new Date() < s.warmStartExpiresAt;
let weight = 0.4; // experimental default
if (s.confidenceTier === 'trusted') weight = 1.0;
else if (s.confidenceTier === 'validated') weight = 0.7;
else if (inWarmStart) weight = 0.7; // warm start gets validated weight
return { ...s, weight };
});
return weighted.sort((a, b) => b.weight - a.weight).slice(0, 3);
- Step 6: Verify it compiles
Run: cd APIs/backend-vitto-api && npx tsc --noEmit 2>&1 | head -20
Expected: 0 errors
- Step 7: Commit
git add APIs/backend-vitto-api/src/app/engine/skill-manager.service.ts
git add APIs/backend-vitto-api/src/app/engine/phase-executor.service.ts
git commit -m "[S238.5] feat: add skill confidence tiers with graduation, warm start, and weighted retrieval"
Task 3.2: Eval Hardening & Persona Auto-Apply
Files:
Modify:
APIs/backend-vitto-api/src/app/eval/eval-runner.service.tsModify:
APIs/backend-vitto-api/src/app/engine/dossier-persona-adapter.service.tsStep 1: Add evalVersion to eval results
In eval-runner.service.ts, find where eval results are stored. Add:
const evalVersion = process.env.GIT_COMMIT_SHA || process.env.RAILWAY_GIT_COMMIT_SHA || 'unknown';
// Include in result document:
{ ...result, evalVersion }
- Step 2: Add per-category scoring
In the nightly cron method, group golden dataset entries by category. Calculate per-category averages. Alert on 5% drop per category (not just 10% overall):
const categories = ['general', 'task', 'code', 'memory', 'edge'];
for (const category of categories) {
const categoryResults = results.filter(r => r.category === category);
const avg = categoryResults.reduce((sum, r) => sum + r.score, 0) / categoryResults.length;
const historical = await this.getHistoricalAvg(category, 7);
if (historical > 0 && avg < historical * 0.95) {
this.logger.warn(`Eval regression in ${category}: ${avg.toFixed(2)} vs ${historical.toFixed(2)} (7-day avg)`);
// Emit alert
}
}
- Step 3: Enable persona auto-apply for low-risk sliders
In dossier-persona-adapter.service.ts, find the auto-apply disabled comment (line ~96-97). Change the logic:
// Low-risk sliders: auto-apply when confidence >= 0.8
const LOW_RISK_TRAITS = ['explanationDepth', 'formality'];
const isLowRisk = LOW_RISK_TRAITS.includes(trait);
if (isLowRisk && confidence >= 0.8) {
// Auto-apply
await this.applyPersonaChange(userId, trait, newValue);
// Emit SSE event for chat card
this.narrativeService.emitConscience(userId, {
type: 'conscience.persona_adapted',
data: { trait, oldValue, newValue, reason, autoApplied: true },
});
} else if (confidence >= 0.5) {
// Surface as suggestion via proactive dispatch (existing behavior)
await this.dispatcher.dispatch({ ... });
}
- Step 4: Verify it compiles
Run: cd APIs/backend-vitto-api && npx tsc --noEmit 2>&1 | head -20
Expected: 0 errors
- Step 5: Commit
git add APIs/backend-vitto-api/src/app/eval/eval-runner.service.ts
git add APIs/backend-vitto-api/src/app/engine/dossier-persona-adapter.service.ts
git commit -m "[S238.5] feat: add eval version tracking, per-category scoring, and persona auto-apply for low-risk traits"
Phase 4: External Reach
Task 4.1: Sub-Agent Autonomy Gating
Files:
Modify:
APIs/backend-vitto-api/src/app/engine/sub-agent.service.tsStep 1: Add elevated tool detection
private readonly ELEVATED_TOOLS = new Set([
'run_command', 'write_file', 'execute_code', 'browser_interact',
'computer_use', 'http_request',
]);
private hasElevatedTools(tools: string[]): boolean {
return tools.some(t => this.ELEVATED_TOOLS.has(t));
}
- Step 2: Add autonomy check before execution
In executeTask(), before calling this.agentLoop.run() (line ~719), add:
const toolList = this.getToolsForAgent(definition);
if (this.hasElevatedTools(toolList)) {
// AutonomyService.shouldProceed() takes positional args: (taskId, phase, action, reasoning, options?)
const decision = await this.autonomyService.shouldProceed(
taskId,
'EXECUTE',
'sub_agent_elevated_execution',
`Sub-agent "${definition.name}" requests elevated tools: ${toolList.join(', ')}`,
{ actionType: 'elevated_tool_execution' },
);
if (!decision.proceed) {
// Emit approval request SSE event
sseSubject?.next({
type: 'subvitto.approval_required',
data: {
agentName: definition.name,
role: definition.role,
tools: toolList,
taskDescription: prompt,
taskId,
checkpointId: decision.checkpoint?.id,
},
});
// Poll checkpoint status until approved/denied (30s timeout)
const approved = await this.pollCheckpointApproval(decision.checkpoint?.id, 30_000);
if (!approved) {
return { status: 'denied', result: 'Sub-agent execution denied by user' };
}
}
}
Add helper method:
private async pollCheckpointApproval(checkpointId: string | undefined, timeoutMs: number): Promise<boolean> {
if (!checkpointId) return false;
const startTime = Date.now();
while (Date.now() - startTime < timeoutMs) {
const checkpoint = await this.autonomyService.getCheckpoint(checkpointId);
if (checkpoint?.status === 'approved') return true;
if (checkpoint?.status === 'denied') return false;
await new Promise(resolve => setTimeout(resolve, 2000)); // Poll every 2s
}
return false; // Timeout = denied
}
- Step 3: Verify it compiles
Run: cd APIs/backend-vitto-api && npx tsc --noEmit 2>&1 | head -20
Expected: 0 errors
- Step 4: Commit
git add APIs/backend-vitto-api/src/app/engine/sub-agent.service.ts
git commit -m "[S238.5] feat: add autonomy gating for sub-agents with elevated tools"
Task 4.2: OAuth Chat Wiring
Files:
Modify:
APIs/backend-vitto-api/src/app/tools/plugins/oauth.plugin.tsStep 1: Emit connect_required event on auth failure
In oauth.plugin.ts, find where oauth_api_call handles the "not connected" case. Add SSE event emission:
// When oauth_api_call fails because user not connected
if (!connection || connection.status !== 'active') {
// Emit SSE event for chat card
const authUrl = await this.oauthService.getAuthUrl(userId, service, requiredScopes);
sseSubject?.next({
type: 'tool.called',
data: {
tool: 'oauth_connect',
success: false,
output: JSON.stringify({ authUrl, service, scopes: requiredScopes }),
// This triggers the oauth-connect card in the frontend
},
});
return { error: `Not connected to ${service}. Authorization required.`, authUrl };
}
- Step 2: Verify it compiles
Run: cd APIs/backend-vitto-api && npx tsc --noEmit 2>&1 | head -20
Expected: 0 errors
- Step 3: Commit
git add APIs/backend-vitto-api/src/app/tools/plugins/oauth.plugin.ts
git commit -m "[S238.5] feat: emit OAuth connect_required event for chat card rendering"
Phase 5: Desktop Daemon
Task 5.1: Smart Heartbeat & Version Tracking
Files:
Modify: Heartbeat service (find exact path via
grep -r "VittoHeartbeatService" libs/)Modify:
apps/vitto-desktop/src-tauri/src/lib.rsModify:
apps/vitto-desktop/src-tauri/Cargo.tomlModify:
docs/english/Apps/vitto-desktop-app/changelog.mdStep 1: Find and verify heartbeat service
Run: grep -rn "VittoHeartbeatService\|IDLE_THRESHOLD" libs/libs-ai/ --include="*.ts" | head -10
Check if idle detection already uses native Tauri commands or JS listeners. If JS-only, upgrade to native.
- Step 2: Add native idle detection (if not already present)
In the heartbeat service, replace JS mousemove/keydown idle detection with Tauri invoke:
private async getSystemIdleTime(): Promise<number> {
try {
// Use Tauri native command for system-wide idle detection
const info = await invoke<{ idleSeconds: number }>('system_info');
return info.idleSeconds * 1000; // Convert to ms
} catch {
// Fallback to JS-based detection
return Date.now() - this.lastActivityTimestamp;
}
}
- Step 3: Add notification dispatch on actionable findings
private async notifyUser(summary: string): Promise<void> {
try {
await invoke('plugin:notification|send_notification', {
title: 'Vitto',
body: summary,
});
} catch (err) {
this.logger.warn('Native notification failed, falling back to console');
}
}
- Step 4: Add version to tray menu in lib.rs
In lib.rs, find the system tray menu setup. Add an "About" item:
let version = env!("CARGO_PKG_VERSION");
let about_item = MenuItemBuilder::new(format!("About Vitto v{}", version))
.enabled(false) // Display only
.build(app)?;
- Step 5: Bump version and update changelog
In Cargo.toml, change version = "0.3.0" to version = "0.4.0".
Update docs/english/Apps/vitto-desktop-app/changelog.md with the v0.4.0 entry.
- Step 6: Commit
git add libs/libs-ai/vitto-core/ apps/vitto-desktop/ docs/english/Apps/vitto-desktop-app/changelog.md
git commit -m "[S238.5] feat: smart heartbeat with native idle detection, version tracking in tray"
Phase 6: Soul & Identity
Task 6.1: Wire Brain Visualization to Live Data
Files:
Modify:
libs/libs-ai/vitto-hub/src/lib/components/brain-visualization/vitto-brain-visualization.component.tsStep 1: Identify the brain component
Run: ls libs/libs-ai/vitto-hub/src/lib/components/brain-visualization/
- Step 2: Wire live data sources
Inject the API services that provide:
- Evolution stage + maturity score (from
VittoEvolutionServiceAPI) - Memory stats (from
VittoMemoryServiceAPI — count, recent recalls) - Skill portfolio (from
SkillManagerServiceAPI — counts by tier)
Replace static/placeholder data with HTTP calls to backend-vitto-api endpoints. Display:
Current stage with progress bar (maturity 0-100)
Memory count, recalls today
Skills: N trusted, M validated, K experimental
Step 3: Verify it compiles and renders
Run: yarn build gograb-space --skip-nx-cache 2>&1 | head -20
Navigate to /vitto/brain — verify live data appears.
- Step 4: Commit
git add libs/libs-ai/vitto-hub/src/lib/components/brain-visualization/
git commit -m "[S238.5] feat: wire brain visualization to live evolution, memory, and skill data"
Task 6.2: Avatar Stage Overlay
Files:
Modify:
libs/libs-anim/vitto-avatar/src/lib/services/vitto-mood.service.tsStep 1: Add stage overlay method
applyStageOverlay(stage: string): Partial<MoodProperties> {
const overlays: Record<string, Partial<MoodProperties>> = {
nascent: { auraIntensity: 0.2, particleCount: 0, glowRadius: 5 },
aware: { auraIntensity: 0.4, particleCount: 3, glowRadius: 10 },
connected: { auraIntensity: 0.6, particleCount: 8, glowRadius: 18 },
attuned: { auraIntensity: 0.8, particleCount: 15, glowRadius: 25 },
transcendent: { auraIntensity: 1.0, particleCount: 25, glowRadius: 35 },
};
return overlays[stage] || overlays.nascent;
}
- Step 2: Apply overlay in mood calculation
Find where the current mood properties are computed. Merge stage overlay:
const stageOverlay = this.applyStageOverlay(currentEvolutionStage);
const finalProperties = { ...baseMoodProperties, ...stageOverlay };
The currentEvolutionStage can be fetched from the backend once on init and updated via conscience SSE events.
- Step 3: Verify avatar renders differently at different stages
Manual test: Change the maturity data to trigger different stages. Check avatar visual.
- Step 4: Commit
git add libs/libs-anim/vitto-avatar/src/lib/services/vitto-mood.service.ts
git commit -m "[S238.5] feat: add evolution stage overlay to avatar mood properties"
Final Verification
After all phases are complete, run through the testing guide from the spec (Part IV: Verification Matrix):
- Phase 0:
ls libs/libs-ai/vitto-chat/src/lib/components/activity-cards/cards/→ 8 directories - Phase 0:
grep "activity_card" libs/libs-ai/vitto-chat/src/lib/services/vitto-chat-streaming.service.ts→ hits - Phase 1:
ls APIs/backend-vitto-api/src/app/engine/reasoning.service.ts→ exists - Phase 1: Send chat message with tool use → SSE has
reasoning.hypothesis - Phase 2:
ls APIs/backend-vitto-api/src/app/engine/background-task.service.ts→ exists - Phase 2: Send complex task → chain card appears
- Phase 3:
grep "confidenceTier" APIs/backend-vitto-api/src/app/engine/skill-manager.service.ts→ exists - Phase 4: Create coder sub-agent → approval card appears
- Phase 5: Build Tauri app → tray shows version
- Phase 6: Navigate to
/vitto/brain→ live data - Full build:
yarn build gograb-space --skip-nx-cache→ 0 errors