Skip to content

[Feature]: Add replace-first editing with recounted apply_patch fallback #7

Description

@GenTang

痛点与背景 / Problem and Context

Yada currently exposes a single mutation tool: apply_patch.

The existing implementation has valuable safety properties:

  • every existing target is bound to the SHA-256 returned by read_file;
  • expected_files must exactly match the files touched by the patch;
  • git apply --check validates the complete transaction before mutation;
  • a failed patch does not partially modify the workspace;
  • every successful edit invalidates previous verification;
  • finish remains blocked until a relevant test or build succeeds after the latest edit.

These guarantees should be preserved.

However, the model must currently solve two different problems at once:

  1. decide what source code should change;
  2. encode that change as a mechanically valid unified diff, including hunk headers, line counts, prefixes, and exact context.

This creates a reliability bottleneck. A logically correct change can be rejected because the model miscounted hunk lines or produced slightly incorrect context. The failure is safe and visible, but it consumes additional model turns and can prevent otherwise valid tasks from completing.

The current failure observation is also too coarse:

{
  "ok": false,
  "error": "git apply check failed: ..."
}

Although this observation is returned to the next model turn, it does not distinguish stale files, incorrect hunk metadata, missing source text, ambiguous locations, unsupported targets, or internal application failures. We want to improve edit reliability without weakening Yada's core principles:

  • zero runtime dependencies;
  • a small and stable tool interface;
  • explicit SHA-based optimistic locking;
  • deterministic and auditable mutations;
  • fail-closed behavior;
  • bounded structured observations;
  • no fuzzy guessing about edit locations;
  • mandatory verification after every successful mutation.

期望的解决方案 / Proposed Solution

Implement a layered editing strategy:

localized edit to existing text
        ↓
replace_text
        ├── success → edit complete
        ├── recoverable failure → return structured evidence
        │                         → re-read in the next Agent turn
        │                         → retry replace_text or call apply_patch
        └── unsupported operation → use apply_patch

new file / deleted file / unsuitable large structural edit
        ↓
apply_patch

The fallback must happen through the normal Agent loop after the model receives the failure observation. It must not silently apply a more permissive mutation inside the failed replace_text call.

Implementation plan

Recommended dependency order:

#8 apply_patch hardening
        ↓
#9 replace_text
        ↓
#10 routing and evaluation

#8 can be implemented and released independently. #10 depends on both mutation tools and their structured failure observations.

Shared safety invariants

Both mutation paths must preserve the following behavior:

  • all existing files are bound to their current SHA-256;
  • paths must remain inside the workspace;
  • protected paths and unsupported targets are rejected;
  • all requested changes are validated before any file is mutated;
  • multi-file edits are atomic from Yada's perspective;
  • ambiguous edit locations are rejected instead of guessed;
  • failures return bounded structured evidence;
  • successful edits update the run revision and touched-file state;
  • every successful mutation invalidates previous verification;
  • finish remains blocked until verification succeeds after the latest edit;
  • traces contain enough information to audit the selected tool, result, and recovery path.

Error contract

Editing tools should return stable machine-readable error codes while retaining a concise human-readable message.

Expected codes include:

  • stale_hash
  • no_match
  • ambiguous_match
  • invalid_edit
  • invalid_patch
  • unsupported_target
  • patch_context_mismatch
  • apply_failed

Error details must be bounded. A failure must not automatically echo an entire large file into the observation.

Completion criteria

This tracking issue is complete when:

  • all three child issues are closed;
  • patch-only editing remains available;
  • replace-first editing can be selected and audited per run;
  • neither path weakens the existing SHA, transaction, or verification guarantees;
  • no runtime dependency is added;
  • unit and integration tests cover success, recoverable failure, rollback, and verification invalidation;
  • traces record the run-level editing strategy and mutation results;
  • benchmark results compare patch-only and replace-first using the same task set and model configuration.

Out of scope

The following are intentionally excluded from this milestone:

  • patch --fuzz or whitespace-insensitive matching;
  • partial patch application or .rej files;
  • implicit three-way merging;
  • silently choosing a “similar” location;
  • AST-specific editing;
  • whole-file rewrite as a separate public tool;
  • binary-file editing;
  • external Fast Apply models or services;
  • automatic fallback inside a single failed tool invocation.

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions