Frame architecture as ports around a decision core

Add the hexagonal architecture section (skeleton/nervous-system/cortex
layering), name the tasks/knowledge/enforce ports as deferred, and extend
the roadmap with M5-M7. Clarifies that the state machine owns transitions
and the LLM advises within the rails.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-05-31 12:20:38 -04:00
parent 06c1b719b3
commit fe2fb4e250
@@ -93,6 +93,54 @@ 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 | `enforce.Guard` | Make drift cost something | window-minimize now (legacy `minimize_other`); nftables/guardian later | deferred (M7) |
| 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 |
@@ -104,6 +152,9 @@ directly — just not re-litigated on every startup.
| `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
@@ -136,23 +187,36 @@ type Assistant interface {
in v1.
The three live roles ship first: planning **coach**, live **drift
interceptor**, ambient **nudge**. The reviewer is deferred.
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.
cycle. M0M4 build the core plus the first two ports (activity, advisor) and the
UI; M5M7 add the remaining ports, each a small interface + adapter following
the pattern M1 establishes.
- **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.** xdotool active-window tracking, evidence health,
per-window time stats, append-only hash-chained audit log, live SSE updates.
- **M2 — AI planning coach.** `ai` package + CLI backend; "sharpen this
- **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 — Enforcement port.** `enforce.Guard`: make drift cost something, starting
with window-minimize (porting legacy `minimize_other`) and later the
privileged guardian / nftables path.
The first sub-project to brainstorm and spec in detail is **M0**.
@@ -164,11 +228,19 @@ The first sub-project to brainstorm and spec in detail is **M0**.
## Out of Scope (v1)
- Privileged enforcement: guardian process, root-owned Unix socket IPC,
nftables/DNS domain blocking, delayed admin, break-glass.
"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 M5M7, but not built in v1. The `enforce.Guard` port starts with
window-minimize; its **privileged** adapters (guardian process, root-owned
Unix socket IPC, nftables/DNS domain blocking, delayed admin, break-glass)
remain out of scope until that milestone and keep the original Stage 2 threat
boundary.
- AI reviewer / session-end reflection.
- Wayland compositor adapters beyond the existing degraded reporting.
- Planner/project/task model and outcome writeback.
- 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