v2.2.308~310: Standing Rules + 조사 파이프라인 + LM Studio 추론 노출 수리
v2.2.308 — Standing Rules (Correction Loop ④): 행동/스타일 교정의 세션 영속화
- 행동 지적 감지("또 ~하네", "하지 말라고 했잖아" 등 5종) → LLM 이 명령형 규칙으로
정규화 → .astra/growth/standing-rules.json 즉시 저장 → 매 턴 보호 구역 주입.
self-reflection 이 컨텍스트 창(단기 기억)에 갇혀 새 세션에서 증발하던 문제 해결.
- 중복 지적은 hits 증가(토큰 자카드), 상한 10개 초과 시 자동 은퇴, 재지적 시 부활.
- "Astra: 상시 행동 규칙" 커맨드 — 활성/은퇴 규칙 + 실제 주입 블록 열람(도달 증거).
v2.2.309 — 조사 파이프라인: 파일명만 보고 상상하는 헛조사 4중 차단
- <investigate_files path focus> 액션: 폴더의 텍스트 파일을 코드가 강제 정독해
파일별 노트 주입(Map-Reduce). 내용 해시 캐시(.astra/cache/investigate)로 재조사
시 재사용 — 온디맨드 지식화.
- 조사 증거 게이트(행동 제약) + Hollow Investigation 감지(list만 하고 read 0회로
파일 서술 시 경고 footer — continuation 경로 포함) + actionStats turn 추적.
- g1nation.investigationModel: 조사형 요청만 상위 모델(예: claude-code:sonnet) 라우팅.
v2.2.310 — LM Studio LSEP 합성 추론 구분자 처리
- 일부 모델 조합에서 추론이 여는 마커 없이 content 앞에 흐르고
__LM_STUDIO_INTERNAL_LSEP_SYNTHETIC_REASONING_END_<hex>__ 로만 경계 표시 →
생각 과정 전체가 채팅에 노출되던 문제.
- LiveReasoningFilter: 마커 감지 시 streamReplace 신호로 화면 즉시 리셋(토큰 분절
대응 holdback 포함), sanitizeAssistantContent: 마커 이전 추론 최종 제거(이중 방어).
기타: tests/wikiSave.test.ts 상대경로 테스트 Windows 호환 수정(드라이브 문자).
검증: 전체 테스트 926건 통과 (신규 39건: standingRules 15 + investigationPipeline 16 + LSEP 8).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+66
-9
@@ -24,7 +24,8 @@ import { isSelfAssessRequest, isAboutSelf, buildSelfAssessContext } from './lib/
|
||||
import { ensureFeatureInventory } from './extension/featureInventory';
|
||||
import { buildUrlContext } from './lib/contextBuilders/urlContext';
|
||||
import { extractUrls } from './features/web/webFetch';
|
||||
import { looksLikeCorrection, captureCorrection } from './intelligence/correctionLoop';
|
||||
import { looksLikeCorrection, looksLikeBehaviorComplaint, captureCorrection } from './intelligence/correctionLoop';
|
||||
import { isLocalInvestigationPrompt, detectHollowInvestigation, formatHollowInvestigationFooter } from './intelligence/investigationPipeline';
|
||||
import { shouldUseMultiAgentWorkflow } from './lib/contextBuilders/multiAgentRouting';
|
||||
import { buildThinkingPartnerResponseContract } from './lib/contextBuilders/thinkingPartnerContract';
|
||||
import { buildDroppedHistorySummary } from './lib/contextBuilders/droppedHistorySummary';
|
||||
@@ -163,6 +164,7 @@ import { applyRunCommandActions } from './agent/actions/runCommand';
|
||||
import { applyCalculateActions } from './agent/actions/calculate';
|
||||
import { applyRunCodeActions } from './agent/actions/runCode';
|
||||
import { applyListFilesActions } from './agent/actions/listFiles';
|
||||
import { applyInvestigateFilesActions } from './agent/actions/investigateFiles';
|
||||
import { applyWebFetchActions } from './agent/actions/webFetch';
|
||||
import { applyBrainOpsActions } from './agent/actions/brainOps';
|
||||
import { applyCalendarActions } from './agent/actions/calendar';
|
||||
@@ -316,6 +318,10 @@ export class AgentExecutor {
|
||||
selfCheckSources: Array<{ title: string; excerpt: string }>;
|
||||
/** Confidence Engine 검색 신호 (Phase 2) — memoryContext 가 채움. */
|
||||
confidenceSignals: import('./intelligence/confidenceEngine').RetrievalConfidenceSignals | null;
|
||||
/** [v2.2.309] 이번 turn 의 액션 실행 통계 — Hollow Investigation 감지용 (loop depth 누적). */
|
||||
actionStats: { reads: number; lists: number; investigates: number };
|
||||
/** [v2.2.309] 조사 턴 모델 오버라이드 — depth 0 에서 결정, continuation 에도 유지. */
|
||||
investigationModelOverride: string | null;
|
||||
} = {
|
||||
retrieval: null,
|
||||
lessons: [],
|
||||
@@ -323,6 +329,8 @@ export class AgentExecutor {
|
||||
dynamicBlocks: new Map(),
|
||||
selfCheckSources: [],
|
||||
confidenceSignals: null,
|
||||
actionStats: { reads: 0, lists: 0, investigates: 0 },
|
||||
investigationModelOverride: null,
|
||||
};
|
||||
|
||||
/** Per-turn state 일괄 정리. turn 시작/abort/load session 시 호출. */
|
||||
@@ -333,6 +341,10 @@ export class AgentExecutor {
|
||||
this._turnCtx.dynamicBlocks.clear();
|
||||
this._turnCtx.selfCheckSources = [];
|
||||
this._turnCtx.confidenceSignals = null;
|
||||
// actionStats / investigationModelOverride 는 여기서 리셋하지 않는다 —
|
||||
// resetTurnContext 는 continuation depth 에서도 호출되는데(메모리 컨텍스트 재구축),
|
||||
// 이 둘은 사용자 turn 전체(모든 depth)에 걸쳐 누적/유지되어야 한다.
|
||||
// 리셋은 loopDepth === 0 진입부에서만 (handlePrompt 초입).
|
||||
}
|
||||
|
||||
private readonly options: AgentExecutorOptions;
|
||||
@@ -545,6 +557,9 @@ export class AgentExecutor {
|
||||
// buildMemoryContext, the previous turn's value would otherwise leak into this turn's
|
||||
// "참조 범위" footer (the exact "안녕 → 🔎 참조: 에피소드기억" bug).
|
||||
this.resetTurnContext();
|
||||
// [v2.2.309] turn 전체(모든 depth) 누적 상태 — depth 0 에서만 초기화.
|
||||
this._turnCtx.actionStats = { reads: 0, lists: 0, investigates: 0 };
|
||||
this._turnCtx.investigationModelOverride = null;
|
||||
}
|
||||
|
||||
// 1. Prepare Context
|
||||
@@ -618,6 +633,7 @@ export class AgentExecutor {
|
||||
- 이 워크스페이스의 코드·문서·기능에 대한 주장은 *이 대화에서 실제로 읽은 파일*에만 근거하라.
|
||||
- 확인하지 않은 구현을 "~로 보입니다", "~일 것입니다"라고 추측 서술하는 것은 금지. 먼저 <list_files path="..."/> 와 <read_file path="..."/> 태그로 관련 파일을 직접 열어 확인한 뒤 답하라. 태그를 emit 하면 시스템이 파일 내용을 주입하고 자동으로 이어서 답변하게 된다.
|
||||
- ⚠️ "소스 코드 확인이 필요합니다"라고 말만 하고 끝내는 것은 금지다. 확인이 필요하다고 판단했다면 *바로 이 답변 안에서* <list_files>/<read_file> 태그를 emit 하라 — 그것이 확인하는 방법이다. 태그로 접근 불가능한 대상(외부 시스템·미설치 도구 등)에 한해서만 "확인하지 못함"으로 명시하라.
|
||||
- 폴더 전체를 조사해야 하면 파일을 하나씩 읽지 말고 <investigate_files path="상대경로" focus="조사 목적"/> 태그 하나를 emit 하라 — 시스템이 폴더의 모든 텍스트 파일을 실제로 읽어 파일별 노트를 주입한다. list_files 로 받은 *파일명*만 보고 내용을 서술하는 것은 날조다.
|
||||
- "X 기능을 추가하라"고 제안하기 전에 그 기능이 이미 구현돼 있는지 해당 모듈을 찾아 읽어라. 이미 있는 기능을 새로 만들라고 제안하는 것은 잘못된 분석이다.
|
||||
- 일반론·추측으로 빈칸을 채우지 마라.`;
|
||||
}
|
||||
@@ -637,24 +653,28 @@ export class AgentExecutor {
|
||||
}
|
||||
}
|
||||
|
||||
// [Correction Loop ①] 이 발화가 직전 답변에 대한 *정정*이면 fire-and-forget
|
||||
// 캡처 — 오류 분류 → 태깅 레슨 + 회귀 케이스(.astra/eval/corrections.jsonl).
|
||||
// 정정 자체가 Ground Truth 가 되어 주간 회귀 테스트·약점 프로필의 원료가 된다.
|
||||
// [Correction Loop ①] 이 발화가 직전 답변에 대한 *정정*(사실) 또는 *행동/스타일
|
||||
// 지적*("또 ~하네", "하지 말라고 했잖아")이면 fire-and-forget 캡처 — 오류 분류 →
|
||||
// 태깅 레슨 + 회귀 케이스. 행동 지적은 추가로 Standing Rule 로 정규화되어
|
||||
// 다음 턴부터(새 세션 포함) 매 턴 주입된다 (v2.2.308 — 세션 휘발 문제 해결).
|
||||
// 턴 응답을 막지 않는다 (await 없음).
|
||||
if (prompt && loopDepth === 0 && activeBrain?.localBrainPath && looksLikeCorrection(prompt)) {
|
||||
const isFactCorrection = !!(prompt && loopDepth === 0 && activeBrain?.localBrainPath && looksLikeCorrection(prompt));
|
||||
const isBehaviorComplaint = !!(prompt && loopDepth === 0 && activeBrain?.localBrainPath && looksLikeBehaviorComplaint(prompt));
|
||||
if (isFactCorrection || isBehaviorComplaint) {
|
||||
const visible = this.chatHistory.filter(m => !m.internal);
|
||||
const lastAssistant = [...visible].reverse().find(m => m.role === 'assistant');
|
||||
const lastUserIdx = lastAssistant ? visible.lastIndexOf(lastAssistant) - 1 : -1;
|
||||
const priorQuestion = lastUserIdx >= 0 && visible[lastUserIdx]?.role === 'user' ? visible[lastUserIdx].content : '';
|
||||
if (lastAssistant && priorQuestion) {
|
||||
void captureCorrection({
|
||||
brainPath: activeBrain.localBrainPath,
|
||||
brainPath: activeBrain!.localBrainPath,
|
||||
question: priorQuestion,
|
||||
wrongAnswer: lastAssistant.content,
|
||||
correction: prompt,
|
||||
correction: prompt!,
|
||||
llm: { baseUrl: config.ollamaUrl, model: configDefaultModel },
|
||||
behaviorHint: isBehaviorComplaint,
|
||||
}).then(file => {
|
||||
if (file) logInfo('Correction Loop: 정정 캡처 완료.', { lesson: file });
|
||||
if (file) logInfo('Correction Loop: 정정 캡처 완료.', { lesson: file, behavior: isBehaviorComplaint });
|
||||
}).catch((e: any) => logError('Correction Loop 캡처 실패 (무시).', { error: e?.message ?? String(e) }));
|
||||
}
|
||||
}
|
||||
@@ -670,7 +690,18 @@ export class AgentExecutor {
|
||||
}
|
||||
|
||||
// 3. API Request Setup (라인 229에서 이미 추출한 ollamaUrl, configDefaultModel 재사용)
|
||||
const actualModel = (modelName && modelName.trim()) || configDefaultModel;
|
||||
let actualModel = (modelName && modelName.trim()) || configDefaultModel;
|
||||
// [v2.2.309-D] 조사형 요청은 설정된 상위 모델로 라우팅 (예: "claude-code:sonnet").
|
||||
// 소형 로컬 모델의 lazy tool-use 할루시네이션 대책의 마지막 층 — depth 0 에서
|
||||
// 결정하고 continuation(액션 결과로 이어지는 답변)에도 같은 모델을 유지한다.
|
||||
if (loopDepth === 0 && prompt && config.investigationModel
|
||||
&& isLocalInvestigationPrompt(prompt)) {
|
||||
this._turnCtx.investigationModelOverride = config.investigationModel;
|
||||
logInfo('조사 요청 감지 — investigationModel 로 라우팅.', { model: config.investigationModel });
|
||||
}
|
||||
if (this._turnCtx.investigationModelOverride) {
|
||||
actualModel = this._turnCtx.investigationModelOverride;
|
||||
}
|
||||
// Bound the in-memory history before building the request — shrinks bulky
|
||||
// older tool-result bodies and drops the oldest messages past the cap.
|
||||
capChatHistory(this.chatHistory, {
|
||||
@@ -994,6 +1025,12 @@ export class AgentExecutor {
|
||||
const liveFilter = postLiveDeltas ? new LiveReasoningFilter() : null;
|
||||
const postLiveToken = (token: string) => {
|
||||
const visible = liveFilter ? liveFilter.push(token) : token;
|
||||
// [v2.2.310] LM Studio LSEP 구분자 — 여는 마커 없이 흐른 추론이 화면에
|
||||
// 이미 표시된 상태. streamReplace 로 걷어내고 실제 답변부터 다시.
|
||||
if (liveFilter?.consumeSeparatorSignal()) {
|
||||
this.webview?.postMessage({ type: 'streamReplace', value: visible });
|
||||
return;
|
||||
}
|
||||
if (visible) this.webview?.postMessage({ type: 'streamChunk', value: visible });
|
||||
};
|
||||
|
||||
@@ -1499,6 +1536,7 @@ export class AgentExecutor {
|
||||
engine,
|
||||
selfCheckSources: this._turnCtx.selfCheckSources,
|
||||
confidenceSignals: this._turnCtx.confidenceSignals,
|
||||
actionStats: this._turnCtx.actionStats,
|
||||
callNonStreaming: (p) => this.callNonStreaming(p),
|
||||
getAbortSignal: () => this.abortController?.signal,
|
||||
getWebview: () => this.webview,
|
||||
@@ -1508,6 +1546,16 @@ export class AgentExecutor {
|
||||
});
|
||||
} else {
|
||||
this.webview.postMessage({ type: 'streamChunk', value: finalAssistantContent });
|
||||
// [v2.2.309] Hollow Investigation — 액션 turn 의 최종 답변(continuation)은
|
||||
// post-answer hooks(depth 0 전용)를 타지 않으므로 여기서 직접 검사한다.
|
||||
// "list_files 만 하고 read/investigate 없이 파일 여러 개를 서술" = 헛조사.
|
||||
try {
|
||||
const hollowInv = detectHollowInvestigation(finalAssistantContent, this._turnCtx.actionStats);
|
||||
if (hollowInv.hollow) {
|
||||
this.webview.postMessage({ type: 'streamChunk', value: formatHollowInvestigationFooter(hollowInv.fileMentions) });
|
||||
logInfo('Hollow Investigation 감지 (continuation).', { files: hollowInv.fileMentions, stats: this._turnCtx.actionStats });
|
||||
}
|
||||
} catch { /* 감지 실패가 답변을 막지 않음 */ }
|
||||
}
|
||||
|
||||
} catch (error: any) {
|
||||
@@ -1766,6 +1814,15 @@ export class AgentExecutor {
|
||||
await applyCalculateActions(ctx);
|
||||
await applyRunCodeActions(ctx);
|
||||
await applyListFilesActions(ctx);
|
||||
await applyInvestigateFilesActions(ctx);
|
||||
|
||||
// [v2.2.309] Hollow Investigation 감지용 액션 통계 (report 마커 기반, turn 누적).
|
||||
for (const line of report) {
|
||||
if (line.startsWith('📖 Read:')) this._turnCtx.actionStats.reads++;
|
||||
else if (line.startsWith('📂 Listed:')) this._turnCtx.actionStats.lists++;
|
||||
else if (line.startsWith('🔎 Investigated:')) this._turnCtx.actionStats.investigates++;
|
||||
}
|
||||
|
||||
await applyWebFetchActions(ctx);
|
||||
await applyBrainOpsActions(ctx);
|
||||
await applyCalendarActions(ctx);
|
||||
|
||||
Reference in New Issue
Block a user