에이전트 큐 우선순위 소비와 tool_result preview 재사용 안정화

목적:
- claude-code 대비 남아 있던 에이전틱 루프/큐/컨텍스트 격차를 줄이기 위한 1차 배치를 반영한다.
- 낮은 우선순위 알림이 같은 턴에 섞여 들어오던 흐름과 tool_result preview가 MsgId에만 묶여 재사용되던 한계를 개선한다.

핵심 수정:
- AgentCommandQueue를 snapshot/peek/dequeue/dequeueAllMatching/dequeuePriorityBatch를 지원하는 우선순위 큐로 재구성했다.
- AgentLoopService가 전체 큐를 한 번에 비우지 않고 같은 우선순위 배치만 소비하도록 조정했다.
- AgentToolResultBudget가 QueryPreviewContent를 tool_use_id 단위로 재사용하도록 확장해 재구성된 tool_result 메시지에서도 동일 preview를 유지한다.
- AgentCommandQueueTests, AgentToolResultBudgetTests를 확장해 priority batch dequeue, predicate matching, tool_use_id preview reuse를 회귀 검증한다.
- README와 DEVELOPMENT 문서에 2026-04-15 07:00 (KST) 기준 이력과 다음 통합 고도화 계획을 반영했다.

검증:
- dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\verify_queue_preview\ -p:IntermediateOutputPath=obj\verify_queue_preview\
- dotnet test src/AxCopilot.Tests/AxCopilot.Tests.csproj -c Release -v minimal --filter AgentCommandQueueTests^|AgentToolResultBudgetTests -p:OutputPath=bin\verify_queue_preview_tests\ -p:IntermediateOutputPath=obj\verify_queue_preview_tests"
This commit is contained in:
2026-04-15 07:01:45 +09:00
parent f33ee7f7db
commit 07fd2267cb
7 changed files with 224 additions and 32 deletions

View File

@@ -52,4 +52,39 @@ public class AgentCommandQueueTests
drained[1].RequestInterrupt.Should().BeTrue();
drained[2].RequestInterrupt.Should().BeTrue();
}
[Fact]
public void DequeuePriorityBatch_ShouldLeaveLowerPriorityItemsQueued()
{
var queue = new AgentCommandQueue();
queue.EnqueueNotification("later-note");
queue.EnqueuePrompt("next-prompt");
queue.EnqueueSteering("now-steer");
var firstBatch = queue.DequeuePriorityBatch();
var secondBatch = queue.DequeuePriorityBatch();
var thirdBatch = queue.DequeuePriorityBatch();
firstBatch.Select(x => x.Content).Should().Equal("now-steer");
secondBatch.Select(x => x.Content).Should().Equal("next-prompt");
thirdBatch.Select(x => x.Content).Should().Equal("later-note");
}
[Fact]
public void PeekAndDequeueAllMatching_ShouldHonorPredicate()
{
var queue = new AgentCommandQueue();
queue.EnqueueNotification("later-note");
queue.EnqueuePrompt("next-prompt");
queue.EnqueueSteering("now-steer");
var peeked = queue.Peek(x => x.Kind == AgentCommandKind.Notification);
var removed = queue.DequeueAllMatching(x => x.Kind == AgentCommandKind.Notification);
var remaining = queue.DrainAll();
peeked.Should().NotBeNull();
peeked!.Content.Should().Be("later-note");
removed.Select(x => x.Content).Should().Equal("later-note");
remaining.Select(x => x.Content).Should().Equal("now-steer", "next-prompt");
}
}

View File

@@ -55,4 +55,47 @@ public class AgentToolResultBudgetTests
second.ReusedPreviewCount.Should().Be(1);
secondWindow[0].Content.Should().Be(sourceMessages[0].QueryPreviewContent);
}
[Fact]
public void Apply_ShouldReusePreviewByToolUseIdAcrossClonedMessages()
{
var longContent = new string('B', 1500);
var sourceMessages = new List<ChatMessage>
{
new()
{
MsgId = "source-tool",
Role = "user",
Content = $$"""{"type":"tool_result","tool_use_id":"call-shared","tool_name":"file_read","content":"{{longContent}}"}""",
QueryPreviewContent = """{"type":"tool_result","tool_use_id":"call-shared","tool_name":"file_read","content":"preview"}"""
},
new()
{
MsgId = "tail-1",
Role = "assistant",
Content = "recent tail"
}
};
var queryView = new List<ChatMessage>
{
new()
{
MsgId = "rebuilt-tool",
Role = "user",
Content = $$"""{"type":"tool_result","tool_use_id":"call-shared","tool_name":"file_read","content":"{{longContent}}"}"""
},
new()
{
MsgId = "tail-2",
Role = "assistant",
Content = "recent tail"
}
};
var result = AgentToolResultBudget.Apply(queryView, protectedRecentNonSystemMessages: 1, sourceMessages: sourceMessages);
result.ReusedPreviewCount.Should().Be(1);
queryView[0].Content.Should().Be(sourceMessages[0].QueryPreviewContent);
}
}