Daemon & configuration
loopkeepd is the resident process that does everything: watches triggers,
executes runs, evaluates policies, writes the event log, delivers
notifications. The CLI and the desktop app are clients — closing them changes
nothing about what's running.
One daemon serves every registered workspace. Each workspace keeps its own independent context: its own event log, policy, trust state, and workflows.
Control
lk start # alias: up
lk stop # alias: down — in-flight runs are restored as interrupted on the next start
lk restart
lk status
Clients talk to the daemon over a local socket, /tmp/loopkeepd.sock by
default (LOOPKEEP_SOCKET overrides — set it for both the daemon and the
clients). No network port is opened; the Console connection, if you pair, is a
single outbound WebSocket.
Files on disk
Global, under ~/.config/loopkeep/ (respects XDG_CONFIG_HOME;
LOOPKEEP_CONFIG overrides the config file path):
~/.config/loopkeep/
├── config.yaml # daemon settings (concurrency cap, retention)
└── policy.yaml # your home policy layer
Per workspace, under <workspace>/.loopkeep/:
.loopkeep/
├── policy.yaml # project policy (commit this)
├── policy.local.yaml # your personal overrides (gitignore this)
├── workflows/ # workflow definitions
├── secrets # secret NAMES only — values live in the OS keychain
├── events.db # append-only event log (SQLite)
└── worktrees/<run_id>/ # each run's isolated git worktree
config.yaml
concurrency:
global_max: 4 # runs in flight across every workspace
retention:
worktree_days: 14 # how long finished runs' worktrees are kept; omit to keep forever
Both are managed from the CLI (lk global-max, lk retention) and the
desktop app; edits are persisted back to this file either way.
The event log
Everything the system does — trigger firings, state changes, proposed actions,
your decisions — is an immutable record in events.db, per workspace. The
inbox, the runs list, and the audit trail are all derived views of this log;
there is no second bookkeeping to drift out of sync. Records are never updated
or deleted — corrections are new events. Treat the file as read-only evidence;
if it can't be persisted at startup, the daemon refuses to start rather than
silently losing history.
This is also where supervision evidence comes from: who approved what, when, with which rule in effect, is a query — not a reconstruction.
Workspace registry
lk workspaces # list
lk workspaces add <path> [--name <n>]
lk workspaces remove <id> # home cannot be removed
Commands addressed at an unregistered path that contains .loopkeep/ register
it on the fly.
Troubleshooting
| Symptom | Likely cause |
|---|---|
| CLI reports the daemon isn't running | lk start; if you use a custom LOOPKEEP_SOCKET, make sure daemon and CLI agree on it. |
| A workflow never fires automatically | Workspace untrusted (lk trust), the workflow disabled (lk enable <workflow>), or the global pause is on (lk resume --all). |
A run sits in waiting | It's waiting for you — check lk inbox, decide with lk approve <seq>. |
A run shows interrupted after a restart | Expected: lk stop restores in-flight runs that way on the next start. lk rerun it. |
run_* worktrees pile up | Retention is off or long — lk retention <days>, or sweep now with lk cleanup. |