learning_ai_common_plat/services/mcp-server/src/modules/a2a/progress-analyst-pipeline.ts
saravanakumardb1 b8e230f018 feat(mcp-server): A2A batch-2 — STTFallbackMonitorAgent (lysnrai) + ProgressAnalystAgent (jarvis) + BrainOverflowAgent (mindlyst)
stt-fallback-monitor-pipeline.ts: lysnrai.stt.monitorFallback
  - STTEventScannerAgent -> DiagnosticsLaunchAgent -> FallbackReportAgent
  - Queries stt_completed telemetry, splits azure vs whisper events, computes offline rate
  - Opens diagnostics session if rate exceeds threshold (default 20%); 6h default window

progress-analyst-pipeline.ts: jarvis.progress.analyze
  - SessionMetricsCollectorAgent -> PlateauDetectorAgent -> ProgressReportAgent
  - Collects skillMetrics timeseries per agent from recent sessions
  - Detects plateau (delta < threshold), recommends increase_difficulty or supplementary_agent

brain-overflow-pipeline.ts: mindlyst.brains.checkOverflow
  - BrainInventoryAgent -> OverflowDetectorAgent -> OverflowReportAgent
  - Flags brains above item count threshold with no acted-on items in N days
  - Per-brain suggestion: archive / reassign / review

MCP server total: 99 tools
2026-03-05 16:31:32 -08:00

273 lines
9.3 KiB
TypeScript

/**
* ProgressAnalystAgent — A2A pipeline for JarvisJr coaching skill progress analysis.
*
* Agent roster (3 steps):
* 1. SessionMetricsCollectorAgent — per agent: list recent sessions, extract skillMetrics timeseries
* 2. PlateauDetectorAgent — identify metrics that have not improved across last N sessions
* 3. ProgressReportAgent — assemble per-agent recommendations (difficulty change / supplementary agent)
*
* MCP tools:
* jarvis.progress.analyze(lookbackSessions?, plateauThreshold?) — run pipeline across all agents
*/
import { randomUUID } from 'node:crypto';
import { z } from 'zod';
import { registerTool } from '../tools/registry.js';
import type { McpToolRequest } from '../tools/types.js';
import {
jarvisAgentsList,
jarvisSessionsList,
type JarvisAgentDoc,
type JarvisSessionDoc,
} from '../../lib/jarvis-client.js';
// ── Types ──────────────────────────────────────────────────────────────────────
interface SkillTrend {
skill: string;
scores: number[];
latest: number;
earliest: number;
delta: number;
isPlateaued: boolean;
}
interface AgentProgressAnalysis {
agentId: string;
agentName: string;
sessionsAnalyzed: number;
skills: SkillTrend[];
plateauedSkills: string[];
recommendation: AgentRecommendation;
}
type AgentRecommendation =
| { action: 'none'; reason: string }
| { action: 'increase_difficulty'; currentLevel: string; reason: string }
| { action: 'supplementary_agent'; suggestedRole: string; reason: string }
| { action: 'insufficient_data'; reason: string };
export interface ProgressAnalystReport {
runId: string;
productId: 'jarvisjr';
lookbackSessions: number;
plateauThreshold: number;
agentsAnalyzed: number;
agentsWithPlateau: number;
agentsProgressing: number;
perAgent: AgentProgressAnalysis[];
summary: string;
generatedAt: string;
}
// ── Step 1: SessionMetricsCollectorAgent ──────────────────────────────────────
async function collectAgentMetrics(
agent: JarvisAgentDoc,
lookbackSessions: number,
opts: { token?: string; requestId?: string }
): Promise<{ agent: JarvisAgentDoc; sessions: JarvisSessionDoc[] }> {
try {
const result = await jarvisSessionsList({ agentId: agent.id, limit: lookbackSessions }, opts);
return { agent, sessions: result.sessions };
} catch {
return { agent, sessions: [] };
}
}
// ── Step 2: PlateauDetectorAgent ──────────────────────────────────────────────
function detectPlateaus(sessions: JarvisSessionDoc[], plateauThreshold: number): SkillTrend[] {
const completed = sessions
.filter(s => s.status === 'completed' && s.skillMetrics)
.sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime());
if (completed.length < 2) return [];
const skillMap = new Map<string, number[]>();
for (const session of completed) {
for (const [skill, score] of Object.entries(session.skillMetrics!)) {
const arr = skillMap.get(skill) ?? [];
arr.push(score);
skillMap.set(skill, arr);
}
}
const trends: SkillTrend[] = [];
for (const [skill, scores] of skillMap.entries()) {
if (scores.length < 2) continue;
const earliest = scores[0]!;
const latest = scores[scores.length - 1]!;
const delta = latest - earliest;
const isPlateaued = Math.abs(delta) < plateauThreshold;
trends.push({ skill, scores, latest, earliest, delta, isPlateaued });
}
return trends;
}
function buildRecommendation(
agent: JarvisAgentDoc,
trends: SkillTrend[],
sessionCount: number
): AgentRecommendation {
if (sessionCount < 3) {
return {
action: 'insufficient_data',
reason: `Only ${sessionCount} completed sessions — need at least 3 to detect plateaus.`,
};
}
const plateaued = trends.filter(t => t.isPlateaued);
const progressing = trends.filter(t => !t.isPlateaued && t.delta > 0);
if (plateaued.length === 0) {
return {
action: 'none',
reason: `${progressing.length} skill(s) improving. No plateau detected.`,
};
}
const avgLatest =
plateaued.length > 0 ? plateaued.reduce((s, t) => s + t.latest, 0) / plateaued.length : 0;
const currentLevel =
((agent as unknown as Record<string, unknown>)['difficultyLevel'] as string) ?? 'intermediate';
if (avgLatest >= 0.75 && currentLevel !== 'advanced') {
return {
action: 'increase_difficulty',
currentLevel,
reason: `${plateaued.map(t => t.skill).join(', ')} plateaued at high scores (avg ${(avgLatest * 100).toFixed(0)}%). User may have outgrown current difficulty.`,
};
}
return {
action: 'supplementary_agent',
suggestedRole: 'specialist',
reason: `${plateaued.map(t => t.skill).join(', ')} stagnant despite ${sessionCount} sessions. A specialist agent targeting these skills may break the plateau.`,
};
}
// ── Step 3: ProgressReportAgent ───────────────────────────────────────────────
function assembleProgressReport(
runId: string,
lookbackSessions: number,
plateauThreshold: number,
analyses: AgentProgressAnalysis[]
): ProgressAnalystReport {
const agentsWithPlateau = analyses.filter(a => a.plateauedSkills.length > 0).length;
const agentsProgressing = analyses.filter(
a => a.plateauedSkills.length === 0 && a.recommendation.action !== 'insufficient_data'
).length;
const summary =
analyses.length === 0
? 'No agents found to analyze.'
: `${agentsWithPlateau}/${analyses.length} agents show skill plateaus. ${agentsProgressing} progressing normally.`;
return {
runId,
productId: 'jarvisjr',
lookbackSessions,
plateauThreshold,
agentsAnalyzed: analyses.length,
agentsWithPlateau,
agentsProgressing,
perAgent: analyses,
summary,
generatedAt: new Date().toISOString(),
};
}
// ── Pipeline runner ────────────────────────────────────────────────────────────
async function runProgressAnalystPipeline(
lookbackSessions: number,
plateauThreshold: number,
req: McpToolRequest
): Promise<ProgressAnalystReport> {
const runId = randomUUID();
const opts = { token: req.headers.authorization?.slice(7), requestId: req.id };
req.log.info(
{ runId, stepId: 'collect', lookbackSessions },
'SessionMetricsCollectorAgent start'
);
const agentsResult = await jarvisAgentsList({ limit: 50 }, opts).catch(() => ({
agents: [] as JarvisAgentDoc[],
total: 0,
}));
const collected = await Promise.all(
agentsResult.agents.map(agent => collectAgentMetrics(agent, lookbackSessions, opts))
);
req.log.info(
{ runId, stepId: 'collect', agentCount: collected.length },
'SessionMetricsCollectorAgent done'
);
req.log.info(
{ runId, stepId: 'plateau', agentCount: collected.length },
'PlateauDetectorAgent start'
);
const analyses: AgentProgressAnalysis[] = collected.map(({ agent, sessions }) => {
const trends = detectPlateaus(sessions, plateauThreshold);
const plateauedSkills = trends.filter(t => t.isPlateaued).map(t => t.skill);
const recommendation = buildRecommendation(
agent,
trends,
sessions.filter(s => s.status === 'completed').length
);
return {
agentId: agent.id,
agentName: agent.name,
sessionsAnalyzed: sessions.length,
skills: trends,
plateauedSkills,
recommendation,
};
});
req.log.info(
{
runId,
stepId: 'plateau',
plateauCount: analyses.filter(a => a.plateauedSkills.length > 0).length,
},
'PlateauDetectorAgent done'
);
req.log.info({ runId, stepId: 'report' }, 'ProgressReportAgent start');
const report = assembleProgressReport(runId, lookbackSessions, plateauThreshold, analyses);
req.log.info({ runId, stepId: 'report', summary: report.summary }, 'ProgressReportAgent done');
return report;
}
// ── MCP tool registration ─────────────────────────────────────────────────────
registerTool({
name: 'jarvis.progress.analyze',
description:
'A2A pipeline: analyzes skill progression across all JarvisJr coaching agents. Collects skillMetrics from recent sessions, detects stagnation/plateaus, and recommends difficulty increases or supplementary agents. Returns a per-agent report with skill trend deltas and actionable recommendations. Requires admin role.',
requiredRole: 'admin',
inputSchema: z.object({
lookbackSessions: z.coerce
.number()
.int()
.min(3)
.max(50)
.default(10)
.describe('Number of most recent sessions to analyze per agent (default 10)'),
plateauThreshold: z.coerce
.number()
.min(0)
.max(0.5)
.default(0.05)
.describe('Max skill score delta to classify as plateaued (default 0.05 = 5% change)'),
}),
async execute(args, req) {
return runProgressAnalystPipeline(args.lookbackSessions, args.plateauThreshold, req);
},
});