feat(harness): add information gain stop and audit

This commit is contained in:
aruo
2026-07-27 01:03:34 +08:00
parent de5a5b09d9
commit d0452184ee
92 changed files with 5019 additions and 122 deletions
@@ -0,0 +1,118 @@
package com.superbiz.agent.harness.progress;
import com.superbiz.agent.harness.contract.EvidenceStatus;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
public final class DiagnosisProgressTracker {
private final int stopAfterConsecutiveNoGain;
private final Set<ToolScopeIdentity> completedScopes = new LinkedHashSet<>();
private final List<CompletedToolCall> completedToolCalls = new ArrayList<>();
private int consecutiveNoGain;
private DiagnosisCollectionState collectionState = DiagnosisCollectionState.COLLECTING;
private DiagnosisStopReason stopReason;
private String pendingToolCallId;
private boolean stopInstructionDelivered;
public DiagnosisProgressTracker(int stopAfterConsecutiveNoGain) {
if (stopAfterConsecutiveNoGain <= 0) {
throw new IllegalArgumentException("stopAfterConsecutiveNoGain must be positive");
}
this.stopAfterConsecutiveNoGain = stopAfterConsecutiveNoGain;
}
public synchronized void applyPreviousObservation(PreviousObservation observation) {
if (pendingToolCallId == null) {
if (observation != null) {
throw new IllegalArgumentException("No Tool observation is pending evaluation");
}
return;
}
if (observation == null) {
throw new IllegalArgumentException("Previous Tool observation must be evaluated");
}
if (!pendingToolCallId.equals(observation.toolCallId())) {
throw new IllegalArgumentException("Previous Tool observation ID is out of order");
}
pendingToolCallId = null;
applyGain(observation.informationGain());
}
public synchronized boolean isDuplicate(String toolName, String normalizedScope) {
return completedScopes.contains(new ToolScopeIdentity(toolName, normalizedScope));
}
public synchronized void recordDuplicateScope() {
applyGain(InformationGain.NO_GAIN);
}
public synchronized void recordCompleted(CompletedToolCall call, EvidenceStatus evidenceStatus) {
if (collectionState == DiagnosisCollectionState.SATURATED) {
throw new IllegalStateException("Cannot record Tool completion after saturation");
}
if (evidenceStatus != EvidenceStatus.EVIDENCE_FOUND
&& evidenceStatus != EvidenceStatus.NO_EVIDENCE) {
throw new IllegalArgumentException("Completed Tool requires a successful evidence status");
}
ToolScopeIdentity scope = new ToolScopeIdentity(call.toolName(), call.normalizedScope());
if (!completedScopes.add(scope)) {
throw new IllegalStateException("Completed Tool scope was already recorded");
}
completedToolCalls.add(call);
if (evidenceStatus == EvidenceStatus.NO_EVIDENCE) {
applyGain(InformationGain.NO_GAIN);
} else {
pendingToolCallId = call.toolCallId();
}
}
public synchronized boolean claimStopInstruction() {
if (collectionState != DiagnosisCollectionState.SATURATED) {
return false;
}
if (stopInstructionDelivered) {
return false;
}
stopInstructionDelivered = true;
return true;
}
public synchronized void markBudgetLimitReached() {
if (stopReason == null) {
stopReason = DiagnosisStopReason.BUDGET_LIMIT_REACHED;
}
}
public synchronized DiagnosisProgressSnapshotState snapshot() {
return new DiagnosisProgressSnapshotState(
consecutiveNoGain,
collectionState,
stopReason,
pendingToolCallId,
stopInstructionDelivered,
completedToolCalls);
}
public int stopAfterConsecutiveNoGain() {
return stopAfterConsecutiveNoGain;
}
private void applyGain(InformationGain gain) {
if (collectionState == DiagnosisCollectionState.SATURATED) {
throw new IllegalStateException("Collection is already saturated");
}
if (gain == InformationGain.GAINED) {
consecutiveNoGain = 0;
return;
}
consecutiveNoGain++;
if (consecutiveNoGain >= stopAfterConsecutiveNoGain) {
collectionState = DiagnosisCollectionState.SATURATED;
stopReason = DiagnosisStopReason.INFORMATION_SATURATED;
}
}
}