Repository navigation
feat(bundle): ask each artifact type what it reads from its folder, and read a universe's as the inverse of get - #232
Conversation
…nvironment-universe get, whatever its names A folder directly holding one file is an environment named by its path from the universe's folder, or, under metadata/, a metadata key named by its path from there, so names and keys holding / round-trip as the nested folders get writes. A name an @Local id can't hold as it is (__, characters outside the id set, a segment opening or closing with a space, too long) is written under a hash of it instead of being refused; every name that works today keeps its ids. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…d can take, and list every path to a folder A hashed id is now <base>__sha256__<16 hex>: names holding __ are always hashed, so no name's own id holds it, and a name spelled like another's hash keeps an id of its own. Folders are listed by every path to them, guarding only against a link back to a folder that holds it, so metadata in another case is refused even as a link to an environment's folder. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Chaos verificationOn an Ubuntu 24.04 VM (m5.2xlarge) and a stock macOS 15.8 VM (below), the old release (agentenv-framework 0.9.1334 from PyPI) against this PR (481e31c, with the review fixes). Both commits gave the same results. Fuzzed round trip.
No regression. 60 flat universes of the kind the old reader accepts were written by the old release, then run by this PR in the same state root. All 60 came back CoopBench. This is the materialized copy, with its universe's 15
macOSThe same battery ran on a stock macOS 15.8 VM (mac-m4.metal; APFS, case-insensitive). Both fuzz passes kept apart names that differ only in case, since
After the maintainability refactor (6c68921)The whole battery ran again on fresh Ubuntu and macOS VMs. That covered the fuzz on both versions, the 60-universe no-regression run, CoopBench (dry runs and the full task) and the NFD probe. Every result matched the earlier rounds:
After moving the format into its own module (7a517bb)The whole battery ran again on fresh Ubuntu and macOS VMs, with the same results as every earlier round on both:
After artifact types describe their own folders (81c3944)On fresh Ubuntu and macOS VMs, the battery's results match every earlier round on both:
A plugin's artifact types, installed as a real
|
… id is refused naming the fix Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…its files, and its children's ids from its own naming _Walk now records every folder it visits, so universe_layout reads files and folders from one walk with one set of rules, and _subfolders goes. _child_id derives through EnvironmentUniverseArtifact.derived_environment_id and derived_metadata_id, which the CLI uses too, adding only the hashed fallback. Every id is unchanged. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ule of its own, read and written from it environment_universe_folder holds the format both ways: environment_path and metadata_path, where environment-universe get writes each file, and read_folder, their inverse, which takes a folder's listing and raises UniverseFolderError naming paths in the folder. The bundle keeps only the walk and the bundle-relative prefix on each problem; messages and ids are unchanged. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…r, so a plugin's type is written as a core one is The bundle kept a table of core types and what each one's write reads from its folder, and branched on the environment and universe readers to list child ids and the environments a load seeds. A plugin type with a from_toml of its own was refused. Artifact.bundle_folder(ctx) now returns what from_toml reads (files, which the ledger hashes) and the ids it writes besides its own (children, each with its path in the folder and the environment it seeds). Each core type describes its own folder, and the plan, ledger and materialize ask the type. A type is written from its folder when one class defines both hooks, so a plugin type defining both is planned, checked, written and reused as a core one is. The universe folder module no longer imports the universe class: it owns METADATA and the ids of what's written from each folder. Ids, digests and messages are unchanged, except the duplicate-name message, which names the type and the folder by its path. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
🤖 Automated Claude routine — Env Pod PR Context Review. This is a bot, not Edgar Arakelyan; not a human review. Context from a sibling PR rather than from this diff: the folder round-trip this PR pins carries a universe's files, but none of the stored document's own fields — and #170 adds the first such field whose loss would change what a restored env does.
With both landed, a snapshot-derived universe downloaded with If carrying it is out of scope, the docstring's "The format's own limits" list looks like the right place to say that a universe's non-file fields don't survive the folder round-trip, so the next field with runtime meaning doesn't get added in the same shape. Ordering-wise it's whichever of the two lands second that would want the note. Generated by Claude Code |
…, and check a file's one file in accept_toml bundle_folder is now optional. Its default is every file in the folder and nothing besides, which is what a file universe, a skill and a CLI read, so they drop their overrides, and a plugin type whose folder is all its files needs only a from_toml. A type is written from its folder when its from_toml isn't the base's. Hashing every file can only rewrite more often, never reuse a stale write. A file's folder reads as a one-file universe: the rule that it holds one file moves into FileArtifact.accept_toml, which the plan runs before anything is written, beside the checks a skill and a CLI make of their folders. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Summary
A bundle's
environment_universefolder is laid out asenvironment-universe get --output-dirwrites one, but the reader only took one folder level.getwrites: each environment to<environment_name>/<file>and each metadata file tometadata/<key>/<file>, names verbatim. A key or name holding/, such asscenarios/<name>.yaml, comes out as nested folders.__, characters an@localid can't hold, or a segment opening or closing with a space, even thoughgetwrites them all.The folder is now read as the exact inverse of
get, whatever it holds:metadata/. A folder holding only folders is part of their names, and a folder can hold its own file and nested entries at once, soaanda/bboth work.@localid can hold keeps the ids it has today:<universe>__<name>over<universe>__<name>__file, and<universe>__metadata__<key>. Any other name is written under…__sha256__<16 hex of its SHA-256>in its place. Names holding__are always hashed, so no name's own id can take that form, and a name spelled like another's hash keeps an id of its own. That covers__(which could clash with another child's ids), characters outside the id set, a segment opening or closing with a space, and ids over 4096 bytes. The universe still stores the name or key itself.metadata/;metadataspelled in another case.metadata/reads back as a metadata key, so it needs an artifact folder of its own.The bundle asks each artifact type about its folder, so a plugin's type can be written from one. The bundle used to keep a table of the core types and what each one's write reads from its folder. It branched on the environment and universe readers to list child ids and the environments a load seeds. A plugin type with a
from_tomlof its own was refused ("writing … isn't supported yet").Artifact.bundle_folder(ctx)returns anArtifactFolderwith two parts:files: everythingfrom_tomlreads, which the plan checks and the ledger hashes;children: the ids it writes besides its own, each aFolderChildwith its path in the folder and the environment it seeds.from_toml.<id>__file, and the universe's environments, their files and its metadata files.accept_toml. The rule that atype = "file"folder holds one file moves there, beside the checks skill and CLI already make of their folders. A file's folder now reads like a file universe holding one file.from_tomlisn't the base's, which writes a document and no folder.artifact/artifacts/environment_universe_folder.py.environment_pathandmetadata_pathsay wheregetwrites each file, andread_folderis their inverse.METADATAand the ids of what's written from each folder, so it doesn't import the universe class.bundle/authoring.pykeeps only generic helpers:listing(),only()andrefuse_within().getdoes) lands with bundle import, its first consumer.Bundle authoring and
getonly, so not hot path.Tests
environment-universe put,get --output-dirinto a bundle, then a bundle write, and compares the stored universe with the original: names, keys, filenames and bytes.gmail,drive/shared,g__mail,crm: v2andslack, and keysmanifest,scenarios,scenarios/a.yaml,scenarios/b.yaml,deep/x/y/z.json,m__kandq&a?.g__mailandq&a?get the hashed form. A name spelled like another's hash keeps an id of its own.Metadatain another case is refused even as a link to an environment's folder. A nested folder with two files is refused, naming it, and the empty-folder test now names the deepest empty folder. The six cases the old reader refused for nesting,__or edge spaces are now accepted layouts.read_folderreads back whatenvironment_pathandmetadata_pathlay out, and names each problem by its path in the folder.bundle_folder_test.py). The test uses a plugin type that subclasses no core type and writes a file artifact per file, each seeding an environment.from_tomlof its own is written by it, rewritten when its folder changes, and checked by theaccept_tomlit inherits.🤖 Generated with Claude Code


Confidence Score: 5/5
The PR appears safe to merge; no new actionable issues were found.Summary
The PR lets artifact types describe their bundle folders and reads nested universe folders back into environment names and metadata keys.
Diagram
%%{init: {'theme': 'neutral'}}%% flowchart LR A[Artifact type] --> B[bundle_folder] B --> C[Files to check and hash] B --> D[Child IDs and environment names] C --> E[Plan checks] D --> E E --> F[Ledger compares inputs] F --> G[Reuse saved version] F --> H[Write through from_toml]Reviews (7) · Last reviewed commit: "refactor(artifact): default bundle_folde..." · Reviewed by Greptile