The checkout IS the deployment and symlinks make live state (memories, auto-learned skills, agent config, desktop settings) dirty tracked files continuously; no commit law covered machine churn, so it accumulated unpushed for weeks — one disk held the only copy of ~61 managed skills, a settings fix stayed local-only, and dirty trees refused merges. scripts/live-sync.sh (systemd user timer, ~15 min) commits ONLY the allowlisted machine/desktop-live tiers with hooks armed, merges origin/main, pushes, and reaps wt/* worktrees older than 7 days. Task dirt outside the allowlist is never staged; conflicts abort and alert via ntfy; branch guard refuses off main; never --no-verify, never force. Black-box contract pinned by tests/live-sync.bats (9 tests, suite 36/36). Refs #11
3.2 KiB
type, title, description, tags, verified, timestamp
| type | title | description | tags | verified | timestamp | |||||
|---|---|---|---|---|---|---|---|---|---|---|
| reference | live-sync heartbeat | Auto-commit daemon for machine-written paths in the command-center checkout — the deployment is live, so symlinks make config churn dirty tracked files continuously. |
|
2026-10-18 | 2026-10-18 |
live-sync heartbeat
The command-center checkout IS the deployment, and several tracked files are
live state: the harness writes memories and managed-skills, the operator
edits config.yml/models.yml through the cfg:// surface, and desktop
GUIs (Noctalia, niri) rewrite dotfiles through symlinks. Before Refs #11 this
machine churn just piled up unpushed — one disk holding the only copy of ~61
auto-learned skills, a settings fix stranded locally, and merge-refusals
because the dirty tree collided with incoming work.
scripts/live-sync.sh is the pressure valve: a heartbeat that commits the
machine's own writes on a schedule. Tier law (which paths it may touch) lives
in AGENTS.md § Live-sync tiers.
Contract (black-box)
Run = one pass: flock → branch guard (main only) → stage allowlisted dirty
tiers → commit (hooks armed) → fetch + merge origin/main → push → reap
stale wt/* worktrees. Exit 0 = quiet heartbeat (synced/no-op/reaped);
exit 1 = an alert was raised. Pinned by tests/live-sync.bats.
Hard properties:
- Allowlist only. Task dirt (
plugins/,omp-docs/, prose) is never staged — merge conflicts with in-flight work become impossible by construction. - Hooks stay armed. A secret-gate refusal aborts the commit, leaves
changes staged for a human, and alerts. Never
--no-verify, never force. - Never fights the remote. Merge conflict →
merge --abort, local commit kept, alert. Retries are automatic next tick. - Branch guard. On a non-
maincheckout it refuses and alerts — same rule as the doctrine ("never end a session off main"). - Polite reaper.
wt/wt-*worktrees untouched for 7+ days are removed with theirwt/*branch; dirty or process-occupied worktrees are skipped.
Alerts
notify() writes stderr always; pushes to ntfy when the topic file
~/.config/ntfy/live-sync (mode 600, topic text only) exists, throttled to
one push per hour. Test seams: LIVE_SYNC_REPO, LIVE_SYNC_NOTIFY,
LIVE_SYNC_NTFY_TOPIC_FILE, LIVE_SYNC_AGE_DAYS.
Install
Units ship in dotfiles/systemd-user/.config/systemd/user/ and propagate
with the usual dotfiles chain:
systemctl --user enable --now live-sync.timer # after the unit is linked
systemctl --user start live-sync.service # one manual heartbeat
journalctl --user -u live-sync.service -n 20 # observe
Failure modes
| Event | Result |
|---|---|
| Harness mid-write | commits a valid snapshot; remainder lands next heartbeat |
| Gitea down | commit safe locally; push retries next tick (≤15 min stale window) |
| Same file edited on two boxes | real merge conflict → ntfy → human resolves (visible, not silent) |
| Feature branch checked out in main checkout | refuses + alerts; protects the never-end-off-main rule |
| Secret shape in machine-written content | hook blocks, changes stay staged, alert |