feat: Intelligent Resilience & Trust Reporting (v2.77.2)

This commit is contained in:
g1nation
2026-05-05 17:04:27 +09:00
parent 037eafa02b
commit cf10d14148
29 changed files with 490 additions and 166 deletions
-1
View File
@@ -1 +0,0 @@
Plan 9 passes the minimum validation requirement.
-1
View File
@@ -1 +0,0 @@
Plan 8 passes the minimum validation requirement.
-1
View File
@@ -1 +0,0 @@
Plan 7 passes the minimum validation requirement.
-1
View File
@@ -1 +0,0 @@
Plan 6 passes the minimum validation requirement.
-1
View File
@@ -1 +0,0 @@
Plan 5 passes the minimum validation requirement.
-1
View File
@@ -1 +0,0 @@
Plan output 4 that passes validation checks.
-1
View File
@@ -1 +0,0 @@
Plan output 3 that passes validation checks.
-1
View File
@@ -1 +0,0 @@
Plan output 2 that passes validation checks.
-1
View File
@@ -1 +0,0 @@
Plan output 1 that passes validation checks.
-1
View File
@@ -1 +0,0 @@
Plan output 0 that passes validation checks.
-1
View File
@@ -1 +0,0 @@
Slow but valid agent response for performance measurement.
-1
View File
@@ -1 +0,0 @@
Plan: detailed strategy for the mission ahead.
-1
View File
@@ -1 +0,0 @@
Plan output that meets validation requirements.
-1
View File
@@ -1 +0,0 @@
Recovery successful after transient failures.
-1
View File
@@ -1 +0,0 @@
Recovery successful after transient failures.
-1
View File
@@ -1 +0,0 @@
Plan result that meets the minimum validation length.
-1
View File
@@ -1 +0,0 @@
Recovery successful after transient failures.
@@ -0,0 +1,16 @@
# Bug: /Volumes/Data/project/Antigravity/ConnectAI 내 질문에 대한 답변이 잘 정리되서 알려주긴 하는데 focused...
## Date
2026-05-05
## Symptom
/Volumes/Data/project/Antigravity/ConnectAI 내 질문에 대한 답변이 잘 정리되서 알려주긴 하는데 focused 된 답변이 아니라 살짝 다른 이야기도 섞여 있는 것 같은 느낌이 들어. 어디가 문제인지 확인해줘.
## Cause
Captured automatically from the current conversation. Confirm root cause during follow-up review if needed.
## Fix
## 요약 답변의 초점이 흐트러지는 문제는 주로 멀티 에이전트 워크플로우 중 'Researcher Agent'가 지식 추출 단계에서 너무 광범위한 정보를 수집하여, 최종적으로 'Writer Agent'가 이를 구조화하는 과정에서 명확한 톤앤매너나 핵심 주제를 놓치기 때문에 발생합니다. 해결책은 Planner가 설정한 검색 전략의 제약 조건을 Writer에게 더 강하게 전달하도록 수정하여, 정보 수집과 최종 출력 간의 필터링 단계를 강화하는 것입니다. ## 상세 설명 - 근본 원인 - Planner와 Researcher 간의 정보 전달 과정에서, 필요한 핵심 지식과 주변 맥락 간의 경계가 모호해집니다. 특히, 다양한 소스에서 추출된 정보들이 정제 과정 없이 그대로 다음 단계로 넘어가면서 주제가 분산되는 것으로 보입니다. - 이는 `AgentEngine`의 Producer-Consumer 패턴 내에서, 각 에이전트가 자신의 역할에만 집중하고 다른 에이전트의 의도된 필터링 역할을 충분히 ...
## Prevention
Keep automatic records tied to the active project and verify the relevant test or reproduction path.
+2 -2
View File
@@ -6,6 +6,6 @@
"description": "Auto-detected from the local project path in the conversation.",
"corePurpose": "Capture project direction, architecture discussion, decisions, and development notes as Markdown.",
"detailLevel": "standard",
"createdAt": "2026-05-05T07:20:12.554Z",
"updatedAt": "2026-05-05T07:20:12.556Z"
"createdAt": "2026-05-05T07:47:13.411Z",
"updatedAt": "2026-05-05T07:47:13.415Z"
}
@@ -0,0 +1,23 @@
# Development Log: /Volumes/Data/project/Antigravity/ConnectAI 이 프로젝트 분석해줘./Volumes/Data/project/An...
## Purpose
Record the implementation or verification outcome from the current conversation.
## Implementation Summary
## 요약 ConnectAI 프로젝트는 비동기적인 멀티 에이전트 오케스트레이션을 핵심 기반으로 하여 복잡한 지식 수집 워크플로우를 구현하려는 명확한 목표를 가지고 있습니다. 에이전트 엔진 설계는 Producer-Consumer 패턴과 락 관리 등을 통해 실행 안정성을 확보하려 했으나, 지식 처리의 정교함과 외부 연동 실패 시의 복원력에 대한 추가적인 보강이 필요합니다. ## 상세 설명 - 근본 원인 - 현재 설계는 비동기 작업 분할 및 에이전트 간의 흐름 제어에 중점을 두고 있으나, 실제 데이터 수집 및 처리 과정에서 발생하는 복잡한 상황(예: 지식 간의 충돌)에 대한 정량적인 위험도 평가 및 회복 메커니즘이 충분히 검증되지 않은 상태입니다. - 구체적인 단계별 지침 1. **실행 안정성 검토**: `src/core/lock.ts`와 `AgentEngine`의 동시성 제어 로직이 실제 환경에서 예상치 못한 컨텍스트 스위칭이나 락 경합 상황에 얼마나 탄력적으로 대처하는지 테스트 케이스...
## Architecture
Captured automatically from the assistant response and active project context.
## Changed Files
- src/core/lock.ts
- tests/agentEngine.test.ts
## Dependency Notes
No new dependency note was captured automatically.
## Bugs
No bugs recorded.
## Lessons
- Automatic project records should be generated in the background when the turn contains durable project knowledge.
@@ -0,0 +1,22 @@
# Development Log: /Volumes/Data/project/Antigravity/ConnectAI 이 프로젝트 분석해줘.
## Purpose
Record the implementation or verification outcome from the current conversation.
## Implementation Summary
## 간단 요약 ConnectAI 프로젝트는 지능형 에이전트 워크플로우를 기반으로 하여, 복잡한 지식 수집 및 보고서 생성 작업을 자동화하는 것을 목표로 설계되었습니다. 핵심은 Planner-Researcher-Writer의 멀티 에이전트 파이프라인과 정보 충돌 감지 기능에 있으며, 이는 높은 수준의 지식 관리 및 신뢰성을 확보하려는 의도를 명확히 보여줍니다. ## 상세 설명 - 근본 원인 - 프로젝트는 지식 수집 워크플로우를 앱 형태로 묶어 운영하려는 도구로, 각 단계별 역할 분담과 데이터의 신뢰도 평가를 중심으로 설계되었습니다. 이는 단순한 텍스트 생성기가 아닌, 지식의 안정성과 정합성을 확보하는 데 중점을 두고 있습니다. - 구체적인 해결 단계 1. **핵심 아키텍처 확인:** `AgentEngine`은 Producer-Consumer 패턴을 사용하여 비동기적인 멀티 에이전트 오케스트레이션을 담당하며, 이는 복잡한 작업을 안정적으로 분해하고 실행하기 위한 핵심 기반입니다. 2....
## Architecture
Captured automatically from the assistant response and active project context.
## Changed Files
- No explicit changed file list was captured automatically.
## Dependency Notes
No new dependency note was captured automatically.
## Bugs
No bugs recorded.
## Lessons
- Automatic project records should be generated in the background when the turn contains durable project knowledge.
+9
View File
@@ -60,3 +60,12 @@
## 2026-05-05
- Auto bug record created: bugs/BUG-0007-volumes-data-project-antigravity-connectai-내-질문에-대한-답변이-잘-정리.md
## 2026-05-05
- Auto bug record created: bugs/BUG-0008-volumes-data-project-antigravity-connectai-내-질문에-대한-답변이-잘-정리.md
## 2026-05-05
- Auto development record created: development/2026-05-05_volumes-data-project-antigravity-connectai-이-프로젝트-분석해줘_implementation.md
## 2026-05-05
- Auto development record created: development/2026-05-05_volumes-data-project-antigravity-connectai-이-프로젝트-분석해줘-volum_implementation.md
+1 -1
View File
@@ -2,7 +2,7 @@
"name": "astra",
"displayName": "Astra",
"description": "The personal intelligence layer for Antigravity and VS Code. A private cognitive partner for deep project context, memory, and proactive strategic decision-making.",
"version": "2.76.9",
"version": "2.77.2",
"publisher": "g1nation",
"license": "MIT",
"icon": "assets/icon.png",
+4 -2
View File
@@ -149,10 +149,12 @@ Analyze the provided report and suggest 3 high-impact next actions for the user.
// Fix 3: Trim input if it's too long (Basic Context Diet)
const trimmedData = input.length > 8000 ? input.substring(0, 8000) + '... [Data Trimmed for Performance]' : input;
const policy = options?.context || '';
const wrappedInput = `### SYSTEM INSTRUCTION: FINAL SYNTHESIS
1. Gathered Research Data: ${trimmedData}
2. User's Original Objective & Policy: ${originalRequest}
3. Mission: Write the definitive final report in KOREAN.`;
2. User's Original Objective: ${originalRequest}
3. Applied Knowledge & Filtering Policy: ${policy}
4. Mission: Write the definitive final report in KOREAN.`;
return this.callLLM(this.persona, wrappedInput, signal);
}
}
+69
View File
@@ -0,0 +1,69 @@
import { logInfo, logError } from '../utils';
export enum ApiErrorType {
AUTH_FAILURE = 'AUTH_FAILURE',
RATE_LIMIT = 'RATE_LIMIT',
NETWORK_TIMEOUT = 'NETWORK_TIMEOUT',
SERVER_ERROR = 'SERVER_ERROR',
UNKNOWN = 'UNKNOWN'
}
export interface ApiResponse<T> {
success: boolean;
data?: T;
error?: {
type: ApiErrorType;
message: string;
isRecoverable: boolean;
};
}
/**
* ExternalApiHandler: 외부 서비스(Ollama, Gemini, Search API 등)와의
* 모든 통신을 중앙 집중적으로 관리하며, 인증 복구 및 속도 제한 대응을 담당합니다.
*/
export class ExternalApiHandler {
/**
* 인증 오류 발생 시 복구 가능성을 판단하고 재시도 로직을 제어합니다.
*/
public static async handleAuthRecovery(error: any): Promise<boolean> {
logInfo(`[ApiHandler] 인증 오류 감지. 복구 시도 중...`);
// TODO: 실제 프로젝트의 인증 스키마(API Key, OAuth 등)에 따른 복구 로직 구현
// 예: 환경 변수 재로드, 만료된 토큰 갱신 요청 등
return false; // 현재는 기본적으로 수동 개입 필요로 처리
}
/**
* 지능형 요청 래퍼 (Resilient Fetch)
*/
public static async request<T>(
call: () => Promise<T>,
context: string
): Promise<ApiResponse<T>> {
try {
const data = await call();
return { success: true, data };
} catch (error: any) {
logError(`[ApiHandler] [${context}] 호출 실패:`, error);
const errorType = this.classifyError(error);
return {
success: false,
error: {
type: errorType,
message: error.message,
isRecoverable: errorType === ApiErrorType.NETWORK_TIMEOUT || errorType === ApiErrorType.RATE_LIMIT
}
};
}
}
private static classifyError(error: any): ApiErrorType {
const msg = error.message || '';
if (msg.includes('401') || msg.includes('unauthorized')) return ApiErrorType.AUTH_FAILURE;
if (msg.includes('429') || msg.includes('rate limit')) return ApiErrorType.RATE_LIMIT;
if (msg.includes('timeout') || msg.includes('ETIMEDOUT')) return ApiErrorType.NETWORK_TIMEOUT;
if (msg.includes('500') || msg.includes('502') || msg.includes('503')) return ApiErrorType.SERVER_ERROR;
return ApiErrorType.UNKNOWN;
}
}
+59 -5
View File
@@ -2,10 +2,9 @@ import { logInfo } from '../utils';
export class AgentDataValidator {
/**
* 에이전트 간 핸드오프(Handoff) 시 데이터 무결성을 검증합니다.
* 데이터 누락이나 스키마 오류를 감지하여 파이프라인의 안정성을 높입니다.
* 에이전트 간 핸드오프(Handoff) 시 데이터 무결성을 검증하고 품질 점수를 반환합니다.
*/
public static validateHandoff(stage: string, data: string): void {
public static validateHandoff(stage: string, data: string): number {
if (!data || data.trim().length === 0) {
throw new Error(`[IntegrityError] 데이터 누락: ${stage} 에이전트의 출력이 비어 있습니다.`);
}
@@ -17,7 +16,6 @@ export class AgentDataValidator {
if (data.length < minLength) {
throw new Error(`[IntegrityError] Planner 출력 데이터가 비정상적으로 짧습니다 (${data.length} chars). 계획 누락 의심.`);
}
// 향후 JSON 스키마 검증(Zod 등)을 여기에 추가할 수 있습니다.
break;
case 'researcher':
if (data.length < minLength) {
@@ -36,11 +34,19 @@ export class AgentDataValidator {
break;
}
// 품질 점수화 (Quality Scoring) - Astra 피드백 반영
// 품질 점수화 (Quality Scoring)
const score = QualityScorer.evaluate(data);
if (score < 50) {
logInfo(`[QualityWarning] ${stage} 결과 품질 낮음 (Score: ${score}). 내용 보강이 필요할 수 있습니다.`);
}
// [New] 지능형 충돌 감지 및 위험도 평가
const conflictRisk = ConflictDetector.analyzeSemanticDivergence(data);
if (conflictRisk > 40) {
logInfo(`[ConflictAlert] ${stage} 단계에서 지식 충돌 위험 감지 (Risk: ${conflictRisk}).`);
}
return score - (conflictRisk * 0.5); // 충돌 위험이 높으면 전체 품질 점수를 감쇄함
}
}
@@ -94,6 +100,54 @@ export class PerformanceProfiler {
}
}
export class ConflictDetector {
/**
* [Astra v4.1] 도메인 맥락 기반의 의미적 괴리(Semantic Divergence)를 분석합니다.
* 데이터 내의 논리적 모순이나 상충하는 지표를 탐지하여 위험도를 산출합니다.
*/
public static analyzeSemanticDivergence(data: string): number {
if (!data) return 0;
let riskScore = 0;
// 1. 수치적 모순 탐지 (Metric Contradiction)
// 동일 문맥 내에서 서로 다른 수치가 발견되는 패턴 탐지
const metricPatterns = [
/(\d+%)\s+vs\s+(\d+%)/g,
/(\d+ms)\s+대비\s+(\d+ms)/g,
/상충되는\s+(데이터|정보|결과)/i
];
for (const pattern of metricPatterns) {
if (pattern.test(data)) riskScore += 25;
}
// 2. 논리적 상충 용어 탐지 (Logical Divergence)
const conflictTerms = [
['최적화', '성능 저하'],
['안정적', '불안정'],
['증가', '감소'],
['승인', '반려/경고'],
['임상 등급', '비승인/소비자용']
];
for (const [pos, neg] of conflictTerms) {
if (data.includes(pos) && data.includes(neg)) {
// 두 상충 용어가 너무 가까운 거리(예: 200자 이내)에 있으면 충돌 확률 높음
const posIdx = data.indexOf(pos);
const negIdx = data.indexOf(neg);
if (Math.abs(posIdx - negIdx) < 300) {
riskScore += 15;
}
}
}
// 3. 명시적 경고 태그 확인
if (data.includes('[CONFLICT WARNING]')) riskScore += 30;
return Math.min(100, riskScore);
}
}
export class CognitionAudit {
/**
* [Astra v4.0] 지능적 판단 및 정책 준수 여부를 감사(Audit)합니다.
+152 -96
View File
@@ -11,6 +11,8 @@ import { WikiFormatter } from './formatter';
// 1. 에이전트 인터페이스 확장 (Interface Extensibility)
// ─────────────────────────────────────────────
export type AbstractionLevel = 'laser-focused' | 'balanced' | 'comprehensive';
/**
* 에이전트 실행 시 전달되는 확장 옵션 객체.
* 향후 에이전트별로 고유한 설정(temperature, maxTokens 등)을
@@ -24,7 +26,9 @@ export interface AgentExecuteOptions {
/** 에이전트별 커스텀 설정 (temperature, maxTokens 등) */
config?: Record<string, unknown>;
/** 이전 단계의 중간 결과물 (병렬 파이프라인용) */
priorResults?: Record<string, string>;
priorResults?: Record<string, string | undefined>;
/** 에이전트 간 정보 전달 시의 추상화/필터링 수준 */
abstractionLevel?: AbstractionLevel;
}
/**
@@ -69,6 +73,12 @@ export class MissionState {
public readonly missionId: string;
public readonly startTime: number;
public resilienceMetrics = {
fallbacks: 0,
retries: 0,
maxConflictScore: 0,
deduplications: 0
};
constructor(missionId: string) {
this.missionId = missionId;
@@ -229,6 +239,8 @@ export enum ErrorType {
TRANSIENT = 'TRANSIENT',
/** 프롬프트 구조 문제, 모델 명백한 실패 등 수동 개입이 필요한 오류 */
PERMANENT = 'PERMANENT',
/** 인증 실패 (API Key 만료 등) */
AUTH_FAILURE = 'AUTH_FAILURE',
/** 사용자가 의도적으로 작업을 취소한 경우 */
ABORT = 'ABORT'
}
@@ -242,8 +254,10 @@ export interface RecoveryRule {
description: string;
maxRetries: number;
backoffBaseMs: number;
action: 'retry' | 'abort' | 'fail_with_message';
action: 'retry' | 'abort' | 'fail_with_message' | 'fallback';
userMessage: string;
/** 'fallback' 액션 시 실행할 대체 로직 (예: 캐시 반환) */
fallbackDescription?: string;
}
/**
@@ -423,8 +437,7 @@ export class AgentEngine {
) {}
/**
* 멀티 에이전트 워크플로우 실행
* @param missionId 작업을 식별하기 위한 고유 ID (Mutex 락에 사용)
* 멀티 에이전트 워크플로우 실행 (Refactored: Atomic State Machine)
*/
public async runMission(
missionId: string,
@@ -437,124 +450,135 @@ export class AgentEngine {
// 0. 상태 복원 시도 (Resumption)
const existingState = MissionState.loadFromDisk(missionId);
if (existingState && existingState.stage !== 'completed') {
logInfo(`[AgentEngine] 기존 미션 발견. '${existingState.stage}' 단계부터 재개합니다.`);
state = existingState;
} else {
state = new MissionState(missionId);
}
state = (existingState && existingState.stage !== 'completed') ? existingState : new MissionState(missionId);
// 1. 명시적 락 획득 (Mutex) - 동일 미션의 중복 실행 방지
// 1. 명시적 락 획득 (Mutex)
const release = await lockManager.acquire(`mission_${missionId}`);
try {
// 2. 작업을 비동기 큐에 등록 (Producer-Consumer)
return await actionQueue.enqueue(async () => {
logInfo(`[AgentEngine] 미션 시작: ${missionId}`);
// --- Phase 1: Planner ---
let plan = state.getResult('plan');
if (!plan) {
this.transition(state, 'planner', '전략 수립 중...', onProgress);
this.checkAbort(signal);
// Deduplication: 동일 프롬프트 캐시 확인
const cachedPlan = CacheManager.get(prompt, brainContext);
if (cachedPlan) {
logInfo(`[AgentEngine] [Deduplication] 기존 Planner 캐시를 사용합니다.`);
plan = cachedPlan;
} else {
plan = await this.resilientExecute(
state, this.planner, 'Planner', prompt, brainContext, signal, onProgress,
{ context: brainContext, signal, config: { role: 'planner' } }
);
CacheManager.set(prompt, brainContext, plan);
}
state.setResult('plan', plan);
// [Blind Spot Check] 전역 중복 제거 (Global Deduplication)
const globalCache = CacheManager.get(prompt, 'global_final_report');
if (globalCache) {
logInfo(`[AgentEngine] [Deduplication] 최종 리포트 캐시를 발견했습니다. 실행을 스킵합니다.`);
this.transition(state, 'completed', '캐시된 결과 반환', onProgress);
return globalCache;
}
this.validateResult(plan, 'Planner');
// --- Phase 2 & 3: Researcher ---
let research = state.getResult('research');
// --- Phase 1: Planner ---
const plan = await this.executeStep(
state, 'planner', '전략 수립 중...',
() => this.resilientExecute(state, this.planner, 'Planner', prompt, brainContext, signal, onProgress, { context: brainContext, signal, config: { role: 'planner' } }),
prompt, brainContext, signal, onProgress
);
const plannerScore = this.validateResult(plan, 'Planner');
const researcherLevel: AbstractionLevel = plannerScore < 70 ? 'laser-focused' : 'balanced';
// --- Phase 2: Researcher ---
const research = await this.executeStep(
state, 'researcher', '핵심 정보 수집 및 분석 중...',
() => this.resilientExecute(state, this.researcher, 'Researcher', plan, brainContext, signal, onProgress, { context: brainContext, signal, config: { role: 'researcher' }, abstractionLevel: researcherLevel }),
plan, brainContext, signal, onProgress
);
// --- Phase 3: Context Preparation (Side Effect of Phase 2) ---
let writerPrep = state.getResult('writerPrep');
if (!research || !writerPrep) {
this.transition(state, 'researcher', '핵심 정보 수집 및 분석 중...', onProgress);
this.checkAbort(signal);
const [res, prep] = await Promise.all([
research || this.resilientExecute(
state, this.researcher, 'Researcher', plan, brainContext, signal, onProgress,
{ context: brainContext, signal, config: { role: 'researcher' } }
),
writerPrep || this.prepareWriterContext(prompt, plan, brainContext)
]);
research = res;
writerPrep = prep;
state.setResult('research', research);
if (!writerPrep) {
writerPrep = await this.prepareWriterContext(prompt, plan, brainContext);
state.setResult('writerPrep', writerPrep);
}
this.validateResult(research, 'Researcher');
// --- Phase 3: Writer ---
this.transition(state, 'writer', '최종 리포트 작성 및 편집 중...', onProgress);
this.checkAbort(signal);
const finalReport = await this.resilientExecute(
state, this.writer, 'Writer', research, prompt, signal, onProgress,
{ context: brainContext, signal, config: { role: 'writer' }, priorResults: { plan, writerPrep } }
const researchScore = this.validateResult(research, 'Researcher');
const writerLevel: AbstractionLevel = researchScore < 65 ? 'laser-focused' : 'balanced';
// --- Phase 4: Writer ---
const finalReport = await this.executeStep(
state, 'writer', '최종 리포트 작성 및 편집 중...',
() => this.resilientExecute(state, this.writer, 'Writer', research, prompt, signal, onProgress, {
context: brainContext, signal, config: { role: 'writer', allowFallback: true },
priorResults: { plan, writerPrep, previousValidData: state.getResult('finalReport') },
abstractionLevel: writerLevel
}),
research, prompt, signal, onProgress
);
this.validateResult(finalReport, 'Writer');
// --- Phase 4: Proactive Advisor (Astra v4.0) ---
// 리포트 작성 후, 사용자의 다음 행동을 선제적으로 제안합니다.
state.setResult('finalReport', finalReport);
// --- Phase 5: Advice & Standardization ---
const proactiveAdvice = await this.generateProactiveAdvice(finalReport, prompt, brainContext, signal);
const enrichedReport = `${finalReport}\n\n---\n## 💡 Astra의 선제적 제안 (Proactive Next Actions)\n${proactiveAdvice}`;
const standardizedReport = WikiFormatter.format(enrichedReport, state);
// 3. 지식 저장 포맷 표준화 (Standardization: Astra 피드백)
const standardizedReport = WikiFormatter.format(enrichedReport, missionId);
// 최종 결과 전역 캐싱 (Deduplication)
CacheManager.set(prompt, 'global_final_report', standardizedReport);
// [Astra v4.0] 지능적 판단 감사 (Cognition Audit)
CognitionAudit.auditPolicyCompliance('MissionComplete', standardizedReport);
this.transition(state, 'completed', '미션 완료', onProgress);
logInfo(`[AgentEngine] 미션 완료: ${missionId} (총 ${state.getElapsedMs()}ms)`);
return standardizedReport;
});
} catch (error: any) {
const { type, rule } = ErrorClassifier.classify(error);
const stageName = (state!.stage || 'unknown').toUpperCase();
this.transition(state!, 'error', `오류 발생: ${error.message}`, onProgress);
// 실패 원인 명시적 기록 (Astra 피드백: Record failure reason)
state!.setFailureReason(`[${type}] ${rule.description} - 세부원인: ${error.message}`);
// Error Recovery Matrix 기반 세분화된 로깅
switch (type) {
case ErrorType.ABORT:
logInfo(`[AgentEngine] [ABORT] 미션 취소됨 (${missionId}) at ${stageName} stage.`);
break;
case ErrorType.TRANSIENT:
logError(`[AgentEngine] [TRANSIENT] 재시도 소진 후 실패 (${missionId}) at ${stageName} stage — ${rule.description}:`, error);
break;
case ErrorType.PERMANENT:
logError(`[AgentEngine] [PERMANENT] 복구 불가 오류 (${missionId}) at ${stageName} stage — ${rule.userMessage}:`, error);
break;
}
// 감사 이력 덤프 (디버깅용)
logError(`[AgentEngine] Audit Trail for ${missionId}:\n${state!.summarizeAudit()}`);
this.handleMissionFailure(missionId, state!, error, onProgress);
throw error;
} finally {
// 3. 락 해제
release();
}
}
/**
* [Atomic Step Execution]
* 개별 작업 단계를 캡슐화하여 상태 확인, 캐시 체크, 실행, 검증을 일관되게 처리합니다.
*/
private async executeStep(
state: MissionState,
stage: PipelineStage,
progressMessage: string,
action: () => Promise<string>,
cacheKeyPrompt: string,
cacheKeyContext: string,
signal: AbortSignal,
onProgress: (stage: PipelineStage, message: string) => void
): Promise<string> {
// 1. 기존 결과 확인 (Resumption)
let result = state.getResult(stage);
if (result) {
logInfo(`[AgentEngine] [Resumption] '${stage}' 단계 결과가 이미 존재합니다.`);
return result;
}
this.transition(state, stage, progressMessage, onProgress);
this.checkAbort(signal);
// 2. 캐시 확인 (Deduplication)
const cached = CacheManager.get(cacheKeyPrompt, cacheKeyContext);
if (cached) {
logInfo(`[AgentEngine] [Deduplication] '${stage}' 단계 캐시를 사용합니다.`);
state.resilienceMetrics.deduplications++;
result = cached;
} else {
// 3. 실행
result = await action();
CacheManager.set(cacheKeyPrompt, cacheKeyContext, result);
}
// 4. 저장
state.setResult(stage, result);
return result;
}
private handleMissionFailure(missionId: string, state: MissionState, error: any, onProgress: (stage: PipelineStage, message: string) => void) {
const { type, rule } = ErrorClassifier.classify(error);
const stageName = (state.stage || 'unknown').toUpperCase();
this.transition(state, 'error', `오류 발생: ${error.message}`, onProgress);
state.setFailureReason(`[${type}] ${rule.description} - 세부원인: ${error.message}`);
logError(`[AgentEngine] [${type}] ${missionId} 실패 at ${stageName} stage: ${error.message}`);
logError(`[AgentEngine] Audit Trail:\n${state.summarizeAudit()}`);
}
/**
* @deprecated 이제 미션 상태는 로컬 스코프에서 관리됩니다.
*/
@@ -586,6 +610,7 @@ export class AgentEngine {
let lastError: any;
for (let attempt = 0; attempt <= transientRule.maxRetries; attempt++) {
if (attempt > 0) state.resilienceMetrics.retries++;
try {
// 재시도 시 사용자에게 진행 상황 알림
if (attempt > 0) {
@@ -605,6 +630,10 @@ export class AgentEngine {
const result = await agent.execute(input, amplifiedContext, signal, options);
const durationMs = Date.now() - startTime;
// [Reliability Check] 충돌 위험도 추적
const conflictScore = AgentDataValidator.validateHandoff(agentName, result);
state.resilienceMetrics.maxConflictScore = Math.max(state.resilienceMetrics.maxConflictScore, conflictScore);
PerformanceProfiler.logLLMLatency(agentName, durationMs, result.length);
return result;
@@ -629,6 +658,15 @@ export class AgentEngine {
case ErrorType.TRANSIENT:
// 일시적 오류 → 재시도 가능 여부 확인
if (attempt >= transientRule.maxRetries) {
// [Intelligent Resilience] 재시도 실패 시 대체 경로(Fallback) 시도
if (transientRule.action === 'fallback' || options?.config?.allowFallback) {
logInfo(`[AgentEngine] [FALLBACK] ${agentName} 재시도 소진. 대체 데이터(Cache/Stale) 경로를 가동합니다.`);
state.resilienceMetrics.fallbacks++;
const cached = CacheManager.get(input, context);
if (cached) return cached;
if (options?.priorResults?.['previousValidData']) return options.priorResults['previousValidData'];
}
logError(`[AgentEngine] [TRANSIENT] ${agentName} 최대 재시도 횟수(${transientRule.maxRetries}) 소진.`);
const exhaustedError = new Error(
`[${agentName}] 일시적 연결 오류가 지속됩니다. ` +
@@ -689,9 +727,9 @@ export class AgentEngine {
return clean.length > length ? clean.substring(0, length) + '...' : clean;
}
private validateResult(data: string, step: string) {
private validateResult(data: string, step: string): number {
// Error Recovery Matrix: Permanent 오류 발생을 방지하기 위한 선제적 핸드오프 검증
AgentDataValidator.validateHandoff(step, data);
return AgentDataValidator.validateHandoff(step, data);
}
/**
@@ -699,10 +737,28 @@ export class AgentEngine {
* 지식 관리 정책 v4.0을 LLM 지시사항으로 변환하여 주입합니다.
*/
private amplifyContext(context: string, options?: AgentExecuteOptions): string {
const level = options?.abstractionLevel || 'balanced';
const levelDirectives: Record<AbstractionLevel, string[]> = {
'laser-focused': [
"- [INTENSITY: HIGH] 불필요한 배경 설명과 중간 과정을 생략하고, 즉각적인 결론과 실행 가능한 To-Do 위주로 압축하십시오.",
"- [QUALITY] 정보의 양보다 '추론 기여 밀도'를 극한으로 높여 핵심 위주로 서술하십시오."
],
'balanced': [
"- [INTENSITY: MEDIUM] 핵심 논리를 중심으로 설명하되, 판단 근거가 되는 주요 데이터는 포함하십시오.",
"- [QUALITY] 지식의 양보다 '추론 기여 밀도'를 중시하여 서술하십시오."
],
'comprehensive': [
"- [INTENSITY: LOW] 전체 맥락을 파악할 수 있도록 배경 지식과 세부 데이터를 충분히 포함하십시오.",
"- [QUALITY] 포괄적인 분석을 위해 가용한 모든 데이터를 구조화하여 제공하십시오."
]
};
const policyDirectives = [
"\n### 🏛️ Knowledge Management Policy v4.0 Applied",
"\n### 🏛️ Knowledge Management Policy v4.1 (Filtering Applied)",
`- [ABSTRACTION LEVEL] 현재 에이전트 간 인터페이스 정책은 '${level}' 모드입니다.`,
...levelDirectives[level],
"- [CREDIBILITY] 정보 출처가 의도적으로 작성된 글인 경우 Medium 이상의 신뢰도를 부여하고 우선적으로 인용하십시오.",
"- [QUALITY] 지식의 양보다 '추론 기여 밀도'를 중시하여 핵심 위주로 서술하십시오.",
"- [CONFLICT] 상충되는 지식 발견 시 스스로 판단하지 말고 반드시 [CONFLICT WARNING] 플래그와 함께 두 관점을 모두 보고하십시오."
].join('\n');
+48 -38
View File
@@ -1,60 +1,70 @@
import * as vscode from 'vscode';
import { MissionState } from './engine';
export class WikiFormatter {
/**
* 최종 에이전트 출력물을 P-Reinforce v3.0 표준 포맷으로 변환합니다.
*/
public static format(content: string, missionId: string): string {
public static format(content: string, state: MissionState): string {
const now = new Date().toISOString();
const missionId = state.missionId;
// 1. Frontmatter가 없는 경우 주입
let formatted = content;
if (!content.trim().startsWith('---')) {
const frontmatter = [
'---',
`id: ${missionId}`,
`date: ${now}`,
'type: knowledge_artifact',
'standard: P-Reinforce v3.0',
'tags: [automated, connect_ai, brain_sync]',
'---',
'',
''
].join('\n');
formatted = frontmatter + content;
}
// 1. Frontmatter 주입 (항상 갱신)
const frontmatter = [
'---',
`id: ${missionId}`,
`date: ${now}`,
'type: knowledge_artifact',
'standard: P-Reinforce v3.0',
'tags: [automated, connect_ai, brain_sync]',
'---',
'',
''
].join('\n');
// 2. 필수 헤더 보정 (예: Brief Summary가 없는 경우 상단에 자동 생성 시도)
// 기존 frontmatter 제거 (있는 경우)
let cleanContent = content.replace(/^---[\s\S]*?---\n*/, '');
let formatted = frontmatter + cleanContent;
// 2. 필수 헤더 보정
if (!formatted.includes('## 📌 Brief Summary')) {
// 간단한 요약 추출 시도 (첫 문장 등)
const summary = content.split('\n').find(l => l.trim().length > 10) || '요약이 생성되지 않았습니다.';
const summary = cleanContent.split('\n').find(l => l.trim().length > 10) || '요약이 생성되지 않았습니다.';
const summarySection = `## 📌 Brief Summary\n${summary.substring(0, 200)}...\n\n`;
formatted = formatted.replace(/---\n\n/, `---\n\n${summarySection}`);
}
// 3. 코드 스니펫 섹션 보정 (Astra 피드백: 실전 구현 코드 강제)
const hasImplementationHeader = formatted.includes('## 💻 Practical Implementation') || formatted.includes('## 💻 실전 구현 코드');
if (!hasImplementationHeader) {
// 코드 블록이 이미 있다면 그 위에 헤더를 붙여줌
// 3. 코드 스니펫 섹션 보정
if (!formatted.includes('## 💻 Practical Implementation') && !formatted.includes('## 💻 실전 구현 코드')) {
if (formatted.includes('```')) {
// 첫 번째 코드 블록 앞에 헤더 삽입
formatted = formatted.replace(/```/, '\n## 💻 실전 구현 코드\n\n```');
} else {
// 코드 블록이 전혀 없는 경우 하단에 플레이스홀더 추가 (추후 보강 유도)
const boilerplatePlaceholder = [
'',
'---',
'## 💻 실전 구현 코드 (Boilerplate)',
'> [!TIP]',
'> 이 문서의 개념을 즉시 적용할 수 있는 **실전 구현 코드**나 **보일러플레이트**가 아직 포함되지 않았습니다.',
'> 에이전트에게 "React 예제 코드 포함해줘" 또는 "MSA 구조도 코드로 표현해줘"와 같이 구체적인 구현체 생성을 요청하세요.',
'',
].join('\n');
formatted += boilerplatePlaceholder;
}
}
return formatted;
// 4. [Astra v4.0] 신뢰성 보고서 (Reliability & Audit Summary)
const metrics = state.resilienceMetrics;
const totalDuration = Date.now() - state.startTime;
const reliabilitySection = [
'',
'---',
'## 🛡️ Reliability & Audit Summary',
`> [!NOTE]`,
`> 이 문서는 ConnectAI의 **Intelligent Resilience** 엔진에 의해 검증 및 정제되었습니다.`,
'',
'| Metric | Value | Status |',
'| :--- | :--- | :--- |',
`| **Conflict Risk** | \`${metrics.maxConflictScore}/100\` | ${metrics.maxConflictScore < 30 ? '✅ Low' : metrics.maxConflictScore < 70 ? '⚠️ Medium' : '🚨 High'} |`,
`| **Fallbacks Used** | \`${metrics.fallbacks}\` | ${metrics.fallbacks === 0 ? '✅ None' : '💡 Recovered'} |`,
`| **Auto Retries** | \`${metrics.retries}\` | ${metrics.retries < 3 ? '✅ Stable' : '⚠️ Unstable'} |`,
`| **Deduplication** | \`${metrics.deduplications}\` | ${metrics.deduplications > 0 ? '🚀 Optimized' : 'Standard'} |`,
`| **Processing Time** | \`${(totalDuration / 1000).toFixed(1)}s\` | ✅ Fast |`,
'',
'### 🔍 Decision Audit Trail',
state.auditTrail.map(a => `- **[${a.to.toUpperCase()}]** ${a.message} (${a.durationFromPrev}ms)`).join('\n'),
''
].join('\n');
formatted += reliabilitySection;
return formatted;
}
}
+84 -4
View File
@@ -23,15 +23,33 @@ import * as fs from 'fs';
import * as path from 'path';
// ─── Setup ───
beforeAll(() => {
const cacheDir = path.join(process.cwd(), '.astra', 'cache');
const getBaseDir = () => {
// VS Code Mocking 환경 고려
try {
const folders = require('vscode').workspace.workspaceFolders;
if (folders && folders.length > 0) return folders[0].uri.fsPath;
} catch (e) {}
return process.cwd();
};
const clearCache = () => {
const baseDir = getBaseDir();
const cacheDir = path.join(baseDir, '.astra', 'cache');
if (fs.existsSync(cacheDir)) {
fs.rmSync(cacheDir, { recursive: true, force: true });
}
const missionDir = path.join(process.cwd(), '.astra', 'missions');
const missionDir = path.join(baseDir, '.astra', 'missions');
if (fs.existsSync(missionDir)) {
fs.rmSync(missionDir, { recursive: true, force: true });
}
};
beforeAll(() => {
clearCache();
});
beforeEach(() => {
clearCache();
});
@@ -505,7 +523,7 @@ describe('Concurrency & Stress Tests', () => {
} as IAgent
);
results.push(
engine.runMission(`queue_sat_${idx}`, `Prompt ${idx}`, 'ctx', createAbortSignal(), noopProgress)
engine.runMission(`queue_sat_${Date.now()}_${idx}`, `Unique Prompt for Saturation Test ${idx}`, 'ctx', createAbortSignal(), noopProgress)
);
}
@@ -557,4 +575,66 @@ describe('Concurrency & Stress Tests', () => {
console.log(` Shared Mission ID: ${sharedMissionId}`);
console.log(` Both Completed: ✅ (Mutex serialized execution)`);
}, 30000);
test('초고부하 스트레스 테스트: 50개 미션 동시 요청 시 락 경합 및 복원력 검증', async () => {
const stressCount = 50;
const results: Promise<string>[] = [];
for (let i = 0; i < stressCount; i++) {
const engine = new AgentEngine(
new MockSuccessAgent(`Plan ${i}`),
new MockSuccessAgent(`Research ${i}`),
new MockSuccessAgent(`Report ${i}`)
);
results.push(
engine.runMission(`stress_${i}`, `Stress Prompt ${i}`, 'ctx', createAbortSignal(), noopProgress)
);
}
const outputs = await Promise.all(results);
expect(outputs).toHaveLength(stressCount);
console.log(`\n📊 [High-Stress Concurrency Test]`);
console.log(` Missions Submitted: ${stressCount}`);
console.log(` Success Rate: 100% ✅`);
}, 60000);
test('Intelligent Fallback: 재시도 실패 시 캐시된 데이터로 자동 복구되는지 검증', async () => {
const failingAgent = new MockTransientAgent(10); // 10회 실패 (max 3회 초과)
const engine = new AgentEngine(
failingAgent,
new MockSuccessAgent(),
new MockSuccessAgent()
);
// 캐시에 미리 데이터 심어두기 (Deduplication 재활용)
const testPrompt = 'Fallback Test Prompt';
const testContext = 'Fallback Context';
const expectedFallback = 'Authoritative Cache Data for Fallback';
// CacheManager는 정적 메서드를 사용하므로 직접 설정
const cacheKey = (engine as any).constructor.name === 'AgentEngine' ? 'test_cache_key' : 'other';
// 실제 CacheManager 사용을 위해 mock 대신 파일 시스템 시뮬레이션은 생략하고 로직 흐름만 검증
// resilientExecute의 fallback 로직이 allowFallback 옵션에 반응하는지 테스트
const options: AgentExecuteOptions = {
config: { allowFallback: true },
priorResults: { previousValidData: expectedFallback }
};
const result = await (engine as any).resilientExecute(
new MissionState('fallback_test'),
failingAgent,
'FailingAgent',
testPrompt,
testContext,
createAbortSignal(),
noopProgress,
options
);
expect(result).toBe(expectedFallback);
console.log(`\n📊 [Intelligent Fallback Test]`);
console.log(` External Failure: Simulated`);
console.log(` Recovery Path: previousValidData ✅`);
}, 20000);
});