# 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 | M2–M3 | | 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. M0–M4 build the core plus the first two ports (activity, advisor) and the UI; M5–M8 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, **M0–M4** (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 M5–M8, 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.