Skip to content

feat(OMN-20392): add replace_in_files multi-file edit action to the delegated code edit node - #3270

Merged
jonahgabriel merged 3 commits into
devfrom
OMN-20392-code-edit-bulk-replace
Oct 3, 2026
Merged

jonahgabriel merged 3 commits into
devfrom
OMN-20392-code-edit-bulk-replace

Conversation

@jonahgabriel

@jonahgabriel jonahgabriel commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Ticket: OMN-20392

Lab: host=h201 lane=codeedit-bulk-2a21 head=7dc39e3cd7d983693836b027742a0ba46c3d630a command=in-process code-edit loop (real handler, worktree, checks; Qwen3.8-27B on the h201 model server) observed=loop e58a5fbb-afa0-4c8c-aafe-44615e78b96a accepted, 110 files changed in 4 turns, rubric PASS

Summary

The delegated code edit node allowed at most 12 actions per turn and had no multi-file action, so the crush replay's 105-file and 114-file tasks (f76301f0, 8e1b5f72) needed at least 9 and 10 turns of single edits. This blocks cutover parity (OMN-20291, OMN-20292).

This change adds one typed action, replace_in_files, which replaces every occurrence of old_string with new_string across many files in a single action.

Design

One typed action replace_in_files, declared in the node contract description, TOOL_SCHEMAS and RESPONSE_CONTRACT.

  • Arguments: exactly one of file_paths (array, at most MAX_BULK_FILES=200) or glob (same syntax as writable globs, expanded over the worktree manifest); plus old_string (required) and new_string (may be empty).
  • Replaces every occurrence of old_string in each file.

Per-file all-or-nothing — content is computed in memory and written once. A named file that is outside the worktree, outside the writable globs, lacks old_string, would exceed MAX_WRITE_BYTES, or whose write is refused is reported FAILED <path>: <reason> and left untouched. Refusals count into the result's refusals.

Glob is a scope — only writable manifest files are targeted; files without old_string are skipped and counted; files matching the glob but outside the writable globs are counted; a glob that edits nothing is an error.

Feedback to the model lists failures first, then each edited file with its occurrence count, capped at MAX_OBSERVATION_BYTES — so a cap cuts successes only.

No shell, no generated script — only this typed action, same confinement and writable globs. The 12-actions-per-turn cap is unchanged.

Rubric:

  • replace_in_files added to edits_apply edit_tools.
  • file_paths added to no_phantom_paths path_argument_names; no_phantom_paths now reads list-valued path arguments.
  • One call is recorded per action (status error when any file failed), because within_budget caps tool calls at 80 — per-file calls would break it for a 105-file edit.

Also fixed: DelegatedCodeEditPorts.delegate raised AttributeError on a failed delegate receipt whose terminal_payload is null; it is now a failed turn reply (test added).

Tests

Focused run of tests/nodes/node_delegated_code_edit_orchestrator, tests/unit/delegated_code_edit and tests/nodes/node_delegation_rubric_check_compute: 382 passed. ruff check and format clean, mypy --strict on src/omnimarket clean, runtime sweep import check and metadata dependency check pass.

New tests cover:

  • 30 files in one action and one turn
  • glob scope
  • glob that edits nothing
  • partial failure (one error call, rubric edits_apply fails)
  • per-file write error
  • over-cap and zero-target refusals
  • every-occurrence count
  • failures-before-successes ordering
  • parser rejections
  • schema array types
  • rubric edits_apply and no_phantom_paths on replace_in_files

Lab proof

Real Qwen3.8-27B on the h201 lab model server, the real loop handler, worktree and checks, in-process:

  • Loop run f477e734-4b9a-49f1-8c73-a1d6562d1304 — 41-file fixture (40 files to migrate): status accepted in 3 turns, one replace_in_files action by glob, 40 files changed, check passed, rubric outcome PASS.
  • Loop run e58a5fbb-afa0-4c8c-aafe-44615e78b96a — 110-file fixture across two directories: status accepted in 4 turns, 110 files changed, check passed, rubric outcome PASS.
  • Turn delegate run ids of the first: 14ed37af-fe89-4149-bc35-a7789e3bf5f6, baaeb8b9-9d51-46b9-8397-388a6e525a01, 4f5ddbcd-b9c7-4dd5-8726-56463f9e5131 (in the lane scratch state root).

Notes

Limit of the proof: onex code-edit run itself could not reach a model from h201 today. The dev lane failed with "delegate receipt carries no resolvable delegation terminal" (loop f86b9d8f-33a2-4a20-b14e-f1ab7562a4ab), and the in-process route failed with the DelegatePhaseTimedBus.subscribe TypeError (delegate run 141fdc8d-13eb-4c0c-a6e2-dba8ef6a7d8e, loop 1974e6bc-4140-4dc0-a7a1-a1506347d1bd). The lab runs therefore replaced only the onex delegate hop with a direct call to the model server; the prompt, response parsing, apply, checks, receipt and rubric score are the shipped code.

Follow-up: re-running through onex code-edit run on the dev lane once the delegate route is back.

Not covered: the crush replay's actual 105-file and 114-file tasks were not replayed.

Evidence-Ticket: OMN-20392
Evidence-Source: OCC#12449

@github-actions

github-actions Bot commented Oct 2, 2026 •

Copy link
Copy Markdown

✅ Architectural Review — PASSED

Errors: 0
Warnings: 0

What this checks

Rule Description
ARCH-TOPIC-001 No hardcoded Kafka topic strings in handler code
ARCH-DI-001 No event_bus=None bypass
ARCH-DI-002 No direct Handler instantiation outside workflow_runner/adapters
ARCH-DI-003 No reinvented DI containers
ARCH-TOPIC-002 contract.yaml topics follow `onex.{cmd

No architectural violations found.

Static architectural lint — no model inference (OMN-14176).

@github-actions

github-actions Bot commented Oct 2, 2026 •

Copy link
Copy Markdown

✅ Hostile Reviewer — PASSED

Blocking findings (critical/error): 0
Total findings: 13
Models succeeded: qwen3-review,gpt-oss-review


Gate semantics

Verdict Meaning Blocks merge?
passed >=2 models succeeded, no critical/error findings No
blocked CRITICAL or ERROR findings found Yes
degraded Fewer than 2 models succeeded (infra unavailable/timeout) Yes (OMN-15110)

Powered by omniintelligence.review_pairing.cli_review — node-based adversarial review via HandlerLlmCliSubprocess (OMN-8524)

jonahgabriel and others added 2 commits October 2, 2026 23:58
The delegated code edit node allowed at most 12 actions per turn and had
no multi-file action, so a 105-file edit needed at least 9 turns of
single edits.

Add one typed action, replace_in_files, declared in the node contract,
the tool schemas and the response contract. It takes file_paths (list,
at most 200) or glob (exactly one), plus old_string and new_string, and
replaces every occurrence of old_string in each file.

Each file is all-or-nothing: computed in memory and written once. A
named file without old_string, outside the writable globs, or whose
write is refused is reported as FAILED and left untouched. A glob covers
only writable manifest files and skips files without old_string.

The result lists failures first, then every edited file with its
occurrence count, then is capped. There is no shell and no script; the
same writable globs and confinement apply.

In the tool_use rubric, replace_in_files is an edit tool (edits_apply)
and file_paths is a path argument (no_phantom_paths now reads lists).
One call is recorded per action, failed when any file failed, so
within_budget's 80-call cap still holds.

Also, loop_ports no longer crashes with AttributeError when a failed
delegate receipt carries terminal_payload null; it becomes a failed
turn reply.

Tests: handler (30-file one-action edit, glob scope, partial failure,
per-file write error, caps, ordering), parser, rubric, loop_ports.
_cf_cost built the premium counterfactual with the default baseline model,
which returned None and failed collection of the module.
@jonahgabriel
jonahgabriel force-pushed the OMN-20392-code-edit-bulk-replace branch from e8642a5 to dc9a05c Compare October 3, 2026 04:04
Add contracts/OMN-20392.yaml with two acceptance criteria bound to replace_in_files handler tests and tool-use rubric tests. This resolves repo-evidence / dod-verify failure where the PR cites OMN-20392 but carries no contracts/OMN-20392.yaml.

Revert dc9a05c (pin of premium model in tests/test_omn20303_real_postgres_savings_one_row_per_run.py). The canonical handler path uses the default baseline model claude-sonnet-5-5, so CI saw savings 0.002178 against the pinned claude-opus-4-6 expectation 0.016335 in Integration Silent-Skip Guard.

Lab h201: default counterfactual for 319/154 tokens is 0.002178; focused tests 141 passed, 2 skipped.

Refs: OMN-20392, PR 3270
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant