2026 03 12 Vitto Mediated Training System Design

Documentation for Plans

Vitto-Mediated Agent Training System — Design Specification

Date: 2026-03-12 Session: S210.3 Status: Draft Supersedes: Direct OpenAI fine-tuning pipeline in TrainingPipelineService


1. Problem Statement

The current agent training system is passive and admin-driven: a human opens the Training Center UI, manually adds examples, clicks "Train", and backend-ai-api validates them. Vitto is completely out of the loop — it doesn't learn from its own successes, doesn't curate its own training data, and can't improve agents through conversation. The pipeline also had an OpenAI fine-tuning dependency that was replaced with few-shot injection (S209), but the architecture still treats training as an external process rather than an intrinsic capability.

Goal: Make Vitto an active participant in training — using conversation, its existing tool access, and its intelligence to observe what works, extract patterns, and improve sub-agents autonomously. All LLM operations flow through Vitto's provider-agnostic LLM layer. The Training Center UI becomes one of two surfaces (alongside chat) that converge on the same Vitto-mediated pipeline.


2. Backend Responsibility Split

backend-vitto-api (port 3003) — Orchestration + Intelligence

Owns: Training decisions, LLM evaluation, conversational teaching, self-learning loops, plugin lifecycle.

New components:

All LLM calls for training operations use Vitto's existing BaseLLMProvider + ProviderFactory (Claude, OpenAI, Gemini, DeepSeek with automatic failover). No direct provider dependency.

backend-ai-api (port 3001) — Data + Query Layer

Owns: Storage, retrieval, heavy queries, metrics aggregation.

Keeps existing:

New endpoints:

Deprecated (orchestration moves to Vitto):

Follow-on cleanup: The hardcoded qualityScore: isSuccess ? 0.8 : 0.3 in VittoEngineService.postExecutionOutcome() (line ~443) should be replaced with the SelfLearningPlugin's actual LLM-scored quality once Phase 2 is live.

The Routing Rule

Operation Source Target Backend
Dashboard reads (stats, examples, patterns) Training Center UI backend-ai-api (direct)
Training actions (teach, train, evaluate) Training Center UI backend-vitto-api
Conversational teaching Vitto chat backend-vitto-api
Self-learning (autonomous) Vitto engine task completion backend-vitto-api → backend-ai-api for persistence

3. Training Plugin Architecture

Plugin Interface

interface TrainingPlugin {
  id: string;
  name: string;

  // Tools this plugin adds to Vitto's tool registry
  tools?: ToolHandler[];

  // Hooks into task lifecycle
  onTaskCompleted?: (task: VittoTask, outcome: TaskOutcome) => Promise<void>;
  onDecisionLogged?: (decision: McpDecision) => Promise<void>;
  onSkillExtracted?: (skill: ExtractedSkill) => Promise<void>;

  // REST endpoints for Training Center UI
  endpoints?: TrainingEndpoint[];

  // Plugin lifecycle
  onInit?: () => Promise<void>;
  onDestroy?: () => Promise<void>;
}

Plugin Registry

Uses direct invocation (not an event bus). VittoEngineService injects TrainingPluginRegistry and calls notifyTaskCompleted() directly — matching how SkillExtractionService is already called at task completion. This avoids introducing a pub/sub dependency that doesn't exist in the current architecture.

@Injectable()
class TrainingPluginRegistry implements OnModuleInit {
  private plugins = new Map<string, TrainingPlugin>();

  constructor(
    private toolRegistry: ToolRegistryService,
  ) {}

  register(plugin: TrainingPlugin): void {
    plugin.tools?.forEach(t => this.toolRegistry.register(t));
    this.plugins.set(plugin.id, plugin);
  }

  // Called directly by VittoEngineService after task completion
  // (same pattern as SkillExtractionService.extractIfEligible)
  async notifyTaskCompleted(task: VittoTask, outcome: TaskOutcome): Promise<void> {
    for (const plugin of this.plugins.values()) {
      if (plugin.onTaskCompleted) {
        await plugin.onTaskCompleted(task, outcome).catch(err =>
          this.logger.warn(`Plugin ${plugin.id} onTaskCompleted failed: ${err.message}`)
        );
      }
    }
  }

  // Called by SkillExtractionService after createSkill() succeeds
  // (requires adding a ~2-line callback in extractIfEligible)
  async notifySkillExtracted(skill: ExtractedSkill): Promise<void> {
    for (const plugin of this.plugins.values()) {
      if (plugin.onSkillExtracted) {
        await plugin.onSkillExtracted(skill).catch(err =>
          this.logger.warn(`Plugin ${plugin.id} onSkillExtracted failed: ${err.message}`)
        );
      }
    }
  }

  // Called by MemoryPersistenceService after learnFromMcpDecision() succeeds
  // (requires adding a ~2-line callback in that method)
  async notifyDecisionLogged(decision: McpDecision): Promise<void> {
    for (const plugin of this.plugins.values()) {
      if (plugin.onDecisionLogged) {
        await plugin.onDecisionLogged(decision).catch(err =>
          this.logger.warn(`Plugin ${plugin.id} onDecisionLogged failed: ${err.message}`)
        );
      }
    }
  }

  getPlugin(id: string): TrainingPlugin | undefined {
    return this.plugins.get(id);
  }

  getAllPlugins(): TrainingPlugin[] {
    return Array.from(this.plugins.values());
  }
}

Hook wiring points (3 callsites to add, ~2 lines each):

  1. VittoEngineService.processTaskWorkflow() — after this.skillExtraction.extractIfEligible(task) (line ~483), add this.trainingRegistry.notifyTaskCompleted(task, outcome).catch(() => {})
  2. SkillExtractionService.extractIfEligible() — after this.skillManager.createSkill() succeeds, add callback to trainingRegistry.notifySkillExtracted(skill)
  3. MemoryPersistenceService.learnFromMcpDecision() — after successful processing, add callback to trainingRegistry.notifyDecisionLogged(decision)

Core Plugins

3.1 ConversationalTeachPlugin

Purpose: Parse natural language teaching instructions into structured training examples or knowledge docs for the target agent.

Tool: teach_agent

Endpoint: POST /vitto-api/training/teach

Chat integration: The existing /teach slash command and "Teach Vitto" quick action in the Teach plugin trigger Vitto's message processing. The PhaseExecutorService already intercepts teach intents in the ANALYZE phase. ConversationalTeachPlugin provides the teach_agent tool that Vitto selects during execution.

3.2 SelfLearningPlugin

Purpose: Automatically extract training data from successful task executions.

Tools: None (autonomous, triggered by lifecycle hooks only)

Hook — onTaskCompleted:

  1. Filter: Only process tasks with status === 'completed' and cost.durationMs > 0
  2. Extract the effective prompt (user's original message) and the task's final output
  3. Use a cheap LLM model (getForPhase('VERIFY') — Haiku/GPT-4o-mini) to score quality on a 1-10 rubric:
    • Correctness (does it solve the stated problem?)
    • Completeness (are all requirements addressed?)
    • Convention adherence (follows platform patterns?)
    • Code quality (if code output: compilable, typed, testable?)
  4. If score >= 7: auto-submit as training example via BackendAiClientService
  5. If score 5-6.9: add to human review queue (stored on ai-api with reviewStatus: 'pending')
  6. If score < 5: discard, log to introspection narrative
  7. Tag all self-learned examples: source: 'vitto-self-learning'

Hook — onDecisionLogged:

Endpoint: GET /vitto-api/training/self-learning/stats

3.3 AgentEvaluationPlugin

Purpose: Test a sub-agent with a real task and score the output using LLM-as-judge.

Tool: evaluate_agent

Endpoint: POST /vitto-api/training/evaluate

3.4 PatternExtractionPlugin

Purpose: Analyze recent successful tasks and extract reusable patterns that improve all agents.

Tool: extract_patterns

Hook — onSkillExtracted:

Endpoint: POST /vitto-api/training/patterns/extract


4. Frontend Integration

Training Center UI (McpService Changes)

The existing McpService in @libs-ai/ai-agents-training-center is modified to route write operations through Vitto:

Reads stay on ai-api (fast, no LLM overhead):

Writes move to vitto-api (LLM-mediated):

Impact: Add VITTO_API_URL injection (already imported). Route 4 write methods to Vitto. Read methods unchanged. ~20 lines of code change in McpService.

Chat Integration (Teach Plugin Enhancement)

The existing createTeachPlugin() in @libs-ai/vitto-chat already provides:

Enhancement: Add parameterized slash command /teach {agent} {instruction} (e.g., /teach angular-agent always use OnPush). The message is processed by Vitto's engine, which uses ConversationalTeachPlugin's teach_agent tool. Response shows what was learned.

No new frontend plugin needed — the existing Teach plugin surface is sufficient. Intelligence moves to backend.

SSE Training Events

Training progress flows through existing VittoNarrativeService:

Event Type Channel Content
conscience.training.started conscience (user-specific) "Training angular-agent with 12 examples..."
conscience.training.completed conscience (user-specific) "Angular agent trained. Validation rate: 85%"
introspection.self-learning introspection (global) "Learned from task X: module generation pattern extracted"
introspection.training.quality introspection (global) "Self-learned example rejected (quality: 5.2/10)"

5. Self-Learning Safety

Quality Gate

Every self-learned example goes through two-step validation:

  1. Automated LLM scoring — Vitto evaluates task output against original prompt on 1-10 rubric (correctness, completeness, convention adherence, code quality). Uses cheap model via getForPhase('VERIFY').
  2. Threshold gate:
    • Score >= 7: auto-accepted
    • Score 5-6.9: human review queue
    • Score < 5: discarded

Rate Limiting

Limit Default Configurable Via
Self-learned examples per hour 10 TRAINING_SELF_LEARN_RATE_HOUR
Self-learned examples per day per agent 50 TRAINING_SELF_LEARN_RATE_DAY_AGENT
LLM evaluation calls per minute 3 TRAINING_EVAL_RATE_MIN
Training cost budget per day $5 TRAINING_COST_BUDGET_DAY

Rate limit storage: Counters stored in Redis (already available via RedisService for BullMQ). In-memory counters reset on deployment and are insufficient for cost control. Keys: training:rate:{agentId}:hour, training:rate:{agentId}:day with TTL-based expiry.

Anti-Pattern Detection

Human Override

Cost Budget

Self-learning LLM calls tracked under training category in existing CostGuard. Default: $5/day. When exhausted, self-learning pauses — Vitto keeps working, just stops learning. Resets daily.


6. New Backend Endpoints

backend-vitto-api (Training Controller)

Method Path Purpose Plugin
POST /vitto-api/training/teach Submit teaching instruction ConversationalTeach
POST /vitto-api/training/train/:agentId Trigger agent training TrainingOrchestrator
POST /vitto-api/training/evaluate Test agent with real task AgentEvaluation
POST /vitto-api/training/patterns/extract Extract patterns from tasks PatternExtraction
GET /vitto-api/training/self-learning/stats Self-learning metrics SelfLearning
GET /vitto-api/training/plugins List registered plugins Registry
POST /vitto-api/training/self-learning/toggle Enable/disable self-learning SelfLearning

backend-ai-api (New Data Endpoints)

Method Path Purpose
GET /ai-api/platform/training/validation-candidates/:agentId Unvalidated examples for Vitto to evaluate
POST /ai-api/platform/training/validate-batch Bulk update validation status

7. New Vitto Tools

Tool Name Plugin Description Layers
teach_agent ConversationalTeach Parse instruction → training example or knowledge doc developer, platform_builder
evaluate_agent AgentEvaluation Run test task, score output via LLM judge developer, platform_builder
extract_patterns PatternExtraction Analyze successful tasks, extract reusable patterns developer
train_agent TrainingOrchestrator Validate examples + activate agent training corpus developer

8. Data Flow Diagrams

Conversational Teaching Flow

User → Vitto Chat: "/teach angular-agent always use OnPush"
  → PhaseExecutorService: detects teach intent
  → AgentLoopService: selects teach_agent tool
  → ConversationalTeachPlugin.teach_agent():
    → LLM classifies as knowledge doc
    → BackendAiClientService.submitTrainingExample() → ai-api stores
    → SubAgentService.addKnowledgeDoc() → vitto-mind stores
  → Response: "Added to Angular Agent's knowledge: 'Always use OnPush...'"
  → VittoNarrativeService.emitConscience('conscience.training.taught')

Self-Learning Flow

VittoEngineService: task completed successfully
  → Emits 'task.completed' event
  → SelfLearningPlugin.onTaskCompleted():
    → Extract prompt + output from task
    → Rate limit check (10/hour, 50/day/agent)
    → LLM scoring via ProviderFactory.getForPhase('VERIFY')
    → Quality >= 7: BackendAiClientService.submitTrainingExample()
    → Quality 5-6.9: Store with reviewStatus: 'pending'
    → Quality < 5: Discard
    → VittoNarrativeService.emitIntrospection('introspection.self-learning')

Training Center UI Flow

User clicks "Train" on angular-agent
  → POST /vitto-api/training/train/angular-agent
  → TrainingOrchestratorService:
    → BackendAiClientService.getValidationCandidates('angular-agent')
    → For each candidate: LLM validates quality
    → BackendAiClientService.validateBatch(validatedIds)
    → BackendAiClientService.updateAgentStatus('angular-agent', 'trained')
    → VittoNarrativeService.emitConscience('conscience.training.completed')
  → Response: { success, validatedCount, totalCount, avgQuality }

9. File Inventory

New Files (backend-vitto-api)

File Purpose
APIs/backend-vitto-api/src/app/training/training.module.ts NestJS module for training plugin system
APIs/backend-vitto-api/src/app/training/training-plugin.interface.ts TrainingPlugin interface + types
APIs/backend-vitto-api/src/app/training/training-plugin-registry.service.ts Plugin registry (tool + hook registration)
APIs/backend-vitto-api/src/app/training/training-orchestrator.service.ts Coordinates training operations
APIs/backend-vitto-api/src/app/training/training.controller.ts REST endpoints for UI
APIs/backend-vitto-api/src/app/training/plugins/conversational-teach.plugin.ts Teach via natural language
APIs/backend-vitto-api/src/app/training/plugins/self-learning.plugin.ts Autonomous learning from task outcomes
APIs/backend-vitto-api/src/app/training/plugins/agent-evaluation.plugin.ts Test agents with LLM-as-judge
APIs/backend-vitto-api/src/app/training/plugins/pattern-extraction.plugin.ts Extract reusable patterns

Modified Files (backend-vitto-api)

File Change
APIs/backend-vitto-api/src/app/engine/engine.module.ts Import TrainingModule
APIs/backend-vitto-api/src/app/engine/vitto-engine.service.ts Emit task.completed event for plugin hooks (~5 lines)
APIs/backend-vitto-api/src/app/connectivity/backend-ai-client.service.ts Add getValidationCandidates() and validateBatch() methods

Modified Files (backend-ai-api)

File Change
APIs/backend-ai-api/src/app/platform-ai/platform-ai.controller.ts Add validation-candidates + validate-batch endpoints
APIs/backend-ai-api/src/app/platform-ai/services/training-data.service.ts Add getValidationCandidates() query method

Modified Files (Frontend)

File Change
libs/libs-ai/ai-agents-training-center/src/lib/services/mcp.service.ts Route 4 write methods to VITTO_API_URL

10. Implementation Phases

Phase 1 — Foundation (Gateway + Plugin Infra)

Phase 2 — Self-Learning Loop

Phase 3 — Agent Evaluation

Phase 4 — Pattern Extraction + Cross-Teaching


11. Success Criteria

  1. All training write operations route through Vitto (zero direct ai-api writes from UI)
  2. Self-learning produces >= 5 valid training examples per day from organic task execution
  3. Agent success rates improve measurably within 2 weeks of self-learning activation
  4. No circular learning detected (< 30% self-learned examples per agent)
  5. Training cost stays within $5/day budget
  6. "Test Agent" returns real quality scores (not hardcoded 0.85)
  7. Chat /teach command successfully structures instructions into agent training data

12. Risks and Mitigations

Risk Impact Mitigation
Self-learning quality drift Agents learn bad patterns Quality gate (>= 7), human review queue, regression guard
Circular learning Echo chamber of self-generated content Source tracking, 30% self-learned cap, duplicate detection
Cost overrun from LLM scoring Training budget exhausted CostGuard integration, $5/day default, cheap model for scoring
Vitto API overload from training Core chat/task functionality degraded Reads stay on ai-api, training is async/non-blocking, rate limits
Breaking existing Training Center Users lose current functionality Phase 1 is additive — existing ai-api endpoints kept, deprecated later

13. Relationship to Vitto Intelligence v2 Spec

This design implements Subsystem 1: Skills Closed Loop from the Vitto Intelligence v2 spec and advances it from 70% to ~95% completion by:

It also partially implements Subsystem 7: Execution Consolidation by routing all training LLM calls through Vitto's unified provider layer.