Skip to content

feat(personal-assets): canonical home is private machine-<host> dir - #548

Merged
sonichi merged 1 commit into
mainfrom
feat/personal-assets-canonical-private-home
Apr 30, 2026
Merged

sonichi merged 1 commit into
mainfrom
feat/personal-assets-canonical-private-home

Conversation

@sonichi

@sonichi sonichi commented Apr 29, 2026

Copy link
Copy Markdown
Owner

Summary

Each Stand has its own identity + avatar. These files are gitignored and machine-local. This PR makes the canonical home the per-machine private dir ($SUTANDO_PRIVATE_DIR/machine-<hostname>/), with public-workspace fallback preserved so existing installs keep working until they migrate naturally.

Pieces

  • src/util_paths.py — Python personal_path(filename, workspace). Lookup: private machine dir → public assets/ (avatar only) → public root. Returns the preferred private-dir path on miss so callers' .exists() checks fail gracefully.
  • src/voice-agent.ts + skills/phone-conversation/scripts/conversation-server.ts — TypeScript twin personalPath(filename), same lookup order.
  • Wired callers — src/agent-api.py (/avatar, /stand-identity), src/dashboard.py (same two endpoints), src/event_log.py (_machine_id), voice-agent.ts (system prompt + greeting), conversation-server.ts (call instructions).
  • src/migrate.sh — bundle/restore now uses the correct $REPO/assets/stand-avatar.png source path (was $REPO/stand-avatar.png, silently failing for everyone). Restore lands the file under assets/.

Companion fix already in private memory-sync

Commit 50e095c in ~/.sutando-memory-sync/scripts/sync-memory.sh: MACHINE_FILES entry for the avatar is now assets/stand-avatar.png instead of stand-avatar.png (same root cause as the migrate.sh bug — backup was silently skipping the avatar). After this PR + that fix, the avatar lives in machine-<host>/ for real.

Test plan

  • SUTANDO_PRIVATE_DIR=~/.sutando-memory-sync python3 -c "from util_paths import personal_path; print(personal_path('stand-avatar.png'))" returns the private path post-sync.
  • npx tsc --noEmit clean.
  • python3 -m py_compile on all touched Python files clean.
  • Existing public-workspace path still resolves when SUTANDO_PRIVATE_DIR is unset (fallback path verified by reading the helper).

Deferred (follow-ups, not in this PR)

  • Slide-server WORKSPACE_ROOT default flip — already env-overridable, currently working via the shim copies from yesterday's talk-prep.
  • Removing the public copies after Chi's machine has fully migrated — keeping them as fallback for backwards compat for now.
  • Personal skills migration — checked inventory; the personal-* prefixed skills already moved (personal-talk-highlight, personal-voice-context, etc. live private-only). Framework skills (x-twitter, phone-conversation) stay public.

🤖 Generated with Claude Code

Each Stand has its own identity + avatar. These files are gitignored and
machine-local; the canonical home moves to
`$SUTANDO_PRIVATE_DIR/machine-<hostname>/<file>` so they live with the rest
of the per-machine memory under the private sync repo. Public-workspace
fallback is preserved so existing installs keep working until they
migrate naturally.

Pieces:

- `src/util_paths.py` — Python helper `personal_path(filename, workspace)`.
  Lookup order: private machine dir → public assets/ (avatar only) →
  public root. Falls back to preferred private path on miss so the
  caller's `.exists()` check fails gracefully.

- `src/voice-agent.ts` + `skills/phone-conversation/scripts/conversation-server.ts`
  — TypeScript twin `personalPath(filename)` with the same lookup order.

- Wired callers: `src/agent-api.py` (/avatar, /stand-identity), `src/dashboard.py`
  (/avatar, /stand-identity), `src/event_log.py` (_machine_id),
  `src/voice-agent.ts` (system-prompt + greeting),
  `skills/phone-conversation/scripts/conversation-server.ts` (call instructions).

- `src/migrate.sh` — bundle/restore now uses the correct
  `$REPO/assets/stand-avatar.png` source path (was `$REPO/stand-avatar.png`,
  silently failing). Restore lands the file under `assets/` so the public
  fallback path keeps working alongside the private canonical home.

Companion fix already shipped in private memory-sync repo
(scripts/sync-memory.sh, commit 50e095c): `MACHINE_FILES` entry for
the avatar is now `assets/stand-avatar.png` instead of `stand-avatar.png`,
so the per-machine backup actually copies it. After this PR + that fix,
the Mac's avatar lives in `machine-<host>/` and `personal_path()`
resolves there directly.

Verified end-to-end: `SUTANDO_PRIVATE_DIR=... python3 -c "from util_paths
import personal_path; print(personal_path('stand-avatar.png'))"` returns
the private path post-sync.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@sonichi
sonichi merged commit 4f4380d into main Apr 30, 2026
1 check passed
@sonichi
sonichi deleted the feat/personal-assets-canonical-private-home branch April 30, 2026 01:28
sonichi added a commit that referenced this pull request Apr 30, 2026
)

Migration #4 + #5 in the private-repo plan. Notes/ are SHARED across the
fleet (top-level private dir); pending-questions.md, PERSONAL_CLAUDE.md,
tab-aliases.json are PER-MACHINE (machine-<host>/, like #548).

Two resolver families:

- `personal_path()` (existing, #548) — per-machine: $SUTANDO_PRIVATE_DIR/machine-<host>/<file>
- `shared_personal_path()` (new) — fleet-shared: $SUTANDO_PRIVATE_DIR/<file>

New `src/util_paths.ts` is the TS twin of util_paths.py with both helpers
exported. voice-agent.ts replaces its inline `personalPath` with imports
from the new module.

Wired callers:

NOTES (shared_personal_path):
- src/inline-tools.ts NOTES_DIR → save_note / read_note / list_notes / delete_note
- src/voice-agent.ts: meeting-note save (line 285)
- src/dashboard.py: /notes/{slug} route + /notes index (lines 48, 437)
- src/daily-insight.py: NOTES_DIR
- src/friction-detector.py: notes_dir
- src/discord-bridge.py: SEND_ALLOWED_ROOTS adds private notes path
- src/health-check.py: notes-dir check now points at canonical home

PER-MACHINE (personal_path):
- src/dashboard.py: get_pending_count
- src/agent-api.py: pq_file (2 sites)
- src/friction-detector.py: pending-questions.md
- src/check-pending-questions.py: PQ_FILE
- src/session-handoff.sh: pending-questions.md (via inline python3)

PERSONAL_CLAUDE.md and tab-aliases.json: no runtime callers — only
referenced in migrate.sh bundle/restore (#548 already covers per-machine
shape, so they ride along without extra wiring).

Test plan:
- [x] `npx tsc --noEmit` clean
- [x] All Python files compile (`ast.parse`)
- [x] Resolver verified end-to-end with SUTANDO_PRIVATE_DIR set:
      shared_personal_path('notes') → /Users/wangchi/.sutando-memory-sync/notes
      personal_path('pending-questions.md') → .../machine-Chis-MacBook-Air/pending-questions.md
- [ ] Drop a voice-tool note via save_note, verify it lands in private dir
- [ ] Open the note in dashboard, verify /notes/{slug} reads from private dir
- [ ] Restart check-pending-questions cron, verify it reads private PQ file

After merge: kickstart voice-agent (touches voice-agent.ts + inline-tools.ts),
restart web-client/dashboard/agent-api/discord-bridge (Python files), restart
conversation-server. Same pattern as #548.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
liususan091219 pushed a commit to liususan091219/sutando-fork that referenced this pull request May 28, 2026
…onichi#548)

Each Stand has its own identity + avatar. These files are gitignored and
machine-local; the canonical home moves to
`$SUTANDO_PRIVATE_DIR/machine-<hostname>/<file>` so they live with the rest
of the per-machine memory under the private sync repo. Public-workspace
fallback is preserved so existing installs keep working until they
migrate naturally.

Pieces:

- `src/util_paths.py` — Python helper `personal_path(filename, workspace)`.
  Lookup order: private machine dir → public assets/ (avatar only) →
  public root. Falls back to preferred private path on miss so the
  caller's `.exists()` check fails gracefully.

- `src/voice-agent.ts` + `skills/phone-conversation/scripts/conversation-server.ts`
  — TypeScript twin `personalPath(filename)` with the same lookup order.

- Wired callers: `src/agent-api.py` (/avatar, /stand-identity), `src/dashboard.py`
  (/avatar, /stand-identity), `src/event_log.py` (_machine_id),
  `src/voice-agent.ts` (system-prompt + greeting),
  `skills/phone-conversation/scripts/conversation-server.ts` (call instructions).

- `src/migrate.sh` — bundle/restore now uses the correct
  `$REPO/assets/stand-avatar.png` source path (was `$REPO/stand-avatar.png`,
  silently failing). Restore lands the file under `assets/` so the public
  fallback path keeps working alongside the private canonical home.

Companion fix already shipped in private memory-sync repo
(scripts/sync-memory.sh, commit 50e095c): `MACHINE_FILES` entry for
the avatar is now `assets/stand-avatar.png` instead of `stand-avatar.png`,
so the per-machine backup actually copies it. After this PR + that fix,
the Mac's avatar lives in `machine-<host>/` and `personal_path()`
resolves there directly.

Verified end-to-end: `SUTANDO_PRIVATE_DIR=... python3 -c "from util_paths
import personal_path; print(personal_path('stand-avatar.png'))"` returns
the private path post-sync.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
liususan091219 pushed a commit to liususan091219/sutando-fork that referenced this pull request May 28, 2026
…onichi#555)

Migration sonichi#4 + sonichi#5 in the private-repo plan. Notes/ are SHARED across the
fleet (top-level private dir); pending-questions.md, PERSONAL_CLAUDE.md,
tab-aliases.json are PER-MACHINE (machine-<host>/, like sonichi#548).

Two resolver families:

- `personal_path()` (existing, sonichi#548) — per-machine: $SUTANDO_PRIVATE_DIR/machine-<host>/<file>
- `shared_personal_path()` (new) — fleet-shared: $SUTANDO_PRIVATE_DIR/<file>

New `src/util_paths.ts` is the TS twin of util_paths.py with both helpers
exported. voice-agent.ts replaces its inline `personalPath` with imports
from the new module.

Wired callers:

NOTES (shared_personal_path):
- src/inline-tools.ts NOTES_DIR → save_note / read_note / list_notes / delete_note
- src/voice-agent.ts: meeting-note save (line 285)
- src/dashboard.py: /notes/{slug} route + /notes index (lines 48, 437)
- src/daily-insight.py: NOTES_DIR
- src/friction-detector.py: notes_dir
- src/discord-bridge.py: SEND_ALLOWED_ROOTS adds private notes path
- src/health-check.py: notes-dir check now points at canonical home

PER-MACHINE (personal_path):
- src/dashboard.py: get_pending_count
- src/agent-api.py: pq_file (2 sites)
- src/friction-detector.py: pending-questions.md
- src/check-pending-questions.py: PQ_FILE
- src/session-handoff.sh: pending-questions.md (via inline python3)

PERSONAL_CLAUDE.md and tab-aliases.json: no runtime callers — only
referenced in migrate.sh bundle/restore (sonichi#548 already covers per-machine
shape, so they ride along without extra wiring).

Test plan:
- [x] `npx tsc --noEmit` clean
- [x] All Python files compile (`ast.parse`)
- [x] Resolver verified end-to-end with SUTANDO_PRIVATE_DIR set:
      shared_personal_path('notes') → /Users/wangchi/.sutando-memory-sync/notes
      personal_path('pending-questions.md') → .../machine-Chis-MacBook-Air/pending-questions.md
- [ ] Drop a voice-tool note via save_note, verify it lands in private dir
- [ ] Open the note in dashboard, verify /notes/{slug} reads from private dir
- [ ] Restart check-pending-questions cron, verify it reads private PQ file

After merge: kickstart voice-agent (touches voice-agent.ts + inline-tools.ts),
restart web-client/dashboard/agent-api/discord-bridge (Python files), restart
conversation-server. Same pattern as sonichi#548.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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