Files
mempalace-toolkit/contrib/README.md
T
Joakim Persson 720245e010 Add macOS launchd template, bringing automation parity to macOS
Ship a launchd user agent plist alongside the existing systemd and
cron templates so macOS users can schedule mempalace-session without
falling back to cron. launchd is the macOS-native equivalent of a
systemd user timer: same scheduling model, same log conventions, same
single-instance guarantees.

- contrib/launchd/se.jordbo.mempalace-session.plist:
  - Label uses reverse-DNS from the jordbo.se domain for consistency
    with other user-installed launchd jobs; fork the prefix if reusing
    this template in a different org.
  - ProgramArguments points at /Users/USER/.local/bin/mempalace-session
    (USER is substituted at install time, same pattern as
    contrib/cron/).
  - EnvironmentVariables.PATH covers ~/.local/bin, Apple Silicon
    Homebrew, Intel Homebrew, and system defaults — launchd agents
    get a minimal PATH by default and the wrapper needs to find
    mempalace + python3.
  - StartCalendarInterval matches systemd unit's schedule: Monday
    03:00 local.
  - RunAtLoad=false — load shouldn't trigger a run; schedule does.
  - ProcessType=Background + LowPriorityIO=true + Nice=10 mirror
    the systemd unit's Nice=10 + IOSchedulingClass=idle. macOS's
    automatic App Nap and resource throttling for Background jobs
    yields to interactive work cleanly.
  - ExitTimeOut=7200 matches systemd's TimeoutStartSec=7200.
  - StandardOut/ErrorPath under ~/Library/Logs/ so Console.app
    surfaces them.

- contrib/README.md gains a full launchd section:
  - Caveat table comparing to systemd (Persistent=true isn't quite
    matched; RandomizedDelaySec has no equivalent; overlap prevention
    is automatic).
  - Install recipe using launchctl bootstrap (modern) with a fallback
    note for legacy launchctl load -w on older macOS.
  - Verify section shows launchctl list, launchctl print, log tails,
    and launchctl kickstart for manual testing.
  - Uninstall via launchctl bootout.
  - Chooser table updated: macOS now explicitly points at launchd,
    not cron.

- ARCHITECTURE.md §5, SKILL.md Quick automation pitch, and README.md
  Keeping it fresh section all updated to mention the three scheduler
  options and give per-platform quick-starts.

Plist XML validated with plistlib.
2026-04-30 06:51:17 +00:00

226 lines
9.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# contrib/ — automation recipes for `mempalace-session`
Manual invocation of `mempalace-session` is fine on a machine you actively drive. For long-running devboxes, a weekly automated mine keeps the palace fresh without thinking about it. This directory ships ready-to-use templates for two common scheduling mechanisms.
> **Before using either**: confirm the toolkit is installed and the wrapper works —
> `mempalace-session --dry-run` should list qualifying sessions. If that errors, fix the install before scheduling.
Pick **one**. Running both would double-mine (harmless — dedup skips everything on the second run — but wastes wall time on the HNSW repair).
---
## systemd user timer (recommended on modern Linux)
**Why:** runs without the user logged in (with `loginctl enable-linger`), survives reboots, logs to `journalctl`, Persistent=true catches missed runs after the machine was off. No root required — it's a *user* unit.
**Install:**
```bash
mkdir -p ~/.config/systemd/user
cp contrib/systemd/mempalace-session.service ~/.config/systemd/user/
cp contrib/systemd/mempalace-session.timer ~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user enable --now mempalace-session.timer
# Optional: keep the timer running when you log out (needed on headless servers)
sudo loginctl enable-linger "$USER"
```
**Verify:**
```bash
# Is the timer active and when will it next fire?
systemctl --user list-timers mempalace-session.timer
# Last run status + log tail
systemctl --user status mempalace-session.service
# Full run log (since today)
journalctl --user -u mempalace-session --since today
# Force a run right now (outside the schedule), for testing
systemctl --user start mempalace-session.service
```
**Uninstall:**
```bash
systemctl --user disable --now mempalace-session.timer
rm ~/.config/systemd/user/mempalace-session.{service,timer}
systemctl --user daemon-reload
```
### What the service does
- `Type=oneshot` — runs to completion, not a long-lived daemon.
- `ConditionPathExists=%h/.local/share/opencode/opencode.db` — skips silently on machines that haven't used opencode (no wasted boot-time runs).
- `ConditionPathExists=!%t/mempalace-session.lock` + `ExecStartPre/ExecStopPost` — soft mutual exclusion between overlapping runs.
- `Nice=10` + `IOSchedulingClass=idle` — background priority; won't interfere with interactive work.
- `TimeoutStartSec=7200` — 2 hour ceiling. The reference 60-session mine takes ~21 min; this is headroom for large corpora + slow disks.
### What the timer does
- `OnCalendar=Mon 03:00` — weekly, Monday 03:00 local time. Edit to taste (see `man systemd.time` for syntax).
- `Persistent=true` — if the machine was off at the scheduled time, run on next boot.
- `RandomizedDelaySec=30m` — jitters up to 30 minutes to avoid thundering-herd across a fleet.
---
## launchd user agent (macOS)
**Why:** the macOS-native equivalent of a systemd user timer. Runs without a Terminal window open, logs to `~/Library/Logs/`, single-instance guarantees baked in, background-priority scheduling via `ProcessType=Background`. No Homebrew or third-party scheduler required.
**Caveats vs. systemd:**
| Systemd feature | launchd equivalent | Notes |
|---|---|---|
| `Persistent=true` catches missed runs | Partial — `StartCalendarInterval` fires on next system-awake time | If the Mac is fully off at scheduled time, the run is skipped. Sleep-at-schedule → fires on wake. |
| `RandomizedDelaySec=30m` | None native | Single-user machines rarely need jitter; add a `sleep $((RANDOM % 1800))` wrapper if you do. |
| `ConditionPathExists` | None native | `mempalace-session` exits cleanly when the opencode DB is missing, so no guard is strictly needed. |
| Lock file for overlap prevention | Automatic | launchd refuses to start a second instance of the same `Label` while one is running. |
**Install:**
```bash
# Substitute your username into the template
sed "s|USER|$USER|g" contrib/launchd/se.jordbo.mempalace-session.plist \
> ~/Library/LaunchAgents/se.jordbo.mempalace-session.plist
# Ensure the log directory exists
mkdir -p ~/Library/Logs
# Modern load (macOS 10.11+). "gui/$(id -u)" targets your login session.
launchctl bootstrap "gui/$(id -u)" ~/Library/LaunchAgents/se.jordbo.mempalace-session.plist
launchctl enable "gui/$(id -u)/se.jordbo.mempalace-session"
```
> On older macOS or if you hit permissions errors with `bootstrap`, fall back to the legacy form:
> `launchctl load -w ~/Library/LaunchAgents/se.jordbo.mempalace-session.plist`
**Verify:**
```bash
# Quick check — is the job registered?
launchctl list | grep mempalace-session
# Detailed state (next run time, last exit code, throttling)
launchctl print "gui/$(id -u)/se.jordbo.mempalace-session"
# Run log tail
tail -f ~/Library/Logs/mempalace-session.log
tail -f ~/Library/Logs/mempalace-session.err.log
# Force a run right now (outside the schedule), for testing
launchctl kickstart -p "gui/$(id -u)/se.jordbo.mempalace-session"
```
**Uninstall:**
```bash
launchctl bootout "gui/$(id -u)" ~/Library/LaunchAgents/se.jordbo.mempalace-session.plist
rm ~/Library/LaunchAgents/se.jordbo.mempalace-session.plist
# Optional — keep the old logs for post-mortem, or delete them:
# rm ~/Library/Logs/mempalace-session{,.err}.log
```
### What the plist does
- `Label=se.jordbo.mempalace-session` — reverse-DNS label; shows up in `launchctl list` and Console.app. Change the prefix if you're forking this for a different org.
- `ProgramArguments` — absolute path to `mempalace-session`. Template uses `/Users/USER/.local/bin/mempalace-session`; the install `sed` substitutes your actual username.
- `EnvironmentVariables.PATH` — covers `~/.local/bin`, Apple Silicon Homebrew (`/opt/homebrew/bin`), Intel Homebrew (`/usr/local/bin`), and system defaults. launchd agents get a minimal PATH by default, and `mempalace-session` needs to find `mempalace` + `python3`.
- `StartCalendarInterval``Weekday=1, Hour=3, Minute=0` = Monday 03:00. Omit any key to match "any" (e.g. drop `Weekday` for daily).
- `RunAtLoad=false` — don't run on load/reboot, only on schedule. Flip to `true` if you want a run at every boot.
- `ProcessType=Background` + `LowPriorityIO=true` + `Nice=10` — macOS throttles this job's CPU and I/O so it yields to interactive work.
- `ExitTimeOut=7200` — 2h ceiling, matches the systemd unit.
- `StandardOut/ErrorPath``~/Library/Logs/` is the macOS convention; Console.app picks these up automatically.
---
## cron
**Why:** simpler, ubiquitous, works on any UNIX. No `loginctl enable-linger` dance, no user-units awareness required.
**Caveats:** no "persistent" semantics (a missed run while the machine was off stays missed); default cron output goes to mail or is silently dropped if no MTA.
**Install:**
```bash
# Edit the template first — replace USER with your actual username
sed "s|USER|$USER|g" contrib/cron/mempalace-session.cron > /tmp/mempalace-session.cron
# Append to your existing crontab (preserves any entries you already have)
(crontab -l 2>/dev/null; cat /tmp/mempalace-session.cron) | crontab -
rm /tmp/mempalace-session.cron
# Verify
crontab -l | grep mempalace
```
Ensure `~/.cache/mempalace-session/` exists so the log file can be written:
```bash
mkdir -p ~/.cache/mempalace-session
```
**Verify a run is happening:**
```bash
# Tail the log the cron entry writes to
tail -f ~/.cache/mempalace-session/cron.log
# Or force a run manually to prove the command is well-formed
mempalace-session
```
**Uninstall:**
```bash
crontab -e # remove the mempalace-session line by hand
```
---
## Which should I pick?
| Situation | Pick |
|---|---|
| Desktop / laptop, modern systemd-based Linux distro | systemd user timer |
| macOS (any recent version) | launchd user agent |
| Long-running Linux devbox or server, wants "Persistent=true" catch-up | systemd user timer |
| BSD, Alpine, or Linux distro without systemd | cron |
| You already have a cron-based job scheduler on the box | cron |
| You want logs in `journalctl` (Linux) or Console.app (macOS) rather than a file | systemd user timer / launchd |
If you're not sure: **systemd on Linux, launchd on macOS, cron only when neither is available**. All three wrap the same `mempalace-session` command — the difference is purely in *how* the box remembers to run it.
---
## Running inside a container (devbox)
Inside a Docker-based devbox, neither systemd nor cron typically runs by default. Two options:
1. **Schedule on the host, not the container** — have the host run `docker exec -u <user> <container> mempalace-session` on a timer. The container must be long-running (not per-invocation) for this to work.
2. **Run a systemd-in-container setup** — viable but usually not worth the complexity for this alone.
For most devbox users, a simple weekly manual run via `mempalace-session` (or a host-side cron that shells into the container) is the pragmatic choice. The tool is cheap enough that skipping a week costs nothing — dedup will catch up on the next run.
---
## Tuning
**Frequency.** Weekly is the default because:
- New sessions you care about are typically a handful per week per user.
- Dedup is free on unchanged sessions, so there's no cost to running daily other than the ~5 min post-mine repair.
- Weekly keeps the palace fresh enough that searches almost always return current context.
**Daily or more:** edit `OnCalendar=` or the cron DOW field. On a daily schedule, add `--no-repair` to the wrapper invocation and let a separate weekly unit handle repair — otherwise you repair 7× more often than you need.
**Monthly:** probably too infrequent. You'll search for "that thing we discussed last Tuesday" and miss it.
---
## See also
- [`../../ARCHITECTURE.md`](../../ARCHITECTURE.md) §5 — operational routine (triggers, cadence) in full context.
- [`../../SKILL.md`](../../SKILL.md) — the agent-side Operational Routine section for when an AI agent should suggest running this.