Skip to content

(MOT-4184) feat(harness): add prompt evaluation support - #595

Merged
ytallo merged 9 commits into
mainfrom
feat/harness-eval-draft
Jul 24, 2026
Merged

(MOT-4184) feat(harness): add prompt evaluation support#595
ytallo merged 9 commits into
mainfrom
feat/harness-eval-draft

Conversation

@ytallo

@ytallo ytallo commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

What changed

Adds the harness-side surface prompt evaluation needs, and — since #596 was merged into this branch — the eval worker that consumes it.

New harness functions

  • harness::metrics (src/functions/metrics.rs) — consolidates usage, cost, function-call, trace, and span metrics for a session behind one call, so callers stop stitching them together per collector.
  • harness::session-tree (src/functions/session_tree.rs) — inspect a session and its descendants.
  • harness::send / harness::spawn gain execution budgets: max_output_tokens, max_total_tokens, max_cost_usd, max_turns. Enforcement and reservation live in src/budget.rs; teardown of an evaluation's sessions in src/functions/teardown.rs.

Behavior changes worth a reviewer's attention — these are easy to miss in a 19k-line diff:

  1. default_functions is now allow: ["*"] (src/config.rs, −65 lines). It was a curated read-only baseline. Per the doc comment this PR rewrites, that value doubles as the ask-mode ceiling — so this widens what every ask-mode turn may call, not just parentless spawns. It now admits state::set, harness::spawn, router::chat, shell::exec, and engine::register_trigger, each of which the removed test asserted was excluded. Deliberate (discovery-denied policies break models), but it is a security-posture change and should be reviewed as one.
  2. Native exposure now publishes the subscription controls (src/functions/subscribe.rs). engine::register_trigger and engine::unregister_trigger are harness-intercepted, so the engine registry never listed them and an agent whose policy allowed them saw no tool. native_control_tools publishes their real contracts, ahead of registry functions.
  3. max_output_tokens is forwarded only when the caller set one (src/turn_loop.rs). The first pass sent it on every request, filled with the model's own ceiling — a request-shape change for callers that never asked, which lets a provider apply a different policy than its default. The internal reservation still budgets against the effective limit; when a caller does set a cap we send the reservation, so the provider is never told it may emit more than we reserved.

Also included

  • harness/tests/agent-quality/ — a package that runs two harness subjects against the same real-model scenarios (same prompt / two models, or same model / two prompts) on one path: build prompt → harness::send → await harness::turn-completed → read harness::metrics → evaluate.
  • The eval worker and its console page, merged from (MOT-4184) feat(eval): add prompt comparison worker #596. See that PR's description for its surface.
  • E2E fixtures updated for (2) and (3) above, plus a tools_exact_after_controls matcher — the control tools' prose lives in the harness crate, which the e2e crate cannot import, so they are pinned by name while registry tools stay byte-exact.

Size

19k additions reads alarming; roughly a third is generated and another third is the eval worker merged in from #596.

Lines Share
eval worker 4,345 23%
Golden schemas (generated) 3,744 20%
eval console UI 3,598 19%
agent-quality package 2,634 14%
Lockfiles (generated) 2,621 14%
harness src/ 1,960 10%
e2e fixtures, docs, wiring 166 1%

The harness change under review is the 1,960-line row, concentrated in metrics.rs (567), budget.rs (519), session_tree.rs (193), clients/session.rs (173), and turn_loop.rs (116).

Why

The eval worker needs a durable, queryable harness API with bounded execution and reliable cleanup. Keeping the flow at send -> metrics -> result reuses the existing harness and engine primitives instead of growing a parallel evaluation stack.

Validation

  • cargo test --manifest-path harness/Cargo.toml -p harness-integration — 84 passed, 0 failed (includes every scenario's fixture_is_valid and the compiled-scenario round-trip).
  • cargo check --manifest-path harness/Cargo.toml --all-targets --offline — clean.
  • cargo test --manifest-path eval/Cargo.toml --offline — 36 passed, 0 failed, run against the eval tree as merged.

Not verified locally: the full make integration-e2e was not run to completion, so the 9 E2E scenarios rest on CI. The two fixture-affecting behavior changes above (native control tools, max_output_tokens) were derived from the failing run's artifacts and from turn_loop.rs's tool-assembly order rather than from a passing local run.

@vercel

vercel Bot commented Jul 24, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
workers Ready Ready Preview, Comment Jul 24, 2026 8:24pm
workers-tech-spec Ready Ready Preview, Comment Jul 24, 2026 8:24pm

Request Review

@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown

Important

Review skipped

Too many files!

This PR contains 122 files, which is 22 over the limit of 100.

To get a review, narrow the scope:
• coderabbit review --committed # exclude uncommitted changes
• coderabbit review --dir # limit to a subdirectory
• coderabbit review --base # compare against a closer base

Upgrade to a paid plan to raise the limit.

This review couldn't start because sufficient usage credits or metered capacity aren't available. Add credits or update usage-based reviews in the billing tab, then retry.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 907e43d1-334c-45f9-80aa-1a7b65624f4d

📥 Commits

Reviewing files that changed from the base of the PR and between 6452708 and 70bbaba.

⛔ Files ignored due to path filters (4)
  • approval-gate/Cargo.lock is excluded by !**/*.lock
  • eval/Cargo.lock is excluded by !**/*.lock
  • harness/Cargo.lock is excluded by !**/*.lock
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (122)
  • .github/workflows/create-tag.yml
  • .github/workflows/release.yml
  • README.md
  • approval-gate/iii.worker.yaml
  • console/web/e2e/ui-send.spec.ts
  • eval/Cargo.toml
  • eval/README.md
  • eval/build.rs
  • eval/iii.worker.yaml
  • eval/src/contract.rs
  • eval/src/error.rs
  • eval/src/events.rs
  • eval/src/functions.rs
  • eval/src/ids.rs
  • eval/src/lib.rs
  • eval/src/limits.rs
  • eval/src/locks.rs
  • eval/src/main.rs
  • eval/src/manifest.rs
  • eval/src/queue.rs
  • eval/src/report.rs
  • eval/src/runtime.rs
  • eval/src/state.rs
  • eval/src/surface.rs
  • eval/src/ui.rs
  • eval/tests/golden/schemas/eval.assert.exact.json
  • eval/tests/golden/schemas/eval.assert.normalized_text.json
  • eval/tests/golden/schemas/eval.cancel.json
  • eval/tests/golden/schemas/eval.delete.json
  • eval/tests/golden/schemas/eval.list.json
  • eval/tests/golden/schemas/eval.on-turn-completed.json
  • eval/tests/golden/schemas/eval.rerun.json
  • eval/tests/golden/schemas/eval.result.json
  • eval/tests/golden/schemas/eval.start.json
  • eval/tests/golden/schemas/eval.status.json
  • eval/tests/golden/schemas/eval.step.json
  • eval/tests/golden/schemas/eval.sweep.json
  • eval/tests/manifest.rs
  • eval/tests/schemas.rs
  • eval/tests/support/mod.rs
  • eval/ui/build.mjs
  • eval/ui/package.json
  • eval/ui/page.tsx
  • eval/ui/src/api.ts
  • eval/ui/src/components.tsx
  • eval/ui/src/events.ts
  • eval/ui/src/form.ts
  • eval/ui/src/page/EvaluationDetail.tsx
  • eval/ui/src/page/History.tsx
  • eval/ui/src/page/NewEvaluationForm.tsx
  • eval/ui/src/page/index.tsx
  • eval/ui/src/types.ts
  • eval/ui/styles.css
  • eval/ui/tsconfig.json
  • harness/Cargo.toml
  • harness/Makefile
  • harness/README.md
  • harness/architecture/README.md
  • harness/iii.worker.yaml
  • harness/prompts/default.txt
  • harness/src/budget.rs
  • harness/src/clients/router.rs
  • harness/src/clients/session.rs
  • harness/src/config.rs
  • harness/src/functions/metrics.rs
  • harness/src/functions/mod.rs
  • harness/src/functions/on_session_deleted.rs
  • harness/src/functions/react.rs
  • harness/src/functions/send.rs
  • harness/src/functions/session_tree.rs
  • harness/src/functions/spawn.rs
  • harness/src/functions/status.rs
  • harness/src/functions/stop.rs
  • harness/src/functions/subscribe.rs
  • harness/src/functions/teardown.rs
  • harness/src/hooks/runner.rs
  • harness/src/lib.rs
  • harness/src/policy.rs
  • harness/src/prompt/mod.rs
  • harness/src/prompt/mode.rs
  • harness/src/prompt/tests.rs
  • harness/src/subagent.rs
  • harness/src/surface.rs
  • harness/src/turn_loop.rs
  • harness/src/types/turn.rs
  • harness/tests/agent-quality/Cargo.toml
  • harness/tests/agent-quality/README.md
  • harness/tests/agent-quality/src/bin/agent-quality.rs
  • harness/tests/agent-quality/src/bin/prompt-eval.rs
  • harness/tests/agent-quality/src/comparison.rs
  • harness/tests/agent-quality/src/completion.rs
  • harness/tests/agent-quality/src/context.rs
  • harness/tests/agent-quality/src/error.rs
  • harness/tests/agent-quality/src/lib.rs
  • harness/tests/agent-quality/src/limits.rs
  • harness/tests/agent-quality/src/report.rs
  • harness/tests/agent-quality/src/scenarios/common.rs
  • harness/tests/agent-quality/src/scenarios/mod.rs
  • harness/tests/agent-quality/src/scenarios/plain_response.rs
  • harness/tests/agent-quality/src/scenarios/security_review.rs
  • harness/tests/agent-quality/src/scenarios/single_function.rs
  • harness/tests/agent-quality/src/scenarios/triggered_work.rs
  • harness/tests/agent-quality/src/subject.rs
  • harness/tests/agent-quality/src/suite.rs
  • harness/tests/agent-quality/subjects/evaluation.md
  • harness/tests/agent-quality/subjects/luna.json
  • harness/tests/agent-quality/subjects/sol.json
  • harness/tests/e2e/src/scenarios/coalesced_fire.rs
  • harness/tests/e2e/src/scenarios/dsl.rs
  • harness/tests/e2e/src/scenarios/join_spec_mismatch.rs
  • harness/tests/e2e/src/scenarios/late_join_replay.rs
  • harness/tests/e2e/src/scenarios/reaction_policy_inheritance.rs
  • harness/tests/e2e/src/scenarios/reaction_unregisters_run.rs
  • harness/tests/e2e/src/scenarios/state_worker_sidecar.rs
  • harness/tests/golden/schemas/harness.metrics.json
  • harness/tests/golden/schemas/harness.send.json
  • harness/tests/golden/schemas/harness.session-tree.json
  • harness/tests/golden/schemas/harness.spawn.json
  • harness/tests/manifest.rs
  • harness/tests/schemas.rs
  • iii-permissions.yaml
  • pnpm-workspace.yaml

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/harness-eval-draft

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

skill-check — worker

0 verified, 49 skipped (no docs/).

Layer Result
structure
vale
ai
render

Four for four. Nicely done.

* (MOT-4184) feat(eval): add prompt comparison worker

* (MOT-4184) fix(eval): satisfy clippy and refresh schemas
ytallo added 2 commits July 24, 2026 16:09
…er set one

This PR introduced both the `ChatParams.max_output_tokens` field and the
`Some(generation_max_output_tokens)` that fills it, so every harness turn
started carrying an explicit output cap equal to the model's own ceiling —
a request-shape change for callers that never asked for one, which lets a
provider apply a different policy than its default.

`generation_max_output_tokens` stays the internal reservation that context
assembly and `budget::reserve` budget against. Send it only when the caller
configured a cap, and send the reservation rather than the raw request so the
provider is never told it may emit more than we reserved.

Callers that want a cap enforced still get one: `eval` sets it from
`limits.execution.max_output_tokens_per_call`, as does agent-quality.

Fixes the E2E floor failures where the scripted provider rejected generation
1 with `max_output_tokens: expected absent, got 4096`.
Native exposure now publishes the harness-intercepted subscription controls
(`subscribe::native_control_tools`) ahead of the registry functions a turn's
policy allows — correct, since the engine registry never listed them and an
agent allowed to call them previously saw no tool for it.

Every fixture send compiles to `expose: Native`, so all six scenarios that
`allow_id(engine::register_trigger)` now receive that extra leading tool.
Only E2E-004 surfaced in CI; the other five died at generation 1 on the
`max_output_tokens` floor first and would have failed on the next run.

The control tools' descriptions and schemas live in the harness crate, which
the e2e crate does not depend on, so `tools_exact_after_controls` pins them by
name while the registry tools that follow stay byte-exact. The list as a whole
stays an exact match — an unexpected extra tool keeps its prose and fails,
which is what caught this change to begin with.
@ytallo
ytallo merged commit 7bfc1fe into main Jul 24, 2026
25 checks passed
rohitg00 added a commit that referenced this pull request Jul 27, 2026
Resolves against the prompt-evaluation work that landed in #595, which
edited the same sections this branch moves out of the identity prompt.

- `default.txt` keeps this branch's trimmed identity. The `curl`
  prohibition is dropped with it: the web worker has injected that rule
  through its own pre-generate hook since #385, so the line was a stale
  duplicate that also fired on rigs without the web worker installed.
- #595's dispatch doctrine (audit every resolved resource selector before
  dispatching; derive a per-run namespace suffix and confirm the namespace
  is absent) moves into `harness/skills/orchestration.md`, which now owns
  spawn and reaction mechanics.
- #595 also corrected the reaction session semantics: an ordinary event
  reaction MUST omit `metadata.session_id`, and every event then creates a
  fresh distinct child under the resolved console root. The playbook still
  carried the older rule, that omitting it delivers back into the
  registering chat, so it was describing behaviour the runtime no longer
  has. Same for `once`, which is a top-level registration field and is
  standing by default on join predecessor edges.
- The three tests #595 added assert that doctrine against the identity
  prompt. They now assert it against the orchestration playbook, which is
  where the text lives after this branch.
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