Vigil Steward / Iris

Locally-hosted personal AI OS for Teagan. The conversational face is Iris.

What it is live

Vigil Steward is Teagan's locally-hosted personal-assistant operating system — a 297-route FastAPI service running on vigil-server that orchestrates morning briefings, portfolio status sync, project triage, calendar/email digestion, and cross-product agent work. Iris is the conversational persona; the product name on the repo and in code is vigil-steward, but the face the user interacts with is "Iris" (named after the Goo Goo Dolls song).

The product is the eventual destination for the operator-mode workflows currently being prototyped in the Workstation Hub repo — once Iris can do morning briefings, status checks, calendar triage, and project triage natively, the manual workflows migrate here.

Hosting

ComponentLocation
Code/home/teagan/vigil-steward on vigil-server
API serviceFastAPI, port 8003, systemd vigil-steward-api.service
Celery workervigil-steward-worker.service
Celery beatvigil-steward-beat.service
DatabasePostgres on vigil-server (shared vigil-postgres instance)
Public surfaceNone — Tailscale-only at http://100.98.48.21:8003
Production env/home/teagan/vigil-steward/.env.production (teagan:teagan 0600), shared across all three units
Hyphens, not underscores. The systemd unit names use hyphens: vigil-steward-{worker,beat,api}.service. The Python package is being renamed from vigil_steward to iris (PR #22 on feat/iris-rename-pr1a — 1,211 occurrences across 329 files). Until that ships, code-level imports use the old name.

Architecture lock — Ollama orchestrates, Claude Code executes

A Phase 2 architectural decision (XDEC-122, locked 2026-05-10) sets the dispatch model: Ollama does upstream reasoning for planning and routing, and Iris invokes Claude Code as a subprocess for execution rather than calling the Anthropic SDK directly. This shape leans into Teagan's Max plan instead of paying per-call API rates on agent fan-out.

flowchart TB USR[Teagan] -->|email / chat / ntfy| API[Vigil Steward API
FastAPI :8003] CRON[Celery beat
scheduled jobs] --> WORKER[Celery worker] API --> WORKER WORKER -->|plans + tools| OL[Ollama
local LLM
39.8 GiB VRAM] OL -->|dispatch| CLAUDE[claude subprocess
Max plan] CLAUDE -->|tool calls| TOOLS[GitHub / Mailcow / CF /
vigil-server / dev-context] WORKER --> PG[(vigil-postgres)] WORKER -->|outbound| SMTP[Mailcow SMTP
iris@sandpointstudios.ltd] SMTP -->|via SES| USR

Daily flows

Morning briefing

A Celery-beat job runs each morning and produces a "morning briefing" digest covering: portfolio rollup (active products, recent commits, open PRs, deploy state), calendar events for the day, overnight email triage, anomaly signals (failed jobs, error spikes, GlitchTip new issues). Output emails from iris@sandpointstudios.ltd to Teagan's primary inbox, with a 24-hour dedupe window so the same item doesn't echo across multiple days.

Owned by XDEC-126 Option C. PR #21 was merged 2026-05-18 (merge commit 7e44b1f); IRIS_SMTP_* env vars are staged in .env.production; the celery units still need a restart to pick up the new code.

Mood signal — YouTube Music

A separate Celery-beat job runs every 30 minutes hitting the unofficial ytmusicapi library (cookie-authenticated) to capture what Teagan is currently listening to. Result lands in the mood_signals Postgres table and gets surfaced to the briefing orchestrator as tone-aware context — so a morning briefing on the day after a heavy listening session in a particular genre can adapt tone accordingly. Low-stakes if Google breaks the unofficial API; the signal degrades, the rest keeps working.

Data

idle_in_transaction guard. A defensive idle_in_transaction_session_timeout=30min is set on vigil-postgres after 61/70 connections leaked from a Vigil Steward cron leaking BEGIN; statements. The underlying VS bug is still owed, but the guard prevents the connection-pool exhaustion mode.

Dependencies

Roadmap

Related