Skip to content

feat: move the runtime onto the agent and stamp the agent config onto the trace - #2106

Merged
mikasenghaas merged 17 commits into
mainfrom
feat/agent-runtime-config
Jul 23, 2026
Merged

feat: move the runtime onto the agent and stamp the agent config onto the trace#2106
mikasenghaas merged 17 commits into
mainfrom
feat/agent-runtime-config

Conversation

@mikasenghaas

@mikasenghaas mikasenghaas commented Jul 22, 2026

Copy link
Copy Markdown
Member

Summary

  • Runtime is an agent field: runtime moves from HarnessConfig to AgentConfig — where a run provisions its box is the seat's placement decision, not the harness program's. It addresses as --env.<role>.runtime.* ([env.agent.runtime] in TOML); the harness config keeps its own fields nested under --env.<role>.harness.*. A harness-level runtime key now fails as an unknown field.
  • The trace records the agent losslessly: AgentInfo is {config, runtime, name, trainable}. config is the resolved AgentConfig the rollout ran as — the exact value that rebuilds the agent (make_agent(trace.agent.config)) — embedded as a plain typed field (the configs package sits below the record, so no forward references). runtime is the box it ran in (the resolved config plus the provisioned resource ID and borrowed), moved off the top-level trace under the agent that owns it (Trace.runtime stays as a read-only property).
  • Strict vs. wire reads are the reader's explicit choice (mirroring WireTaskData): a bare Trace is the strict read — agent.config validates as AgentConfig, narrowing the harness by id, so its package must be importable — while WireTrace/WireEpisode parameterize the new AgentConfigT with WireAgentConfig (extra-allow WireHarnessConfig inside), so consumers without the run's packages (e.g. a trainer) load any record plugin-free, knobs readable via model_extra. The serve wire and replay read loose; CLI/config parsing stays strict.
  • Configs separate from logic: the main abstractions' config classes move into a verifiers/v1/configs/ package — one module per domain (configs.agent, configs.env, configs.harness, configs.task, configs.taskset, configs.judge, configs.retries) — and the CLI entrypoints' run configs move to configs/cli/. Config modules import only types and other configs, so any module (the trace record included) can embed them without import cycles. Hard cut internally; the public vf.* surface is unchanged.
  • The env's subprocess warning, _runs_local, the agentic-judge container check, and the dashboard read each seat's runtime; runtime overrides get their own overview row.
  • The debug CLI (agent-less traces) stamps an explicit AgentInfo(name="debug", trainable=False) seat carrying the runtime policy, and lands the provisioned box on it — persisted debug records keep the box (resource id, resolved type, cache status) under the same shape as eval records.
  • TRACE_VERSION bumped to 3.

Breaking

  • --env.<role>.harness.runtime.*--env.<role>.runtime.* (TOML: runtime moves from [env.agent.harness] to [env.agent.runtime]). The old placement fails validation as an unknown harness field.
  • HarnessConfig.runtime is gone; code reading harness.config.runtime should use the agent's config (AgentConfig.runtime / Agent.runtime_config).
  • Trace records (TRACE_VERSION 3): trace.agent is {config, runtime, name, trainable}agent.model/agent.sampling/agent.harness become agent.config.model/.sampling/.harness, and the top-level trace.runtime field moves to agent.runtime (still readable as the Trace.runtime property). Custom harness knobs serialize with the record; read them plugin-free via WireTrace/WireEpisode (WireAgentConfig/WireHarnessConfig), or fully typed via a bare Trace read where the harness package is importable.
  • Internal imports of config classes move to verifiers.v1.configs.* (e.g. from verifiers.v1.configs.harness import HarnessConfig); the old logic-module paths no longer export them. vf.* names are unchanged.

Verification

  • Rollout smoke test: the record carries agent.config (model, narrowed harness, runtime policy, resolved sampling) and agent.runtime (type, resource id, borrowed); Trace.model_validate round-trips typed (NullHarnessConfig narrows back), and make_agent(trace.agent.config) rebuilds an equivalent agent.
  • uv run eval @ configs/<each>.toml --dry-run passes for every eval config.
  • uv run pytest tests/v1 -k "not docker and not prime and not modal" passes (excluding the docker/prime legs plus two pre-existing macOS-only failures — rlm/kimi-code installs hard-code flock — all unrelated and failing on main too).
  • uv run ruff check . and uv run pre-commit run --all-files pass.

🤖 Generated with Claude Code

Note

Move runtime config from HarnessConfig to AgentConfig and stamp it onto traces

  • Runtime isolation policy (subprocess, docker, prime) is now configured at AgentConfig.runtime instead of HarnessConfig.runtime. CLI flags change from --env.<agent>.harness.runtime.type to --env.<agent>.runtime.type.
  • AgentInfo in traces now carries the full AgentConfig (including resolved harness and sampling) under agent.config, and runtime info is recorded at agent.runtime instead of a top-level trace.runtime field.
  • Trace schema version bumped from 2 to 3; serialized traces no longer include a top-level runtime field.
  • Config types (HarnessConfig, TaskConfig, TasksetConfig, RetryConfig, JudgeConfig, AgentConfig) are relocated from their runtime modules into a new verifiers/v1/configs/ package, with CLI-specific configs under verifiers/v1/configs/cli/.
  • Risk: breaking change to trace schema and all import paths for config types; TOML config files referencing [env.agent.harness.runtime] must be updated to [env.agent.runtime].

Changes since #2106 opened

  • Introduced WireAgentConfig and WireHarnessConfig classes that bypass plugin resolution and preserve extra harness-specific fields, and added a field_validator on AgentInfo.config that validates dict inputs as WireAgentConfig instead of AgentConfig [20d01f0]
  • Made Trace and AgentInfo models generic over a new AgentConfigT type parameter [9c27ec5]
  • Made Episode model generic over a third type parameter AgentConfigT [9c27ec5]
  • Updated WireTrace and WireEpisode type aliases to specify WireAgentConfig as the concrete agent configuration type [9c27ec5]
  • Updated RunGroupResponse and EnvClient.run_group to use WireTrace type [9c27ec5]
  • Updated run_replay function to read episodes with WireAgentConfig generic parameter [9c27ec5]
  • Pinned ruff version to 0.15.21 in the .github/workflows/style.yml CI workflow [9d158c8]
📊 Macroscope summarized 9b4cd26. 3 files reviewed, 0 issues evaluated, 0 issues filtered, 0 comments posted

🗂️ Filtered Issues

No issues evaluated.


Note

High Risk
Breaking changes to eval TOML/CLI (harness.runtimeagent.runtime), trace JSON schema v3, and internal config import paths affect every rollout consumer and saved runs.

Overview
Runtime placement moves from the harness to the agent seat: RuntimeConfig lives on AgentConfig (--env.<role>.runtime.*, TOML [env.agent.runtime]), not under HarnessConfig. Harness configs keep program knobs only; pairing/validation and env warnings read each seat's runtime.

Trace records bump to TRACE_VERSION 3: AgentInfo carries the resolved agent.config (full AgentConfig, including harness and sampling) plus agent.runtime (provisioned box). The old top-level trace.runtime field is removed (a Trace.runtime property still reads agent.runtime). WireTrace / WireEpisode use WireAgentConfig / WireHarnessConfig so replay and trainers can load records without resolving harness plugins.

Config vs logic: pydantic config types move into verifiers/v1/configs/ (domain modules) and verifiers/v1/configs/cli/ (eval/serve/replay/etc.); runtime modules import from there. Public vf.* re-exports are preserved; internal imports and docs/skills reference the new paths.

Misc: sample TOMLs and tests switch to runtime on agents; eval dashboard shows per-seat runtime overrides; debug traces stamp a minimal AgentInfo; CI pins ruff 0.15.21 to match uv.lock.

Reviewed by Cursor Bugbot for commit 9d158c8. Bugbot is set up for automated code reviews on this repo. Configure here.

Where a run provisions its box is the seat's placement decision, not the
program's: `runtime` moves from `HarnessConfig` to `AgentConfig`, addressed
as `--env.<role>.runtime.*` ([env.agent.runtime] in TOML). The old
harness-level key errors with a pointer to the seat.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
macroscopeapp[bot]
macroscopeapp Bot previously approved these changes Jul 22, 2026
@macroscopeapp

macroscopeapp Bot commented Jul 22, 2026

Copy link
Copy Markdown

Approvability

Verdict: Needs human review

This PR restructures the configuration hierarchy by moving runtime from harness to agent level, reorganizes config modules into a new directory structure, and changes the trace schema (version bump 2→3). While largely mechanical, the scope of architectural changes to core configuration infrastructure warrants human review.

You can customize Macroscope's approvability policy. Learn more.

`AgentInfo` now records the agent losslessly: `config` is the resolved
`AgentConfig` the rollout ran as (harness, runtime policy, model, sampling,
caps — the exact value that rebuilds the agent), and `runtime` is the box it
ran in (the per-task resolved config plus the provisioned resource ID),
moved off the top-level trace. `AgentConfig`/`TimeoutConfig` move to
harness.py so the trace record can embed them without an import cycle
(agent.py re-exports).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@mikasenghaas mikasenghaas changed the title feat: move the runtime config from the harness onto the agent feat: move the runtime onto the agent and stamp the agent config onto the trace Jul 22, 2026
Comment thread verifiers/v1/cli/debug.py
The debug CLI mints agent-less traces, so moving RuntimeInfo under
AgentInfo dropped the box (resource id, resolved type, cache status) from
persisted debug records. Debug now stamps an explicit agent-less seat —
`AgentInfo(name="debug", trainable=False)` carrying only the runtime
policy in its config — and lands the provisioned box on it, so debug
records keep the box under the same shape as eval records.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
macroscopeapp[bot]
macroscopeapp Bot previously approved these changes Jul 22, 2026
Conflict: terminus_2's manual subprocess refusal (whose flag path this
branch had reworded) was superseded by NEEDS_CONTAINER on main — main's
side kept. Main's new NEEDS_CONTAINER/skills messages and the runtime
blocks it added to docs/configs updated to the seat-level flag paths
(--env.<role>.runtime.*).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…n guard

AgentConfig/TimeoutConfig return to agent.py (config next to its class,
like every other plugin). The trace record keeps its typed embed without
an import cycle by base-annotating `AgentInfo.config` and narrowing to
`AgentConfig` in a validator whose lazy import runs at validation time,
not import time. Also removes HarnessConfig's harness-level `runtime`
pointer validator — the stray key now fails as a plain unknown field.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment thread verifiers/v1/harness.py
mikasenghaas and others added 2 commits July 22, 2026 15:38
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Conflict: the REFERENCE.md runtime-configs line — combined this branch's
seat-level flag path (--env.<agent>.runtime.type) with main's removal of
the retired UserConfig.runtime reuse.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment thread tests/v1/test_e2e.py
mikasenghaas and others added 3 commits July 22, 2026 16:00
The base-annotated field plus narrowing validator was a circular-import
workaround; replace it with the honest annotation. `AgentConfig` lives
above the record (agent.py imports the rollout engine, which imports
trace.py), so each module binds it at its bottom: trace.py imports it
once all record classes exist, and agent.py finishes the build when it
entered the cycle first. Also trims the AgentInfo docstrings.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replace the bottom-of-module import handshake with one model_rebuild()
at the package __init__ — which provably runs before any verifiers.v1
submodule is reachable, and where AgentConfig is already imported.
trace.py keeps only the TYPE_CHECKING import.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The record says who played and where — model, sampling, harness (knobs
kept via SerializeAsAny, read back tolerantly as WireHarnessConfig into
model_extra, mirroring WireTaskData), and the provisioned box — while
operational policy (client, timeouts, retries, caps) stays in the run's
saved config.toml. Decouples the trace schema from AgentConfig and
removes the forward-reference machinery entirely. Also fixes
test_acp_resume_with_tool still passing the removed harness_overrides
kwarg (its docker/prime gating hid the TypeError locally).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
macroscopeapp[bot]
macroscopeapp Bot previously approved these changes Jul 22, 2026
Config classes for the main abstractions move out of their logic modules
into verifiers/v1/configs/ — one module per domain (agent, env, harness,
task, taskset, judge, retries) — and the CLI entrypoints' run configs
move to configs/cli/. Config modules import only types and other configs,
so any module (the trace record included) can embed them without cycles.
Hard cut: import sites use the configs paths; the public vf.* surface is
unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
With configs separated from logic, the embed is a plain leaf import:
`AgentInfo` is `{config, runtime, name, trainable}`, where `config` is
the resolved `AgentConfig` the rollout ran as — the exact value that
rebuilds the agent (`Agent(trace.agent.config)`) — and `runtime` is the
provisioned box. `Agent.__init__` resolves the unpinned identity fields
(default harness, default sampling) into its config so the stamp is
fully resolved; the debug CLI's agent-less seat records its runtime
policy the same way. Drops the now-unused `WireHarnessConfig` (reads
narrow the harness by id through `AgentConfig` validation).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment thread verifiers/v1/configs/agent.py
eligotts
eligotts previously approved these changes Jul 23, 2026
hallerite
hallerite previously approved these changes Jul 23, 2026
A record's `agent.config` now validates as `WireAgentConfig` — an
`AgentConfig` that resolves no plugins, its harness parsed as the
extra-allow `WireHarnessConfig` (mirroring `WireTaskData`) — so traces
round-trip on machines without the harness package installed, knobs
readable on `model_extra`. Live rollout objects keep their exact classes;
`AgentConfig.model_validate` re-narrows a record when the typed subclass
is wanted. CLI/config parsing stays strict (unknown ids still resolve,
install, or error).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@mikasenghaas
mikasenghaas dismissed stale reviews from hallerite and eligotts via 20d01f0 July 23, 2026 18:10
Comment thread verifiers/v1/configs/agent.py

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit c5c62d0. Configure here.

Comment thread verifiers/v1/configs/agent.py
mikasenghaas and others added 3 commits July 23, 2026 12:04
The reader picks the contract, mirroring WireTaskData: a bare `Trace` is
the strict read — `agent.config` validates as `AgentConfig`, narrowing
the harness by id, so its package must be importable — while `WireTrace`
/ `WireEpisode` parameterize the new `AgentConfigT` with `WireAgentConfig`
for the plugin-free read (a trainer loads any record without the harness
installed). Replaces the implicit dict-vs-instance dispatch on
`AgentInfo.config`; the serve wire and replay read loose.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The action installs the latest ruff release by default; today's 0.16.0
flags pre-existing code under new rules that the locked 0.15.21 (what
`uv run ruff` uses) does not, failing CI on untouched files. Pin CI to
the lockfile's version so both always agree.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@mikasenghaas
mikasenghaas merged commit 73067e2 into main Jul 23, 2026
11 of 12 checks passed
mikasenghaas added a commit that referenced this pull request Jul 23, 2026
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
mikasenghaas added a commit that referenced this pull request Jul 29, 2026
One to_v<n> util per historical schema bump, chained by the migrate
hook: v1->v2 lifts node-level usage/finish_reason into synthesized
ModelCalls (#2061), v2->v3 nests the flat agent identity and top-level
runtime into AgentInfo (#2106), v3->v4 wraps float rewards as
Reward(score, weight=1) preserving reward sums (#2119), v4->v5 as
before. Steps copy what they mutate, so validating the same dict twice
is stable.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
mikasenghaas added a commit that referenced this pull request Jul 30, 2026
* fix: pin exclude-newer-package cutoffs as UTC timestamps

Bare dates resolve to midnight in the machine's local timezone, so every
timezone relocks uv.lock with different exclude-newer timestamps and the
--locked pre-commit hooks fail for anyone outside the tz that produced
the lock.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore: align trace mutators on the record_* verb

stamp -> record_run, capture_error -> record_error, matching the
existing record_metric/record_reward/record_judge family and the trace's
own 'record' vocabulary (to_record, record schema). Also drop the
override warnings on record_metric/record_reward: overriding is defined
behavior, last write wins.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore: rename trace dump exclusions to EXCLUDE_FIELDS

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat: an expired agent timeout is an agent error, not a truncation

The rollout deadline expiring now records a HarnessError (ok=False, no
scoring) instead of the clean harness_timeout stop that scored the
partial trajectory: a timeout is the agent breaking its time budget,
not a healthy run cut short. The stop-condition name is gone from the
vocabulary; the legacy v0 bridge's timeout_reached maps through the
generic truncation fallback.

The concept is renamed harness timeout -> agent timeout throughout:
TaskTimeout.harness -> TaskTimeout.agent, the rollout plumbing, and
cap_remote_harness_timeout -> cap_remote_agent_timeout.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore: slim the Trace surface

Drop the agent_name/trainable/runtime passthroughs (read trace.agent
directly) and the duplicate error property (last_error is the one
reader); align tool_messages with assistant_messages (nodes-based,
branch-independent); require an explicit stop condition (the 'done'
default was never used); correct the stop_condition docstring to the
real vocabulary; tighten field docstrings and ordering.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat: require agent and verifiers on Trace, default tools to empty

agent: every producer sets a seat — the rollout its resolved config, the
debug CLI its synthetic seat, and now the v0 bridge and validate CLI
theirs — so the None guards at every consumer were dead weight.
verifiers: stamped by default_factory at construction; stored records
keep their serialized build. tools: the empty state was unreachable
(dialects normalize [] to None to avoid clearing a recording), so None
carried no signal over []. Bumps TRACE_VERSION to 5: old records
without an agent no longer validate.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore: hoist TRACE_VERSION to the module top

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore: tighten trace docstrings, require RunInfo.id

Every consumer stamps a run id (the eval CLI its uuid, trainers their
own), so the None default was unreachable. Docstrings across the trace
models trimmed to the constraint they actually add; kept_tokens aligned
with routed_experts' shape-first style.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix: last trace.runtime accesses missed in the property removal

Agent.interaction's two borrowed-runtime stamps and the dashboard's
boot-vs-build stage probe still read the removed Trace.runtime
passthrough; all three now read trace.agent.runtime. Verified live:
eval with the rich dashboard renders and pushes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat: migrate pre-v5 trace records on read

A version-gated before-validator upgrades v4 records to the v5 shape
(drop the explicit nulls v5 defaults now fill, seat records that
predate the required agent, drop an id-less run stamp) so eval resume
and replay keep reading old outputs. Current-version records validate
strictly: a v5 record with tools=null is rejected. Delete the validator
when v4 support is dropped.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore: restructure trace migration as chained per-version utils

The migrate hook sits at the bottom of Trace and chains one _to_v<n>
util per schema bump; the next bump adds _to_v6 and a new chain link.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat: retroactive trace migrations back to v1

One to_v<n> util per historical schema bump, chained by the migrate
hook: v1->v2 lifts node-level usage/finish_reason into synthesized
ModelCalls (#2061), v2->v3 nests the flat agent identity and top-level
runtime into AgentInfo (#2106), v3->v4 wraps float rewards as
Reward(score, weight=1) preserving reward sums (#2119), v4->v5 as
before. Steps copy what they mutate, so validating the same dict twice
is stable.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore: drop trace record migrations, reset TRACE_VERSION to 1

The tightened schema restarts the version counter; pre-existing records
are not loadable and old eval runs cannot be resumed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix: migrate the standalone agent example and env docs off removed trace APIs

* chore: drop the seat term from new comments, restore trimmed timing docstrings

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
eligotts added a commit that referenced this pull request Jul 31, 2026
…fload

Picks up 35 main commits: the v1 structure cleanup (#2146) that retires
StrictBaseModel and renames _NODE_DUMP_EXCLUDE -> EXCLUDE_FIELDS, the
ruff 0.16 / ty tooling bump (#2147, #2148), runtime-on-agent + agent
config stamped on the trace (#2106), Reward score/weight records (#2119),
MCP tools for Codex (#2140), and assorted v1 fixes.

Resolutions:
- trace.py: main's restructure subsumes this branch's block wholesale —
  EXCLUDE_FIELDS already carries multi_modal_data, and TRACE_VERSION=1 is
  main's deliberate reset (this branch never touched it). Took main.
- graph.py: kept the raw-mm sidecar validators and
  previous_multi_modal_data; adopted main's plain BaseModel now that
  StrictBaseModel is gone.
- clients/train.py: kept the is_multimodal import — still used by the
  bridge path that threads previous_multi_modal_data.

Ruff 0.16 flagged two spots this branch added that main's cleanup pass
never saw: a constant getattr in the ingress offload walker, and the
blind except at the prepare_request_body boundary (annotated noqa, per
main's own convention at rollout boundaries).
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.

3 participants