mirror of
https://github.com/esengine/DeepSeek-Reasonix.git
synced 2026-11-07 10:45:59 -10:00
fix(agent): preserve trusted task text in completion contracts
Problem: The first completion-receipt fix used raw user input unconditionally, which did not preserve Goal scope task text or pristine sub-agent classifier text. Its explanatory comment also exceeded repository lint budgets. Root cause: Completion contracts and delivery classification selected their task source independently even though both require the same host-trusted text. Fix: Store the already-selected trusted classification input as the per-turn contract input. Add regressions for Goal continuations and host-wrapped sub-agents, and remove the lint-expanding comment. Verification: - go run ./tools/repolint - go test -race ./internal/agent -run TestCompletionContract -count=1 - go test ./internal/agent ./internal/completion ./internal/taskcontract - go vet ./... - go test ./... - cd desktop && go test ./... - git diff --check
This commit is contained in:
1 parent
95e4be0205
commit
b0fb0fbcde
2 files changed
+44
-12
No files matched your search
@@ -113,11 +113,7 @@ func (a *Agent) beginRunTurn(ctx context.Context, input string) (rawInput string
|
||||
// A fresh user turn starts from zeroed per-turn host state; the new turn's
|
||||
// values are computed below. Cross-turn state (checkpoint, scope, failure
|
||||
// budgets) lives directly on Agent and is reconciled field by field.
|
||||
// Host-composed input can contain transient capability, memory, and hook
|
||||
// blocks. Keep the authenticated user text as the contract source so those
|
||||
// implementation details never become acceptance criteria or completion
|
||||
// receipt text. The provider still receives the composed input below.
|
||||
a.perTurnState = perTurnState{turnInput: rawInput}
|
||||
a.perTurnState = perTurnState{}
|
||||
a.resetStructuralRunGuards()
|
||||
scope, scoped := DeliveryExecutionScopeFromContext(ctx)
|
||||
preserveEvidence := a.preserveEvidenceOnce
|
||||
@@ -182,17 +178,17 @@ func (a *Agent) beginRunTurn(ctx context.Context, input string) (rawInput string
|
||||
// deadlocked read-only subagents. Without the override the raw input is
|
||||
// classified verbatim: stripping user-controllable markup here would let
|
||||
// input dressed up as host framing disarm the delivery gates.
|
||||
classifierInput := a.classifierTaskText
|
||||
a.turnInput = a.classifierTaskText
|
||||
if scoped && strings.TrimSpace(scope.TaskText) != "" {
|
||||
classifierInput = scope.TaskText
|
||||
} else if strings.TrimSpace(classifierInput) == "" {
|
||||
classifierInput = rawInput
|
||||
a.turnInput = scope.TaskText
|
||||
} else if strings.TrimSpace(a.turnInput) == "" {
|
||||
a.turnInput = rawInput
|
||||
}
|
||||
intent := taskintent.Classify(classifierInput)
|
||||
intent := taskintent.Classify(a.turnInput)
|
||||
a.deliveryTaskExpected = intent.NeedsEvidence()
|
||||
a.deliveryMutationExpected = intent == taskintent.Mutation && registryHasWriterTools(a.tools)
|
||||
a.deliveryPersistentExpected = taskintent.NeedsPersistentAction(classifierInput)
|
||||
a.recoveryTaskSummary = boundedRecoveryTaskSummary(classifierInput)
|
||||
a.deliveryPersistentExpected = taskintent.NeedsPersistentAction(a.turnInput)
|
||||
a.recoveryTaskSummary = boundedRecoveryTaskSummary(a.turnInput)
|
||||
// A cancelled/error turn leaves a provider-excluded recovery record at the
|
||||
// transcript tail. Fold its bounded facts into this new user turn exactly
|
||||
// once; the user's raw text remains the classifier source above.
|
||||
|
||||
@@ -119,6 +119,42 @@ Policy: prefer means use the skill for the required change
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompletionContractUsesGoalScopeTaskText(t *testing.T) {
|
||||
prov := &userInputCaptureProvider{}
|
||||
a := New(prov, tool.NewRegistry(), NewSession("system"), Options{}, event.Discard)
|
||||
ctx := WithRawUserInput(context.Background(), "Continue working.")
|
||||
ctx = WithDeliveryExecutionScope(ctx, DeliveryExecutionScope{ID: "goal-1", TaskText: "fix the parser"})
|
||||
|
||||
if err := a.Run(ctx, "<goal-context>continue</goal-context>"); err != nil {
|
||||
t.Fatalf("Run: %v", err)
|
||||
}
|
||||
assertAtomicCriterion(t, a, "fix the parser")
|
||||
}
|
||||
|
||||
func TestCompletionContractUsesPristineSubagentTaskText(t *testing.T) {
|
||||
prov := &userInputCaptureProvider{}
|
||||
a := New(prov, tool.NewRegistry(), NewSession("system"), Options{
|
||||
ClassifierTaskText: "fix the parser",
|
||||
}, event.Discard)
|
||||
const wrapped = "<workspace-context>private host framing</workspace-context>\n\nfix the parser"
|
||||
|
||||
if err := a.Run(context.Background(), wrapped); err != nil {
|
||||
t.Fatalf("Run: %v", err)
|
||||
}
|
||||
assertAtomicCriterion(t, a, "fix the parser")
|
||||
}
|
||||
|
||||
func assertAtomicCriterion(t *testing.T, a *Agent, want string) {
|
||||
t.Helper()
|
||||
receipt := a.CompletionReceipt()
|
||||
if receipt == nil || len(receipt.Gaps) == 0 {
|
||||
t.Fatalf("completion receipt = %+v, want atomic criterion %q", receipt, want)
|
||||
}
|
||||
if got := receipt.Gaps[0].Detail; got != "r1: "+want {
|
||||
t.Fatalf("atomic criterion = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSubagentImageCandidatesAreCopiedAndIsolated(t *testing.T) {
|
||||
images := []string{"data:image/png;base64,AAAA"}
|
||||
ctx := WithSubagentImageCandidates(context.Background(), images)
|
||||
|
||||
Reference in new issue
Block a user