Files
esengine d51d652ef2 feat(agent): a writer that declares its write set is observed like write_file
The host observes a write through Previewer, which describes one file. A
writer that changes several paths named nowhere in its arguments was left an
opaque writer: a coverage gap on the checkpoint, no preimages, and a receipt
with no paths, so neither rewind nor the evidence ledger could say what it did.

tool.WritePathDeclarer lets such a writer name every path before it runs. The
host reserves exactly those paths against background writers, captures each
one's preimage and after-fingerprint, and records them on the receipt. A code
rewind then restores a modified path and removes a created one.
2026-09-24 09:47:14 -07:00

53 lines
1.6 KiB
Go

package agent
import (
"context"
"reasonix/internal/contract/tool"
"reasonix/internal/runtime/writeclaim"
"reasonix/internal/state/checkpoint"
)
// declareWritePaths asks a writer that can name its whole write set for it, once
// per call, and keeps the answer on the plan for every stage that needs it.
func (a *Agent) declareWritePaths(ctx context.Context, plan *toolCallPlan) []string {
if plan.readOnly || plan.runTool == nil {
return nil
}
d, ok := plan.runTool.(tool.WritePathDeclarer)
if !ok {
return nil
}
paths, err := d.DeclaredWritePaths(ctx, plan.runArgs)
if err != nil || len(paths) == 0 {
return nil
}
plan.declaredPaths = paths
return paths
}
// reserveDeclaredWrite holds exactly the paths a declaring writer named, or the
// whole workspace when one falls outside it.
func (a *Agent) reserveDeclaredWrite(paths []string) (func(), error) {
claim, err := writeclaim.NormalizeWritePaths(a.writeWorkspaceRoot, paths)
if err != nil {
if claim, err = writeclaim.WholeWorkspaceWriteClaim(a.writeWorkspaceRoot); err != nil {
return func() {}, err
}
}
return a.svc.writeScheduler.ReserveWrite(claim)
}
// observeDeclaredPaths captures the preimage of every declared path. It reports
// false when the call declared nothing, leaving the caller to record its gap.
func (a *Agent) observeDeclaredPaths(plan *toolCallPlan, toolName string) bool {
obs := a.svc.mutationObserver
if obs == nil || len(plan.declaredPaths) == 0 {
return false
}
for _, p := range plan.declaredPaths {
obs.BeforeMutation(p, toolName, checkpoint.CaptureBeforeMutation)
}
return true
}