Skip to content

fix(agent): content-transition file mutation verifier clears stale footers - #72387

Open
rille111 wants to merge 3 commits into
NousResearch:mainfrom
rille111:fix/file-mutation-verifier-redesign-cursor
Open

rille111 wants to merge 3 commits into
NousResearch:mainfrom
rille111:fix/file-mutation-verifier-redesign-cursor

Conversation

@rille111

@rille111 rille111 commented Jul 27, 2026 •

Copy link
Copy Markdown

Summary

  • Add TurnFileMutationVerifier (content-transition contract): pre-dispatch baselines, local bounded observation, fail-closed reconciliation, and sanitized turn-end footers.
  • Wire verifier through tool executor (dispatch tri-state, write_file/patch barriers), turn context/finalizer, and run_agent legacy sync.
  • Tests cover content-transition suppression, unchanged-file retention, V4A parser parity, path safety, and batch segmentation barriers.

Test plan

  • pytest verifier + redesign + segmentation + dispatch helpers (118 passed locally)
  • scripts/run_tests.sh full matrix on CI/Linux devShell
  • Manual: failed patch then terminal edits same file — footer should clear when content safely observed to change

Notes

Rebased onto current main. The PR is exactly 3 verifier commits (8064355, fb37c82, 64b7e60), no inherited fork history.
Remaining follow-up: full E2E middleware matrix. Subprocess observation timeout kill landed in fb37c82.

@rille111

rille111 commented Jul 27, 2026 •

Copy link
Copy Markdown
Author

Review request (agent core)

Problem: Stale turn-end file-mutation footer after a failed patch/write_file when a later tool legitimately changes the same file.

Contract: CONTENT-TRANSITION verifier — safely observed local content change may clear the footer; fail closed when observation is unavailable. Not payload-equality validation.

Please review these commits (branch is rebased onto main, nothing else on it):

  • 8064355 - verifier module + wiring + tests
  • fb37c82 - failsafe hardening (subprocess observation timeout kill, registry dispatch ContextVar, footer tests)
  • 64b7e60 - fail closed on unparseable mutation targets

Suggested test focus:

scripts/run_tests.sh tests/run_agent/test_file_mutation_verifier.py
scripts/run_tests.sh tests/agent/test_file_mutation_verifier_redesign.py
scripts/run_tests.sh tests/agent/test_turn_finalizer_file_mutation_footer.py
scripts/run_tests.sh tests/run_agent/test_tool_batch_segmentation.py

Touches: agent/file_mutation_verifier.py, agent/tool_executor.py, model_tools.py, run_agent.py, turn_context / turn_finalizer, tests only.

@alt-glitch alt-glitch added type/bug Something isn't working comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint tool/file File tools (read, write, patch, search) P3 Low — cosmetic, nice to have needs-decision Awaiting maintainer decision before any implementation labels Jul 27, 2026
@rille111
rille111 force-pushed the fix/file-mutation-verifier-redesign-cursor branch from e992fa3 to 913ec28 Compare July 27, 2026 02:12
@alt-glitch

Copy link
Copy Markdown

This was generated by AI during triage.

Related: #53165 and #65584 address the same stale verifier-footer outcome with narrower/different observation mechanisms. This content-transition approach is not a duplicate, but the live PR also carries extensive unrelated inherited history; please rebase or isolate the requested verifier commits before maintainers choose a mechanism.

@rille111

Copy link
Copy Markdown
Author

The rebase was already done, this triage ran right as I force-pushed (e992fa3 -> 913ec28). The PR is now exactly 3 verifier commits, no inherited history:

  • 9b2a6dc verifier module + wiring + tests
  • 913ec28 failsafe hardening
  • f37f29f fail closed on unparseable mutation targets

Diff touches agent/file_mutation_verifier.py, executor/turn wiring and tests only. I updated the description, the old note about desktop commits at base was stale.

On mechanism: this one requires an observed SHA-256 content change (not stat metadata, so the chmod case flagged on #65584 keeps the footer) and fails closed when observation is unavailable. It is also built on the current segmented tool-batch executor, the drift called out on #53165. Happy to adapt to whatever contract maintainers pick.

rille111 and others added 3 commits July 27, 2026 22:21
…oters

Extract TurnFileMutationVerifier with pre-dispatch baselines, local
observation, and fail-closed reconciliation so a later legitimate file
change can suppress a failed patch/write footer without proving payload match.

Co-authored-by: Cursor <cursoragent@cursor.com>
Use killable subprocess isolation for bounded local hashing, O_NOFOLLOW
opens with stability checks, registry dispatch tracking via ContextVar,
and tests for middleware short-circuit, observation timeout, and turn-end
footer on interrupted/empty turns.

Co-authored-by: Cursor <cursoragent@cursor.com>
Record an unknown-target ledger entry so invalid or unparseable patch/write
failures still surface in the turn-end footer instead of being dropped.

Co-authored-by: Cursor <cursoragent@cursor.com>
@rille111
rille111 force-pushed the fix/file-mutation-verifier-redesign-cursor branch from f37f29f to 64b7e60 Compare July 27, 2026 20:26
@rille111

Copy link
Copy Markdown
Author

Rebased onto current main (551e1c6), same 3 commits. No content changes except one legacy test updated to the fail-closed contract from the third commit (test_missing_path_arg_recorded_nowhere -> test_missing_path_arg_fails_closed). Verifier suites: 87 passed locally.

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for isolating the content-transition approach and preserving a fail-closed observation contract. The underlying stale-footer case is real on current main: run_agent.py:3272-3298 only reconciles write_file/patch outcomes, so a later terminal edit cannot clear the entry.

Problems

  • agent/turn_finalizer.py:413-426 now emits a footer after an interrupted or empty turn. Current main deliberately suppresses that (agent/turn_finalizer.py:413-416) after it has closed and persisted an interrupted tool tail (agent/turn_finalizer.py:289-291; agent/message_sanitization.py:307-311). This can deliver a footer while the durable assistant row remains Operation interrupted.
  • agent/tool_dispatch_helpers.py:60-63,157-159 serializes every write_file and patch. Current main explicitly permits independent file-tool paths concurrently (agent/tool_dispatch_helpers.py:60-61); the verifier should not regress that batch behavior without a narrower ordering strategy.

Suggested changes

  • Preserve the interrupt gate or make delivered and persisted interrupted responses identical.
  • Add an executor-level failed-patch → terminal-edit regression; the branch's direct Path.write_text test does not exercise the claimed dispatch path.

Automated hermes-sweeper review.

Comment thread agent/turn_finalizer.py
if _failed and agent._file_mutation_verifier_enabled():
footer = agent._format_file_mutation_failure_footer(_failed)
if footer:
# Gate: append when failures remain. Interrupted/empty turns still

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This resurrects a user-visible response after an interrupt. finalize_turn already closes and persists the interrupted tool tail before this section; on an empty interrupt that row is Operation interrupted.. Please retain the interrupt gate, or update the durable close path so the delivered footer and persisted assistant content cannot diverge.


# File tools can run concurrently when they target independent paths.
_PATH_SCOPED_TOOLS = frozenset({"read_file", "write_file", "patch"})
# Read-only path tools can run concurrently when they target independent paths.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This drops the existing independent-path concurrency contract for every write_file and patch, not only ambiguous middleware-rewritten calls. Please keep the path-scoped fast path where final effective paths can be safely determined, rather than making all structured mutations sequential barriers.

@teknium1 teknium1 added sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-platform-windows Sweeper risk: may break or behave differently on native Windows sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 30, 2026
@rille111

Copy link
Copy Markdown
Author

Still conflicted against current main after a rebase attempt.

Why a plain rebase is insufficient

main evolved agent/tool_executor.py to the _ManagedToolResult / middleware-authorization path. This PR’s tip still uses the older (result, args, registry_dispatched) triple return. There are 13 conflict hunks in tool_executor.py alone, plus related call-site churn in run_agent.py.

Next step (not yet pushed)

Surgical port onto latest main:

  1. Keep main’s _ManagedToolResult middleware shape
  2. Bring in agent/file_mutation_verifier.py + prepare/finalize hooks
  3. Wire prepare_mutation_dispatch / observe-after through managed execute paths
  4. Re-run verifier + batch segmentation suites

Leaving the existing branch tip unchanged so review history stays intact until that port is ready.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint needs-decision Awaiting maintainer decision before any implementation P3 Low — cosmetic, nice to have sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-platform-windows Sweeper risk: may break or behave differently on native Windows sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state tool/file File tools (read, write, patch, search) type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants