Repository navigation
feat(cli): workspace asset registry — evo asset put/get/list/use/rm (closes #55) - #109
Srinivasan8888 wants to merge 2 commits into
Conversation
…loses evo-hq#55) Names workspace artifacts so experiments reuse them by handle/tag instead of hardcoding brittle absolute paths, and records produced/consumed lineage. Local-storage-only in this PR; pluggable backends (S3/HF) are a follow-up. - New module evo/assets.py: pure registry logic (put/filter/record_use/remove, asset_env_var, parse_tag, asset_env_for_exp) split from disk I/O (load/save under advisory_lock + atomic_write_json; --copy materialization). Registry at workspace_path/assets.json, per-run scope (resets with `evo reset`). - CLI `evo asset put|get|list|use|rm`: put registers in place (--copy to materialize; --exp sets produced_by; --tag k=v); list filters by kind/tag/produced-by/consumed-by (+--json); use records consumption; rm refuses while consumed unless --force. Names are workspace-unique. - Run integration: `_runtime_env_for_attempt` injects EVO_ASSET_<NAME>=<path> for every asset the experiment consumes, best-effort like the existing EVO_SEED_ARTIFACT block. Complements `evo new --from-artifact`. Tested: 16 pure-logic + 10 CLI round-trip unit tests; verified end-to-end via the real CLI and a real _runtime_env_for_attempt injection. Full unit suite green (minus pre-existing Rust-binary failures unrelated to this change).
| name = str(entry.get("name") or "").strip() | ||
| if not name: | ||
| raise ValueError("asset name must be non-empty") | ||
| if not str(entry.get("kind") or "").strip(): | ||
| raise ValueError("asset kind must be non-empty") | ||
| reg.setdefault("assets", {})[name] = entry |
There was a problem hiding this comment.
🟡 Asset name with surrounding whitespace becomes unreachable
registry_put stores an asset under its whitespace-stripped name, but get, use, rm, and the duplicate check all look it up by the raw name. A name with surrounding whitespace registers under a different key than every lookup uses, so the asset can never be fetched, used, or removed.
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
Fixed in dc0c286. Added normalize_asset_name() and applied it at every entry point: put normalizes before the dup-check and stores the trimmed name; registry_put now rewrites entry['name'] to match its storage key so the two can't diverge; get/use/rm trim the lookup name; a blank/whitespace-only name is rejected. Verified via the real CLI that --name ' spaced ' registers under spaced and resolves by both the trimmed and padded strings, and that duplicates are caught across padding. New tests cover all three.
registry_put keyed assets under the whitespace-stripped name, but the CLI dup-check and get/use/rm looked them up by the raw name, so `--name " x "` registered under `x` yet was unreachable by the string the user typed (review finding). Add normalize_asset_name() and apply it at every asset entry point: put normalizes for the dup-check + entry, and registry_put now rewrites entry["name"] to match its key so the two can never diverge; get/use/rm trim the lookup name; a blank/whitespace-only name is rejected. Tests: padded name registers trimmed and resolves by both trimmed and padded lookups; duplicate detected across padding; blank name rejected.
What & why
Closes #55. evo records per-experiment producer artifacts, but there's no way to name an asset and reuse it across experiments, query by tag rather than experiment id, or record consumed assets. Downstream experiments hardcode brittle absolute paths that break when a source run is gc'd or moved. This adds a local workspace asset registry.
Scope: local-storage-only. Pluggable storage backends (S3 / HF Hub) are intentionally deferred to a follow-up; the schema leaves room for a
backendfield. Kept the surface reviewable rather than shipping the entire proposal at once.Surface
putregisters in place by default;--copymaterializes underworkspace_path/assets/<name>/;--expsetsproduced_by. Names are workspace-unique (rmfirst to replace).userecords consumption; from then on the asset path is injected into that experiment's runs asEVO_ASSET_<NAME>(e.g.base-model→EVO_ASSET_BASE_MODEL), so recipes read it by stable handle instead of a hardcoded path.Design
evo/assets.pysplits pure registry logic (dict in/out, unit-testable with no workspace) from thin disk wrappers (load/saveunderadvisory_lock+atomic_write_json, matchingevo config set).workspace_path/assets.json, per-run scope — resets withevo reset._runtime_env_for_attemptinjectsEVO_ASSET_<NAME>for every consumed asset, best-effort alongside the existingEVO_SEED_ARTIFACTblock. Complementsevo new --from-artifact(per-exp seeding); they coexist.Registry entry shape:
{ "name": "base-model", "kind": "model", "path": "/abs/path", "tags": {"epoch":"2"}, "produced_by": "exp_0001", "consumed_by": ["exp_0002"], "copied": false, "created_at": "<utc>" }Testing
--copy, get, list with filters +--json, use, rm refuse/force, dup/unknown errors) and a real_runtime_env_for_attemptcall confirmingEVO_ASSET_<NAME>is injected for a consumer and absent for a non-consumer.main).Out of scope (follow-ups)
Pluggable storage backends (S3/HF), cross-run/global assets (registry is per-run), gc of orphaned materialized copies.
Note
Issue #55 previously had a full attempt in #80, which its author self-closed (no maintainer objection). This is a fresh, test-driven, deliberately smaller local-first take.