compact 후속 루프 추적과 로그 축약 보강
Some checks failed
Release Gate / gate (push) Has been cancelled

- claw-code post-autocompact turn tracking 흐름을 참고해 AX AgentLoop가 compact 직후 첫 턴을 별도 상태로 추적하도록 보강함

- compact 이후 첫 턴에서는 LLM 요청 중·프롬프트 제출·무료 티어 대기 같은 boilerplate Thinking 로그를 억제하고 compact pill 중심으로 보이도록 정리함

- 개발자용 전체 통계에 compact 로그 축약 건수를 포함하고 README.md, docs/DEVELOPMENT.md에 2026-04-05 00:17 (KST) 기준 이력을 반영함

- 검증: dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\ / 경고 0 / 오류 0
This commit is contained in:
2026-04-05 00:12:28 +09:00
parent 6cc79cf3e5
commit dec288d8f1
4 changed files with 2827 additions and 2729 deletions

View File

@@ -51,6 +51,11 @@ public partial class AgentLoopService
/// <summary>문서 생성 폴백 재시도 여부 (루프당 1회만).</summary>
private bool _docFallbackAttempted;
private string _currentRunId = "";
private bool _runPendingPostCompactionTurn;
private int _runPostCompactionTurnCounter;
private int _runPostCompactionSuppressedThinkingCount;
private string _runLastCompactionStageSummary = "";
private int _runLastCompactionSavedTokens;
/// <summary>일시정지 제어용 세마포어. 1이면 진행, 0이면 대기.</summary>
private readonly SemaphoreSlim _pauseSemaphore = new(1, 1);
@@ -437,7 +442,7 @@ public partial class AgentLoopService
// Context Condenser: 토큰 초과 시 이전 대화 자동 압축
// 첫 반복에서도 실행 (이전 대화 복원으로 이미 긴 경우 대비)
{
var condensed = await ContextCondenser.CondenseIfNeededAsync(
var compactionResult = await ContextCondenser.CondenseWithStatsAsync(
messages,
_llm,
llm.MaxContextTokens,
@@ -445,8 +450,17 @@ public partial class AgentLoopService
llm.ContextCompactTriggerPercent,
false,
ct);
if (condensed)
EmitEvent(AgentEventType.Thinking, "", "컨텍스트 압축 완료 — 입력 토큰을 절감했습니다");
if (compactionResult.Changed)
{
MarkRunPostCompaction(compactionResult, runState);
var compactSummary = !string.IsNullOrWhiteSpace(compactionResult.StageSummary)
? compactionResult.StageSummary
: "기본";
EmitEvent(
AgentEventType.Thinking,
"",
$"컨텍스트 압축 완료 — {compactSummary} · {Services.TokenEstimator.Format(compactionResult.SavedTokens)} tokens 절감");
}
}
EmitEvent(AgentEventType.Thinking, "", $"LLM에 요청 중... (반복 {iteration}/{maxIterations})");
@@ -497,6 +511,7 @@ public partial class AgentLoopService
runState);
runState.ContextRecoveryAttempts = 0;
runState.TransientLlmErrorRetries = 0;
NotifyPostCompactionTurnIfNeeded(runState);
}
catch (NotSupportedException)
{
@@ -1439,6 +1454,11 @@ public partial class AgentLoopService
IsRunning = false;
_currentRunId = "";
_runPendingPostCompactionTurn = false;
_runPostCompactionTurnCounter = 0;
_runPostCompactionSuppressedThinkingCount = 0;
_runLastCompactionStageSummary = "";
_runLastCompactionSavedTokens = 0;
// 일시정지 상태 리셋
if (IsPaused)
@@ -1480,10 +1500,13 @@ public partial class AgentLoopService
var retryQuality = retryTotal > 0
? $"{(statsRecoveredAfterFailure * 100.0 / retryTotal):F0}%"
: "100%";
var compactNoiseSummary = _runPostCompactionSuppressedThinkingCount > 0
? $" | compact 로그 축약 {_runPostCompactionSuppressedThinkingCount}건"
: "";
var topFailed = BuildTopFailureSummary(failedToolHistogram);
var summary = $"📊 전체 통계: LLM {iteration}회 호출 | 도구 {totalToolCalls}회 (성공 {statsSuccessCount}, 실패 {statsFailCount}) | " +
$"토큰 {statsInputTokens:N0}→{statsOutputTokens:N0} (합계 {totalTokens:N0}) | " +
$"소요 {durationSec:F1}초 | 재시도 품질 {retryQuality} (복구 {statsRecoveredAfterFailure}, 차단 {statsRepeatedFailureBlocks}) | " +
$"소요 {durationSec:F1}초 | 재시도 품질 {retryQuality} (복구 {statsRecoveredAfterFailure}, 차단 {statsRepeatedFailureBlocks}){compactNoiseSummary} | " +
$"실패 상위: {topFailed} | 사용 도구: {toolList}";
EmitEvent(AgentEventType.StepDone, "total_stats", summary);
}
@@ -4339,6 +4362,12 @@ public partial class AgentLoopService
long elapsedMs = 0, int inputTokens = 0, int outputTokens = 0,
string? toolInput = null, int iteration = 0, bool? successOverride = null)
{
if (type == AgentEventType.Thinking && ShouldSuppressPostCompactionThinking(summary))
{
_runPostCompactionSuppressedThinkingCount++;
return;
}
// AgentLogLevel에 따라 이벤트 필터링
var logLevel = _settings.Settings.Llm.AgentLogLevel;
@@ -4381,6 +4410,62 @@ public partial class AgentLoopService
}
}
private void MarkRunPostCompaction(ContextCompactionResult result, RunState runState)
{
runState.PendingPostCompactionTurn = true;
runState.PostCompactionTurnCounter = 0;
runState.LastCompactionStageSummary = result.StageSummary;
runState.LastCompactionSavedTokens = result.SavedTokens;
SyncRunPostCompactionState(runState);
}
private void NotifyPostCompactionTurnIfNeeded(RunState runState)
{
if (!runState.PendingPostCompactionTurn)
return;
runState.PendingPostCompactionTurn = false;
runState.PostCompactionTurnCounter++;
SyncRunPostCompactionState(runState);
var stage = string.IsNullOrWhiteSpace(runState.LastCompactionStageSummary)
? "기본"
: runState.LastCompactionStageSummary;
var saved = runState.LastCompactionSavedTokens > 0
? $" · {Services.TokenEstimator.Format(runState.LastCompactionSavedTokens)} tokens 절감"
: "";
EmitEvent(
AgentEventType.Thinking,
"",
$"compact 이후 {runState.PostCompactionTurnCounter}번째 턴 · {stage}{saved}");
}
private void SyncRunPostCompactionState(RunState runState)
{
_runPendingPostCompactionTurn = runState.PendingPostCompactionTurn;
_runPostCompactionTurnCounter = runState.PostCompactionTurnCounter;
_runLastCompactionStageSummary = runState.LastCompactionStageSummary ?? "";
_runLastCompactionSavedTokens = runState.LastCompactionSavedTokens;
}
private bool ShouldSuppressPostCompactionThinking(string summary)
{
if (string.IsNullOrWhiteSpace(summary))
return false;
var postCompactActive = _runPendingPostCompactionTurn || _runPostCompactionTurnCounter > 0;
if (!postCompactActive)
return false;
if (_runPostCompactionTurnCounter > 1)
return false;
return summary.StartsWith("LLM에 요청 중", StringComparison.Ordinal)
|| summary.StartsWith("사용자 프롬프트 제출", StringComparison.Ordinal)
|| summary.StartsWith("무료 티어 모드", StringComparison.Ordinal)
|| summary.StartsWith("컨텍스트 압축 완료", StringComparison.Ordinal);
}
/// <summary>영향 범위 기반 의사결정 체크. 확인이 필요하면 메시지를 반환, 불필요하면 null.</summary>
private string? CheckDecisionRequired(LlmService.ContentBlock call, AgentContext context)
{

View File

@@ -1507,6 +1507,10 @@ public partial class AgentLoopService
public int DocumentVerificationGateRetry;
public int NoProgressRecoveryRetry;
public int TerminalEvidenceGateRetry;
public bool PendingPostCompactionTurn;
public int PostCompactionTurnCounter;
public string LastCompactionStageSummary = "";
public int LastCompactionSavedTokens;
}
}