Skip to content

feat(bundle): write environment and environment_universe artifacts from their artifact folders - #91

Merged
earakely-scale merged 8 commits into
mainfrom
edgararakelyan/artifacts-from-toml
Oct 7, 2026
Merged

earakely-scale merged 8 commits into
mainfrom
edgararakelyan/artifacts-from-toml

Conversation

@earakely-scale

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

Copy link
Copy Markdown
Collaborator

A bundle's artifacts/<name>/ folder whose artifact.toml declares type = "environment" or type = "environment_universe" is now written by agent-env run, as artifact environment put and artifact environment-universe put write them. Until now the run refused such a folder as one it can't write.

What an artifact folder holds

artifacts/crm-data/                 # an environment over the folder's one file
  artifact.toml                     # type = "environment"; environment_name = "crm"
  rows.json                         # written as <id>__file
artifacts/mail-data/artifact.toml   # type = "environment"; environment_name = "gmail"; file = "mail-rows"
artifacts/world/                    # a universe, laid out as `environment-universe get --output-dir` writes one
  artifact.toml                     # type = "environment_universe"; environment_artifacts = ["mail-data"]
  slack/data.json                   # an environment named slack: <id>__slack over <id>__slack__file
  metadata/manifest/manifest.json   # metadata key manifest: <id>__metadata__manifest
  • environment. environment_name is required. The file it wraps is either the one file in its folder (description defaults to the file's name) or the file artifact file names: a bundle artifact, a store id, or { artifact = "…", version = n }.
  • environment_universe. Each <environment_name>/ folder holds one environment's file, and each metadata/<key>/ folder one metadata file. environment_artifacts adds environments from the bundle or the store. Its environments are the folders', in name order, then the ones it names.
  • Stored names. The keys the stored documents use (service_name, file_artifact_id, file_artifact_ref, service_artifact_refs, environment_artifact_refs, service_artifact_ids, metadata, metadata_refs) are refused, naming the key to write instead.
  • Type aliases. A type alias configured for these types is written under the canonical type.

Checked before anything is written

  • Unknown keys, a value of the wrong type, and file naming anything but a file artifact, or environment_artifacts anything but environment artifacts. These are typed refs, checked in the resolver for bundle artifacts and in the plan for store ones.
  • An environment:
    • a folder with no file, or more than one;
    • a folder that holds a file while file names one;
    • file together with description.
  • A universe:
    • no environments at all;
    • a file outside <environment_name>/ or metadata/<key>/, a folder holding anything but one file (empty included), or folders nested inside one;
    • a folder name holding __, opening or closing with a space, or giving an id validate_local_id refuses;
    • two environments of one name across its folders and environment_artifacts. A store environment's name is read at the version the plan read; bundle check, which reads no store, counts only the bundle's.
  • Derived ids. The ids an environment's file and a universe's environments and metadata files are written under join the one-writer check, so one that clashes with, say, an image the bundle builds is refused.

Writing and reuse

  • Each child file is written under a prefix of its own attempt (FileArtifact.put_attempt, now shared with file artifacts), so a write that fails partway never blocks the next.
  • The ledger hashes the files each type's write reads from its folder (folder_walk), and now the versions of what any written document references, tasks and evals aside. So a universe is rewritten when an environment it names is, and an environment when its file is.
  • A store ref named without a version is pinned at the version the plan read.
  • Digests of everything written before are unchanged. ledger_digest_test pins nine digests recorded by v0.9.1278 across files, file universes, built images, envs, agents and tasks.

Round trip

A universe downloaded with environment-universe get --output-dir and dropped into a bundle as-is writes an equivalent universe. A unit test compares the canonical documents, setting aside ids, versions, timestamps, object URLs and file descriptions; environment order is name order. A real 9-environment, 42 MB universe was written in about a second and reused on the rerun; the only field that differed from the original was its legacy unpinned file_artifact_id.

Tests

  • Unit: artifact_toml_test.py (both environment forms, store pins, universe composition, the round trip, refusals, a store-free check), plus toml_refs_test.py, materialize_test.py and ledger_digest_test.py.
  • Slow tier: a new test writes two environments and a universe from a bundle and loads each into an MCP server env deployed through the gateway, checking the server serves the loaded data.
  • By hand, with the CLI on local stores:
    • About 55 malformed layouts and tomls were each refused by --dry-run and by run, with the stores unchanged.
    • SIGINT, SIGTERM and SIGKILL mid-write each recovered on the next run.
    • Two concurrent runs serialized.
    • A 1,500-folder universe wrote in about 5 s and dry-runs in 1.5 s once written.

Not hot path: only the bundle path changes. The two artifact classes gain class variables and classmethods, and their models, schemas and dumps are unchanged.

🤖 Generated with Claude Code

RetriggerConfidence Score: 5/5

The PR appears safe to merge; no actionable new issue or outstanding previous finding remains.

Fix All in CursorFindings

  1. P1 Shared universe loads are refused ▶
Fix with agent prompt
### Issue 1
src/agent_env/bundle/plan.py:485-489
When a task loads a bundle-written universe into one server, the new plan check rejects every environment with a different name. The server already loads its matching environment and skips the others. A universe containing `items` and `mail` can no longer load into the `items` server. Keep the matching-environment check without rejecting the extras.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Summary

agent-env run can now write environment and environment-universe artifacts from bundle folders. The bundle planner checks their layouts and references, and the ledger tracks their source files and referenced versions.

  • Environment folders can now write an environment around a file.
  • Universe folders can now build a universe from files and references.

Reviews (3) · Last reviewed commit: "test(bundle): a universe composed every ..." · Reviewed by Greptile

earakely-scale and others added 4 commits October 6, 2026 20:39
…om their artifact folders

An environment's artifact.toml names its environment_name and either the file artifact to wrap (file, a bundle
file artifact or a store one) or nothing, to wrap the folder's one file, written as <id>__file. A universe's
folder is laid out as environment-universe get --output-dir writes one: each <environment_name>/ folder holds
that environment's file, written as <universe>__<name> over <universe>__<name>__file, and each metadata/<key>/
folder a metadata file, written as <universe>__metadata__<key>; environment_artifacts adds environments by name
or store id. The stored documents' own key names are refused with a hint, and a universe with no environments,
or two of one name, is refused before any write.

The ledger tracks both: each base artifact type's listing in plan.py says what its write reads, and every kind
but tasks and evals now hashes the versions of the bundle writes it names, so a universe is rewritten when an
environment it pins is. No recorded digest changes (a test pins them), so SCHEME stays 1.

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

The data-bundle test bound a module-level DATA dict over the tst/data path that the
gateway multi test reads when it runs, so that test failed with a TypeError. The digest
test's fixture now sets each file to 0644, since a built image hashes other permission
bits and the umask decided them.

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

- A universe folder left without its file (empty, or holding only what the OS leaves)
  is refused rather than dropped, so the environment it names isn't silently missing.
- A folder name that opens or closes with a space is refused, and the environment's
  own id is validated as well as its file's, so neither fails partway through a write.
- The ids a universe's environments and metadata files are written under, and the file
  an environment wraps from its folder, are claimed in the one-writer check, so one
  clashing with an image the bundle builds is refused before anything is written.
- Messages: renaming an environment's folder renames the environment, so the refusal
  says how to keep the name; an environment folder with no file suggests `file`; a
  stored name for the file isn't also reported as an empty folder; a file nested under
  metadata/<key>/ is told where it goes.

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 7, 2026 04:33
Comment thread src/agent_env/bundle/plan.py
Comment thread src/agent_env/bundle/authoring.py Outdated
earakely-scale and others added 3 commits October 6, 2026 22:11
…iverse write beside them

- EnvironmentArtifact.derived_file_id and EnvironmentUniverseArtifact.derived_environment_id /
  derived_metadata_id are the only places these ids are spelled. The bundle's universe layout,
  the plan's one-writer check, from_toml and the CLI all use them, so none can drift from the
  others.
- `artifact environment put` and `environment-universe put --metadata` now name the files they
  write `<id>__file` and `<id>__metadata__<key>` (derive_id), as a bundle does, instead of
  `<id>-file` and `<id>-metadata-<key>`. Stored ids keep resolving; new writes get new names.
- `environment-universe get --output-dir` and the bundle's layout share the metadata folder's
  name.
- The keys that name an environment's file are read from toml_stored_names, not restated.
- FileArtifact and EnvironmentArtifact are imported at module top in these modules; nothing
  forces the imports into the functions.
- A universe's metadata/ folder spelled in another case (Metadata/) is refused, as a case near
  miss of any reserved name in a bundle is, instead of becoming an environment named Metadata.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…he env a task loads it into

A load_artifact step loading the bundle's own environment or universe into an env is checked
before anything is written: each environment it seeds (a universe's folders and what its
environment_artifacts names, or an environment's own name) must be the environment_name of one
of the env's servers (an MCP server's or website's own, or each of a multi's envs'). One named
after no server is a typo or a stray, which a multi would skip without a word and an MCP server
or website would fail on mid-run.

A store environment or universe isn't checked: a server loading a shared universe takes its own
environment from it and skips the rest. A check that reads no store skips a store env's servers.

The slow data-bundle test's universe no longer adds an environment for another server, which this
now refuses.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Comment on lines +485 to +489
for ref in refs:
for what, name in self._seeded(ref, store_latest):
if name not in servers:
listed = ", ".join(repr(server) for server in sorted(servers))
self.problems.append(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Shared universe loads are refused

When a task loads a bundle-written universe into one server, the new plan check rejects every environment with a different name. The server already loads its matching environment and skips the others. A universe containing items and mail can no longer load into the items server. Keep the matching-environment check without rejecting the extras.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agent_env/bundle/plan.py
Line: 485-489

Comment:
**Shared universe loads are refused**

When a task loads a bundle-written universe into one server, the new plan check rejects every environment with a different name. The server already loads its matching environment and skips the others. A universe containing `items` and `mail` can no longer load into the `items` server. Keep the matching-environment check without rejecting the extras.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Cursor Fix in Claude Code Fix in Codex

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Intended. A universe the bundle writes is data its author wrote for the env they load it into, so an environment there that matches no server is treated as a typo or a stray folder and refused before any write, for a single server as for a multi (a multi would otherwise skip it without a word). The skip-the-rest behavior is kept where it's legitimately used: a store universe shared across servers still loads into one server, taking its own environment (test_a_store_universe_loaded_into_one_server_gives_it_its_own_environment). A bundle that wants one server to take part of a universe can name that server's environment artifact instead.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense, and the distinction is valid. _check_loaded_environments intentionally ensures every environment introduced by a bundle-written universe maps to a server in the task’s bundle environment, preventing typo or stray folders from being silently accepted. Store universes retain their shared-universe behavior, and tasks needing only one environment can reference that environment artifact directly.

I’m withdrawing this finding; no change is needed.

…lti of bundle and store servers

A slow-tier test writes, deploys and reads back the combinations the data-bundle test left out:
- a universe whose environments come from its folder, a bundle environment over a store file
  pinned by version, and a store environment, loaded into a multi of two bundle servers and one
  the CLI put in the store; each server holds exactly the data meant for it;
- a store universe shared across servers, loaded into one bundle server, which takes its own
  environment and skips the rest;
- a dry run afterwards reuses everything.

The check reads each server's state behind the gateway rather than over MCP: every items server
registers list_items under the same name, which the gateway serves from only one of them.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@earakely-scale
earakely-scale merged commit b95f360 into main Oct 7, 2026
13 checks passed
@earakely-scale
earakely-scale deleted the edgararakelyan/artifacts-from-toml branch October 7, 2026 06:19
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