feat: move the runtime onto the agent and stamp the agent config onto the trace - #2106
Merged
Conversation
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>
ApprovabilityVerdict: 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>
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>
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>
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>
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>
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>
eligotts
previously approved these changes
Jul 23, 2026
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>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ 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.
…at/agent-runtime-config
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>
eligotts
approved these changes
Jul 23, 2026
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).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Summary
runtimemoves fromHarnessConfigtoAgentConfig— 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-levelruntimekey now fails as an unknown field.AgentInfois{config, runtime, name, trainable}.configis the resolvedAgentConfigthe 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).runtimeis the box it ran in (the resolved config plus the provisioned resource ID andborrowed), moved off the top-level trace under the agent that owns it (Trace.runtimestays as a read-only property).WireTaskData): a bareTraceis the strict read —agent.configvalidates asAgentConfig, narrowing the harness by id, so its package must be importable — whileWireTrace/WireEpisodeparameterize the newAgentConfigTwithWireAgentConfig(extra-allowWireHarnessConfiginside), so consumers without the run's packages (e.g. a trainer) load any record plugin-free, knobs readable viamodel_extra. The serve wire and replay read loose; CLI/config parsing stays strict.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 toconfigs/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 publicvf.*surface is unchanged._runs_local, the agentic-judge container check, and the dashboard read each seat's runtime; runtime overrides get their own overview row.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_VERSIONbumped to 3.Breaking
--env.<role>.harness.runtime.*→--env.<role>.runtime.*(TOML:runtimemoves from[env.agent.harness]to[env.agent.runtime]). The old placement fails validation as an unknown harness field.HarnessConfig.runtimeis gone; code readingharness.config.runtimeshould use the agent's config (AgentConfig.runtime/Agent.runtime_config).TRACE_VERSION3):trace.agentis{config, runtime, name, trainable}—agent.model/agent.sampling/agent.harnessbecomeagent.config.model/.sampling/.harness, and the top-leveltrace.runtimefield moves toagent.runtime(still readable as theTrace.runtimeproperty). Custom harness knobs serialize with the record; read them plugin-free viaWireTrace/WireEpisode(WireAgentConfig/WireHarnessConfig), or fully typed via a bareTraceread where the harness package is importable.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
agent.config(model, narrowed harness, runtime policy, resolved sampling) andagent.runtime(type, resource id, borrowed);Trace.model_validateround-trips typed (NullHarnessConfignarrows back), andmake_agent(trace.agent.config)rebuilds an equivalent agent.uv run eval @ configs/<each>.toml --dry-runpasses 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-codeflock— all unrelated and failing onmaintoo).uv run ruff check .anduv run pre-commit run --all-filespass.🤖 Generated with Claude Code
Note
Move runtime config from
HarnessConfigtoAgentConfigand stamp it onto tracesAgentConfig.runtimeinstead ofHarnessConfig.runtime. CLI flags change from--env.<agent>.harness.runtime.typeto--env.<agent>.runtime.type.AgentInfoin traces now carries the fullAgentConfig(including resolved harness and sampling) underagent.config, and runtime info is recorded atagent.runtimeinstead of a top-leveltrace.runtimefield.runtimefield.HarnessConfig,TaskConfig,TasksetConfig,RetryConfig,JudgeConfig,AgentConfig) are relocated from their runtime modules into a newverifiers/v1/configs/package, with CLI-specific configs underverifiers/v1/configs/cli/.[env.agent.harness.runtime]must be updated to[env.agent.runtime].Changes since #2106 opened
WireAgentConfigandWireHarnessConfigclasses that bypass plugin resolution and preserve extra harness-specific fields, and added afield_validatoronAgentInfo.configthat validates dict inputs asWireAgentConfiginstead ofAgentConfig[20d01f0]TraceandAgentInfomodels generic over a newAgentConfigTtype parameter [9c27ec5]Episodemodel generic over a third type parameterAgentConfigT[9c27ec5]WireTraceandWireEpisodetype aliases to specifyWireAgentConfigas the concrete agent configuration type [9c27ec5]RunGroupResponseandEnvClient.run_groupto useWireTracetype [9c27ec5]run_replayfunction to read episodes withWireAgentConfiggeneric parameter [9c27ec5]ruffversion to0.15.21in the.github/workflows/style.ymlCI 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.runtime→agent.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:
RuntimeConfiglives onAgentConfig(--env.<role>.runtime.*, TOML[env.agent.runtime]), not underHarnessConfig. Harness configs keep program knobs only; pairing/validation and env warnings read each seat's runtime.Trace records bump to
TRACE_VERSION3:AgentInfocarries the resolvedagent.config(fullAgentConfig, including harness and sampling) plusagent.runtime(provisioned box). The old top-leveltrace.runtimefield is removed (aTrace.runtimeproperty still readsagent.runtime).WireTrace/WireEpisodeuseWireAgentConfig/WireHarnessConfigso replay and trainers can load records without resolving harness plugins.Config vs logic: pydantic config types move into
verifiers/v1/configs/(domain modules) andverifiers/v1/configs/cli/(eval/serve/replay/etc.); runtime modules import from there. Publicvf.*re-exports are preserved; internal imports and docs/skills reference the new paths.Misc: sample TOMLs and tests switch to
runtimeon agents; eval dashboard shows per-seat runtime overrides; debug traces stamp a minimalAgentInfo; CI pins ruff 0.15.21 to matchuv.lock.Reviewed by Cursor Bugbot for commit 9d158c8. Bugbot is set up for automated code reviews on this repo. Configure here.