feat(v1): Agent facade — programming over agents#1939
Open
hallerite wants to merge 15 commits into
Open
Conversation
617019a to
20ff1de
Compare
An Agent is (harness x rollout context x runtime policy) with one executable arrow: agent.run(task) -> Trace. Placement is a parameter (runtime= borrows a live box; provision() hands out boxes), judgement is optional (taskset=), and chaining is plain traces -> Task functions (Task.sources/relation stamp lineage). Rollout learns to run in a borrowed runtime (creator owns start/teardown). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fan-out is plain asyncio.gather over agent.run; grouping (and comparative scoring across a group) belongs to the consumer — in training, prime-rl samples the group. No new group surface while @group_reward heads toward deprecation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
An agent IS a model in a harness: Agent("codex", "z-ai/glm-5.2",
PrimeConfig()). Pass a model name (default Prime inference endpoint, or
client=/sampling= overrides), or a prebuilt RolloutContext for callers that
manage their own Client (prime-rl's orchestrator). make_context is folded in.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
v1 construction is explicit — no id-string sugar in the constructor. Build the harness yourself (DefaultHarness(DefaultHarnessConfig())) or via load_harness(config); harnesses are stateless, so one instance can back any number of agents. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
RolloutContext is already the canonical (model + client + sampling) carrier every Rollout holds — the Agent binds one at construction instead of hiding client resolution behind a string. Clients are built by the caller and shared across agents on one endpoint (one connection pool); sampling gains a default. EvalClientConfig/TrainClientConfig join the v1 exports. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…race stamp Align the facade files with main's ModelContext rename (#1940) and fold in review cleanups: run() takes one ctx override instead of loose model=/sampling= kwargs; the trace stamp is built inline where the resolved values live (the fingerprint property promised a shape it never stamped); the shared NullTaskset is a module constant; borrowed-runtime locality reads the instance classvar. Group scoring stays in verifiers — the Agent surface just has no group verb; docs now say exactly that. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Runtime.stop() marks the runtime stopped (before the teardown await — no new borrows once teardown begins); a Rollout handed a stopped borrow raises to the caller up front instead of failing opaquely mid-harness and being captured as trace data. A dead borrow is a lifetime bug in the borrowing program, not a property of the rollout's world. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Extract validate_pairing (MCP / user-sim / NEEDS_CONTAINER) and the 24h remote-sandbox harness-timeout cap out of Environment into shared env helpers and apply them per run in Agent.run, against the resolved (or borrowed) runtime. Also: provision() now reaches stop() when start() fails partway (no leaked remote sandbox), and re-entering an Agent raises instead of leaking the first interception pool. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
same_box_judge still used the pre-ModelContext string constructor (would crash on ctx.model); proposer_solver now fails with a clear error when the proposer's reply has no JSON object; world_verified bounds its world-re-executing reward with a scoring timeout. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
9511796 to
5eac481
Compare
# Conflicts: # .gitignore # docs/mint.json # verifiers/v1/clients/client.py # verifiers/v1/env.py # verifiers/v1/rollout.py # verifiers/v1/task.py
ea4dd75 to
8f4f6ea
Compare
ApprovabilityVerdict: Needs human review This PR introduces a substantial new feature (Agent facade) with new user-facing APIs, new runtime patterns (borrowed boxes, runtime pooling), and modifications to core Rollout lifecycle handling. New features of this scope warrant human review. You can customize Macroscope's approvability policy. Learn more. |
…ng program
The tombstone catches borrow-after-teardown, but an owner tearing the box down
under an in-flight run surfaced as an opaque harness error captured onto the
trace (e.g. 'harness exited 137'). Same lifetime bug, same channel now: the
rollout re-attributes the failure and raises to the caller with the raw error
chained, instead of recording a misattributed world error as data.
Live-verified on docker: owner stop() at t=25s under a sleeping harness ->
ValueError('borrowed runtime ... torn down by its owner mid-run') from
HarnessError(exit 137).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
eligotts
added a commit
that referenced
this pull request
Jul 11, 2026
… the resolved runtime Three correctness holes vs the agent-programs work (#1939), two of them ours: - The dead-borrow tombstone guard existed pre-merge and was silently lost adopting main's rollout.py — Runtime.stopped's docstring promised a refusal nothing enforced. Restored: borrowing a stopped box raises at the caller. - An owner tearing a borrowed box down under an in-flight run surfaced as an opaque harness error captured onto the trace — a lifetime bug misattributed as world failure (and retryable). Ported #1939's re-attribution: same bug, same channel — raised to the caller with the raw failure chained. - NEEDS_CONTAINER was checked against harness.config.runtime, not where the run actually lands: a container-needing task borrowed into a docker box under a subprocess-defaulting harness was wrongly refused, and the mirror case wrongly allowed. validate_task_pairing now takes the resolved runtime_config; Agent already resolved it and keyed its cache on it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
eligotts
added a commit
that referenced
this pull request
Jul 11, 2026
Adds what the reviews surfaced but the ledger didn't yet record: instance-level retries (Topology.complete as the predicate, the RetryConfig slot kept open for it), right-sizing graph wire payloads (eval-mode servers now ship training tensors nobody reads), the best-of-N built-in as the group-reward successor and migration story, the reviewed config-layer cleanup pass (topology.py authoring/runner split killing the EnvConfig model_rebuild trick, RolloutLimits duplication, the multiplex name collision, timeout vocabulary, dead env_id) with its considered-and-rejected list, and the PR #1939 reconciliation end state. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.
What
The
Agentfacade: programming over agents as a first-class v1 surface.An
Agentis the complementary regrouping of theRollouttuple — (harness × model × runtime policy), with the task per call — and one executable arrow:Construction is fully explicit, per v1 style — no id strings, no hidden client resolution: a concrete
Harnessobject (stateless — one instance can back many agents) and aModelContext(#1940's rename, binding cleanly here: an agent IS a model in a harness). The client is the caller's to build and share — agents on one endpoint share one connection pool, and prime-rl hands agents its renderer client through the same type.Everything beyond the arrow is a parameter, not a concept:
runtime=places a run into a live box (borrowed — the run neither starts nor tears it down; creator owns the lifecycle).agent.provision(task)hands the program a box, so several runs — by one agent or several — can share one world (judge in the solver's sandbox). Borrowing a box its owner already tore down raises immediately, and a box torn down mid-run raises at the awaitedrun()with the raw failure chained underneath — lifetime bugs in the program, surfaced to the caller instead of captured as misattributed world errors on the trace.Tasksubclass's hooks (setup/finalize) and signals (@reward/@metric) run exactly as in an eval; a plain basevf.Task(data)has neither, so the run is unscored — a pureTask -> Tracearrow. Impossible pairings are refused per run with the same guards as an eval: MCP / user-sim support, andNEEDS_CONTAINERagainst the runtime the run actually resolves to.ctx=replaces the agent's model context for one run (dataclasses.replace(agent.ctx, model=...)— a judge sweeping models).traces -> Taskfunctions, fan-out is plainasyncio.gather.TaskData.sources(trace ids) +TaskData.relationstamp lineage; each trace carriesinfo["agent"](harness, model, runtime type + descriptor, borrowed flag), so multi-agent programs stay reconstructable after the fact.The Agent surface has no group verb: each run scores its rollout on its own, and comparing siblings (relative success, preference, advantages) belongs to whoever gathered the traces — in training, prime-rl samples the group. Existing group machinery (
@group_reward,Task.score_group) is untouched by this PR; its deprecation is a separate discussion.Every run is a standard
Rollout(staged lifecycle, typed error attribution, token-true trace capture) — the Agent only decides what goes into the rollout. Entered as an async context manager it owns anInterceptionPool; un-entered, each run brings up its own per-rollout server (script mode).Changes
Additive, small surface (15 files, +763/−42):
verifiers/v1/agent.py(new):Agent;EvalClientConfig/TrainClientConfigjoin the v1 exports;ModelContext.samplinggains a default.verifiers/v1/rollout.py: optionalruntime=— a borrowed live runtime skipsmake_runtime/start()/stop(); borrowing a stopped runtime raises up front (Runtime.stop()now marks the runtimestopped).verifiers/v1/task.py:TaskDatagainssources: tuple[str, ...]+relation: str | Nonelineage stamps.verifiers/v1/env.py: one set of pairing guards for both entry paths —validate_pairingtakes the task class and the runtime config the run resolves to (shared byEnvironmentonce at init andAgent.runper run), and the 24h remote-sandbox timeout cap is extracted ascap_remote_harness_timeout.Environmentbehavior unchanged.docs/v1/agent-programs.md+examples/agent_programs/: guide and four runnable programs..gitignore: also match a.venvsymlink (one had slipped into the tree).No behavior changes to
Environment,Episode, scoring, or the serve layer. A follow-up can haveEnvironmentconsume anAgentinternally, collapsing the remaining mirrored resolution logic.Validation
ruff check, the fullpytest tests/suite, and the v1 e2e CI matrix (-m "not prime and not modal") with live model calls all pass. All four examples ran live end-to-end on Prime Inference (a self-hosted GLM-5.2 deployment standing in for the committed model ids):smoke.py— the primitive on subprocess.same_box_judge.py— one prime sandbox: solver runs borrowed, program writes the solver's trace into the box, judge runs in the same box, independently recomputes the answer, checks the trajectory for hardcoding, returns parseable JSON; lineage + same-descriptor stamps intact; sandbox torn down by its creator.proposer_solver.py— cross-model chain, typedProposedData/ProposedTask,asyncio.gatherfan-out (n=4) scored by the task's own@reward, pooled interception.world_verified.py— the realmini-swe-agentCLI through the facade in a fresh sandbox, with a runtime-aware@rewardre-executing the artifact in the live box.Design context
This is step 1 of the agent-programs design (prime-rl
design/agent-programs.md, forthcoming): Agent facade → provenance stamps → judge/harness patterns → agent programs as envs, with groups sampled consumer-side by prime-rl. It is deliberately the library layer — a runnable value usable from any Python (scripts, notebooks, prime-rl's sampler); how multi-agent programs package/ship as evals is the follow-up conversation (see #1941 for a parallel take on that layer).🤖 Generated with Claude Code
Note
Medium Risk
Touches core rollout/runtime lifecycle (borrowed boxes, teardown semantics) and shared pairing validation, but eval
Environmentpaths are refactored rather than behavior-changed; main risk is incorrect sandbox lifetime or pairing edge cases in agent programs.Overview
Introduces
vf.Agent, a reusable harness × model × runtime value withagent.run(task)as the single execution path. Runs still go through standardRolloutmachinery; the agent wires timeouts, pairing checks, optional sharedInterceptionPool(viaasync with agent),agent.provisionfor shared sandboxes, and stampstrace.info["agent"]for attribution.Placement:
Rolloutaccepts an optional borrowedruntime=— no provision/start/stop on that path;Runtime.stoppedis set at teardown and borrowing a dead box raises immediately.Lineage:
TaskDatagainssourcesandrelationfor trace-derived tasks; multi-agent flow is plain functions plusasyncio.gather, not a built-in group API.Shared eval guards:
validate_pairingandcap_remote_harness_timeoutare generalized for per-runAgentuse (runtime-awareNEEDS_CONTAINERchecks);Environmentbehavior is refactored to call the same helpers.API/docs:
Agent,EvalClientConfig,TrainClientConfigexported;ModelContext.samplingdefaults when omitted. Newdocs/v1/agent-programs.md, mint nav entry, and fourexamples/agent_programs/scripts. Minor.gitignoretweak for.venv.Reviewed by Cursor Bugbot for commit 8f4f6ea. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Add
Agentfacade for programming over agents with runtime borrowing and task lineageAgentas a high-level class that wraps aHarness,ModelContext, and runtime policy to runTaskinstances viarun()andprovision(), with support for borrowed runtimes and sharedInterceptionPool.TaskData.sourcesandTaskData.relationfields to carry lineage metadata across proposer→solver and judge workflows.validate_pairinginenv.pyto accept an explicit task class andRuntimeConfig, and extractscap_remote_harness_timeoutas a shared helper used by bothEnvironmentandAgent.Rolloutto accept a borrowedRuntime, with error handling for runtimes torn down before or during a run.examples/agent_programs/.ModelContextnow defaultssamplingto a freshSampling()instance rather than requiring it at construction.Macroscope summarized 58a5e1d.