A single-user, Discord-commanded personal operations system on one VM with no inbound ports. Outside data is normalized into one signal hub, scored by an LLM, and turned into reports and pages. A thin reliable Bridge owns the connection, the job runner, and the scheduler; small decoupled tools do the real work and join as data, not code. The autonomous parts can build the system — but never the parts that keep it safe, unsupervised.
Operus Prima Est — the seeing makes the doing.
Ten named patterns are the whole design. Every part of the system is an application of one of them — learn these and the rest is detail. The first tenet is the one to internalize.
When an action might happen twice, it becomes a
tool: a named, tested entry point an agent (or you) invokes by name — a
run.sh <verb> --format json subcommand, promoted to a one-line bridge
ToolSpec. Never a throwaway shell script an agent has to reproduce from memory.
Tools compound: each one is a verb the next agent already knows. Scripts evaporate.
If you find yourself writing a script, you've found a missing tool.
No fourth is permitted without changing the charter. (1) Voice In — a Discord voice memo is the front door. (2) Reports Out — the bot posts to a Discord channel on a schedule, carrying Cloudflare links to the detail. (3) Forms Back — a Cloudflare form (behind Access) POSTs to a Pages Function that signs it and relays to a private Discord channel; the bot runs only HMAC-verified, allowlisted data-capture. Integrated systems are only Discord (the bus), Cloudflare (the surface), and Fastmail/Todoist (read-only sources, never written). Every channel is outbound-only — no inbound port, no VPN.
Core ↔ tools coupling is exactly one file: integrations.py → REGISTRY.
A tool joins by adding one ToolSpec row (data). No dispatch edits, no new modules.
Autonomous output is quarantined until a human applies it — !code → a
worktree branch you merge; the fitness curator → a reversible overlay; LLM HTML → static,
behind Access. The bot can build the system; it can't silently rewrite its own trust boundary.
Tool logic is deterministic. The LLM appears only as a curator/author at named seams —
relevance scoring, page authoring, voice routing, !code — never in the hot path.
A single authorized-user identity check fronts every command. Everything downstream assumes it passed. New entry points must route through the gate or they bypass it.
Every external touch dials out — Discord gateway, Cloudflare deploy, collectors pull. There is no server to attack and no inbound port to defend.
Tools are standalone and talk only through run.sh … --format json — microservices
whose wire protocol is a command line. One shared _lib is the deliberate exception.
A source is snapshot (overwrite-by-source: weather, tasks, email) or event (append + dedupe: notes, news). One collector flag models both; adding a source is config, not code.
The hub stores normalized, feed-like signals. When a consumer needs a rich/computed view (a fitness suggestion, an accountability report) it reads the tool directly — flattening would lose information. That's correct, not debt.
High-consequence commands (!code, !page) are proposed from
lossy inputs like voice, never auto-run — the bridge hands back a ready command for you to send.
The system reports through the same Cloudflare pipeline it documents. This handbook ships
that way — a static page in local-report/pages/, deployed by publish.
Three layers — collectors feed the hub, the hub feeds the surfaces — with the Bridge as the deterministic core that dispatches everything.
| Bridge | The reliable core: gateway, async job runner (!job/!code),
scheduler, file/log access, the registry. Hand-maintained; never edited by the bot. |
| signalstore | The hub — normalized SQLite signal store, dedupe on
sha256, dynamic feeds, LLM relevance scoring. |
| local-report | LLM-authored evolving HTML, plus hand-authored static pages, assembled into one Cloudflare Pages site behind Access. |
| _lib/claude_client | The one shared library — a headless-claude
CLI wrapper. The deliberate exception to tool decoupling. |
One auth gate fronts a dispatch ladder: built-in core commands → registry tools → a sync fall-through. No shell anywhere; whitelist matches the first token only.
| Command | Does | Class |
|---|---|---|
| !ping · !status | liveness; host uptime/disk/mem from /proc | safe |
| !run · !job <name> | a whitelisted preset, inline or as a background job | whitelisted |
| !jobs · !logs [id] | list/inspect jobs; tail a job log or the service journal | observability |
| !cat <path> | read a file, jailed to ~/projects, secret-names blocked | name-jailed |
| !<tool> … | registry tools: jot, todoist, fitness, fastmail, weather, accountability, uptime, ingest, voiceroute, ops, metrics | tools |
| !ops doctor | system health in one call — secrets, empty domains, drift, pending branches, egress risk | observability |
| !intake here | Channel 3 — bind the channel where HMAC-signed Cloudflare-form submissions run (note/metric/check-in) | forms back |
| !page <slug> | LLM-author a page and deploy to a public Cloudflare URL | public egress |
| !code <proj> <task> | autonomous Claude Code in a sparse git worktree, on a branch | RCE by design |
| !reload | hot-reload integrations.py/config.py, AST-validated first | safe |
local-transcribe (STT) →
either a read-only Claude over your data, or — in a !voiceroute here channel —
routed to a registered command and run. !code/!page are
proposed, not run from voice (Confirm-First for Consequence).
Fifteen registered entries. Each is a self-contained local-* project
(own deps, state, run.sh) joined by one ToolSpec. bridge_sync
proves the registry matches every tool's declared bridge.json (0 pending).
| Tool | Mode | Role |
|---|---|---|
| jot | command | notes; ask/reflect over them (LLM) |
| todoist · todoist-sync | command · job | open tasks + completion metrics (by label/project/priority/weekday) |
| accountability | command | milestones, routines, check-ins → the write-back half |
| fitness · fitness-curate | command · job | Strong workout log; LLM catalog curator (reversible overlay) |
| fastmail | command | read-only inbox + calendar (JMAP) |
| weather | command | forecast/history/climatology (Open-Meteo) |
| uptime | scheduled | endpoint uptime/latency monitor |
| ingest · ingest-score | scheduled · job | the signal hub + LLM relevance scoring |
| voiceroute | command | voice instruction → registered command (decide-only) |
| ops | command | doctor (health in one call) · verify (whole-tree tests) · merge (review+ff a !code branch) |
| metrics · metrics-collect | command · job | time-series metric store → a Cloudflare report extra |
Supporting: local-report (Cloudflare pipeline),
local-transcribe (STT), _lib (Claude client). Archived & recoverable in git:
netscan, locascout, rssdigest. 490 tests across 15 suites run green (!ops verify).
Everything that changes is a codified component with a canonical definition in
one registry — operus/registry.json. !ops components lists them and proves
no drift (every local-*, the bridge, and _lib is registered);
!ops observe attaches live change metrics. The registry is the single source of truth.
| Component | a codified unit — the bridge, the hub, each tool, the library, a
surface, the meta-tool. Fields: name · dir · kind · version · interface_version · summary. |
| Kinds | corehubsurfacelibrarymetastoretool |
| component | semver MAJOR.MINOR.PATCH — the unit's behavior. Bump in the registry in the same commit as the change. |
| interface | an integer contract — a tool's run.sh verbs +
--format json shape, or the bridge's ToolSpec/dispatch. Bump only on a
breaking change; additive verbs keep the same interface_version. |
| report | an integer per report, auto-incremented in reports.db
on each rebuild — shown on the report and on the dashboard. |
| job | an instance, not semver — stamped with the producing component's
version; tracked by count + success rate (!jobs --stats). |
Page hierarchy & naming standard. The Cloudflare site has
a flat, kebab-case slug namespace grouped by page family on the hub (/):
doc documentation (/operus, /architecture) ·
report generated reports (/briefing, /accountability, …) ·
observe the live dashboard (/observability) ·
form capture (/capture). /operus is the
central docs root; it links to the live dashboard, which tracks every
component's version and change count and is regenerated on every publish.
Named, not hidden. The trade-offs are correct for a single-operator bot — but a maintainer should know exactly where the power is.
HIGH !code is unsandboxed RCE, by design. Runs
claude -p --dangerously-skip-permissions as the host user. The sparse worktree scopes
what's visible, not what's reachable — it can read/write $HOME, hit the
network, even push. Contained only by One Gate + Propose-Don't-Self-Edit (it can't target the bridge,
its output waits on a branch).
HIGH !page is public egress. Publishes signal data (email, tasks, calendar) to a Cloudflare URL. LLM-authored, not human-reviewed; the "no-network script" rule for interactive pages is a prompt instruction, not an enforced CSP. Sits behind Cloudflare Access — that gate is load-bearing.
MED One Gate is the whole perimeter. A single Discord identity is authentication, authorization, and audit. Compromise of that account is total compromise. There is no second factor, no per-command scoping, no action log beyond job history.
MED Secrets ride into autonomous jobs. !code/!page
jobs inherit the bridge's full environment — Cloudflare + Discord tokens, the filesystem. A misbehaving
autonomous run has the keys.
LOW Residual edges. !cat's secret screen is name-based
(misses a token inside an innocently-named file). SIGNALSTORE_DB is shared by path-coincidence,
not wiring (fragile if a caller ever runs from a different root). calendar/tasks
domains are empty until their tokens are set. LLM seams fail-open (kept, not dropped) on a flaky response.
What you can see today, and the gaps. The system is honest about state, but it has no metrics/tracing layer — observability is conversational, pulled on demand.
!job/!code/!page is a row in
jobs.db with status + a per-job log (.jobs/<id>/output.log);
!jobs, !job status|kill <id>, !logs <id> surface them.
Orphaned jobs are reconciled to failed on startup.!logs tails the systemd journal; !status reports
host uptime/disk/mem; !ping for liveness.build_nudge posts only when something is red (overdue/at-risk).bridge_sync.py --check fails when the registry and any
bridge.json disagree; CF_DRY_RUN=1 assembles a deploy without shipping it.!ops doctor — the single health read: unset secrets, empty signal domains,
registry drift, pending code-branches, and the CF public-egress risk, in one call.!ops verify — the whole-tree test runner (490 green);
!metrics summary turns system numbers into a trend + sparkline that feeds the pages.!jobs --failed / --stats — the job-history view: recent failures +
an aggregate success rate over jobs.db, with the hint to pull a full log.The single biggest lever on how long and how big an autonomous job can usefully run is
the quality of the context it wakes up in. A memory-less !code job in a sparse worktree knows
only what it can read relative to its cwd.
What exists. Every project ships VM_LAYOUT.md — a drop-in "where am I?" note an
agent reads to orient (it's in a sparse worktree; siblings are read-only by absolute path). This is the
seed of the pattern. It tells the agent where it is, not what good looks like.
The charter gap — now filled. Every project ships a CHARTER.md — purpose, invariants
(e.g. "read-only; always valid --format json"), definition of done (tests + docs +
bridge_sync green), non-goals, and the Grammar patterns it embodies — and the
VM_LAYOUT.md notes point at it and at this handbook. A task like "add improvements"
is now well-posed: the agent reads the charter first and knows what good looks like, so it runs much
further before it needs you.
!code job at it so the patterns are shared
vocabulary, not rediscovered each run.!code — have the job run the project's tests + bridge_sync
before reporting, and refuse to claim done on red. Self-verification extends safe autonomy.Each "improvement" below is itself a tool to build, not a script to keep — per the tenet.
!ops + !metrics done!ops doctor — one call: unset secrets, empty signal domains, registry drift,
pending code-branches, and the CF public-egress risk. (Token/domain bootstrap folds in here.)!ops merge <jobid> — the !code review ritual as a tool: branch
tests + bridge_sync, ff-only merge, clean the worktree. First use: it merged
code/afb948.!ops verify — every project's suite as a green/red table — the remembered shell
loop, now a verb.!metrics — the time-series store that turns the above into numbers on the pages.bridge.json → bridge_sync → paste the
ToolSpec → !reload. Gateway-file edits (bot.py/voice.py)
need a restart, not !reload.!merge call.todoist-sync and a daily docs republish so state and this
handbook stay current.AUDIT.md is point-in-time; refresh or supersede rather than trust.Independent tracks, each a focused agent. Ordered by leverage; every track lands a callable tool, never a script (the tenet, applied).
!ops doctor shipped — system health in one call, including the CF public-egress risk.
Every later track checks its own work with it.!ops merge shipped — tested ff-merge of a !code branch. Its first run
resolved the pending code/afb948 branch (24 tests + bridge_sync green → merged).VM_LAYOUT.md notes now point at the
charter and this Grammar — autonomous jobs wake up with purpose and a definition of done.!ops verify shipped (whole-tree runner, 473 green) and the scheduler now runs
todoist-sync (06:45), metrics-collect (06:55), and a 07:30 links-digest
posting the Cloudflare report URLs to Discord. The page builds already republish this handbook daily.!jobs --failed shipped — recent failures + an aggregate success rate over
jobs.db (and !jobs --stats), with a !logs <id> hint.calendar/tasks
domains fill (!ops doctor verifies). Plus: confirm Cloudflare Access actually fronts the site.| Verify everything | bridge: venv/bin/python -m pytest -q;
each tool: python3 -m pytest -q (fastmail/ingest need a venv). |
| Promote a tool | bridge.json → python3 bridge_sync.py →
paste the ToolSpec → !reload. |
| Apply a gateway edit | edits to bot.py/voice.py need a
service restart — !reload only refreshes the registry. |
| Publish this handbook | local-report/run.sh publish
(or CF_DRY_RUN=1 … to assemble only) → https://scout.pages.dev/praxis. |
| Secrets | one source of truth: the untracked external-command-bridge/.env,
mirrored by .env.example, loaded via systemd EnvironmentFile. |