Skip to content

feat(turn): add ResolvedAgentExecution::resolve and route prod turn paths - #8179

Merged
Nillth merged 4 commits into
zeroclaw-labs:masterfrom
NNet-Dev:docs/agent-policy-parity-harness
Jun 23, 2026
Merged

feat(turn): add ResolvedAgentExecution::resolve and route prod turn paths#8179
Nillth merged 4 commits into
zeroclaw-labs:masterfrom
NNet-Dev:docs/agent-policy-parity-harness

Conversation

@Nillth

@Nillth Nillth commented Jun 22, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Base branch: master
  • What changed and why:
    • Adds ResolvedAgentExecution::resolve in agent/turn/execution.rs - the single
      constructor seam the agent-policy parity-harness design page describes - and routes
      every production turn-construction path through it. refactor(turn): bundle per-agent ToolLoop fields into ResolvedAgentExecution #8156 landed the flat
      ResolvedAgentExecution carrier but left it assembled inline at each call site; this
      collapses that into one seam.
    • Adds two grouped input layers: ResolvedIo (the per-turn I/O wiring: tool
      registry, observer, approval, hooks, receipts, multimodal, activation set,
      model-switch callback) and ResolvedRuntimeKnobs (the values derived from the
      agent's resolved config: iteration cap, excluded/dedup-exempt tools, pacing,
      parsing/parallel flags, budgets, loop knobs). Named ResolvedRuntimeKnobs (not
      ResolvedRuntime) so it does not shadow the existing
      zeroclaw_config::schema::ResolvedRuntime. These are the future sealed sub-bundles,
      introduced now as resolve()'s inputs. Re-exported via agent::turn + agent::loop_.
    • Routes all 8 production turn-construction sites through resolve(), across both
      crates: the channels orchestrator (zeroclaw-channels), loop_ (run /
      process_message / agent_turn), Agent::turn (x2), tools/delegate, skills/review.
      The channel path is the same class of construction site that fix(mcp): scope MCP tools per-agent and enforce the denylist #8120 was about, so it is
      routed here rather than left outside the seam.
    • Updates the parity-harness doc: flipped to present tense for resolve() and its input
      layers (they exist + every prod path uses them), with the current/future boundary made
      explicit. The harness (agent/parity.rs), per-field resolution, and field sealing stay
      marked as later surface PRs.
    • Why: packs the documented seam together with the doc that describes it, so the page
      documents present-tense code. This resolves the review on the doc (it referred to
      resolve() / sealed inputs as if they already existed).
  • Scope boundary: the seam + routing (behavior-neutral) and the doc update. No behavior
    change: resolve() is a pure field spread, and the #[cfg(test)] literals are untouched
    (the bundle's fields stay pub).
  • Blast radius: internal turn-construction only. run_tool_call_loop's signature is
    unchanged; the channels orchestrator now constructs through resolve() (so it imports the
    new input types); no other consumer is affected.
  • Linked issue(s): Related refactor(turn): bundle per-agent ToolLoop fields into ResolvedAgentExecution #8156 (the carrier this builds the seam on) and fix(mcp): scope MCP tools per-agent and enforce the denylist #8120 (the
    cross-agent tool divergence the seam exists to make uncompilable).
  • Labels: agent, tool, skills, runtime, docs, size: M, risk: medium.

Validation Evidence (required)

Toolchain pinned to 1.93.0 (matches CI). Branch merged current with upstream/master
(clean auto-merge, no conflicts) and validated on the MERGED tree, since CI builds the PR
merged with base. Literal output:

  • cargo fmt --all -- --check: clean.
  • cargo clippy -p zeroclaw-channels -p zeroclaw-runtime --all-targets -- -D warnings:
    Finished in 58.21s, EXIT 0 (compiles the new channels routing plus the ~56
    #[cfg(test)] literals).
  • cargo check -p zeroclaw-channels -p zeroclaw-runtime on the merged tree: EXIT 0.
  • Full push-branch validate.sh workspace battery (fmt + clippy --all-targets +
    cargo test, the CI-equivalent set): RESULT: PASS (0 blocking, 0 warning); the known
    cron log-capture flaky did not fire this run.
  • Docs gate scripts/ci/docs_quality_gate.sh: no prose em-dashes, markdownlint 0 errors.
  • Intentionally skipped: web / shell batteries (no such files changed).

Security & Privacy Impact (required)

  • New permissions, capabilities, or file system access scope? No.
  • New external network calls? No.
  • Secrets / tokens / credentials handling changed? No.
  • PII or personal data in diff/tests/docs? No.
  • Behavior-neutral: resolve() spreads the same already-resolved values each site computed,
    so approval policy, excluded tools, and every other policy field reach the engine exactly
    as before. No trust-boundary or default change. The doc references only merged, public
    facts and names no still-unmitigated divergence.

Compatibility (required)

  • Backward compatible? Yes. resolve() and the two input structs are additive; the
    bundle's pub fields are unchanged, so existing constructors (including the test
    literals) still compile. The channels orchestrator additionally imports
    ResolvedIo/ResolvedRuntimeKnobs to construct through the seam.
  • Config / env / CLI surface changed? No.
  • Upgrade steps: none.

Rollback (required for risk: medium and risk: high)

Low risk: git revert <squash-sha>. Three self-contained commits (the doc, the seam +
runtime routing, then the channels-orchestrator routing) on top of the merged #8156 carrier;
nothing else depends on resolve() yet.

Documents the agent-policy parity harness for the enforcement-unification
program: the distinct turn-construction paths, the (setting x path) divergence
matrix and the secure-by-default principle (omission is not a grant), the
convergence target (one ResolvedAgentExecution::resolve seam, the zeroclaw-labs#8156 trunk),
and how the harness in agent/parity.rs grows one surface at a time, asserting
only the cross-path parity property no per-primitive or safety_net test covers.

Motivated by zeroclaw-labs#8120 (cross-agent MCP tool divergence). Docs-only; registered in
the mdbook SUMMARY under Contributing.
@github-actions github-actions Bot added the docs Auto scope: docs/markdown/template files changed. label Jun 22, 2026
@Audacity88 Audacity88 added this to the v0.8.2 milestone Jun 22, 2026

@Audacity88 Audacity88 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 @Nillth. I reviewed the live PR state at 9bbe5ec, the added mdBook page and SUMMARY.md entry, the merged #8120 and #8156 context the page cites, current ResolvedAgentExecution source, and the public CI/check status. I did not run local docs validation for this review-only pass; I relied on the PR's reported docs validation, green public CI, and source review.

🟢 What looks good — The parity-harness scope is useful

The page is otherwise pointed at the right gap. It keeps the harness focused on cross-path parity instead of duplicating per-primitive tests or the safety_net turn-engine oracle, and it explains the secure-by-default rule clearly: omission is not a grant. That is the right principle to preserve once the current/future wording is corrected.

🔴 Blocking — The page presents future seams as current source

The new page needs to distinguish the carrier that #8156 actually merged from the future resolver/harness work this page is describing. Right now it says every path resolves through ResolvedAgentExecution::resolve as "the behavior-neutral trunk introduced by #8156", and the "Adding a surface" steps tell contributors to move each surface into that associated function. Current crates/zeroclaw-runtime/src/agent/turn/execution.rs says the opposite: #8156/G0 is the behavior-neutral regrouping, and later epics move resolution into a single resolve() constructor. There is no ResolvedAgentExecution::resolve today.

The same issue appears in the harness section. It says the parity harness lands in crates/zeroclaw-runtime/src/agent/parity.rs, but current master has agent/safety_net.rs and agent/turn/execution.rs; it does not have agent/parity.rs. Since this PR's value is the contributor-facing architecture/methodology doc, the doc cannot imply those source seams already exist.

Please rephrase the page so the current/future boundary is explicit. For example: #8156 introduced the ResolvedAgentExecution carrier/trunk; future surface PRs should add the resolver/sealed inputs and parity harness, with agent/parity.rs as the intended location if that remains the chosen file shape.

…aths

Adds the resolution seam the agent-policy parity doc (zeroclaw-labs#8179) describes, so the
doc documents present-tense code instead of future work (addresses the review).

- New `ResolvedAgentExecution::resolve(model_access, io, runtime)` constructor in
  agent/turn/execution.rs, plus the two grouped input layers `ResolvedIo` and
  `ResolvedRuntimeKnobs` (the future sealed sub-bundles, introduced now as resolve()'s
  inputs). Re-exported via agent::turn and agent::loop_.
- Routes all 7 production turn-construction sites (loop_ run/process_message/agent_turn,
  Agent::turn x2, delegate, skills review) through resolve(). Pure field spread:
  behavior-neutral. The ~56 #[cfg(test)] literals keep the flat struct (fields stay pub).
- Doc rephrased to mark the current/future boundary and flipped to present tense for
  resolve() (it exists + every prod path uses it); the parity harness / agent/parity.rs
  and the per-field resolution + field sealing remain explicitly future.

Validation (toolchain 1.93.0): cargo check + clippy --all-targets -D warnings clean;
cargo test -p zeroclaw-runtime 2342 passed, 1 failed = the pre-existing cron flaky
(zero cron files in the diff; passes in isolation).
@Nillth Nillth changed the title docs(contributing): add agent-policy parity-harness design feat(turn): add ResolvedAgentExecution::resolve and route prod turn paths Jun 23, 2026
@Nillth

Nillth commented Jun 23, 2026

Copy link
Copy Markdown
Collaborator Author

Good call, @Audacity88 - you were right that the page implied ResolvedAgentExecution::resolve and the sealed inputs already existed.

Rather than just soften the wording, I packed the seam into this PR: added ResolvedAgentExecution::resolve and routed every production turn-construction path through it (loop_ run/process_message/agent_turn, Agent::turn x2, delegate, skills review). So the page now documents present-tense code: resolve() and its ResolvedIo / ResolvedRuntimeKnobs input layers exist and every prod path uses them.

I kept the current/future boundary explicit: the parity harness (agent/parity.rs), absorbing each surface's per-field resolution into resolve(), and sealing the bundle's fields are still future surface PRs, and the doc says so.

The seam is behavior-neutral (a pure field spread; the ~56 test literals are untouched). Validation: clippy --all-targets -D warnings clean, 2342 runtime tests pass (the one failure is the known cron log-capture flaky; no cron files in the diff).

@github-actions github-actions Bot added agent Auto scope: src/agent/** changed. runtime Auto scope: src/runtime/** changed. skills Auto scope: src/skills/** changed. labels Jun 23, 2026
@Nillth Nillth added size: M tool Auto scope: src/tools/** changed. and removed size: S labels Jun 23, 2026

@Audacity88 Audacity88 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 @Nillth. I reviewed the live PR state at 2032445, the pushed update after my prior review, the top-level comment, the current captured PR diff against master, the added parity-harness doc, the current ResolvedAgentExecution source, and the linked #8120 / #8156 context. I did not run local Cargo validation for this review-only pass; the remaining issue is visible from source and does not depend on test execution.

✅ Resolved — The resolver and current/future boundary now exist

The earlier doc/source mismatch is addressed for the runtime-side seam. ResolvedAgentExecution::resolve, ResolvedIo, and ResolvedRuntimeKnobs now exist in agent/turn/execution.rs, and the added doc now says agent/parity.rs / the parity harness are future work rather than current source. That fixes the core problem from my prior review: the page no longer describes the parity harness as already present.

🔴 Blocking — The channel construction path still bypasses resolve()

The PR still claims more coverage than the diff provides. The new doc lists Channel | the channels orchestrator as one of the construction paths and then says every production path constructs through ResolvedAgentExecution::resolve. The PR body also frames this as routing every production turn-construction path through the new seam.

But crates/zeroclaw-channels/src/orchestrator/mod.rs still calls run_tool_call_loop(ToolLoop { exec: ResolvedAgentExecution { ... } }) directly. That is production channel code, not a test literal. So the channel path still assembles the execution bundle inline instead of going through ResolvedAgentExecution::resolve(ResolvedModelAccess, ResolvedIo, ResolvedRuntimeKnobs).

That matters because the whole point of this slice is to establish the single seam the parity work can build on. Leaving the channel path outside that seam keeps one of the documented production construction paths in the old shape, and it is the same class of path that made #8120 worth documenting.

Please either route the channel orchestrator through ResolvedAgentExecution::resolve with the same field values, or narrow the doc/PR body so they do not claim every production construction path uses the seam. Given the doc explicitly includes the channel path in the parity matrix, routing the orchestrator through resolve() in this PR is the cleaner fix.

Nillth added 2 commits June 23, 2026 12:19
…lve()

The channels orchestrator was the one production turn-construction path still
assembling the engine bundle inline (ResolvedAgentExecution { .. }) instead of going
through ResolvedAgentExecution::resolve, which the parity doc's matrix lists as a
construction path and the PR claims uses the seam.

Route crates/zeroclaw-channels/src/orchestrator/mod.rs through
resolve(ResolvedModelAccess, ResolvedIo, ResolvedRuntimeKnobs) with the same field
values (behavior-neutral), and import the two input types. All 8 production sites (this
one plus the 7 in zeroclaw-runtime) now construct through the single seam. The channel
path is the same class as the zeroclaw-labs#8120 leak, so it belongs inside the seam.

Validation (1.93.0): cargo check -p zeroclaw-channels EXIT 0; clippy --all-targets
-D warnings clean across zeroclaw-channels + zeroclaw-runtime; fmt clean.
@Nillth

Nillth commented Jun 23, 2026

Copy link
Copy Markdown
Collaborator Author

You're right, thanks @Audacity88 - the channels orchestrator was still assembling the bundle inline, and since the doc's matrix lists the Channel path and the body claimed every production path uses the seam, that was an overclaim.

Fixed it the way you suggested: routed crates/zeroclaw-channels/src/orchestrator/mod.rs through ResolvedAgentExecution::resolve(ResolvedModelAccess, ResolvedIo, ResolvedRuntimeKnobs) with the same field values (behavior-neutral). All 8 production construction sites now go through the single seam - the channel orchestrator plus the 7 in zeroclaw-runtime. The channel path is the same class as #8120, so bringing it under the seam in this slice is the right call rather than narrowing the doc.

Also merged current with master (three landed commits touched files this PR edits, including a channels orchestrator fix) and re-validated on the merged tree: clippy --all-targets -D warnings clean across zeroclaw-channels + zeroclaw-runtime, validate.sh full workspace battery PASS, fmt clean.

@singlerider singlerider left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

First review from me. Checked the current head f9fcb080 (newer than @Audacity88's reviewed 2032445c). Read the source; did not run local Cargo. @Audacity88's blocker is resolved at this head; one CI item is still pending.

🟢 RESOLVED at head: the channel path now routes through the seam

@Audacity88's 🔴 was that crates/zeroclaw-channels/src/orchestrator/mod.rs still built ResolvedAgentExecution { ... } inline, bypassing resolve(). At f9fcb080 that call site (orchestrator/mod.rs:4845) is now exec: ResolvedAgentExecution::resolve(...). I swept all production construction paths and they now go through the seam: agent.rs:1970/:2371, loop_.rs:752/:1885/:2394, skills/review.rs:98, tools/delegate.rs:1895, and the channel orchestrator. The remaining inline ResolvedAgentExecution { ... } literals (loop_.rs:4919+) are all past the #[cfg(test)] at loop_.rs:806, i.e. test code, not production paths. So the doc/PR-body claim that every production turn-construction path uses resolve() is now accurate.

🔴 Blocking: CI must go green on this head before merge

The earlier Format check failed on the prior commit, and at f9fcb080 the checks (Format, main, Apply path labels) are currently QUEUED, not yet run. The seam routing is correct in source, but I will not call this merge-ready until Format and the build/test gate pass on the current head, since a cargo fmt --check failure is a hard gate. Push nothing further if CI comes back green; if Format fails again, run cargo fmt --all and repush.

Code substance is sound and @Audacity88's channel blocker is closed. The only open item is green CI on f9fcb080. Once it lands green, this is approvable. Not approving over @Audacity88's active changes-requested; @Audacity88, your channel blocker is resolved at this head if you want to re-check.

@github-actions github-actions Bot added channel Auto scope: src/channels/** changed. and removed tool Auto scope: src/tools/** changed. labels Jun 23, 2026

@Audacity88 Audacity88 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 @Nillth. I re-reviewed current head f9fcb08 after the channel-orchestrator follow-up and the merge from current master. The live PR was still CHANGES_REQUESTED because my earlier review was active, so this approval is meant to clear that source-level block. I checked your latest update, @singlerider's review, the current diff and source for the channel orchestrator / runtime / delegate / skill-review call sites, the added parity-harness doc, and the linked #8120 / #8156 context. I did not run local Cargo validation for this review-only pass; this approval is based on source review, the PR's validation evidence, and current GitHub CI status.

✅ Resolved — The channel path now uses the resolver seam

My prior blocker was that crates/zeroclaw-channels/src/orchestrator/mod.rs still built ResolvedAgentExecution { ... } inline while the doc and PR body claimed every production turn-construction path used ResolvedAgentExecution::resolve.

That is resolved at this head. The channel orchestrator now passes exec: ResolvedAgentExecution::resolve(...) with the same model access, I/O, and runtime-knob values. I agree with @singlerider's source sweep here: the production construction sites now route through the seam across Agent::turn, loop_, delegate, skill review, and the channel orchestrator. The remaining direct ResolvedAgentExecution { ... } literals I saw are in test / safety-net code, so they do not contradict the production-path claim.

🟢 What looks good — The current/future boundary is now honest

The doc now distinguishes the current seam from the future per-field resolution, sealing, and parity-harness work. That keeps #8156's carrier and this PR's resolve() constructor from being oversold as complete policy parity, while preserving the important rule that an omitted policy setting is a defect rather than a grant.

🟡 Warning — Wait for current-head CI before merge

This approval clears my source-level blocker; it is not a merge-readiness claim. At live readback, several Quality Gate jobs for f9fcb08 were still queued or in progress. If those all pass, I do not see a remaining blocker from my side. If any check fails, handle that before merge.

@Nillth
Nillth merged commit 63e0ef5 into zeroclaw-labs:master Jun 23, 2026
19 checks passed
Nillth added a commit that referenced this pull request Jul 3, 2026
…AgentExecution::resolve (#8629)

#8179 routed the channel orchestrator's turn construction through the
ResolvedAgentExecution::resolve seam. #8011 (per-sender /thinking overrides), branched
before that landed, rewrote the same block and re-inlined the flat
ResolvedAgentExecution { .. } literal - reverting the routing without meaning to. CI
stayed green because resolve() is a pure field spread, so an un-routing is invisible
to tests; only the structure regressed.

Restore the routing: construct through resolve(ResolvedModelAccess, ResolvedIo,
ResolvedRuntimeKnobs) with the exact field values the inline literal used - including
thinking.effective_temperature, so #8011's per-sender thinking behavior is untouched.
Behavior-neutral by construction.

With this, every production turn path again constructs through the one seam the
agent-policy parity-harness doc describes (docs/book/src/contributing/
agent-policy-parity-harness.md). Follow-up work makes this class of silent un-routing
uncompilable.

Validation (toolchain 1.93.0): cargo check -p zeroclaw-channels clean; cargo clippy
-p zeroclaw-channels -p zeroclaw-runtime --all-targets -- -D warnings clean (2m27s);
cargo fmt --all --check clean.
singlerider added a commit to NNet-Dev/zeroclaw that referenced this pull request Jul 6, 2026
The parity-harness comments narrated specific merged-PR numbers
(zeroclaw-labs#8640/zeroclaw-labs#8700/zeroclaw-labs#8701/zeroclaw-labs#7415/zeroclaw-labs#8179) in free-text prose, which goes stale the
moment the next cut-over lands and duplicates what git blame and the
row's own tracking/evidence fields already carry. Rewrite the narration
in timeless behavioral terms and drop the bare issue numbers, keeping the
structural owner_epic/tracking/evidence fields and the Epic-surface
naming the harness is organized around.

No test or behavior change; the parity matrix and L1/L2 locks are
untouched.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agent Auto scope: src/agent/** changed. channel Auto scope: src/channels/** changed. docs Auto scope: docs/markdown/template files changed. runtime Auto scope: src/runtime/** changed. skills Auto scope: src/skills/** changed.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants