Nine TDD tasks: internal/aw REST client (EnsureBucket/Insert/Recent),
internal/memory port (Store + awStore over-fetch filter + nop + fake),
KEEL_AW_URL setting, harness.Services.Memory wiring with nop fallback,
off-screen write path (proposal_made/action_taken/proposal_dismissed) and
read path (## Recently proposed + follow-up prompt rule), plus full and
live verification. Derived from the 2026-06-05 design spec.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Brainstormed design for Keel's first cross-run memory: a thin memory.Store
port backed by an internal/aw REST client writing to the keel.events bucket,
with off-screen as the single wired consumer (records proposal_made /
action_taken / proposal_dismissed and reads its recent history back into the
brief for fresher, follow-up-aware proposals). Brain-only, best-effort,
degrades to nop when AW is down. Realizes keel-architecture.md §5/§7 at the
smallest honest vertical.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>
- 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>
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>
Three independent defects made the focus state feel flaky and let the OS
status bar disagree with the web UI:
- Status file lagged the web by up to a minute: it rendered only on a 60s
ticker with no hook into state changes. notify() now fans out to multiple
listeners (AddOnChange) and the writer has a coalesced Wake() so drift
reaches the bar as promptly as the browser.
- A brief off-task visit could latch drift for the whole session. Sibling
windows of one app (a browser's reading tab vs its chat tab) share a window
class, but the judge verdict was cached by class alone, so one tab's verdict
poisoned the rest and never re-judged. Cache is now keyed by class + scrubbed
title (judgedWindows) so siblings are judged independently.
- Allowed-class matching was exact equality, so a short token ("brave") never
matched the real WM_CLASS ("Brave-browser") and every window was routed to
the LLM. Matching is now substring-based, and planning surfaces the live
window class with a click-to-add chip so users pick a token that matches.
Also fix the planning checkbox alignment: the global full-width input rule was
stretching the "Enforce focus" checkbox; scope it out for checkboxes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Remove the legacy/ Rust tree (Cargo manifests, .rs sources, desktop
entry), drop Rust-specific .gitignore blocks and README note, and strip
now-dangling "ported from Rust" comments from the Go sources.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Polling active-window sensor and window-minimize guard as //go:build windows
adapters over a shared internal/winapi Win32 binding. Pure logic (class
normalization, emit-on-change tracking) is TDD-tested on Linux; syscall code is
cross-compile verified. No consumer changes.
- GOOS=windows GOARCH=amd64 go build ./... (whole module links)
- GOOS=darwin GOARCH=amd64 go build ./... (!linux && !windows fallback intact)
- go build ./... && go test ./... (Linux host green, incl. new winapi/evidence tests)
- go vet ./... on linux and windows (clean)
- cmd/antidriftd cross-compiles to a real PE32+ x86-64 .exe
Six TDD/cross-compile tasks: pure class normalization and emit-on-change
tracker (unit-tested on Linux), Win32 binding, the two windows-tagged port
adapters, build-tag narrowing, and a whole-module cross-compile gate.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Full-parity design: evidence.Source (polling) and enforce.Guard
(ShowWindow) behind the existing X11/no-op port boundaries, pure Go,
verified compile-only.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
antidriftd binds localhost only and sits behind no proxy, so trusting all
proxies (gin's default) is both wrong and a startup warning. Set an empty
trusted-proxy list to disable forwarded-IP header trust and silence it.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bucket keys are {Class, ScrubTitle(Title)}, so each animated progress frame a
terminal app cycles into its title (Claude Code's braille and dingbat-star
glyphs) became a distinct bucket — fragmenting one task into dozens of rows and
inflating the switch count, since applyEvent counts a switch per key change.
ScrubTitle now drops any non-ASCII symbol or control rune and collapses leftover
whitespace. This is category-based rather than range-based, so future spinner
sets are covered too, while letters and digits of every script survive and ASCII
symbols (e.g. "C++") are left intact.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>