Skip to content

feat(bundle): leave a step's user-scoped fields out of an import, and name what a task needs beyond core - #243

Merged
earakely-scale merged 8 commits into
mainfrom
edgararakelyan/fd-3989-import-store-only-values
Oct 19, 2026
Merged

earakely-scale merged 8 commits into
mainfrom
edgararakelyan/fd-3989-import-store-only-values

Conversation

@earakely-scale

@earakely-scale earakely-scale commented Oct 18, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

A stored task carries values that only work in the setup it was stored from:

  • the sandbox provider its run used, where a name like beta_scale exists only where a plugin registers it;
  • the agent and models it ran with, including a custom agent another user doesn't have;
  • endpoints, output locations and inputs held in the user's object store: a LiteLLM URL, a private trajectory bucket, a build context or a skill;
  • billing tags (metadata.attribution).

agent-env bundle init copied all of them. A folder written for another machine could then fail on a provider plugin or an agent that has nothing to do with the task.

The principle: each field of a step describes either the task itself or the setup of the user who ran it. An import keeps the first kind and leaves out the second; a run elsewhere fills those in from its own setup.

  • Field scopes. FieldScope (agent_env/entity/scopes.py) has two values. TASK is the default for any field not declared otherwise; USER marks setup fields. Each step type declares field_scopes, a path → scope mapping that merges its parent's. Paths are dotted keys, with [] for each element of a list. These core fields are user-scoped:

    • every step: metadata.attribution;
    • deploy_env: sandbox_type, env_state_type, env_state_instance_id;
    • deploy_agent: a2a_agent_id, a2a_agent_version, env_vars, sandbox_type, litellm_base_url;
    • install_agent: a2a_agent_id, a2a_agent_version;
    • deploy_sandbox: sandbox_type;
    • deploy_human_agent: a2a_url;
    • prompt_agent: model, model_params, effort, max_thinking_tokens, harness, user_model, trajectory_output_prefix;
    • rubrics_verifier: judge_a2a_agent_id, default_model, default_model_api_base, effort, max_thinking_tokens, judge_sandbox_type;
    • load_artifact: urls;
    • run_docker_container: docker_context_url;
    • add_skills: each skill's object_url (skills[].object_url);
    • snapshot_env: env_instance_id.

    Each one is optional. When it's left out, the run uses its own setup: the --sandbox flag or config default, the configured default agent and models, its own object store, and its own endpoints. install_agent with no agent now installs the configured default, as deploy_agent already did.

    Three of them have no default to fall back on: load_artifact.urls, docker_context_url and a skill's object_url can each be a step's only input. A step left without its only input still builds, so the import works. A new TaskStep.config_problems() names what's missing: problems in a step's own fields, found without a store. It's checked wherever preflight() is, including for a step a dry run can't preflight, and the step raises the same at run time. So a receiver's run stops before it starts and names the field to supply.

    These stay task-scoped:

    • deploy_agent's changelog (agent_changelog_object_url and its cutoff): without it the agent starts fresh instead of from the earlier run's state, and nothing left in the task says so;
    • a2a_agent_id on the verify_a2a_* steps: the agent is what the task verifies;
    • verify_a2a_skill_config.skill_bundle_object_url: without it the bundle probe scores as failed, which lowers the grade instead of stopping the run;
    • snapshot_env.gateway_url: with no deployed env, it's how the step finds the gateway;
    • prompt_agent.user_a2a_url: setting it is what tells a human counterpart from a simulator.
  • Imports leave user-scoped fields out by default. Any set value is left out, 0 and False included, and listed under "Left out". A step left with no agent is listed under "Floating", as deploying the configured default. --include-scope user keeps the fields as stored, for a folder that runs against the same store.

  • "Needs beyond core". The report and README list each step, env and artifact type that core doesn't ship. Each type is credited to the distribution whose entry point registered it, or to "configuration". These types are task-scoped: a run without the plugin fails, and the folder now says so up front.

  • An agent_env.entity package for what an entity's fields say:

    • entity/refs.py holds the ref declarations, which were agent_env/entity_refs.py;
    • entity/scopes.py holds the field scopes and prune, moved out of the importer;
    • entity/paths.py holds the path syntax both use.

    agent_env/entity_refs.py stays as a re-export of its six public names, so plugins importing the old path, the sdk among them, keep working; the plugin-api check reports no break. git diff -C shows refs.py as entity_refs.py with 24 lines changed.

This is off the hot path: imports change, and steps gain a class attribute and a store-free check.

Tests

  • Contract (tst/unit/task_step/test_field_scopes.py), for every registered step type:
    • each scoped field is an optional parameter, or a path the base scopes for every step;
    • each type keeps the base's scopes;
    • each built-in type that scopes fields of its own builds from a minimal stored step without them;
    • each step left without an input that has no default names it in config_problems().
  • Importer:
    • user-scoped fields are left out and reported, including list paths and a field set to zero, and the rest is kept;
    • include_scopes=[FieldScope.USER] keeps them as stored;
    • each need is credited to its plugin or to configuration, including a type a config file replaces and a plugin the registry build skipped;
    • a core-only task needs nothing;
    • the CLI flag works.
  • Integration (tst/integration/cli/bundle_init_scopes_test.py): two machines as two local stores. The source's config registers its own sandbox provider and step type; the receiver's registers nothing. A folder imported by default runs on the receiver, while an --include-scope user copy runs only at its source. An agent task takes the receiver's own default agent. A plugin step, and an input left out with no default, are refused on the receiver before anything runs, naming what's missing.
  • Unit tier: 8,029 passed.

Checked on two machines

Two fresh Ubuntu VMs:

  • A: core plus a stand-in plugin that registers a sandbox provider acme_vm and a step type acme_note;
  • B: core only.

A stored three tasks that name A's own setup: its provider, a custom agent, models, a LiteLLM URL, env vars, a trajectory bucket and attribution. A imported each task by default and with --include-scope user, and B ran the folders.

Folder On A On B
sandbox task, default (not run) runs and passes, twice
sandbox task, --include-scope user runs and passes refused: Unknown sandbox backend: 'acme_vm'
agent + judge task, default (not run) dry run passes once B registers its own default agent; without one, it names the missing a2a-default
agent + judge task, --include-scope user dry run passes refused: no agent acme-solver in the store
plugin-step task, default runs and passes refused: 'acme_note' is not a known step type; its README names agentenv-acme

The default folders mention acme in only two places: the plugin step itself, and the README line naming the plugin it needs.

Real CoopBench tasks. I imported five CoopBench tasks (t01 to t05) from the prod store, each by default and with --include-scope user. All ten imports succeeded.

  • The default folders hold no setup values. There's no beta_scale or modal provider, claude-code-cli agent, model, attribution, or s3:// trajectory prefix; the agents float to the configured default. The --include-scope user copies keep all of these.
  • Core-only dry run. On a core-only VM, each default folder is refused only for its task material in the store: env coopbench-star-pm-v2, the universe, and the task's evidence-collector script. Bringing those along is the next step. Each user copy is also refused for claude-code-cli.

🤖 Generated with Claude Code

RetriggerView in GreptileConfidence Score: 5/5

The PR appears safe to merge; no new actionable issues remain.

Summary

Bundle imports now leave out user-specific setup by default and report types that need plugins or configuration.

  • Bundle imports now leave machine-specific fields to each machine.
  • Bundle reports name the types that need more than core.
  • Entity references now live under the entity package.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A[Stored task or eval] --> B[Leave out user-scoped fields]
  B --> C[Pin retained references]
  C --> D[Report needs beyond core]
  D --> E[Check staged bundle]
  E --> F[Write bundle folder]
  F --> G[Check receiver setup and missing inputs]
  G --> H[Run with receiver defaults]
Loading

Reviews (6) · Last reviewed commit: "refactor(entity): keep agent_env.entity_..." · Reviewed by Greptile

… name what a task needs beyond core

A stored task carries values that only work where it was stored: the sandbox provider its run used, where it wrote
trajectories, the model endpoint, billing tags. An import copied them, so a folder written for elsewhere could fail on
a provider plugin it has no use for, or leak internal ids.

Each step type now declares its fields' scopes, field_scopes (dotted path to a FieldScope). A scoped field is optional,
and a field with no scope is material. The base scopes metadata.attribution to the user. The deploy steps scope
sandbox_type, the judge judge_sandbox_type, prompt_agent its trajectory prefix and user endpoint, deploy_agent its
LiteLLM URL, deploy_human_agent its endpoint and snapshot_env its gateway URL. bundle init leaves user-scoped fields out
and reports each one, unless --include-scope user keeps them for a folder run against the same store.

The import also lists, in its report and README, each step type and referenced env or artifact type that core
doesn't ship, by the distribution that registers it (or configuration). A test holds every step type's scoped fields
to optional parameters of its own.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@earakely-scale
earakely-scale requested a review from a team as a code owner October 18, 2026 23:26
Comment thread src/agent_env/task_step/task_steps/snapshot_env.py Outdated
Comment thread src/agent_env/task_step/task_steps/prompt_agent.py Outdated
Comment thread src/agent_env/bundle/importer.py Outdated
earakely-scale and others added 2 commits October 18, 2026 16:51
…ser runs a task with to the user

FieldScope gains TASK, the scope of every field not declared otherwise, which an import always writes. A task doesn't
depend on the agent or the models that attempt it, and a user's agent ids and model names may not exist for another
user, so these are now user-scoped: deploy_agent and install_agent's a2a_agent_id and version, and deploy_agent's
env_vars; prompt_agent's model, model_params, effort, max_thinking_tokens, harness and user_model; and the judge's
agent, model, model endpoint, effort and thinking budget. So are deploy_env's env state provider and instance, and
snapshot_env's env instance. Left out, each falls back to the run's own: the configured default agent, its model
config, its providers.

An import prunes a step before collecting its references, so a pruned agent id is never pinned back. The verify_a2a
steps keep their agent id, which they require.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…'s scope, and credit a type to what registered it

snapshot_env's gateway_url is how the step finds its gateway when no deployed env gives one, so leaving it out wrote a
task that couldn't run. prompt_agent's user_a2a_url also tells a human counterpart from a simulator, so leaving it
out turned a human into a simulator and dropped the attachments of its replies. Both stay task-scoped.

A need was credited to whichever installed distribution claimed its name, even when a config file's impls replaced
that class. It's now credited to the distribution only when its entry point names the class that registered, and to
configuration otherwise.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Comment thread src/agent_env/task_step/task_steps/install_agent.py
Comment thread src/agent_env/bundle/importer.py
earakely-scale and others added 2 commits October 18, 2026 17:11
…on't load a skipped plugin to credit a need

install_agent requires a2a_agent_id and has no default to install in its place, so leaving it out broke the import.
The contract test now builds each step type that scopes fields of its own from a minimal step without them.

A plugin the registry build skipped registers nothing, so crediting a need reads that record instead of importing
the plugin again, which could raise and abort the import.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ent changelogs out of an import as user scope

install_agent naming no agent installs the configured default, as deploy_agent does, so its agent is user-scoped.
So are load_artifact.urls, run_docker_container.docker_context_url, a skill's object_url, and deploy_agent's changelog
url and position: each points into its user's store. A step left without its only source still builds; the new
TaskStep.config_problems() names what's missing wherever preflight runs, including for a step a dry run can't
preflight, and the step raises the same at run time.

An integration test imports for another machine (two local stores, the source's config registering a sandbox provider
and a step type) and runs each folder on both.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Comment thread src/agent_env/task_step/task_steps/deploy_agent.py Outdated
Comment thread src/agent_env/bundle/importer.py Outdated
earakely-scale and others added 3 commits October 18, 2026 17:39
…out a user-scoped field set to zero

Without the changelog the agent starts fresh instead of from the earlier run's state, and nothing left in the task says
so; a field an import can drop with no trace changes what the task is.

An import now leaves out any user-scoped field that is set, a zero or false included, and keeps one set to nothing.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…tity

agent_env.entity.refs holds the ref declarations, agent_env.entity.scopes the field scopes and prune (moved out of
the importer, since leaving out the fields of other scopes isn't import-specific), and agent_env.entity.paths the
path syntax both use. Imports move with them; nothing changes behavior.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
It re-exports what moved to agent_env.entity.refs, so a plugin importing the old path keeps working.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@earakely-scale
earakely-scale merged commit 315f358 into main Oct 19, 2026
15 checks passed
@earakely-scale
earakely-scale deleted the edgararakelyan/fd-3989-import-store-only-values branch October 19, 2026 03:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant