Generalize the focus controller into a harness hosting swappable modes
Loosen AntiDrift's session controller into Keel's general collect→brain→act
loop. A new internal/harness runs at most one mode.Mode at a time, fanning
async completions out to the web SSE and status-bar surfaces.
- internal/mode: the Mode contract (Kind/Command/View/Active) plus optional
EvidenceConsumer and Expirer ports, and the surfacing Envelope.
- internal/mode/focus: the former session/domain/statemachine packages moved
under the mode, now satisfying the harness contracts unchanged.
- internal/mode/offscreen: a one-shot away-from-desk mode built on the new
ai.Proposer, which turns a life-domain brief into one off-screen action.
- cmd/keeld replaces cmd/antidriftd; daemon wires focus + offscreen factories
with per-mode persistence under ~/.keel/modes/<kind>.
- Finish the rename: KEEL_* env refs in the README, /keeld build artifact
ignored, stale antidriftd binary removed.
Include the design + implementation plan this refactor was built from under
docs/superpowers/{specs,plans}/2026-06-04-controller-refactor*.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+56
-41
@@ -18,12 +18,11 @@ status: authoritative
|
||||
> **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 still `antidrift`** (Go module
|
||||
> `antidrift`, binary `antidriftd`, 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 a `mv`.
|
||||
> 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 all `keel`. 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.
|
||||
|
||||
@@ -54,13 +53,15 @@ operator.
|
||||
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.
|
||||
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 `~/owc` goals and life-domain `bug-*.md`, the brain
|
||||
proposes one off-screen action, the UI asks Felix for feedback, and on
|
||||
approval the Marvin 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
|
||||
@@ -72,7 +73,7 @@ operator.
|
||||
- **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
|
||||
`~/.keel_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
|
||||
@@ -115,7 +116,7 @@ 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
|
||||
AntiDrift `tail ~/.keel/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.
|
||||
@@ -168,33 +169,38 @@ Keel is AntiDrift's port set, widened. The parts already exist:
|
||||
| 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 |
|
||||
| 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; 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.
|
||||
`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
|
||||
|
||||
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).
|
||||
Off-screen mode, from the phone, end to end — **this is what ships today**:
|
||||
1. **Collect** — today's Marvin tasks (`am --json`) + the `~/owc/goals-2026.md`
|
||||
goals + the life-domain `~/owc/resources/bug-*.md` notes, assembled into a
|
||||
single budgeted brief.
|
||||
2. **Brain** — the chosen brain (`ai.Proposer`) returns one worthwhile off-screen
|
||||
action plus a short rationale.
|
||||
3. **Surface** — the phone-first web UI renders a proposal card and asks Felix to
|
||||
confirm or dismiss.
|
||||
4. **Act** — on confirm, the Marvin `Create` effector files the action as a task
|
||||
(CRDT-preserving `am 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.
|
||||
|
||||
**Next increment:** memory. Off-screen does not yet read or write Keel's AW
|
||||
buckets, so it cannot remember what it proposed last time. Adding a *remember*
|
||||
step (read `keel.state` for prior proposals) and an *act* event
|
||||
(`proposal_made`/`action_taken` to `keel.events`) closes the loop per §7.
|
||||
|
||||
---
|
||||
|
||||
## 6. Sources & targets (the real ecosystem)
|
||||
@@ -202,7 +208,7 @@ No new store touched. The brain was rented for one call. Everything else was Kee
|
||||
| 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/` |
|
||||
| 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` |
|
||||
@@ -246,15 +252,22 @@ behavioral story) or left out initially.
|
||||
- **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 when the *code* rename
|
||||
lands** — Go module / `antidriftd` binary / `ANTIDRIFT_*` env and
|
||||
`~/.antidrift/` → `~/.keel/` with a state migration. (The directory is already
|
||||
`keel`; only the code identity still says `antidrift`.)
|
||||
- **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 is `keeld`, runtime is `~/.keel/`, env prefix is `KEEL_*`.
|
||||
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.Controller` is now a generic `harness.Harness` that hosts
|
||||
one `mode.Mode` at a time (the `collect → brain → act` loop 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/`)
|
||||
@@ -278,9 +291,11 @@ behavioral story) or left out initially.
|
||||
|
||||
## 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.
|
||||
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.
|
||||
|
||||
Reference in New Issue
Block a user