Skip to content

feat(cli): workspace asset registry — evo asset put/get/list/use/rm (closes #55) - #109

Open
Srinivasan8888 wants to merge 2 commits into
evo-hq:mainfrom
Srinivasan8888:feat/issue-55-asset-registry
Open

Srinivasan8888 wants to merge 2 commits into
evo-hq:mainfrom
Srinivasan8888:feat/issue-55-asset-registry

Conversation

@Srinivasan8888

Copy link
Copy Markdown

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 backend field. Kept the surface reviewable rather than shipping the entire proposal at once.

Surface

evo asset put <path> --name N --kind K [--exp E] [--tag k=v]... [--copy]
evo asset get <name>                       # prints canonical local path
evo asset list [--kind] [--tag k=v] [--produced-by] [--consumed-by] [--json]
evo asset use <name> --exp E               # records consumption
evo asset rm <name> [--force]              # refuses while consumed unless --force
  • put registers in place by default; --copy materializes under workspace_path/assets/<name>/; --exp sets produced_by. Names are workspace-unique (rm first to replace).
  • use records consumption; from then on the asset path is injected into that experiment's runs as EVO_ASSET_<NAME> (e.g. base-model → EVO_ASSET_BASE_MODEL), so recipes read it by stable handle instead of a hardcoded path.

Design

  • New module evo/assets.py splits pure registry logic (dict in/out, unit-testable with no workspace) from thin disk wrappers (load/save under advisory_lock + atomic_write_json, matching evo config set).
  • Registry at workspace_path/assets.json, per-run scope — resets with evo reset.
  • Run integration: _runtime_env_for_attempt injects EVO_ASSET_<NAME> for every consumed asset, best-effort alongside the existing EVO_SEED_ARTIFACT block. Complements evo 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

  • 16 pure-logic unit tests (put/replace, filter by kind/tags/produced_by/consumed_by, record_use idempotency, remove refuse/force/unknown, env-var mapping, tag parsing) + 10 CLI round-trip tests against a temp workspace.
  • Verified end-to-end via the real CLI (put in-place + --copy, get, list with filters + --json, use, rm refuse/force, dup/unknown errors) and a real _runtime_env_for_attempt call confirming EVO_ASSET_<NAME> is injected for a consumer and absent for a non-consumer.
  • Full unit suite green (minus pre-existing Rust hook-binary failures unrelated to this change; they reproduce on 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.

…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).

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Devin Review found 1 potential issue.

Devin Review

Comment thread plugins/evo/src/evo/assets.py Outdated
Comment on lines +33 to +38
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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 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.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

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

Asset registry: tag and store experiment-produced artifacts for downstream reuse

1 participant