Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
739 changes: 739 additions & 0 deletions agent/file_mutation_verifier.py

Large diffs are not rendered by default.

42 changes: 21 additions & 21 deletions agent/tool_dispatch_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,10 @@
"web_search",
})

# 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.

# Structured mutations (write_file/patch) are always sequential barriers —
# request/execution middleware may alias paths after planning.
_PATH_SCOPED_TOOLS = frozenset({"read_file"})

# Patterns that indicate a terminal command may modify/delete files.
_DESTRUCTIVE_PATTERNS = re.compile(
Expand Down Expand Up @@ -152,6 +154,10 @@ def _add_sequential(tc) -> None:
_add_sequential(tool_call)
continue

if tool_name in _FILE_MUTATING_TOOLS:
_add_sequential(tool_call)
continue

try:
function_args = json.loads(tool_call.function.arguments)
except Exception:
Expand Down Expand Up @@ -353,26 +359,20 @@ def _extract_file_mutation_targets(tool_name: str, args: Dict[str, Any]) -> List
body = args.get("patch") or ""
if not isinstance(body, str) or not body:
return []
try:
from tools.patch_parser import OperationType, parse_v4a_patch

operations, parse_error = parse_v4a_patch(body)
except Exception:
return []
if parse_error:
return []
paths: List[str] = []
for _m in re.finditer(
r'^\*\*\*\s+(?:Update|Add|Delete)\s+File:\s*(.+)$',
body,
re.MULTILINE,
):
p = _m.group(1).strip()
if p:
paths.append(p)
for _m in re.finditer(
r'^\*\*\*\s+Move\s+File:\s*(.+?)\s*->\s*(.+)$',
body,
re.MULTILINE,
):
src = _m.group(1).strip()
dst = _m.group(2).strip()
if src:
paths.append(src)
if dst:
paths.append(dst)
for op in operations:
if op.file_path:
paths.append(op.file_path)
if op.operation == OperationType.MOVE and op.new_path:
paths.append(op.new_path)
return paths
return []

Expand Down
Loading