compact 직후 도구 결과 축약과 후속 루프 보강
Some checks failed
Release Gate / gate (push) Has been cancelled

- claw-code post-autocompact turn 흐름을 참고해 AX AgentLoop가 compact 직후 첫 턴의 도구 결과를 별도 정책으로 축약하도록 보강함

- process/build_run/test_loop/git/http 계열의 긴 결과는 head/tail 중심 post-compaction 요약으로 피드백해 freshly compacted context가 다시 커지는 현상을 줄임

- compact 로그 축약 건수와 compact 결과 축약 건수를 전체 통계에 반영하고 README.md, docs/DEVELOPMENT.md에 2026-04-05 00:24 (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:19:00 +09:00
parent dec288d8f1
commit d40b80ee96
4 changed files with 68 additions and 2 deletions

View File

@@ -11,6 +11,7 @@ Windows 전용 시맨틱 런처 & 워크스페이스 매니저
- 업데이트: 2026-04-05 00:17 (KST) - 업데이트: 2026-04-05 00:17 (KST)
- AX Agent 루프도 `claw-code`의 post-autocompact turn tracking 흐름을 참고해 compact 직후 턴을 별도 상태로 추적하도록 보강했습니다. 이제 compact 직후 첫 턴은 저노이즈 compact pill 중심으로 보이고, 불필요한 `LLM 요청 중`류 Thinking 로그는 자동으로 줄입니다. - AX Agent 루프도 `claw-code`의 post-autocompact turn tracking 흐름을 참고해 compact 직후 턴을 별도 상태로 추적하도록 보강했습니다. 이제 compact 직후 첫 턴은 저노이즈 compact pill 중심으로 보이고, 불필요한 `LLM 요청 중`류 Thinking 로그는 자동으로 줄입니다.
- 개발자용 전체 통계에는 compact 직후 자동 축약된 Thinking 로그 건수도 함께 표시해, compact 이후 루프가 실제로 얼마나 조용해졌는지 바로 확인할 수 있게 했습니다. - 개발자용 전체 통계에는 compact 직후 자동 축약된 Thinking 로그 건수도 함께 표시해, compact 이후 루프가 실제로 얼마나 조용해졌는지 바로 확인할 수 있게 했습니다.
- compact 직후 첫 턴의 `process/build_run/test_loop/git/http` 계열 도구 결과는 head/tail 중심의 post-compaction 요약으로 더 짧게 전달해, 압축 직후 문맥이 다시 길어지는 현상을 줄였습니다.
- 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\` 경고 0 / 오류 0 - 검증: `dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\` 경고 0 / 오류 0
- 업데이트: 2026-04-04 23:47 (KST) - 업데이트: 2026-04-04 23:47 (KST)

View File

@@ -1,6 +1,7 @@
# AX Copilot - 媛쒕컻 臾몄꽌 # AX Copilot - 媛쒕컻 臾몄꽌
- Document update: 2026-04-05 00:17 (KST) - Upgraded the AX Agent loop to track the first post-compaction turn explicitly, mirroring claw-code's post-autocompact turn handling so compact-follow-up state now survives into the next loop turn instead of ending at the condenser call. - Document update: 2026-04-05 00:17 (KST) - Upgraded the AX Agent loop to track the first post-compaction turn explicitly, mirroring claw-code's post-autocompact turn handling so compact-follow-up state now survives into the next loop turn instead of ending at the condenser call.
- Document update: 2026-04-05 00:24 (KST) - Added compact-aware tool result trimming for the first post-compaction turn so heavy `process`, `build_run`, `test_loop`, `git_tool`, `http_tool`, and similar outputs are fed back to the loop as head/tail summaries instead of re-expanding the freshly compacted context.
- Document update: 2026-04-05 00:17 (KST) - Added post-compaction thinking suppression for boilerplate loop events (`LLM request`, prompt submit, free-tier wait) during the first compact-follow-up turn, while still surfacing a compact-specific lightweight pill and compact-noise counts in total stats. - Document update: 2026-04-05 00:17 (KST) - Added post-compaction thinking suppression for boilerplate loop events (`LLM request`, prompt submit, free-tier wait) during the first compact-follow-up turn, while still surfacing a compact-specific lightweight pill and compact-noise counts in total stats.

View File

@@ -56,6 +56,7 @@ public partial class AgentLoopService
private int _runPostCompactionSuppressedThinkingCount; private int _runPostCompactionSuppressedThinkingCount;
private string _runLastCompactionStageSummary = ""; private string _runLastCompactionStageSummary = "";
private int _runLastCompactionSavedTokens; private int _runLastCompactionSavedTokens;
private int _runPostCompactionToolResultCompactions;
/// <summary>일시정지 제어용 세마포어. 1이면 진행, 0이면 대기.</summary> /// <summary>일시정지 제어용 세마포어. 1이면 진행, 0이면 대기.</summary>
private readonly SemaphoreSlim _pauseSemaphore = new(1, 1); private readonly SemaphoreSlim _pauseSemaphore = new(1, 1);
@@ -1338,7 +1339,9 @@ public partial class AgentLoopService
// 도구 결과를 LLM에 피드백 // 도구 결과를 LLM에 피드백
messages.Add(LlmService.CreateToolResultMessage( messages.Add(LlmService.CreateToolResultMessage(
effectiveCall.ToolId, effectiveCall.ToolName, TruncateOutput(result.Output, 4000))); effectiveCall.ToolId,
effectiveCall.ToolName,
BuildLoopToolResultMessage(effectiveCall, result, runState)));
if (TryHandleReadOnlyStagnationTransition( if (TryHandleReadOnlyStagnationTransition(
consecutiveReadOnlySuccessTools, consecutiveReadOnlySuccessTools,
@@ -1459,6 +1462,7 @@ public partial class AgentLoopService
_runPostCompactionSuppressedThinkingCount = 0; _runPostCompactionSuppressedThinkingCount = 0;
_runLastCompactionStageSummary = ""; _runLastCompactionStageSummary = "";
_runLastCompactionSavedTokens = 0; _runLastCompactionSavedTokens = 0;
_runPostCompactionToolResultCompactions = 0;
// 일시정지 상태 리셋 // 일시정지 상태 리셋
if (IsPaused) if (IsPaused)
@@ -1503,10 +1507,13 @@ public partial class AgentLoopService
var compactNoiseSummary = _runPostCompactionSuppressedThinkingCount > 0 var compactNoiseSummary = _runPostCompactionSuppressedThinkingCount > 0
? $" | compact 로그 축약 {_runPostCompactionSuppressedThinkingCount}건" ? $" | compact 로그 축약 {_runPostCompactionSuppressedThinkingCount}건"
: ""; : "";
var compactToolResultSummary = _runPostCompactionToolResultCompactions > 0
? $" | compact 결과 축약 {_runPostCompactionToolResultCompactions}건"
: "";
var topFailed = BuildTopFailureSummary(failedToolHistogram); var topFailed = BuildTopFailureSummary(failedToolHistogram);
var summary = $"📊 전체 통계: LLM {iteration}회 호출 | 도구 {totalToolCalls}회 (성공 {statsSuccessCount}, 실패 {statsFailCount}) | " + var summary = $"📊 전체 통계: LLM {iteration}회 호출 | 도구 {totalToolCalls}회 (성공 {statsSuccessCount}, 실패 {statsFailCount}) | " +
$"토큰 {statsInputTokens:N0}→{statsOutputTokens:N0} (합계 {totalTokens:N0}) | " + $"토큰 {statsInputTokens:N0}→{statsOutputTokens:N0} (합계 {totalTokens:N0}) | " +
$"소요 {durationSec:F1}초 | 재시도 품질 {retryQuality} (복구 {statsRecoveredAfterFailure}, 차단 {statsRepeatedFailureBlocks}){compactNoiseSummary} | " + $"소요 {durationSec:F1}초 | 재시도 품질 {retryQuality} (복구 {statsRecoveredAfterFailure}, 차단 {statsRepeatedFailureBlocks}){compactNoiseSummary}{compactToolResultSummary} | " +
$"실패 상위: {topFailed} | 사용 도구: {toolList}"; $"실패 상위: {topFailed} | 사용 도구: {toolList}";
EmitEvent(AgentEventType.StepDone, "total_stats", summary); EmitEvent(AgentEventType.StepDone, "total_stats", summary);
} }
@@ -4446,6 +4453,7 @@ public partial class AgentLoopService
_runPostCompactionTurnCounter = runState.PostCompactionTurnCounter; _runPostCompactionTurnCounter = runState.PostCompactionTurnCounter;
_runLastCompactionStageSummary = runState.LastCompactionStageSummary ?? ""; _runLastCompactionStageSummary = runState.LastCompactionStageSummary ?? "";
_runLastCompactionSavedTokens = runState.LastCompactionSavedTokens; _runLastCompactionSavedTokens = runState.LastCompactionSavedTokens;
_runPostCompactionToolResultCompactions = runState.PostCompactionToolResultCompactions;
} }
private bool ShouldSuppressPostCompactionThinking(string summary) private bool ShouldSuppressPostCompactionThinking(string summary)
@@ -4466,6 +4474,61 @@ public partial class AgentLoopService
|| summary.StartsWith("컨텍스트 압축 완료", StringComparison.Ordinal); || summary.StartsWith("컨텍스트 압축 완료", StringComparison.Ordinal);
} }
private string BuildLoopToolResultMessage(LlmService.ContentBlock call, ToolResult result, RunState runState)
{
var output = result.Output ?? "";
if (!ShouldCompactToolResultForPostCompactionTurn(runState, call.ToolName, output))
return TruncateOutput(output, 4000);
runState.PostCompactionToolResultCompactions++;
SyncRunPostCompactionState(runState);
var compacted = CompactToolResultForPostCompaction(call.ToolName, output);
var stage = string.IsNullOrWhiteSpace(runState.LastCompactionStageSummary)
? "compact"
: runState.LastCompactionStageSummary;
return $"[POST_COMPACTION_TOOL_RESULT:{call.ToolName}] {stage}\n{compacted}";
}
private static bool ShouldCompactToolResultForPostCompactionTurn(RunState runState, string toolName, string output)
{
if (runState.PostCompactionTurnCounter != 1)
return false;
if (string.IsNullOrWhiteSpace(output))
return false;
if (output.Length < 900)
return false;
return toolName is "process" or "build_run" or "test_loop" or "snippet_runner" or "git_tool" or "http_tool"
|| output.Length > 1800;
}
private static string CompactToolResultForPostCompaction(string toolName, string output)
{
var normalized = output.Replace("\r\n", "\n");
var lines = normalized.Split('\n', StringSplitOptions.None)
.Select(line => line.TrimEnd())
.Where(line => !string.IsNullOrWhiteSpace(line))
.ToList();
if (lines.Count == 0)
return TruncateOutput(output, 1200);
var headCount = toolName is "build_run" or "test_loop" or "process" ? 6 : 4;
var tailCount = toolName is "build_run" or "test_loop" or "process" ? 4 : 3;
var kept = new List<string>();
kept.AddRange(lines.Take(headCount));
if (lines.Count > headCount + tailCount)
kept.Add($"...[post-compact snip: {lines.Count - headCount - tailCount:N0}줄 생략]...");
if (lines.Count > headCount)
kept.AddRange(lines.Skip(Math.Max(headCount, lines.Count - tailCount)));
var compacted = string.Join("\n", kept.Distinct(StringComparer.Ordinal));
return TruncateOutput(compacted, 1400);
}
/// <summary>영향 범위 기반 의사결정 체크. 확인이 필요하면 메시지를 반환, 불필요하면 null.</summary> /// <summary>영향 범위 기반 의사결정 체크. 확인이 필요하면 메시지를 반환, 불필요하면 null.</summary>
private string? CheckDecisionRequired(LlmService.ContentBlock call, AgentContext context) private string? CheckDecisionRequired(LlmService.ContentBlock call, AgentContext context)
{ {

View File

@@ -1511,6 +1511,7 @@ public partial class AgentLoopService
public int PostCompactionTurnCounter; public int PostCompactionTurnCounter;
public string LastCompactionStageSummary = ""; public string LastCompactionStageSummary = "";
public int LastCompactionSavedTokens; public int LastCompactionSavedTokens;
public int PostCompactionToolResultCompactions;
} }
} }