mirror of
https://github.com/esengine/DeepSeek-Reasonix.git
synced 2026-11-07 10:45:59 -10:00
A turn that ends with its answer is done. The agent no longer asks, at the point of stopping, whether a check ran after the last write, whether a high-risk change was reviewed, or whether captured test criteria still hold; it no longer ends a run with a readiness error, and the controller no longer re-runs the turn to satisfy one. Goal keeps its update_goal report and the evaluator, and still holds a complete claim while the task list is unfinished; it no longer reads a readiness result or freezes a verification contract. Review sub-agents are ordinary sub-agents: nothing requires them to file a typed report. Removes the readiness, owed-review, baseline-criteria, carried-proof and review-report code, the delivery checkpoint, the readiness continuation, the orphaned helpers they left in evidence, and the tests that asserted the removed behaviour.
111 lines
3.8 KiB
Go
111 lines
3.8 KiB
Go
package agent
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
|
|
"reasonix/internal/contract/tool"
|
|
"reasonix/internal/safety/evidence"
|
|
)
|
|
|
|
// recordToolReceipts files the turn-scoped evidence for one executed call:
|
|
// always the model-visible call for audit, plus the real target's attributes
|
|
// for mutation/read classification when a proxy resolved elsewhere.
|
|
func (a *Agent) recordToolReceipts(ctx context.Context, plan *toolCallPlan, result string, execution *tool.ShellExecution, err error) {
|
|
if a.task.ledger == nil {
|
|
return
|
|
}
|
|
call := plan.call
|
|
args := json.RawMessage(call.ExecutionArguments())
|
|
switch {
|
|
case call.Name == "complete_step":
|
|
rec := evidence.ReceiptFromToolCall(call.Name, args, err == nil, plan.facts())
|
|
a.task.ledger.Record(rec)
|
|
if err == nil {
|
|
a.advanceCanonicalTodo(rec.Step)
|
|
}
|
|
case plan.evidenceName != call.Name:
|
|
a.task.ledger.Record(evidence.ReceiptFromToolCall(call.Name, args, err == nil, evidence.ToolFacts{ReadOnly: true}))
|
|
rec := evidence.ReceiptFromToolCall(plan.evidenceName, plan.evidenceArgs, err == nil, plan.facts())
|
|
decorateExecutionReceipt(&rec, result, execution)
|
|
a.observeReceiptMutation(ctx, &rec, plan)
|
|
a.reviewCoverageOf(&rec, plan, result)
|
|
a.task.ledger.Record(rec)
|
|
default:
|
|
rec := evidence.ReceiptFromToolCall(call.Name, args, err == nil, plan.facts())
|
|
// A named-path write that reached the disk stays a write when tool.after
|
|
// reports it failed: the extension rules on content, not on what landed.
|
|
if rec.Write && plan.ranClean {
|
|
rec.Success = true
|
|
}
|
|
decorateExecutionReceipt(&rec, result, execution)
|
|
a.recordLinkTargets(&rec)
|
|
a.observeReceiptMutation(ctx, &rec, plan)
|
|
a.reviewCoverageOf(&rec, plan, result)
|
|
a.task.ledger.Record(rec)
|
|
if err == nil && call.Name == "todo_write" {
|
|
before := a.CanonicalTodoState()
|
|
a.setTodoState(rec.Todos)
|
|
a.observeTodoTransition(before, a.CanonicalTodoState())
|
|
if len(rec.Todos) > 0 {
|
|
a.turn.deliveryCriteriaEstablished = true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (a *Agent) observeReceiptMutation(ctx context.Context, rec *evidence.Receipt, plan *toolCallPlan) {
|
|
attributable := plan.leasePreparation.ObservationEpoch() == plan.claimObservationEpoch
|
|
if attributable {
|
|
decorateObservedPaths(rec, plan)
|
|
} else if rec.Success {
|
|
rec.CriteriaRewritten = plan.criteriaRewritten
|
|
}
|
|
a.stampHostOwned(rec)
|
|
if attributable {
|
|
a.settleUnchangedWorkspace(ctx, rec, plan)
|
|
}
|
|
}
|
|
|
|
// notifyToolHooks lets success and failure hooks observe a finished call. The
|
|
// name is the real target's, so a proxied tool reaches hooks under the tool
|
|
// they were written against rather than under the proxy.
|
|
func (a *Agent) notifyToolHooks(ctx context.Context, name string, args json.RawMessage, result string, err error) {
|
|
if a.svc.hooks == nil {
|
|
return
|
|
}
|
|
if err != nil {
|
|
a.svc.hooks.PostToolUseFailure(ctx, name, args, result, err)
|
|
return
|
|
}
|
|
a.svc.hooks.PostToolUse(ctx, name, args, result)
|
|
}
|
|
|
|
// decorateExecutionReceipt records what the host itself observed about one tool
|
|
// call. The output length and the process outcome never come from model
|
|
// arguments, which is what makes the resulting attestation trustworthy.
|
|
func decorateExecutionReceipt(rec *evidence.Receipt, result string, ex *tool.ShellExecution) {
|
|
if rec == nil {
|
|
return
|
|
}
|
|
rec.ObserveOutput(result)
|
|
if ex == nil {
|
|
return
|
|
}
|
|
if ex.ExitCode != nil {
|
|
code := *ex.ExitCode
|
|
rec.ExitCode = &code
|
|
}
|
|
rec.Verification = ex.Verification
|
|
// A screenshot of a local file is a look at that file. The page is the one
|
|
// the browser reported, so a model naming a different file changes nothing.
|
|
if ex.Kind == browserExecutionKind && ex.State == tool.ShellStateCompleted && rec.ToolName == "browser_read" {
|
|
if viewed := evidence.ViewedPath(ex.Subject); viewed != "" {
|
|
rec.Viewed = []string{viewed}
|
|
}
|
|
}
|
|
}
|
|
|
|
// browserExecutionKind marks a host record a browser tool produced.
|
|
const browserExecutionKind = "browser"
|