Files
antidrift/docs/superpowers/specs/2026-05-31-go-focus-os-design.md
T
felixm cd5d69dec5 Reframe roadmap around the gate-first focus loop
Make the OS-level "gate → plan → work → reflect → gate" loop the explicit
destination. Promote the deferred AI reviewer into the loop as M7 (Reflection),
and reframe enforcement as M8 (Enforcement & gate) building toward the Guard
checking for a declared intention before the machine is usable — kept as the
last, heaviest milestone.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 13:22:03 -04:00

267 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# AntiDrift Go Reimagining — Design
Date: 2026-05-31
## Purpose
AntiDrift is being reimagined from its current Rust implementation (~7,500
lines) into Go, to become the user's focus operating system on the computer:
fast, good-looking, and deeply integrated with AI from the start.
This is **not a faithful 1:1 port**. The existing domain model and the
`commitment-os-design.md` spec remain the north star. The Rust code is a
reference, not a thing to replicate line-for-line. The move to Go is an
opportunity to shed incidental complexity — most notably the token-heavy
event-log replay/revalidation design — while preserving what is genuinely
valuable.
### Why Go, why now
- **Token efficiency for AI-assisted development.** The current pain is not
runtime cost; it is that any AI edit to the core must load
`session.rs` (3,475 lines, ~80% replay-validation logic and its tests).
Go's smaller idioms plus a redesigned, smaller core directly reduce the
context any change requires.
- **Predictable LLM codegen.** Go's rigid syntax produces functional code on
the first pass with fewer correction loops.
- **Runtime fit.** Concurrency and local HTTP serving are first-class, which
suits a long-running focus daemon that also talks to AI.
## What Carries Over vs. What Changes
**Preserved (high value, ports cleanly):**
- The domain model: `Commitment`, `PolicySnapshot`, `RuntimeState`,
`CommitmentState`, `AllowedContext`, `EnforcementLevel`.
- The pure runtime/commitment state machines (`state_machine.rs`) — a near 1:1
port.
- The `commitment-os-design.md` spec as the conceptual foundation, including
"no unchosen transitions" and the staged threat model.
- Hash-chained tamper evidence — but relocated to the audit log only.
**Reimagined:**
- **Persistence.** Replace replay-everything-and-revalidate-on-startup with an
in-memory state-of-truth, a persisted **snapshot**, and an append-only audit
log. This removes roughly 3,000 lines (the bulk of `session.rs`).
- **UI.** Replace the ratatui TUI with a **local web app** (Gin backend +
browser). This is the surface that must "look good."
- **AI.** AI is a first-class participant from the start, not a later add-on.
**Deferred for v1:**
- The AI **reviewer** role (session-end reflection). The three live roles ship
first; the reviewer returns as **M7 — Reflection**, which closes the loop.
- Privileged enforcement (guardian, IPC, nftables, delayed admin) — same Stage 2
boundary as the original spec. This is the path toward the **entry gate**
(see "Destination" in the Roadmap) and is deliberately the last milestone.
## Process Model
A single Go binary, `antidriftd`, runs as a **local daemon** and owns all
state. The **browser** is its face.
```
┌─────────────────────────────────────────┐
│ antidriftd (one Go process) │
│ │
│ web (Gin) ──HTTP + SSE──▶ browser UI │
│ │ │
│ session ── statemachine ── domain │
│ │ │
│ store (snapshot + audit log) │
│ evidence (xdotool/X11) │
│ ai (CLI backend, async workers) │
└─────────────────────────────────────────┘
```
- The daemon holds live state **in memory** as the single source of truth.
- It persists a **snapshot** on every state change (crash/restart recovery).
- It appends every significant event to an **append-only audit log** (the
tamper-evident, hash-chained trail — for audit and later review, not for
state reconstruction).
- The browser is stateless: it renders what the daemon pushes over Server-Sent
Events (SSE) and POSTs user actions back. No business logic in the browser.
### Why snapshot instead of replay
The original Rust design reconstructs all state by replaying the entire event
log on startup and re-validating every transition, with a dedicated test per
illegal sequence. That is correct and tamper-aware, but it is the single
largest source of code and token weight. A snapshot of current state plus an
append-only audit trail gives the same recoverability and keeps tamper evidence
on the log, at a fraction of the code. State-machine *correctness* is still
enforced — by the pure transition functions at the point of transition, tested
directly — just not re-litigated on every startup.
## Architecture: Ports Around a Decision Core
AntiDrift is a **focus brain**: a decision core surrounded by pluggable
interfaces (ports) to the outside world. This is a ports-and-adapters
(hexagonal) architecture, and it is the organizing principle the whole system
grows along. New capability is almost always "a new port + adapter," not a
change to the core.
The core is layered, and the layering is load-bearing:
- **Skeleton — deterministic, no I/O** (`domain` + `statemachine`). The rails.
Owns what moves are *legal*. The original spec's safety property, "no
unchosen transitions," lives here: the system can only ever be in a legal
state, reached by a legal move.
- **Nervous system — the orchestrator** (`session.Controller`). The single hub.
Holds the in-memory state-of-truth, routes signals between ports and the
skeleton, persists snapshots, appends to the audit log, and broadcasts.
Everything connects through here.
- **Cortex — the advisor** (the LLM, via the `ai` port). Powerful *judgment* at
the decision points the state machine exposes — sharpen this commitment, is
this window drift, nudge me. It informs and proposes; **it can never force an
illegal transition.** The LLM is the most powerful adapter, not the kernel.
"The brain" is all three together. Critically, the state machine — not the LLM
— owns transitions; the LLM acts only within the rails the skeleton enforces.
### Ports
Each port is a small Go interface with one real adapter (and a fake for tests).
| Port | Interface | "Answers" | Adapter(s) | Milestone |
| ---- | --------- | --------- | ---------- | --------- |
| Activity | `evidence.Source` | What am I doing right now? | X11 / xgbutil (was xdotool) | M1 |
| Advisor | `ai.Assistant` (`Coach`/`JudgeDrift`/`Nudge`) | What's the smart call here? | `claude`/`codex` CLI | M2M3 |
| Tasks | `tasks.Provider` | What *should* I be doing? | Amazing Marvin (existing `ampy` + marvin MCP) | deferred (M5) |
| Knowledge | `knowledge.Source` | Who am I; what are my priorities? | PKM / files | deferred (M6) |
| Enforcement / Gate | `enforce.Guard` | Make drift cost something — ultimately, gate the machine on a declared intention | window-minimize (legacy `minimize_other`) → nftables/guardian → entry gate | deferred (M8) |
| UI | `web` | Show me; take my input | Gin + browser over SSE | M0, ongoing |
Persistence (`store`) is infrastructure shared by the orchestrator, not a port.
The `tasks`, `knowledge`, and `enforce` ports are **named now but built later**
— defining them keeps the architecture coherent without expanding near-term
scope. We resist designing their interfaces in detail until the milestone that
builds them, to avoid speculative abstraction (YAGNI). M1 ships the first real
port end-to-end (`evidence.Source` + X11 adapter + fake), establishing the
pattern every later port copies.
## Package Layout
| Package | Ports from | Size | Purpose |
| -------------- | ------------------------ | ------ | ------- |
| `domain` | `domain.rs` | small | Commitment, PolicySnapshot, runtime/commitment states, AllowedContext, EnforcementLevel, validation |
| `statemachine` | `state_machine.rs` | small | Pure transition functions (1:1 port) |
| `session` | reimagined `session.rs` | medium | In-memory controller; drives transitions, snapshots, audit appends; no replay validation |
| `store` | `event_log.rs` | small | Snapshot file (current state) + append-only hash-chained audit JSONL |
| `evidence` | `window/*` + `context.rs`| small | Active-window snapshot (xdotool/X11), evidence health, allowed-context matching |
| `ai` | new | small | `Coach` / `JudgeDrift` / `Nudge` behind one interface; CLI backend |
| `web` | new (replaces TUI) | medium | Gin routes, SSE stream, static browser UI |
| `tasks` | new (deferred, M5) | small | `Provider` port over current to-do items; Amazing Marvin adapter |
| `knowledge` | new (deferred, M6) | small | `Source` port over personal priorities / about-me context |
| `enforce` | `window/*` (minimize) | small | `Guard` port; make drift cost something (window-minimize → nftables/guardian) |
Design constraint: every package stays small and single-purpose so an AI edit
loads one focused file, not a monolith. This is the concrete mechanism for the
token-efficiency goal.
## AI Integration
AI is reached through one narrow interface with a single CLI backend to start:
```go
type Assistant interface {
// Planning: turn a vague intent into a concrete commitment.
Coach(ctx context.Context, intent string) (domain.Commitment, error)
// Live: is the current window on-task for this commitment?
JudgeDrift(ctx context.Context, c domain.Commitment, w evidence.WindowSnapshot) (Verdict, error)
// Ambient: periodic check-in based on recent activity.
Nudge(ctx context.Context, c domain.Commitment, recent []evidence.WindowSnapshot) (string, error)
}
```
- **Backend (v1):** shell out to `claude`/`codex` with a strict prompt that
demands JSON output. Reuses existing CLI auth; no API key plumbing.
- **Latency containment** (the CLI is slow, ~seconds, and AI is in the live hot
path): all AI calls run in **background goroutines**; the UI never blocks.
Drift judgments are **debounced** (no faster than ~10s) and **cached per
(commitment, window-class)** so the same window is not re-judged. The UI
shows a pending state and updates via SSE when a verdict lands.
- **Swap path:** the interface boundary lets an Anthropic API backend (faster,
structured, prompt-cached) drop in later without touching callers. Not built
in v1.
The three live roles ship first: planning **coach**, live **drift
interceptor**, ambient **nudge**. The reviewer is deferred. The advisor sits at
the **cortex** layer of the decision core (see "Architecture: Ports Around a
Decision Core"): it proposes and judges at the decision points the state
machine exposes, but never owns a transition.
## Roadmap
Each milestone is independently shippable and gets its own spec → plan → build
cycle. M0M4 build the core plus the first two ports (activity, advisor) and the
UI; M5M8 add the remaining ports and close the loop, each a small interface +
adapter following the pattern M1 establishes.
**Destination — the gate-first loop.** The end state is an OS-level focus loop:
the **Guard** checks for a declared intention *before* the machine is usable,
the user commits, works under monitoring, and every cycle ends in
**reflection** before returning to the gate (gate → plan → work → reflect →
gate). The earlier milestones build "track and advise"; the later ones turn
that into "you don't drift in the first place." We get there incrementally — the
Guard's privileged entry-gate behavior is deliberately the last and heaviest
step (M8), and everything before it is valuable standalone. The runtime state
machine already mirrors this loop: `locked` is the gate, `planning`/`active` are
declare-and-work, `review` is reflection.
- **M0 — Walking skeleton.** Daemon + Gin + minimal browser UI; port `domain` +
`statemachine`; snapshot persistence; manual commitment → timebox → end.
Proves the full stack end-to-end. No AI, no window tracking.
- **M1 — Evidence & audit.** X11 (xgbutil) active-window tracking via the
`evidence.Source` port, evidence health, per-window time stats, append-only
hash-chained audit log, live SSE updates. Establishes the port pattern.
- **M2 — AI planning coach.** `ai` port + CLI backend; "sharpen this
commitment" in the Planning view.
- **M3 — Drift interceptor + ambient nudge.** Allowed-context matching + live
AI drift judgment (debounced/cached) + violation friction UI.
- **M4 — Look good.** A real design pass on the web UI.
- **M5 — Tasks port.** `tasks.Provider` over current to-do items; Amazing Marvin
adapter. Pull the day's commitments from real tasks.
- **M6 — Knowledge port.** `knowledge.Source` over personal priorities and
about-me context, feeding the advisor richer grounding.
- **M7 — Reflection.** The deferred AI **reviewer** role, promoted into the main
loop: a session-end reflection that reads the audit trail and per-window stats
(built in M1) to summarize what happened and feed the next planning cycle.
Closes the "Focus OS Reflection" step and makes the loop self-reinforcing.
- **M8 — Enforcement & gate.** `enforce.Guard`: make drift cost something,
starting with window-minimize (porting legacy `minimize_other`) and the
nftables/DNS path, building toward the **entry gate** — the Guard checking for
a declared intention before the machine is usable. The privileged guardian
process, root-owned IPC, and break-glass keep the original Stage 2 threat
boundary and are the final, heaviest step.
The first sub-project to brainstorm and spec in detail is **M0**.
## Repo Strategy
- New Go module at the repository root.
- Move the existing Rust into a `legacy/` directory (or a `rust` branch) so it
remains available as reference while the Go code becomes the front door.
## Out of Scope (v1)
"v1" here means the first shippable arc, **M0M4** (core + activity/advisor
ports + UI). The items below are deferred past it; some are now named ports with
their own later milestones (see Roadmap), others remain fully out of scope.
- **Tasks, knowledge, and enforcement ports** — named in the architecture and
slotted as M5M8, but not built in v1. The `enforce.Guard` port starts with
window-minimize and builds toward the entry gate; its **privileged** adapters
(guardian process, root-owned Unix socket IPC, nftables/DNS domain blocking,
delayed admin, break-glass) remain out of scope until M8 and keep the original
Stage 2 threat boundary.
- AI reviewer / session-end reflection — now scheduled as **M7 — Reflection**.
- Wayland compositor adapters beyond the existing degraded reporting.
- Planner/project model and outcome writeback (beyond the M5 tasks port).
- Presence sensing.
These remain governed by `commitment-os-design.md` and may return as later
milestones.