Files
AX-Copilot-Codex/src/AxCopilot/Services/Agent/AgentLoopTransitions.Verification.cs
lacvet 5511c620de AX Agent의 Cowork·Code 강제 로직을 claude-code 기준으로 추가 완화
Cowork·Code 프롬프트의 text-only 완료 조건을 완화하고, 실제 산출물 생성이나 코드 수정이 필요한 경우에만 도구 재강제를 걸도록 AgentLoopService를 조정했다.

Code 검증 게이트는 diff 또는 최근 build/test 근거가 있으면 중복 재검증을 덜 하도록 줄였고, docs 정책은 creation tool 우선 + document_plan 선택형으로 정리했으며 folder_map 노출 우선순위를 한 단계 낮췄다.

README.md와 docs/DEVELOPMENT.md에 2026-04-10 09:02 (KST) 기준 변경 이력을 반영했다.

검증: dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\ (경고 0 / 오류 0)
2026-04-10 09:00:10 +09:00

252 lines
11 KiB
C#

using AxCopilot.Models;
namespace AxCopilot.Services.Agent;
public partial class AgentLoopService
{
private void ApplyCodeQualityFollowUpTransition(
ContentBlock call,
ToolResult result,
List<ChatMessage> messages,
TaskTypePolicy taskPolicy,
ref bool requireHighImpactCodeVerification,
ref string? lastModifiedCodeFilePath)
{
var highImpactCodeChange = IsHighImpactCodeModification(ActiveTab ?? "", call.ToolName, result);
requireHighImpactCodeVerification = highImpactCodeChange;
if (highImpactCodeChange && ShouldInjectCodeQualityFollowUp(ActiveTab ?? "", call.ToolName, result))
{
lastModifiedCodeFilePath = result.FilePath;
messages.Add(new ChatMessage
{
Role = "user",
Content = BuildCodeQualityFollowUpPrompt(
call.ToolName,
result,
highImpactCodeChange,
HasAnyBuildOrTestEvidence(messages),
taskPolicy)
});
EmitEvent(AgentEventType.Thinking, "", highImpactCodeChange
? "고영향 코드 변경으로 분류돼 참조 검증과 build/test 검증을 더 엄격하게 이어갑니다."
: "코드 변경 후 build/test/diff 검증을 이어갑니다.");
}
else if (HasCodeVerificationEvidenceAfterLastModification(messages, requireHighImpactCodeVerification))
{
requireHighImpactCodeVerification = false;
}
}
private bool TryApplyCodeCompletionGateTransition(
List<ChatMessage> messages,
string? textResponse,
TaskTypePolicy taskPolicy,
bool requireHighImpactCodeVerification,
int totalToolCalls,
RunState runState,
ModelExecutionProfileCatalog.ExecutionPolicy executionPolicy)
{
if (!string.Equals(ActiveTab, "Code", StringComparison.OrdinalIgnoreCase) || totalToolCalls <= 0)
return false;
var hasCodeVerificationEvidence = HasCodeVerificationEvidenceAfterLastModification(
messages,
requireHighImpactCodeVerification);
var hasDiffEvidence = HasDiffEvidenceAfterLastModification(messages);
var hasRecentBuildOrTestEvidence = HasBuildOrTestEvidenceAfterLastModification(messages);
var hasSuccessfulBuildAndTestEvidence = HasSuccessfulBuildAndTestAfterLastModification(messages);
var hasLightweightCompletionEvidence = requireHighImpactCodeVerification
? hasCodeVerificationEvidence
: hasDiffEvidence || hasRecentBuildOrTestEvidence || hasCodeVerificationEvidence;
if (executionPolicy.CodeVerificationGateMaxRetries > 0
&& !(requireHighImpactCodeVerification ? hasCodeVerificationEvidence : hasLightweightCompletionEvidence)
&& runState.CodeVerificationGateRetry < executionPolicy.CodeVerificationGateMaxRetries)
{
runState.CodeVerificationGateRetry++;
if (!string.IsNullOrEmpty(textResponse))
messages.Add(new ChatMessage { Role = "assistant", Content = textResponse });
messages.Add(new ChatMessage
{
Role = "user",
Content = requireHighImpactCodeVerification
? "[System:CodeQualityGate] 공용/핵심 코드 변경 이후 검증 근거가 부족합니다. 종료하지 말고 file_read, grep/glob, git diff, build/test까지 확인한 뒤에만 마무리하세요."
: "[System:CodeQualityGate] 마지막 코드 수정 이후 build/test/file_read/diff 근거가 부족합니다. 종료하지 말고 검증 근거를 보강한 뒤에만 마무리하세요."
});
EmitEvent(AgentEventType.Thinking, "", requireHighImpactCodeVerification
? "핵심 코드 변경의 검증 근거가 부족해 추가 검증을 진행합니다..."
: "코드 결과 검증 근거가 부족해 추가 검증을 진행합니다...");
return true;
}
if (requireHighImpactCodeVerification
&& executionPolicy.HighImpactBuildTestGateMaxRetries > 0
&& !HasSuccessfulBuildAndTestAfterLastModification(messages)
&& runState.HighImpactBuildTestGateRetry < executionPolicy.HighImpactBuildTestGateMaxRetries)
{
runState.HighImpactBuildTestGateRetry++;
if (!string.IsNullOrEmpty(textResponse))
messages.Add(new ChatMessage { Role = "assistant", Content = textResponse });
messages.Add(new ChatMessage
{
Role = "user",
Content = "[System:HighImpactBuildTestGate] 핵심 코드 변경입니다. 종료하지 말고 build_run과 test_loop를 모두 실행해 성공 근거를 확보한 뒤에만 마무리하세요."
});
EmitEvent(AgentEventType.Thinking, "", "핵심 변경이라 build+test 성공 근거를 모두 확보할 때까지 진행합니다...");
return true;
}
var hasBlockingCodeEvidenceGap = !(requireHighImpactCodeVerification ? hasCodeVerificationEvidence : hasLightweightCompletionEvidence)
|| (requireHighImpactCodeVerification && !hasSuccessfulBuildAndTestEvidence);
var shouldRequestStructuredFinalReport =
taskPolicy.IsReviewTask || requireHighImpactCodeVerification;
if (executionPolicy.FinalReportGateMaxRetries > 0
&& shouldRequestStructuredFinalReport
&& !hasBlockingCodeEvidenceGap
&& !HasSufficientFinalReportEvidence(textResponse, taskPolicy, requireHighImpactCodeVerification, messages)
&& runState.FinalReportGateRetry < executionPolicy.FinalReportGateMaxRetries)
{
runState.FinalReportGateRetry++;
if (!string.IsNullOrEmpty(textResponse))
messages.Add(new ChatMessage { Role = "assistant", Content = textResponse });
messages.Add(new ChatMessage
{
Role = "user",
Content = BuildFinalReportQualityPrompt(taskPolicy, requireHighImpactCodeVerification)
});
EmitEvent(AgentEventType.Thinking, "", "최종 보고에 변경·검증·리스크 요약이 부족해 한 번 더 정리합니다...");
return true;
}
return false;
}
private bool TryApplyCodeDiffEvidenceGateTransition(
List<ChatMessage> messages,
string? textResponse,
RunState runState,
ModelExecutionProfileCatalog.ExecutionPolicy executionPolicy)
{
if (!string.Equals(ActiveTab, "Code", StringComparison.OrdinalIgnoreCase))
return false;
if (executionPolicy.CodeDiffGateMaxRetries <= 0 || runState.CodeDiffGateRetry >= executionPolicy.CodeDiffGateMaxRetries)
return false;
if (HasDiffEvidenceAfterLastModification(messages) || HasBuildOrTestEvidenceAfterLastModification(messages))
return false;
runState.CodeDiffGateRetry++;
if (!string.IsNullOrEmpty(textResponse))
messages.Add(new ChatMessage { Role = "assistant", Content = textResponse });
messages.Add(new ChatMessage
{
Role = "user",
Content = "[System:CodeDiffGate] 코드 변경 이후 diff 근거가 부족합니다. git_tool 도구로 변경 파일과 핵심 diff를 먼저 확인하고 요약하세요. 지금 즉시 git_tool 도구를 호출하세요."
});
EmitEvent(AgentEventType.Thinking, "", "코드 diff 근거가 부족해 git diff 검증을 추가합니다...");
return true;
}
private bool TryApplyRecentExecutionEvidenceGateTransition(
List<ChatMessage> messages,
string? textResponse,
TaskTypePolicy taskPolicy,
RunState runState,
ModelExecutionProfileCatalog.ExecutionPolicy executionPolicy)
{
if (!string.Equals(ActiveTab, "Code", StringComparison.OrdinalIgnoreCase))
return false;
if (executionPolicy.RecentExecutionGateMaxRetries <= 0 || runState.RecentExecutionGateRetry >= executionPolicy.RecentExecutionGateMaxRetries)
return false;
if (HasDiffEvidenceAfterLastModification(messages))
return false;
if (!HasAnyBuildOrTestEvidence(messages))
return false;
if (HasBuildOrTestEvidenceAfterLastModification(messages))
return false;
runState.RecentExecutionGateRetry++;
if (!string.IsNullOrEmpty(textResponse))
messages.Add(new ChatMessage { Role = "assistant", Content = textResponse });
messages.Add(new ChatMessage
{
Role = "user",
Content = BuildRecentExecutionEvidencePrompt(taskPolicy)
});
EmitEvent(AgentEventType.Thinking, "", "최근 수정 이후 실행 근거가 부족해 build/test 재검증을 수행합니다...");
return true;
}
private bool TryApplyExecutionSuccessGateTransition(
List<ChatMessage> messages,
string? textResponse,
TaskTypePolicy taskPolicy,
RunState runState,
ModelExecutionProfileCatalog.ExecutionPolicy executionPolicy)
{
if (!string.Equals(ActiveTab, "Code", StringComparison.OrdinalIgnoreCase))
return false;
if (executionPolicy.ExecutionSuccessGateMaxRetries <= 0 || runState.ExecutionSuccessGateRetry >= executionPolicy.ExecutionSuccessGateMaxRetries)
return false;
if (HasDiffEvidenceAfterLastModification(messages))
return false;
if (!HasAnyBuildOrTestAttempt(messages))
return false;
if (HasAnyBuildOrTestEvidence(messages))
return false;
runState.ExecutionSuccessGateRetry++;
if (!string.IsNullOrEmpty(textResponse))
messages.Add(new ChatMessage { Role = "assistant", Content = textResponse });
messages.Add(new ChatMessage
{
Role = "user",
Content = BuildExecutionSuccessGatePrompt(taskPolicy)
});
EmitEvent(AgentEventType.Thinking, "", "실패한 실행 근거만 있어 build/test 성공 결과를 다시 검증합니다...");
return true;
}
private bool TryApplyTerminalEvidenceGateTransition(
List<ChatMessage> messages,
string? textResponse,
TaskTypePolicy taskPolicy,
string userQuery,
int totalToolCalls,
string? lastArtifactFilePath,
RunState runState,
int retryMax)
{
if (totalToolCalls <= 0)
return false;
if (ShouldSkipTerminalEvidenceGateForAnalysisQuery(userQuery, taskPolicy))
return false;
if (runState.TerminalEvidenceGateRetry >= retryMax)
return false;
if (HasTerminalProgressEvidence(taskPolicy, messages, lastArtifactFilePath))
return false;
runState.TerminalEvidenceGateRetry++;
if (!string.IsNullOrEmpty(textResponse))
messages.Add(new ChatMessage { Role = "assistant", Content = textResponse });
messages.Add(new ChatMessage
{
Role = "user",
Content = BuildTerminalEvidenceGatePrompt(taskPolicy, lastArtifactFilePath)
});
EmitEvent(AgentEventType.Thinking, "", $"종료 전 실행 증거가 부족해 보강 단계를 진행합니다 ({runState.TerminalEvidenceGateRetry}/{retryMax})");
return true;
}
}