From f9e580ff7ebbb818039ad7b5fce4344757461c64 Mon Sep 17 00:00:00 2001 From: Felix Martin Date: Thu, 4 Jun 2026 12:57:14 -0400 Subject: [PATCH] docs: name the cockpit Keel and add its architecture Move the higher-goals cockpit architecture into this repo: AntiDrift is Keel's seed, with its focus harness becoming one mode of a general collect -> brain -> act loop. Records the locked decisions: the product is the harness around a swappable brain (claude/codex/Hermes); storage is ActivityWatch; two surfaces (web UI + status bar); gated -> autonomous effectors. Code/repo rename to keel deferred until the controller-refactor decision. Co-Authored-By: Claude Opus 4.8 --- README.md | 9 +- docs/keel-architecture.md | 282 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 290 insertions(+), 1 deletion(-) create mode 100644 docs/keel-architecture.md diff --git a/README.md b/README.md index bfa2013..13fdf15 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,14 @@ A personal focus operating system: treat each work session as an explicit commitment (next action, success condition, timebox), and make drift visible. -See `docs/superpowers/specs/` for the design. +> **This repo is the seed of [Keel](docs/keel-architecture.md)** — a human-harness +> that helps Felix hold course toward his higher goals. AntiDrift's focus harness +> becomes Keel's first *mode*; the broader architecture (collectors, AW-as-memory, +> swappable brain, web UI, gated effectors) is in `docs/keel-architecture.md`. The +> repo/code rename is deferred until the controller-refactor decision. + +See `docs/superpowers/specs/` for the focus-OS design; `docs/keel-architecture.md` +for where it's headed. ## Run diff --git a/docs/keel-architecture.md b/docs/keel-architecture.md new file mode 100644 index 0000000..a2030d6 --- /dev/null +++ b/docs/keel-architecture.md @@ -0,0 +1,282 @@ +--- +title: Keel — Architecture +name: keel +category: architecture +created: 2026-06-04 +updated: 2026-06-04 +tags: + - keel + - human-harness + - architecture + - antidrift + - activitywatch +status: authoritative +--- + +# Keel — Architecture + +> **Name & home.** *Keel* is the system: a human-harness that helps Felix hold +> course toward his higher goals. The name answers AntiDrift's metaphor — *drift* +> is what a vessel does with nothing to hold it; the *keel* resists drift and +> keeps you on an even keel (steady mind). **This repo (`antidrift`) is Keel's +> seed**: AntiDrift's focus harness becomes Keel's first *mode*. The code/repo +> rename is **deferred** until the controller-refactor decision (§8); for now +> only this design doc lives here as the authoritative home. Moved from +> `~/dev/higher/` on 2026-06-04 — that directory was the scaffold where the +> decision to extend AntiDrift was made, and now retires. + +--- + +## 1. What we're building (one sentence) + +**A harness** — a framework that pulls Felix's real state out of his existing +tools, hands it to a swappable brain, and turns the reply into actions and two +surfaces. The brain, the storage, and the data sources are all **reused**; the +harness is the only thing we build. The target is one system (Keel — the single +screen), grown by **loosening AntiDrift's controller**, not by starting over. + +The inversion holds: this is a *human-harness*. In an agent harness a model is +the observed worker; here Felix is the observed subject and the model is the +operator. + +--- + +## 2. Core decisions (locked 2026-06-04) + +1. **The product is the harness.** The brain (`claude` / `codex` / **Hermes**) is + rented, interchangeable compute reached over a CLI or the network — *not* the + point. The value is the framework that feeds information *in* and acts on what + comes *back*. Hermes is real (an agent harness on another machine); it is one + brain option, not a dependency. + +2. **Reuse AntiDrift's engine; loosen its controller.** AntiDrift already is this + harness for one plane. Its controller is welded to *commitment = next_action + + success_condition + timebox*. We loosen that into a general **collect → brain → + act** loop where a focus-session is just **one mode**. Other modes: house, + body, review, capture, planning. The core architecture is salvageable — we + widen scope, we don't rebuild. + - Worked example (a non-focus mode): from the phone web UI, *"what work on the + house should I do next?"* → the harness reads the house bugs/tasks, the brain + proposes a strategy, the UI asks Felix for feedback, and on approval an + effector files the task. + +3. **Storage = ActivityWatch.** AW is already a general append-only event store + with a REST API, and it is currently the single richest *unused* asset + (~1 yr / ~287k events). Keel gets its **own AW bucket(s)** for memory. **No new + database, no new event store.** AW is therefore both a *sensor* (screen-time + ground truth) and the *store* (Keel's memory). + +4. **Two surfaces, both — no fork.** + - **Web UI** — configure, interact, discuss; phone-friendly. The place Felix + *talks to* Keel. + - **WM status bar** — reflect current status (AntiDrift already writes + `~/.antidrift_status`). The reminder sentence lives here; it is one *status + element*, **not** a deliverable. (No "nice summaries" as a product.) + +5. **Life-information root = `~/owc`** (owncloud markdown), formerly called + "clawd". Values, goals, the 22 `resources/bug-*.md`, weekly reviews, + self-management frameworks, capture stream. **Reference, never duplicate.** + +6. **Effectors: start gated, trend toward autonomy.** Day one: read + *propose, + Felix confirms in the web UI*. Target: the brain does a lot on its own — + **update Beeminder datapoints, update Marvin to-dos, update `~/owc` notes.** + The gate (propose-and-confirm vs. just-do-it) is a **config surface per + effector**, not a vibe — this is the agency-vs-cage line and it stays Felix's + to set. + +7. **Still in force:** don't build a new event store; AntiDrift is the substrate; + reference source-of-truth; local-first; every session ends with visible proof; + the status line stays small. + +--- + +## 3. The harness (the components we build) + +``` + SOURCES (read) KEEL (the framework = the product) BRAIN (swappable) + ───────────── ────────────────────────────────────── ────────────────── + AW :5600 ───────┐ ┌──────────────────────────────────────┐ claude (local CLI) + AntiDrift ──────┤ feed │ ① COLLECTORS → ③ ASSEMBLER → [BRAIN] │ send ──▶ codex (local CLI) + HQ hq.db ───────┤ ───────▶ │ ▲ │ │ Hermes (remote API) + Consume :8000 ──┤ │ │ ▼ │ ◀─ reply + daily.db :2200 ─┤ │ ② MEMORY = AW buckets ④ EFFECTORS │ (just compute; + Beeminder ──────┤ │ (reuse AW; no new DB) │ │ interchangeable) + Marvin ammcp ───┘ └────────────────────────────────┼─────┘ + ~/owc (frame) ──── grounds the brief ────────────────────────┤ + ⑤ renders to ──┴──────────┐ + ▼ ▼ + WEB UI WM STATUS BAR + (configure/interact/discuss) (current status) +``` + +Each component has one job, a defined interface, and can be built/tested alone: + +- **① Collectors** — one small read-only adapter per source. Contract: + `read(window) -> Signal[]`. Today's real interfaces: AW `POST :5600/api/0/query/`; + AntiDrift `tail ~/.antidrift/audit.jsonl` (+ live SSE `:7777/events`); HQ + read-only `hq.db` (+ `:8765`); Consume `GET :8000/api/...`; daily read-only + `daily.db` (+ `:2200`); Beeminder `beeline`; Marvin `ammcp` MCP / `am --json`. + Depends only on the source being up; degrades to a dropped signal if not. + +- **② Memory** — AW buckets owned by Keel. Keel writes its own derived events here + (mode_started, proposal_made, action_taken, spiral_flagged, …) so it *remembers + across runs* and can see trends a single tool can't. Rebuildable, queryable via + AW's REST API. Depends on AW only. (See §7.) + +- **③ Context assembler** — builds the brief handed to the brain = relevant + collector signals + memory (recent + trend) + the `~/owc` frame (the value/goal + at stake, the relevant `bug-*.md`). Budgets it. Depends on ① ② and `~/owc`. + Privacy boundary lives here: titles that can leak secrets are filtered before + they reach the brain. + +- **Brain adapter** — pluggable. Local: shell `claude --print` / `codex exec` + (exactly what AntiDrift's `ai.Backend` already does). Remote: call Hermes over + the network. Interface: `decide(brief) -> {status, proposals[]}`. Keel treats it + as a black box; swapping it changes nothing else. + +- **④ Effectors** — one gated adapter per write target. Today's safe writes: + Marvin task create/complete (`am` / `ammcp`, **preserve the `fieldUpdates` + CRDT** on write), capture append (one file), AntiDrift start-session / enforce. + Roadmap writes: Beeminder datapoint (`beeline`), `~/owc` note edits. Each + effector reads its gate from config (propose-and-confirm | auto). + +- **⑤ Surfaces** — render Keel's state. Web UI (rich, phone-friendly, the + interaction/approval surface) and the WM status bar (one live line). Both read + the same Keel state; neither owns data. + +- **Control loop / scheduler** — drives the cycle: when to collect, when to think, + when to surface, when to act. Cheap triggers (a switch-count spike, a Beeminder + losedate approaching, a manual web-UI request) decide when to spend a brain + call, instead of polling the LLM blindly. This is AntiDrift's state machine, + generalized past the single session. + +- **Policy / gates** — per-effector authority + the value frame. Felix's dial + from "read-only" to "act freely". Configured in the web UI. + +--- + +## 4. AntiDrift is the seed, not a peer + +Keel is AntiDrift's port set, widened. The parts already exist: + +| Keel part | AntiDrift today | Keel (generalized) | +|---|---|---| +| Collectors | `evidence.Source` (X11), `tasks.Provider` (Marvin) | + AW, HQ, Consume, daily, Beeminder, `~/owc` | +| Frame | `knowledge.Source` → one file | `~/owc`: values, goals, 22 `bug-*.md`, frameworks | +| Brain | `ai.Backend` → local `claude`/`codex` | + Hermes (remote); pluggable interface | +| Memory | ephemeral (dies in `state.json`) | **AW buckets** (durable, queryable) | +| Effectors | `enforce.Guard` (window-minimize) | + Marvin, capture, Beeminder, `~/owc` notes | +| Surfaces | web UI `:7777` + `~/.antidrift_status` | richer web UI (phone) + status bar | +| Control loop | session state machine (locked/planning/active/review) | **modes** (focus is one); a session is one mode | + +**Loosening the controller** concretely means: generalize `Commitment` from a +work-session into a *mode invocation* (a mode has its own collectors, frame slice, +brain prompt, and allowed effectors); the focus-session remains the +`next_action/success_condition/timebox` mode; a "house" mode reads house bugs and +proposes a strategy; a "capture" mode just routes a thought. The runtime state +machine becomes the loop in §3. + +--- + +## 5. One loop, concrete + +House mode, from the phone, end to end: +1. **Collect** — `~/owc/resources/bug-*.md` house items + Marvin "house" tasks + + (memory) what was proposed/done last weekend. +2. **Remember** — read Keel's AW `keel.state` bucket for prior house proposals so + it doesn't repeat itself. +3. **Assemble** — those + the `house-integrity` goal from `~/owc/goals-2026.md`. +4. **Brain** — chosen brain returns a proposed next house task + short strategy. +5. **Surface** — web UI shows the proposal and asks for feedback. +6. **Act** — on approval, the Marvin effector files the task; Keel writes an + `action_taken` event to its AW bucket (memory closes the loop). + +No new store touched. The brain was rented for one call. Everything else was Keel. + +--- + +## 6. Sources & targets (the real ecosystem) + +| Tool | Read | Write | Role | Source of truth | +|---|---|---|---|---| +| ActivityWatch | `POST :5600/api/0/query/`, sqlite | new buckets via REST | sensor **+ store** | `peewee-sqlite.v2.db` | +| AntiDrift | `audit.jsonl`, SSE `:7777` | `POST :7777` cmds, enforce | focus mode + enforcement | `~/.antidrift/` | +| HQ | `hq.db` r/o, `:8765` | `dashboard.write_artifact` | agent-execution evidence | `hq.db` (~23k events) | +| Consume | `GET :8000/api` | (later) | intake/reflection signal | item frontmatter | +| daily | `daily.db` r/o, `:2200` | — | mood/habit checkout | `daily.db` | +| Beeminder | `beeline` | `beeline` datapoints | committed-evidence rollups | Beeminder API | +| Marvin | `ammcp` / `am --json` | `am` / `ammcp` (keep CRDT) | intent / to-dos / bugs | CouchDB | +| `~/owc` | markdown globs, `.zk` | note edits (later) | values/goals/bugs frame | `~/owc` files | +| Brain | — | — | rented compute | claude/codex/Hermes | + +Known frictions Keel inherits (from the survey): beesync has **no scheduler** +(Beeminder data lags; `[daily]` bridge commented out); the life-bug concept is +**forked** (22 rich `~/owc` markdown bugs vs. 5 thin Marvin tasks); **capture is +broken** (`blurt` → clipboard, dead `inbox.md`, two rival `stream.md`); AW's year +of data is read by nothing today. + +--- + +## 7. Memory in ActivityWatch (the storage design) + +AW is a typed, append-only, timestamped event store with buckets + a REST/query +API — i.e. it is already the event store the §0 correction said not to rebuild. +Keel gets dedicated buckets (e.g. `keel.state`, `keel.events`) and writes +**derived** events: `mode_started`, `proposal_made`, `feedback_given`, +`action_taken`, `spiral_flagged`, `coach_line`. Properties: +- **Reference, don't duplicate** — these hold pointers (a Marvin id, a `bug-*.md` + path, an audit `session_id`), not copies of other tools' truth. +- **Rebuildable** — derivable from the underlying ledgers; AW is a cache/index of + Keel's own decisions plus a join surface over the sources. +- **Queryable over time** — this is what unlocks trend/spiral detection ("caffeine + bug + mood dip + rising switches, same shape as three weeks ago") that no single + tool can do, because each tool only answers "what now". + +Open sub-decision: whether work-laptop AW data is peer-synced in (cross-device +behavioral story) or left out initially. + +--- + +## 8. Open questions (not yet decided) + +- **Effector gate policy** — exact default authority per write target on day one, + and how Felix raises/lowers it from the web UI. +- **Capture unification** — which single file is canonical (resolve + `~/owc/stream.md` vs `~/wrk/pkb/stream.md` vs dead inboxes) and rewiring `blurt` + to append there. +- **Controller refactor shape** — how invasive the AntiDrift generalization is; do + we branch the daemon or grow it in place. **This is also the trigger for the + repo/code rename to `keel`.** +- **Cross-device AW** — sync work-laptop AW or not (see §7). +- **Secret hygiene** — an increasingly autonomous operator runs as the full Unix + user; plaintext keys exist on disk; the privacy boundary in ③ must be real. + +--- + +## 9. What this supersedes (from `~/dev/higher/`) + +- The whole `~/dev/higher/` planning directory: it was the scaffold for deciding + to extend AntiDrift. This doc is now the authoritative home; that dir retires to + a pointer. +- "clawd" → **`~/owc`** everywhere. +- The plan's premise that the operator "already knows Felix via `~/.hermes`" and + the survey finding "Hermes doesn't exist" are both resolved: **Hermes is real + but remote, and the brain is pluggable** — grounding comes from the `~/owc` + frame regardless of which brain runs. +- The plan's §6 recommendation ("a thin *reader* layer is the real architecture") + is **superseded**: the reader is merely the **collectors layer** of Keel. The + target is the full system (one screen, two surfaces, durable memory, + gated→autonomous effectors), grown from AntiDrift. +- "Don't build an event store" is **sharpened**: storage = **ActivityWatch + buckets**. + +--- + +## 10. Smallest real slice (a vertical, not a summary) + +To stay honest to "ship visible proof" without boiling the ocean: the first slice +is **house mode, end to end** (§5) on the existing AntiDrift web surface — one +collector (`~/owc` house bugs + Marvin), the brain, the web UI proposal, and the +Marvin effector behind a confirm gate. It exercises every Keel component once, on +the smallest mode, and produces a real filed task — interaction and action, not a +coaching sentence.