π₯ VM OPERATOR β zee-ops-1 (the always-on cloud brain)
Activated 2026-07-02 ~1 AM ET (Sam pasted the key-grant; Fable provisioned). The box that runs Claude Code with Sam's PC OFF.
Access
- Host: DigitalOcean droplet
zee-ops-1Β· public104.248.227.149Β· Tailscale100.79.23.85(prefer Tailscale). - SSH: key-only (
hookstreet-pc-fableed25519 key from the main PC in~/.ssh/authorized_keys). Hardened 7/2:PasswordAuthentication no,PermitRootLogin prohibit-password, UFW active (allow SSH + tailscale0, default-deny inbound). Config backed up to/etc/ssh/sshd_config.bak.*. - Specs: Ubuntu 24 (kernel 6.8), 1.9 GB RAM, 48 GB disk (7% used). Installed: git, node, npm, python3, claude 2.1.198 (authenticated, oauthAccount), tmux, tailscale. No docker. 4 GB swapfile added 7/2 by VM-OPS (3 parallel Claude sessions β1.3 GB on a 2 GB box = OOM risk; swappiness 10; persisted in fstab β Sam can veto).
State (7/2)
- β
ALL 7 REPOS cloned on the VM (workspace, MIS, HSS site, Calculator, Eden, CM Invoice, lawn co-op, BOS sheets) β push verified, notify secrets set. Both rebuild prompts run here. HABIT:
git pullin the target repo at kickoff (clones don't self-refresh; operator.sh pulls only the workspace). - β
Claude Code installed + AUTHENTICATED + proven: ran headless against the workspace and responded (
claude -pβ VM_OPERATOR_LIVE). - β
Workspace present at
~/hookstreet-workspaceβ 1,055 tracked files (git-archive transfer from the PC; docs/outputs/ops-api/scripts/CLAUDE.md all there). NOT a git clone (no .git) β a working copy. - β οΈ No .git β can't
git pull/pushyet. The ops-apiGITHUB_TOKENis read-scoped (403 on clone). Needs a proper repo-scoped PAT OR a VM deploy key added to GitHub (Sam 2-min) to make it a live syncing clone. - β οΈ No local secrets β
command-inbox/.claude-notify.json(tg + inbox) is gitignored, so it wasn't transferred; the VM can't notify Sam or hit the queue until it's copied over (scp from PC). - β οΈ No memory files β
.claude/projects/.../memory/is local-only; the VM's Claude should pull from the D1 cloud brain (629 facts via ops-api/memory/search) rather than local memory. That's the design.
Remaining to make it a full autonomous operator
- ~~Push/pull~~ β DONE (workspace = live clone w/ stored PAT; all 7 nested repos cloned into place 7/2 ~1:45 PM by the MIS session: MIS, HookStreetServices-Site, Calculator, eden-gardens-os, CM_Invoice_System, lawn-coop-proposal, HookStreet-Business-OS/sheets β push --dry-run verified OK on workspace+MIS+HSS).
- ~~Notify~~ β
DONE (
.claude-notify.jsonpresent;scripts/tg.shexists). - Trigger: how Sam starts a VM session from his phone β SOLVED for interactive (Tailscale SSH enabled 7/2, Sam connected via Termius β
~/operator.sh; tmux windows = parallel Claude sessions, ctrl-b w = list). Still open: the botβVM command channel ("paste a task into the bot β VM session picks it up") β Sam has explicitly asked for this. - Decide: keep at ~$12/mo β YES (locked; live + proven + Sam operating it).
- Non-root
samuser for clean skip-permissions (root prompts per tool). - Nested-repo freshness: the VM's clones don't auto-pull β a session working there should
git pullits repo at open (same discipline as the PC). - ~~clasp deploys~~ β
DONE (7/2 ~3:45 PM, MIS session, Sam-ordered):
.clasprc.jsonon the VM (600) + clasp installed + MIS/v2.clasp.json+appsscript.jsonplaced deliberately (gitignored β clones lack them; without them clasp WALKS UP and binds the WRONG project β near-miss caught live: it resolved a different script). Verified: VM lists the true engine (7 deployments, @143). LAW: neverclasp pushin any folder without confirming its.clasp.jsonexists + the scriptId is right. Other clasp projects (command-inbox, LevSMSβ¦) still need their bindings placed before VM deploys. - Gitignored-but-needed files don't travel by clone β placed so far: notify config, connections keyring, FULL ops-api secrets incl. HOSPITABLE_TOKEN (landed 7/2 ~4 PM β Sam-directed), MIS/v2 clasp bindings, MIS/data broker archives. Still PC-only by choice: wrangler/CF auth (Fable-3 lane), MCP connectors (one-time interactive auth in a VM session if wanted).
- β
GUEST-COMMS SEND RAIL LIVE ON THE VM (proven 7/2 ~4:21 PM by VM-OPS): pool-situation msg β BOTH in-house guests (Jessika/9312 + Lakia/9332) via
POST public.api.hospitable.com/v2/reservations/<id>/messages(token fromops-api/.secrets.json), HTTP 202 then verified in-thread via GET (Rule 9). Gotchas: ops-api/hospitable/reservationsshows only stays STARTING β₯ today β mid-stay guests are INVISIBLE there; resolve current guests via the Hospitable API date-window query (or mildred.html KNOWN{} map). Guest voice rules: no em-dashes, warm/plain, Sam's voice.
β οΈ ONE SHARED WORKING TREE β all VM sessions edit the SAME checkout (verified 7/2)
The tmux windows are NOT separate clones: window 0/1/2 all work in ~/hookstreet-workspace (and the same nested repos). PLUS ~/brain-pulse.sh (cron :17 every 3h + @reboot) runs git pull --rebase in it. Consequences:
- HEAD can move UNDER you mid-session (another window commits/pulls). Re-check git log before commit; never assume your last read of a shared doc is current.
- Uncommitted edits to the same file from two windows = a real clobber, worse than the PC pattern. Commit small and often; stay in your court's files.
- Heartbeat proof (Rule 9): ~/brain-pulse.log β 7/2 runs show 633 facts β D1 clean.
βοΈ TELEGRAM SIGNING β 3 parallel VM sessions (locked by Sam 7/2, relayed via MIS session)
Multiple Claude sessions run in parallel on this VM (tmux windows). One Telegram chat β Sam must see WHO is texting. scripts/tg.sh takes a TG_SIGN env var:
TG_SIGN="π₯ VM-OPS" ./scripts/tg.sh "subject" "message"
- Window names locked by Sam 7/2 evening:
0-OPS(signπ₯ VM-OPS) Β·1-PORTALΒ·2-HSS(rethink, nothing public) Β·3-STRΒ·4-REV(when spawned). Sign with your window's name. - Unsigned default = generic
π₯ VMβ fine for one-off scripts/cron, NOT for a live session.
π΅ TELEGRAM NOISE RULE (Sam, 7/2 evening β he is drowning in texts; ALL VM sessions obey)
Text Sam ONLY for: (a) something needing HIS action Β· (b) a milestone he explicitly asked about Β· (c) urgent guest/money issues. Everything else (status, progress, FYI, "shipped X") goes to docs/steering/relay-log.md or CONTEXT.md instead. Batch what you do send β one text with 3 items beats 3 texts.
π± HOW SAM OPERATES IT FROM HIS PHONE (added 7/2)
The VM fixes the "remote-control disconnected β lost it" problem: the session lives in tmux ON the VM, so a dropped phone connection doesn't kill it β you reattach.
Two phone paths, neither drops:
1. The Telegram bot (use NOW, zero setup) β the bot reads the D1 cloud brain (629 facts). Always on, in your pocket, never disconnects. For ask/tell/brief/quick-ops = your everyday brain-on-phone. Replaces remote-control for most things.
2. Full Claude Code session on the VM (the upgrade):
- Phone: install Blink Shell (or Termius) + the Tailscale app (same tailnet as the VM 100.79.23.85).
- Easiest auth = Tailscale SSH (enable on VM: tailscale up --ssh) β phone connects by Tailscale identity, NO key juggling. (Alt: add a phone-generated public key to ~/.ssh/authorized_keys.)
- Connect β run ~/operator.sh β you're in the persistent "brain" tmux session running Claude Code.
- Detach = Ctrl-b then d (or just close the app) β it keeps running. Reattach anytime = ~/operator.sh again. That's the "never lose it" property.
- Note: interactive claude as root prompts on tool use (no --dangerously-skip-permissions for root). Refinement: a non-root sam user lets skip-permissions run clean β do in the next VM pass.