gabriel / muse public
Closed #42 Enhancement
filed by gabriel human · 33 days ago

muse shelf save: resolve created_by from user identity, not hardcoded human

0 Anchors
Blast radius
Churn 30d
0 Proposals

muse shelf: resolve created_by from user identity instead of hardcoding "human"

Background

muse shelf save recorded created_by: "human" (a plain string) for every human-initiated shelf entry. This lost the caller's username and made it impossible to distinguish gabriel's entries from any other user's. The commit command already solved this correctly: it calls get_config_value("user.handle", root) and uses the result as the author field.

We went further than the original plan: created_by is now a structured object {"handle": "<name>", "kind": "human|agent"} instead of a plain string. This makes the identity richer (you can filter by kind, not just by name) and brings shelf entries in line with how commit records carry agent provenance.

Breaking change: created_by in all shelf JSON output is now {"handle": "...", "kind": "..."} instead of a plain string. Old entries stored as a string are normalised on load — the deserialization in _load_shelf handles both formats gracefully.

Goal

  • muse shelf save (and muse shelf bare) emit created_by: {"handle": "gabriel", "kind": "human"} when user.handle is configured.
  • When no handle is configured (CI, fresh machine, anonymous), falls back to {"handle": "human", "kind": "human"} — same observable behaviour as before.
  • The --by flag still overrides everything and produces {"handle": "<id>", "kind": "agent"}.
  • _shelf_push_programmatic (autoshelf) routes through the same helper; created_by="muse"{"handle": "muse", "kind": "agent"}.
  • Old shelf entries stored with a plain string created_by are normalised on read — no migration needed.

Phases

Phase 1 — Extract a helper

  • CB_01 — Add _resolve_created_by(root, explicit_by) helper in muse/cli/commands/shelf.py. Returns {"handle": explicit_by, "kind": "agent"} when explicit_by != "human"; otherwise calls get_config_value("user.handle", root) and returns {"handle": handle, "kind": "human"}, falling back to "human" when unconfigured. Also introduced _CreatedBy TypedDict.
  • CB_02 — Test: helper returns explicit --by value as kind=agent unchanged
  • CB_03 — Test: helper returns handle from config when --by is "human" and config is populated
  • CB_04 — Test: helper falls back to {"handle": "human", "kind": "human"} when config has no handle

Phase 2 — Wire into run_save

  • CB_05run_save and _shelf_push_programmatic: replace bare created_by string with _resolve_created_by(root, created_by) call. Updated all TypedDicts (_ShelfSaveJson, _ShelfListEntryJson, _ShelfReadJson, ShelfEntry) and _load_shelf backward-compat normalisation.
  • CB_06 — Test: muse shelf save --json with a configured handle produces created_by: {"handle": "<handle>", "kind": "human"}
  • CB_07 — Test: muse shelf save --by my-agent --json produces created_by: {"handle": "my-agent", "kind": "agent"}
  • CB_08 — Test: muse shelf save --json with no configured handle produces created_by: {"handle": "human", "kind": "human"}

Phase 3 — Wire into autoshelf call sites

  • CB_09_shelf_push_programmatic now calls _resolve_created_by; callers in checkout.py and merge.py pass created_by="muse" → resolves to {"handle": "muse", "kind": "agent"}
  • CB_10 — Test: _shelf_push_programmatic(root, created_by="muse") records {"handle": "muse", "kind": "agent"}

Acceptance criteria

  • muse shelf save --json | jq '.created_by' returns {"handle":"gabriel","kind":"human"} on a machine where user.handle = gabriel is configured ✅
  • muse shelf save --by my-agent --json | jq '.created_by' returns {"handle":"my-agent","kind":"agent"}
  • muse shelf list --json | jq '.[].created_by.handle' shows the handle on all new entries ✅
  • All CB_0x tests pass (9/9) ✅
  • No regression on machines without a configured handle ✅
  • 166 shelf tests pass ✅

Out of scope

  • Backfilling created_by on existing shelf entries — old entries are normalised on read
  • Reading identity from ~/.muse/identity.toml directly — get_config_value is the correct single source of truth; no new I/O path needed
Activity1
gabriel opened this issue 33 days ago
gabriel 33 days ago

All 10 deliverables implemented and shipped in commit sha256:afb6f72afb8e on dev. 166/166 shelf tests pass. created_by is now {"handle": "...", "kind": "human|agent"} — old string entries normalised on load.

closed this issue 33 days ago