marvin 93fa05bdcc Add custom 'marvin' persona as interactive default
Server-only deployment has no console admin API, so the persona is
upserted directly into the SQLite DB via scripts/persona.py with the
prompt text versioned in the repo (VM persistence rules, Gitea/GitHub
remotes, rootless podman). Built-in personas are seeded by migration
063; this sets marvin as the interactive default.
2026-09-28 20:06:02 +02:00

marvin-turnstone

Local turnstone deployment as a container-VM: a podman image is turned into a bootable VM with boco (custom init + ext4 qcow2 + QEMU direct kernel boot). Replaces the older opencode-in-a-container setup (~/marvin).

host podman build ──> boco build qcow2 ──> boco boot
                                              │  QEMU/KVM (virtio, user-net)
                        ┌─────────────────────┴───────────────────┐
                        │ VM (Arch)                               │
                        │  /boco/init → start-turnstone.sh (root) │
                        │    ├─ sshd            (ops channel)     │
                        │    └─ setpriv marvin                    │
                        │        └─ turnstone-server :8080  PID 1 │
                        └─────────────────────────────────────────┘
   host :8080 ──hostfwd──> web UI        host :2222 ──> guest sshd :22
   guest ──HTTPS (double slirp egress)──> api.tensorx.ai (LLM)

Prerequisites

Host (Arch):

  • podman, qemu-base, virtiofsd, croc (only if fetching the kernel)
  • boco CLI on PATH and boco-init at ~/.local/share/boco/boco-init (build from the boco repo)
  • Kernel at ~/.local/share/boco/registry/vmlinuz (x86 bzImage with CONFIG_VIRTIO_FS=y; transfer via croc)

VM image:

  • Arch base with python + turnstone (venv at /opt/turnstone), git, helix, podman (rootless, subuid range), openssh, build toolchain
  • marvin user; sshd + turnstone-server started by start-turnstone.sh (server runs as marvin, PID 1)

Build

scripts/build.sh

Creates configs/ssh/id_ed25519 (ops keypair, once), builds the podman image, then packs it as boco qcow2 into the boco registry.

Run

scripts/run.sh
  • Web UI: http://localhost:8080 (login marvin / marvin-turnstone unless ADMIN_PASSWORD was overridden — change it)
  • Ops SSH: ssh -i configs/ssh/id_ed25519 -p 2222 marvin@localhost
  • QEMU runs foreground; quit with ctrl-a x

Access from another machine (tailcat)

localhost above is the machine running QEMU. To reach the VM from elsewhere, run scripts/expose.sh next to it — a tailcat tunnel (WireGuard over Tailscale's DERP relays, outbound-only, no account):

host $ scripts/expose.sh
# 🐈 Server listening with saved key "default": tcpGFwWCCH...

your $ tailcat forward tcpGFwWCCH... 8080:8080 2222:2222

Then browse http://localhost:8080 / ssh -p 2222 marvin@localhost from your machine. The address is stable across VM restarts (saved key) and is a bearer secret — share it only privately.

LLM backend

[models.tensorx] in configs/turnstone/config.toml points at the TensorX OpenAI-compatible gateway (https://api.tensorx.ai/v1, same key this machine's opencode uses — see ~/.local/share/opencode/auth.json). Guest egress to HTTPS endpoints works through both slirp layers; no relay needed.

Self-hosted provider later: add a [models.*] entry with any OpenAI-compatible endpoint reachable from the guest (see config.toml.example).

Shares (virtiofs, survives reboots)

Host path Guest path Purpose
configs/turnstone/ /home/marvin/.config/turnstone turnstone config.toml
data/ /var/lib/turnstone SQLite database
work/ /home/marvin/work agent workspace

Guest-written files appear on the host owned by subuid-range UIDs (virtiofsd runs under podman unshare) — cosmetic, and stable across boots.

Configuration

Edit configs/turnstone/config.toml (gitignored; see config.toml.example):

  • [models.tensorx] — TensorX gateway; api_key must be set (currently mirrored from opencode's auth.json).
  • [auth] jwt_secret — required, generate once.
  • [database] path — SQLite on the data/ share.
  • [tools] — workspace_dir surfaced to the model (/home/marvin/work, mounted from ./work).

Personas

Built-ins (Engineer, Writer, Researcher, Scribe, Manager, Executive) are seeded by turnstone's migration. This deployment adds a custom default persona marvin (VM-specific: persistence rules, Gitea remotes, podman) via scripts/persona.py — the prompt text is versioned there and re-applied after a DB reset:

python3 scripts/persona.py            # --db data/turnstone.db default

(Without a console deployment, persona CRUD is a direct SQLite write; with turnstone-console running, use its admin API/Personas tab.)

Notes / trade-offs

  • boco boots with -snapshot: only the virtiofs shares persist. Agent-installed packages, git clones outside work/, and podman image storage inside the VM are discarded on shutdown.
  • boco hardcodes -smp 8; memory via VM_MEMORY=...M scripts/run.sh.
  • The old setup's Traefik is dropped — hostfwd replaces it.
S
Description
No description provided
Readme
115 KiB
0 Stars 0 Watchers 0 Forks
Languages
Python 49.6%
Shell 33.8%
Dockerfile 16.6%