Skip to content

Mobile Agent Ops — Moshi + NAS

Authority: Operator Playbook (Engineering Standard)
Host: blueflynas.tailcf98b3.ts.net (Synology DS224+, Tailscale)
Mobile surface: Moshi (upstream — do not fork)

Roles

Surface Role
Moshi (iPhone/iPad) Mobile operator: SSH/Mosh terminal, inbox, Live Activity, approvals
NAS (agent-ops container) Persistent babysitting host: tmux agent-ops, agent CLIs, moshi-hook serve
Mac M4 Validate only: DDEV, local IDE, MR prep — not primary babysitting host
code-server Browser lane: https://code.blueflyagents.com / NAS :8080
Gas City (Oracle) Execution SoR — read-only on phone via gateway HTTP, never raw gt/bd

Connect from phone (Moshi)

  1. Install Moshi; join Tailscale.
  2. Saved host: blueflynas.tailcf98b3.ts.net, port 2222, user agent, Mosh enabled.
  3. Pair hooks: Moshi app → Settings → Hooks → copy token; on NAS container:
    moshi-hook pair --token <TOKEN>
    moshi-hook install
    moshi-hook serve
    
  4. Attach tmux:
    mosh --ssh="ssh -p 2222" [email protected] -- "tmux attach -t agent-ops"
    
  5. Host status dot in Moshi must be green (moshi-hook serve on gateway 127.0.0.1:24543).

blu-cli (Mac / NAS)

blu-remote connect              # mosh + Moshi instructions
blu-remote moshi-doctor         # upstream moshi-hook status
blu-remote ops                  # agent-ops: shell | A2A | MCP | gascity-status
blu-remote gascity-status       # read-only gateway JSON

Defaults (override with env):

  • BLU_REMOTE_MOSH_HOST=blueflynas.tailcf98b3.ts.net
  • BLU_REMOTE_MOSH_PORT=2222
  • BLU_REMOTE_MOSH_USER=agent
  • BLU_REMOTE_TMUX_SESSION=agent-ops
  • GASTOWN_GATEWAY_BASE_URL=https://gascity.blueflyagents.com

Hook stack (layering)

Layer Config Purpose
User-global moshi-hook in ~/.claude, ~/.cursor, ~/.codex Moshi inbox, diff, Live Activity
Project .cursor/hooks.json, .codex/hooks.json gt-policy, bd codex-hook

Do not remove Bluefly project hooks when installing Moshi; Moshi docs preserve user-owned entries.

Preflight (before structural work)

git status
qmd status
blu-remote moshi-doctor

See: BluCity-Docs/lanes/gascity-blu-realignment/gascity/conflicts/gt-bd-qmd-git-preflight.md

NAS babysitting container

Runtime path on NAS: ~/deploy/agent-ops/ (Docker Compose).

export PATH="/var/packages/ContainerManager/target/usr/bin:$PATH"
cd ~/deploy/agent-ops && docker compose up -d

Volumes: /volume1/AgentPlatform → /workspace

What phone must NOT do

  • gt convoy activate, bd create, or other Gas City mutators
  • Treat tmux sessions as work authority (Oracle ~/gt + beads is SoR)

Termius

Deprecated fallback. Use Moshi saved hosts. Legacy: blu-remote termius (deprecated).

Troubleshooting

Symptom Fix
Moshi dot hollow/grey Install moshi-hook on babysitting host
Moshi dot grey not running moshi-hook serve (or restart container entrypoint)
Moshi dot amber brew upgrade moshi-hook / re-run NAS install script
Unpaired moshi-hook pair --token … from Moshi app
SSH denied Use bluefly@blueflynas for DSM; agent@:2222 for babysitting container
No iPhone push Pairing required; custom watcher removed — use Moshi only

Skill (upstream)

npx skills add rjyo/moshi-skill -g -a claude-code -a cursor -y

Claude Code hook wiring (verified 2026-07-30)

~/.claude/settings.json (user-global, per the layering table above) wires moshi-hook claude-hook into six Claude Code lifecycle events:

Event Async Purpose
PermissionRequest sync Routes the pending tool-call approval through Moshi (phone tap) before Claude Code proceeds
PreToolUse (matcher: AskUserQuestion, ExitPlanMode) async Notify-only
PostToolUse (matcher: AskUserQuestion, ExitPlanMode) async Notify-only
SessionStart / SessionEnd / Stop async Lifecycle notifications (Live Activity, inbox)

Only PermissionRequest is synchronous — it blocks the tool call until Moshi returns a decision. Everything else is fire-and-forget notification.

Execution flow (traced from moshi-hook --help + settings.json; local leg VERIFIED, cloud/phone leg INFERRED from the app's documented feature set — inbox/Live Activity/approvals)

Claude Code tool call
  → PermissionRequest hook fires (sync)
  → moshi-hook claude-hook (local binary, reads the request over stdin/args)
  → local Unix socket → moshi-hook serve daemon
  → (if paired) persistent WebSocket → Moshi backend (getmoshi.app)
  → push notification / Live Activity → phone
  → operator taps approve/deny
  → decision flows back over the same WebSocket → socket → claude-hook exit code
  → Claude Code allow/deny → tool executes or is blocked

moshi-hook serve additionally exposes a local HTTP gateway (--gateway-listen) — this is the 127.0.0.1:24543 status endpoint the "Host status dot" in the Moshi app polls (see Connect-from-phone step 5).

Known failure mode: unattended sessions have no approver

PermissionRequest is synchronous and requires a human to tap approve/deny on the paired phone. In a background/unattended session (no operator present to receive the push), every gated Bash call — including read-only ones — denies or times out with no retry path, since Claude Code correctly avoids re-issuing an identical denied call. Observed directly in a background session on 2026-07-30: git commit, and later plain ls/find probes of the same host, were denied back-to-back with no operator interaction. This is a structural consequence of Moshi's design center (a human with a phone), not a Moshi defect — background/CI-style agent sessions are a use case the pairing model doesn't cover today.

Engineering integration assessment (2026-07-30)

Moshi is upstream, closed-source, do-not-fork (per the Authority line at the top of this doc) — a commercial mobile app, not a Bluefly-owned service. That bounds every recommendation below: Bluefly can integrate at the edges Moshi already exposes (CLI hooks, local socket/gateway, the project-hook layer sitting alongside it), not by modifying Moshi's own approval-routing or notification backend.

CURRENT - Local daemon + CLI (moshi-hook), Unix socket, optional local HTTP gateway, WebSocket bridge to a hosted backend. - Approval delivery is phone-only (push + Live Activity) via the paired Moshi app. - Bluefly's own hook layer (gt-policy, bd codex-hook via .cursor/hooks.json/.codex/hooks.json) already sits alongside Moshi's user-global hooks without conflict (see Hook stack table) — this is the existing, correct seam for Bluefly-side additions. - No Bead/receipt integration exists today. No policy-based auto-approve exists today. Routing is phone-only — no Telegram/Slack/Synology Chat/BLU/OpenClaw destination exists today.

RECOMMENDED (buildable by Bluefly, at the existing seam, without touching Moshi) - Add a project-level PostToolUse/Stop hook (same layer as gt-policy) that writes a five-field receipt or Bead entry for every tool call Moshi's PermissionRequest approved/denied — turns the approval event into a governed audit record without needing anything from Moshi itself. - For unattended/background sessions specifically: don't route around PermissionRequest — instead, have the launching side (whatever starts a background job) pre-flight-check moshi-hook status/pairing liveness and refuse to start unattended work that will need approvals no one can grant, surfacing that as a scheduling decision rather than a silent per-call deny.

BLOCKERS - Cannot add alternate approval destinations (Telegram, Synology Chat, Slack, BLU, OpenClaw) inside Moshi's routing — that requires a Moshi product feature, not something this repo can build. Would need to be a feature request to the vendor, not an integration task here. - Cannot make approvals policy-driven (auto-approve low-risk, escalate production) inside Moshi — same reason. Policy-based gating already exists at the Bluefly layer (gt-policy) for mutating tools in Gas City workspaces; it is independent of and does not currently coordinate with Moshi's approve/deny. - Cannot verify or change the WebSocket approval protocol — closed, no source access. - "Single execution-control plane" spanning Oracle/NAS/BLU/OpenClaw/Gas City/Gas City/GitLab/CI/Beads/Cedar/OSSA cannot include Moshi's own decision engine as a component under Bluefly control — only the client-side hook/CLI surface is ours to extend.

OPPORTUNITIES - moshi-hook exposes --base-url globally (override Moshi API base URL) — unconfirmed whether Moshi offers a self-hosted/enterprise backend mode; worth a direct question to the vendor rather than assuming either way. - The existing project-hook layer (gt-policy) is already the right integration point for Bead receipts and policy — extending that layer (not Moshi) is how "policy-driven approval" and "Bead receipts" actually get built.