- README: lead with Keel (the human-harness), frame AntiDrift as the focus mode, keep the milestone history. Fix the "rename deferred" wording (the dir/repo is already keel; only the code identity stays antidrift) and drop the dangling 2026-05-31 spec reference (that spec was intentionally removed). - keel-architecture.md: update the rename status in the header and §8 — the directory is keel; the Go module / antidriftd binary / ANTIDRIFT_* env / ~/.antidrift -> ~/.keel migration land with the controller refactor. - add AGENTS.md (agent orientation) + CLAUDE.md symlink so claude / codex / Hermes auto-load Keel context when launched in this repo. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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 |
|
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 is now
keel— AntiDrift's focus harness becomes Keel's first mode. The directory and git repo are renamed; the code identity is intentionally stillantidrift(Go moduleantidrift, binaryantidriftd, runtime~/.antidrift/,ANTIDRIFT_*env). That code rename is deferred until the controller refactor (§8) because~/.antidrift/owns the live ledger/state and needs a migration, not amv. 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)
-
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. -
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.
-
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).
-
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.)
-
Life-information root =
~/owc(owncloud markdown), formerly called "clawd". Values, goals, the 22resources/bug-*.md, weekly reviews, self-management frameworks, capture stream. Reference, never duplicate. -
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
~/owcnotes. 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. -
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: AWPOST :5600/api/0/query/; AntiDrifttail ~/.antidrift/audit.jsonl(+ live SSE:7777/events); HQ read-onlyhq.db(+:8765); ConsumeGET :8000/api/...; daily read-onlydaily.db(+:2200); Beeminderbeeline; MarvinammcpMCP /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
~/owcframe (the value/goal at stake, the relevantbug-*.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'sai.Backendalready 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 thefieldUpdatesCRDT on write), capture append (one file), AntiDrift start-session / enforce. Roadmap writes: Beeminder datapoint (beeline),~/owcnote 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:
- Collect —
~/owc/resources/bug-*.mdhouse items + Marvin "house" tasks + (memory) what was proposed/done last weekend. - Remember — read Keel's AW
keel.statebucket for prior house proposals so it doesn't repeat itself. - Assemble — those + the
house-integritygoal from~/owc/goals-2026.md. - Brain — chosen brain returns a proposed next house task + short strategy.
- Surface — web UI shows the proposal and asks for feedback.
- Act — on approval, the Marvin effector files the task; Keel writes an
action_takenevent 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-*.mdpath, an auditsession_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.mdvs~/wrk/pkb/stream.mdvs dead inboxes) and rewiringblurtto append there. - Controller refactor shape — how invasive the AntiDrift generalization is; do
we branch the daemon or grow it in place. This is also when the code rename
lands — Go module /
antidriftdbinary /ANTIDRIFT_*env and~/.antidrift/→~/.keel/with a state migration. (The directory is alreadykeel; only the code identity still saysantidrift.) - 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" →
~/owceverywhere. - 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~/owcframe 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.