Skip to content

feat(OMN-20290): a delegated code edit node runs an edit-verify loop over a worktree, each model turn one onex delegate run - #3186

Merged
jonahgabriel merged 6 commits into
devfrom
jonah/omn-20290-delegated-code-edit-node
Oct 1, 2026
Merged

jonahgabriel merged 6 commits into
devfrom
jonah/omn-20290-delegated-code-edit-node

Conversation

@jonahgabriel

@jonahgabriel jonahgabriel commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

OMN-20290 (epic OMN-20287). This PR follows the operator ruling of 2026-10-01 (RULING row 2026-10-01T11:28:57Z): internal agentic code drafting moves off crush, a second non-canonical script, and onto the delegation workflow customers get.

What this adds

  • node_delegated_code_edit_orchestrator (ORCHESTRATOR, ports pattern like the delegated test loop). The input is one task plus a git worktree. The output is a verified diff, a loop receipt and a tool_use rubric verdict.
    • Each model turn is one onex delegate --task-type code_generation --response-contract <schema> run, so every model call is routed by the routing contract, gated, receipted and projected.
    • A turn returns typed actions: view (paged with offset), ls, grep, write, edit, run_check and finish. The tool names match crush's, so the same rubric scores both engines.
    • Writes are confined to the worktree and to the declared writable globs. * stays inside one segment and ** crosses segments. Paths that are absolute, contain .., point into .git, or resolve through a symlink are refused.
    • run_check runs only declared checks, as argv with no shell. There is no shell tool. finish runs every check.
    • The loop stops on accepted, on no_progress (the same check fingerprints over the same diff twice), on budget_exhausted at the turn cap, on delegate_failed (two failed runs in a row), or on infra_error.
    • Each correlation id gets exactly one receipt. The claim is an exclusive create.
  • onex code-edit run, registered under onex.cli, plus the real ports (omnimarket.delegated_code_edit.loop_ports).
    • The default sends turns to the dev lane's deployed orchestrator. --delegate-in-process keeps them on this machine instead.
    • --caller-lane and --ticket are stamped on every turn.
  • recorded_calls_request in delegation/rubric/tool_use_transcript.py, so a recorder that already holds typed calls can be scored without a session store.

Model before build

omnibase_infra formal/delegated_code_edit_loop/ has its own PR. TLC ran on .201 (TLC 2.19, 2 runners, 3 turns).

  • Model: no error found. It checks one receipt per correlation id, no forbidden write, no undeclared check, the turn bound, and that every runner terminates.
  • Mutations: each one breaks its property (a check-then-set claim, no glob guard, no declared-name guard, no turn cap).
  • What it changed in the code: the check-then-set mutation breaks one-receipt, which is why the port claims with an exclusive create.

Lab proof (.201 dev lane, deployed-lane delegate turns, worktrees on .201)

Every turn below has a delegation_events row on the dev-lane projection with caller_lane=deleg-nodes-fix, ticket_id=OMN-20290 and terminal_ok=t. That is 18 rows across the three loops. First attempts went to both local-coder (.201) and local-omnipc2-chat (.202) under the routing contract.

loop task (omnibase_core, throwaway worktree off dev) status turns checks rubric wall
2d3d4d64 ModelDomainQueries to extra=forbid, baseline line and count, new test file accepted 5 new-test 3 passed; agent-definition 59 passed FAIL (no_phantom_paths: manifest cap; fixed at c85d93b) 344 s (turn 1 waited 210 s on the dev runtime recreated at 12:19:20Z)
08719e89 ModelRagQueries, same burn-down no_progress 10 rag-tests failed FAIL 294 s
df08d355 ModelRagQueries, rerun at df3aa83 accepted 3 rag-tests and agent-definition passed PASS 103 s

The no_progress run stopped as designed. It also showed a real gap. A view was cut at 6,000 characters, so the model never saw line 145 and its edits failed on text it had guessed. Commit df3aa83 added view paging with offset, recovery when an edit's old_string carries copied line-number prefixes, and a hint naming the line where the first line of a failed edit occurs. The rerun was then accepted in 3 turns.

Local checks

  • 47 focused tests: tests/nodes/node_delegated_code_edit_orchestrator, tests/unit/delegated_code_edit, tests/unit/cli/test_cli_code_edit.py.
  • tests/test_market_node_runtime_dogfood.py (11 passed). The node metadata dependency check passes.
  • node_runtime_sweep --import-check is clean.
  • mypy --strict passes on the new modules, and ruff is clean.
  • Every git subprocess (in the ports and in the tests) runs with the git location variables scrubbed.

Not in this PR

  • Cutting callers over: delegation_step.py code (OMN-20292) and remote-lane local mode (OMN-20293).
  • The 26-task replay against crush (OMN-20291).
  • lab_crush.py stays until the replay proves the node.
  • The per-turn overhead remains: about 15 to 20 s of CLI startup and bus hops around 2 to 9 s of model time. That is item 1 work under OMN-20287.

Evidence-Ticket: OMN-20290
Evidence-Source: OCC#12143

…over a worktree, each model turn one onex delegate run
…nifest covers large repositories, and checks run in the worktree's own environment
…ine-number prefixes and says where its first line is, and check output loses its colour codes
@github-actions

github-actions Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

⚠️ Architectural Review — WARNINGS

Errors: 0
Warnings: 1

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

ARCH-DI-002: Direct Handler instantiation (warning)\n\n```\nsrc/omnimarket/cli/cli_code_edit.py:246: result = HandlerDelegatedCodeEditOrchestrator(ports).run(request)

src/omnimarket/delegation/rubric/tool_use_record.py:27: verdict = HandlerDelegationRubricCheck().handle(request)\n```\n\nHandler classes should be wired only in workflow_runner.py or adapter modules. Direct instantiation elsewhere bypasses the contract system.\n\n

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

@github-actions

github-actions Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

✅ Hostile Reviewer — PASSED

Blocking findings (critical/error): 0
Total findings: 21
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)

loop_run_id = request.correlation_id
self._ports.claim_loop_receipt(loop_run_id)
state = _State()
status = EnumCodeEditStatus.INFRA_ERROR
class ProtocolDelegatedCodeEditPorts(Protocol):
def claim_loop_receipt(self, loop_run_id: str) -> None:
"""RAISE LoopReceiptExistsError when a receipt for this id exists."""
...

def write_loop_receipt(
self, loop_run_id: str, payload: dict[str, object]
) -> None: ...
self, request: ModelDelegatedCodeEditRequest
) -> tuple[tuple[str, int], ...]:
"""Tracked and untracked files of the worktree, with their line counts."""
...

def read_file(self, request: ModelDelegatedCodeEditRequest, path: str) -> str:
"""RAISE WorkspacePathError for a path outside the worktree or not a file."""
...
self, request: ModelDelegatedCodeEditRequest, path: str, content: str
) -> None:
"""RAISE WorkspacePathError for a path outside the worktree."""
...

def run_check(
self, request: ModelDelegatedCodeEditRequest, check: ModelDeclaredCheck
) -> ModelCheckResult: ...
turn: int,
) -> ModelTurnReply:
"""One ``onex delegate`` run; the reply's actions are already parsed."""
...

def diff(self, request: ModelDelegatedCodeEditRequest) -> str:
"""The worktree's diff against its HEAD, untracked files included."""
...
transcript: dict[str, object],
) -> dict[str, object]:
"""The tool_use rubric verdict record of this run's transcript."""
...
onexbot-occ-writer Bot added a commit to OmniNode-ai/onex_change_control that referenced this pull request Oct 1, 2026
…nimarket#3186

The declared checks were executed for real in the OmniNode-ai/omnimarket checkout at the PR head by the product-repo OCC receipt runner, and the results written append-only into this companion. Producer-minted PENDING receipts are superseded by a net-new record; no existing receipt was edited.

Ticket: OMN-16859 (AC3b)
Onex-Workflow: OCC Receipt Runner
Onex-Run: https://github.com/OmniNode-ai/omnimarket/actions/runs/36864577685
@jonahgabriel
jonahgabriel enabled auto-merge (squash) October 1, 2026 13:04
jonahgabriel pushed a commit to OmniNode-ai/onex_change_control that referenced this pull request Oct 1, 2026
)

* evidence(OMN-20290): author OCC companion for OmniNode-ai/omnimarket#3186

OCC companion by node_pr_lifecycle_fix_effect (OMN-13317 F1 / OMN-13990 / OMN-14285). Product PR head df3aa834dbce191434ec5b7807270d9c3156f1bb.

* evidence(OMN-20290): self-bind OCC#12143 + rebind contract_sha256

* evidence(OMN-16859): executed test_passes receipts for OmniNode-ai/omnimarket#3186

The declared checks were executed for real in the OmniNode-ai/omnimarket checkout at the PR head by the product-repo OCC receipt runner, and the results written append-only into this companion. Producer-minted PENDING receipts are superseded by a net-new record; no existing receipt was edited.

Ticket: OMN-16859 (AC3b)
Onex-Workflow: OCC Receipt Runner
Onex-Run: https://github.com/OmniNode-ai/omnimarket/actions/runs/36864577685

---------

Co-authored-by: omnimarket-bot <bot@omninode.ai>
Co-authored-by: onexbot-occ-writer[bot] <307849072+onexbot-occ-writer[bot]@users.noreply.github.com>
The CI imperative-contract-guard failed on PR 3186 because loop_ports.py imported score() from tool_use_score.py, making that module live due to its sqlite3 usage. We moved score() and RECORD_SCHEMA unchanged to a new I/O-free module tool_use_record.py. Both tool_use_score.py and loop_ports.py now import score from the new module. Local guard run exits 0, 2311 tests pass, and ruff and mypy --strict are clean.
The merge integrates the dev commit 96d3415 which adds a required engine argument to _request and records measured.engine in score(). The conflict in tool_use_score.py is resolved by keeping score() in the I/O-free tool_use_record.py module, which now records the engine as dev does. The recorded_calls_request function takes an optional engine argument and passes it through, while the code edit loop records the model of the last turn that named one as the engine. Verification confirms ruff and mypy strict are clean on 4001 source files, 2599 tests pass with 2 skipped, and the imperative contract guard exits 0.
Release Identity Gate failed on PR head 890d916 because the pyproject version
0.4.273 matched the latest published tag v0.4.273. This commit bumps
project.version in pyproject.toml and the omnimarket entry in uv.lock to
0.4.274. scripts/check_release_identity.py --base origin/dev now reports
"OK: version 0.4.274 is ahead of latest published 0.4.273."
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.

2 participants