Repository navigation
feat(task): run a task with some of its agents played from your own loop - #177
Open
earakely-scale wants to merge 10 commits into
Open
earakely-scale wants to merge 10 commits into
earakely-scale wants to merge 10 commits into
Conversation
Which agents are played from outside, their turns (prompt and system prompt, seeded) and roles, the reward (the one verifier no aggregate_verifiers step lists), and the copy of the task that runs, in which the outside agents' deploy_agent and prompt_agent steps become episode_agent and episode_turn stand-ins. Refuses, naming each step, what an outside player can't stand behind. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
EpisodeSpec.start() runs one Task.run of the episode's copy of the task and returns once its record exists. The run waits at each outside turn: next_turn() hands it out with the agent's role, env MCP URLs and the headers each needs, and turn.end() records the reply as prompt_agent would. result() is the reward verifier's score once the run and its teardown are over; abandon() cancels the run, waits for teardown and records it cancelled. A caller's cancellation never reaches the run. The seed pass rebuilds each step with its own class, so the stand-ins take a seed, and deploy_agent's env binding rule is a module function both deploy_agent and episode_agent call. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The items env deployed and loaded and the echo agent prompted around an outside agent, which plays its turn through ToolSession as its role and is never deployed. A played episode scores 1.0 and an unplayed one 0.0, side by side; an abandoned one is recorded cancelled; none leaves a container or a work folder. local_stack now gives each test its own state root and closes the grant server, as the other local fixtures do. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- An outside agent named by a seed placeholder is replaced too: the stand-ins are judged by each step as the seed makes it. Before, the real agent was deployed and prompted, and its score was the reward. - A cancelled start() cancels its run, so no episode is left running that nobody holds. - Abandoning a run a step has already failed ends its open turns, so the run's error and failed record stand instead of reading cancelled. - verify_a2a_* validator steps are refused: they check the task's one deployed agent, which an episode plays from outside. - A turn's wake-up is a fresh future per change, taken before the state is read, so a concurrent caller can't swallow it. Tests for each, a no-score reward, a failure elsewhere surfacing once the turn ends, a cancelled abandon() caller, and the integration test now checks for leftovers after each ending. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The instance ids of envs, tasks, A2A agents and env states, and the first host port a fresh state root hands out, were drawn from the global random. A training loop reseeds it, so ids repeated: a GRPO run on episodes lost 32 of 352 env-instance records to DuplicateKeyError, which registration logs and swallows. Fresh state roots started at the same port too, which the random start exists to prevent. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…g it EpisodeSpec.from_task(turn_timeout=) replaces every outside turn's deadline, since a prompt's timeout_seconds budgets an agent's run: a training loop needs its own. EpisodeError is a RuntimeError, as most of what it reports happens at run time. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The caller holds no handle on the run, so it couldn't tell when cleanup ended. The README's sample now plays every turn before reading the reward, and shows turn_timeout. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ode state with one writer - The v1 rules move to task/episode_rules.py: CHECKS maps a step type to its check, UNAFFECTED lists the types an outside agent leaves alone, and a test holds the step registry to the two, so a new step type is classified before an episode runs it. The refusals and their messages are unchanged. - Episode takes its task and reward verifier at construction; _launch() owns starting and cancelling the run, _bind() the agent bindings, and only Turn touches its reply future. - episode_test.py splits into episode_spec_test.py (from_task) and episode_run_test.py (the run); the run tests wait for the end on the run's record rather than on the episode's task. - deploy_agent's live_mcp_url is public and bound_deployment takes env_step_id and step_id by keyword. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A plugin or a [task_steps] impls step is in the configured registry but not in the rules, which don't inspect plugin types, so the guard failed on a valid local setup. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This branch has not been deployed
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
An episode is one run of a task in which some of its agents are played from outside AgentEnv: by a training loop's model, or any code of your own. AgentEnv runs everything else exactly as
agent-env rundoes (env deploys, setup, the other agents, scoring), so the reward is the scoreagent-env runwould give. It's the core piece a trainer adapter (TRL'sGRPOTrainerfirst) builds on.How it works
from_taskbuilds a copy of the task in which each outside agent'sdeploy_agentbecomesepisode_agent(deploys nothing; binds the agent to its envs withdeploy_agent's own rule) and each of itsprompt_agentsteps becomesepisode_turn(hands the turn to the caller, waits forend()within the prompt'stimeout_seconds, records the reply as aPromptResponse). Ids, edges andfail_task_on_errorare kept; every other step is untouched.start()runs oneTask.run()of that copy as a task on the caller's loop, taggedcontext.metadata["episode"]. However the run ends,teardown_runtakes down what it deployed before any caller sees the end.asyncio.wait, never by awaiting the run, so cancelling a caller ofnext_turn(),result()orabandon()never reaches the run. A cancelledstart()takes its run down with it, and returns once that run is torn down.timeout_seconds, orturn_timeoutfor every outside turn: a prompt budgets an agent's run, and the loop playing it may need its own.EpisodeErroris aRuntimeError.What
from_taskrefuses (v1)The rules are in
task/episode_rules.py. Each built-in step type is either checked there (CHECKS, type to check) or listed as untouched by an outside agent (UNAFFECTED). A unit test fails when a registered type is in neither, so a new step type has to be classified before an episode can run it. Plugin step types aren't inspected.It reports every problem at once, naming the step:
deploy_agent, or deployed byinstall_agent/deploy_human_agent;parts, a user simulator,output_format,agentenv_toolsorsnapshot_config;deploy_agentwithskillsor agent state (snapshot files, changelog);add_skills,register_agent_triggers,snapshot_agent_state,load_artifact/verify_sandbox/run_codeinto its sandbox,collect_artifactsfrom it, a trigger executor,peer_agents, a rubric that reads its trajectory or judges in its sandbox, a user simulator played from outside, andverify_a2a_*validators;retry_config, since a rollback could replay an outside turn;Deliberately not handled, with no defensive code: user simulators, trajectory-based rubric judging and peer tools aimed at an outside agent, retries, and per-agent rewards.
Other changes
substitute_seed_in_stepsrebuilds each step with its own class (type(step).from_dict) instead of looking it up in the registry. That's the same class for every registered type, and its docstring already said so; the episode's unregistered stand-ins can now take a seed.deploy_agent's env binding rule is a module function,bound_deployment(context, env_id, *, env_step_id, step_id), shared bydeploy_agentandepisode_agent, andlive_mcp_urlis public for the same reason. Their behaviour is unchanged.secrets, not the globalrandom. A training loop reseeds that, and ids repeated: a GRPO run lost about one env-instance record in ten to a swallowedDuplicateKeyError.local_stackinserver_env_local_test.pynow gives each test its own state root, removes every container its sandbox root names, and closes the grant server, as thetask_step/*_localfixtures do.Task.run()embedder contract.Tests
tst/unit/task/episode_spec_test.py, 52 tests onfrom_task: every refusal, table-driven; the reward pick; seeded turns and a seeded agent name; the copy; the registry held toCHECKSandUNAFFECTED.tst/unit/task/episode_run_test.py, 25 tests on the runtime, on fake steps: turns, two outside agents, 8 concurrent episodes, deadlines, the reply graded by a realagent_prompt_response_verifier, abandon at every stage, cancelled callers andstart(), and records.-n 6. The full unit tier passes.tst/integration/env/envs/server_env_local_test.py(fast tier, local Docker), with the items env and the echo agent:ToolSessionas the agent's role) scores 1.0 side by side with an unplayed one at 0.0;cancelled;Validated end to end
A prototype adapter drove TRL 1.15's
GRPOTrainer(environment_factory, vLLM colocate) over this API on PortSim's dock tasks, each rollout an episode deployed by the task's owndeploy_envon local Docker:That run motivated
turn_timeoutand found the id collisions.🤖 Generated with Claude Code


Confidence Score: 5/5
The PR appears safe to merge; the previous test finding is fixed and no new blocking issue remains.Summary
This PR adds episodes that let a caller play selected agents while AgentEnv runs the remaining steps, scoring, and cleanup.
Diagram
Reviews (4) · Last reviewed commit: "test(task): hold only the built-in step ..." · Reviewed by Greptile