16 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
keel— AntiDrift's focus harness becomes Keel's first mode. The rename is complete: directory, git repo, Go module (keel), binary (keeld), runtime (~/.keel/), and env (KEEL_*) are allkeel. The old~/.antidrift/ledger needs a one-time manual move to~/.keel/for existing installs. 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. The harness now hosts a second, off-screen mode; further modes (body, review, capture) are aspirational. The core architecture is salvageable — we widen scope, we don't rebuild.
- Worked example (the shipped non-focus mode): from the phone web UI, "what
worthwhile off-screen thing should I do now?" → the harness reads today's
Marvin tasks + the
~/owcgoals and life-domainbug-*.md, the brain proposes one off-screen action, the UI asks Felix for feedback, and on approval the Marvin effector files the task.
- Worked example (the shipped non-focus mode): from the phone web UI, "what
worthwhile off-screen thing should I do now?" → the harness reads today's
Marvin tasks + the
-
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
~/.keel_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 ~/.keel/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 + ~/.keel_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; the off-screen mode reads the
life-domain bugs + goals and proposes one off-screen action; a future "capture"
mode would just route a thought. The runtime state machine becomes the loop in §3.
5. One loop, concrete
Off-screen mode, from the phone, end to end — this is what ships today:
- Collect — today's Marvin tasks (
am --json) + the~/owc/goals-2026.mdgoals + the life-domain~/owc/resources/bug-*.mdnotes — plus recent proposals and their outcomes recalled from thekeel.eventsAW bucket — assembled into a single budgeted brief. - Brain — the chosen brain (
ai.Proposer) returns one worthwhile off-screen action plus a short rationale. - Surface — the phone-first web UI renders a proposal card and asks Felix to confirm or dismiss.
- Act — on confirm, the Marvin
Createeffector files the action as a task (CRDT-preservingam add); the mode is one-shot and the harness returns idle.
No new store touched. The brain was rented for one call. Everything else was Keel.
Shipped: memory. Off-screen now writes its decisions to the keel.events AW
bucket (proposal_made / action_taken / proposal_dismissed, correlated by a
random proposal_id) and reads its recent history back into the brief — so a
proposal can avoid repeating itself and follow up on what was dismissed or left
undone. Storage sits behind a thin memory.Store port (AW-backed, or a nop when
AW is down), the seam every later mode reuses; see §7. The keel.state bucket and
the trend/spiral detection that reads this data remain future increments.
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 | ~/.keel/ |
| 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. - 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.
Resolved
- Code rename — complete as of Phase 0 of the controller refactor. Go module
is
keel, binary iskeeld, runtime is~/.keel/, env prefix isKEEL_*. Existing installs with a live ledger under~/.antidrift/need a one-time manual move to~/.keel/. - Controller refactor shape — resolved: grown in place, not branched. The
focus-only
session.Controlleris now a genericharness.Harnessthat hosts onemode.Modeat a time (thecollect → brain → actloop of §3); focus is the first mode and off-screen the second. Per-mode persistence lives under~/.keel/modes/<kind>/.
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 off-screen mode, end to end (§5) on the existing AntiDrift web surface —
collectors (~/owc goals + life-domain bugs + Marvin), the brain (ai.Proposer),
the phone-first web UI proposal card, and the Marvin Create effector behind a
confirm gate. It exercises every Keel component once except memory, on the
smallest mode, and produces a real filed task — interaction and action, not a
coaching sentence. This slice now ships; durable AW memory (§7) is the next
increment that closes the loop.