Files
antidrift/docs/keel-architecture.md
T
felixm f9e580ff7e 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 <noreply@anthropic.com>
2026-06-04 12:57:14 -04:00

15 KiB

title, name, category, created, updated, tags, status
title name category created updated tags status
Keel — Architecture keel architecture 2026-06-04 2026-06-04
keel
human-harness
architecture
antidrift
activitywatch
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.