Skip to content

feat(task): a registered task records the plugin packages it requires, and task run checks them first - #383

Open
earakely-scale wants to merge 5 commits into
mainfrom
feat/task-requires
Open

earakely-scale wants to merge 5 commits into
mainfrom
feat/task-requires

Conversation

@earakely-scale

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

Copy link
Copy Markdown
Collaborator

Summary

The store side of bundle.toml (the folder side was #377). A task shared through a store now says which plugin packages it needs, and agent-env task run checks them before anything starts.

  • Task.requires: an optional field, saved with the task's document only when it has entries, so every task stored before this, and every task using only core types, is unchanged.
  • Set by bundle register, per task: the packages behind the task's own step types and behind the types of the bundle entities it references (envs, artifacts, skills), not the whole bundle's list. A package bundle.toml requires keeps the range written there; otherwise the detected floor (>= the installed version). The ledger hashes requires, so changing a range in bundle.toml writes only the affected tasks anew (requires agentenv-acme>=1.1,<2 as the reason).
  • task run / run-batch, before the first step:
    • refuses a task whose requirements aren't met, with the one agent-env plugin add command that installs them; at a terminal it offers to run it and then runs the command again (as agent-env run does since feat(bundle): a bundle.toml names the plugin packages a bundle requires #377);
    • refuses a task whose steps read an env or artifact of a type nothing here registers, reading the entities they name from the store, so it fails before any sandbox starts instead of at the step. Only the steps the run runs count: from --start-step on, and without the references a step's step_params replace. An entity the store doesn't hold, or whose id the run fills in from a seed, is left to its step, as today;
    • in local stores, sets up the infra envs its gateway deploys run on (gateway, service-db for local Postgres state, website-browser for sites), when the store doesn't hold them, following the run's --env-state-type / --env-state-instance-id, except for one the run is given (--gateway-env-id, --service-db-env-id). An infra env the store holds is left for agent-env up / run to rebuild. Shared stores are unchanged.
  • A step type nothing registers names the package, wherever a stored task loads: Unknown task step type: acme_check; task reg-check v1: requires 'agentenv-acme>=1.2.0', which isn't installed; install it with: agent-env plugin add 'agentenv-acme>=1.2.0'.
  • Requirements on agent-env itself say to upgrade it, never offered through plugin add, as for bundles. A stored entry that isn't a pip requirement, or holds a character a terminal wouldn't show as it is, is named as unmet and never offered.
  • bundle init --from-task writes the ranges the stored task records.

The hub rebuilds a task from its id and steps when saving a new version, so a hub edit drops requires until the hub passes it through; hub workers have every Scale plugin installed, so hub runs aren't affected.

Review guide

  • task/task.py: the field, and UnknownStepTypeError carrying the unmet requirements.
  • plugins/_requirements.py: unmet_problems / install_command moved here from bundle/requires.py, with an owner (bundle.toml, or task <id> v<n>), so a task can say the same thing without the bundle package.
  • bundle/register.py: with_task_requires; bundle/requires.py: entry_uses, and detected taking the declared lines.
  • bundle/plan.py / ledger.py / materialize.py: Write.requires carried, hashed and stored.
  • cli/task/_checks.py: what task run checks; bundle/preflight.py: stored_infra, factored out of the walk's gateway logic.

Test plan

  • make lint
  • Unit: the field's round trip and old documents; the load error with and without a record; per-task requires at register (own steps, a referenced artifact's package, declared ranges, re-register unchanged, a new range writing only that task); every task run refusal before the run, run-batch, the prompt, the agent-env-itself case, entities left to their step, and infra set up in local stores only, but for those the run is given; stored_infra
  • E2E + chaos on an agent-environments-admin VM: 55 cases on badb9a5, all as designed (results in a comment below)

🤖 Generated with Claude Code

RetriggerView in GreptileConfidence Score: 5/5

The changes since the previous review appear safe to merge.

Summary

Stored tasks now record their required plugin packages, and task commands check those packages before starting.

  • Registered tasks record the plugin packages their steps and referenced entities need.
  • Task runs check package and stored entity requirements before starting steps.
  • Local task runs set up missing gateway infrastructure used by stored environment deployments.

All three supplied previous threads were unnumbered, so they receive no numbered finding entries.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[Register bundle] --> B[Record each task's plugin requirements]
  B --> C[Save task and hash requirements]
  C --> D[Run stored task]
  D --> E{Requirements and referenced types available?}
  E -- No --> F[Refuse run and explain missing packages]
  E -- Yes --> G[Set up missing local infrastructure]
  G --> H[Submit run]
Loading

Reviews (5) · Last reviewed commit: "fix(task): stored requires are read defe..." · Reviewed by Greptile

…, and task run checks them first

bundle register records on each task the plugin packages behind its own
step types and the types of the bundle entities it references, in the
range bundle.toml gives a package it requires, else at least the
installed version (Task.requires, saved only when there are some). The
ledger hashes them, so a new range writes the task anew.

Before a stored task's first step, task run and run-batch refuse one
whose requirements aren't met, or whose steps read an env or artifact of
a type nothing here registers, offering in a terminal to install what it
requires with agent-env plugin add. In local stores they then set up the
infra envs the task's gateway deploys run on, as agent-env run does.

A step type nothing registers names the task's unmet requirements and
the command that installs them wherever a stored task is loaded. A task
stored without requires loads and runs as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@earakely-scale
earakely-scale requested a review from a team as a code owner October 29, 2026 04:56
Comment thread src/agent_env/cli/task/_checks.py Outdated
Comment thread src/agent_env/cli/task/_checks.py Outdated
…s them

A resumed run (--start-step) no longer reads what the steps it skips
reference, and a step the run gives step_params, which it reads in place
of what it names, isn't checked against its stored references. The infra
envs follow the run's env state overrides, so an external state needs no
service-db and an attached instance's own type decides.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comment thread src/agent_env/cli/task/_checks.py Outdated
A step's step_params that name one of its fields referencing a kind
replace what it reads of that kind, so only those references are left
to it; overrides that leave its references as stored, such as a
load_artifact destination_path, still have them checked before the run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@earakely-scale

Copy link
Copy Markdown
Collaborator Author

@greptileai review

@earakely-scale

earakely-scale commented Oct 29, 2026 •

Copy link
Copy Markdown
Collaborator Author

E2E + chaos verification (badb9a5)

These ran on a fresh agent-environments-admin VM for each of 53d92ee, d19e6f9, 29bc743 and badb9a5, each torn down after: Ubuntu 24.04, m5.2xlarge, Docker 28, uv 0.10. The wheel came from this branch.

Every environment shared one local store. The environments were:

  • one with a fake step-type plugin, agentenv-e2e-acme 1.2.0;
  • one with none;
  • one that answers yes at the prompt;
  • one with a plugin that fails on import;
  • agent-env 0.9.1380 from PyPI, the release before requires, to check compatibility.

On badb9a5 there were 55 cases, all as designed, and all 52 shared with the 29bc743 round gave the same result.

Registered with what it requires

Case Result
register a bundle using the plugin's step stored reg-check v1 with "requires": ["agentenv-e2e-acme>=1.2.0"]; registering again writes nothing
a core-only bundle (the echo-mcp example as shipped) stored with no requires key, and it runs from the environment without plugins (Docker deploy and verifier pass)
a new range in bundle.toml reg-check v2 (requires agentenv-e2e-acme>=1.1); the next register is unchanged
a task the 0.9.1380 release registered no requires; runs where the plugin is, and fails exactly as before where it isn't (Unknown task step type: acme_check)

Fresh local store: task run sets up the infra itself

Case Result
echo-mcp plus the plugin step, registered, then task run --id echo-smoke service-db env 'default-db' (missing): registering its published images… and the same for the gateway, then the run passes (echoes: 1, acme: 1); infra went from None to v1, and a second run sets nothing up
--env-state-type external_db only the gateway is set up (default-db stays None)

Without the plugin: refused before anything starts

Case Result
task run, task run-batch, bundle init --from-task Error: Unknown task step type: acme_check; task reg-check v1: requires 'agentenv-e2e-acme>=1.2.0', which isn't installed; install it with: agent-env plugin add 'agentenv-e2e-acme>=1.2.0', exit 1, no container started
at a terminal: yes / no / Ctrl-C installs, prints Running it again: agent-env task run …, and the rerun passes / exit 1 / Aborted!, nothing installed
an env a step reads, of a type nothing registers task envtask v1: step 'env': env_id 'acme-env': Unknown env type: …, exit 1, no container
the same, resumed with --start-step 1 past that step runs and passes
a load_artifact of an unknown artifact type: no override, or step_params with only destination_path (via --context-json) refused before the sandbox, no container
step_params replacing its artifact_id not refused; the run starts and fails at the step (that artifact doesn't exist), as the run would
the broken plugin installed Unknown task step type: broken_check (registered by … but failed to load: …), no prompt
requires = ["agentenv-framework>=99"] …upgrade agent-env with the installer that installed it, no prompt

Stored records it can't trust (badb9a5)

Case Result
a stored task's requires holding not a requirement! beside a real one, at a terminal names it as isn't a pip requirement that can be shown as written; offers and installs only the real one; the rerun is refused for the bad entry alone, without asking again
a stored requires entry carrying a terminal escape (\x1b[2K\r…) shown escaped and never offered; only the real requirement is installed
bundle init --from-task reg-check after the range change writes requires = ["agentenv-e2e-acme>=1.1"], the range the task records

Chaos

Case Result
20 registrations SIGKILLed at random moments, each registered again 20 of 20 reruns finished; all 20 tasks are stored with ["agentenv-e2e-acme>=1.2.0"]
3 task runs racing on a fresh local store gateway and service-db each set up once; all 3 runs passed
a task run SIGKILLed while setting up the infra, then run again the rerun passed in 28 s, with no stuck lock

Leftovers: only the local registry container; the secret scan of the results is clean.

Found and fixed along the way:

  • Greptile's state-override and skipped-step findings (d19e6f9);
  • partial step_params overrides (29bc743);
  • the review round (badb9a5): stored records read defensively, infra set up only when missing, each entity read once, and imports keeping recorded ranges. The last two are covered by unit tests.

The first round's "core task" was an empty task, which Task.run refuses on main too. It was replaced by the shipped echo-mcp example.

🤖 Generated with Claude Code

earakely-scale and others added 2 commits October 28, 2026 23:05
… when missing, and imports keep recorded ranges

A stored requires entry that isn't a pip requirement, or holds a
character a terminal wouldn't show as it is, is named as unmet and never
offered for install, where it used to crash the load. task run sets up
only the infra envs a local store doesn't hold, leaving one it holds for
agent-env up or run to rebuild. Each entity the steps reference is read
once however many name it. bundle init --from-task writes the ranges the
stored task records.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ined

DeployEnvTaskStep.run_state is the one place a deploy's env state comes
from, for its run, preflight and task run's checks; the gateway and
service-db override keys are constants beside run_env_ids. Setting up
only missing infra is ensure_default_envs(missing_only=True), decided
with its other rules under the same lock. run-batch checks the task with
the metadata it runs with. The checks use EntityRef.field, and the
bundle and stored-task prompt tests share one terminal helper.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
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.

1 participant