Setup Automation And Update
Setup, automation, and update form CodeAlmanac's machine-level maintenance layer. setup installs local agent instructions and writes one user configuration update. The config service then makes launchd match that saved automation policy. automation owns recurring launchd jobs, while update owns package-manager upgrades for the installed CLI [@setup_service][@automation_service][@updates].
The area matters because scheduled work itself remains local product infrastructure. Sync, Garden, and update run local CodeAlmanac entrypoints. When telemetry is enabled, those entrypoints may emit allowlisted command/lifecycle outcomes or sanitized unhandled exceptions, but they never upload wiki, source, prompt, or transcript content [@live_agreement]. Runtime state and scheduler logs belong under the user's machine state, while repository wiki source remains under almanac/ [@live_agreement]. For the telemetry boundary, see Telemetry. For the operator-facing install and troubleshooting path, see Setup local automation.
Setup Boundary#
SetupService.run builds one complete UserConfig, asks the config service to write it, and receives scheduler reconciliation results from that same update. It also installs instruction targets unless skipped and reports runner readiness [@setup_service]. Setup does not construct a separate automation-install command. Instruction installation is a separate concern with its own per-target mechanics; see Instruction installation.
The request model defaults to both instruction targets, Codex as the harness, auto-commit enabled, and all three automation tasks enabled. It accepts setup-time interval and disable controls and validates positive durations [@setup_requests].
Setup's automation policy lives outside the service. The default tasks are sync, Garden, and update. selected_setup_tasks reflects the three enable/disable choices, which are persisted in TOML before scheduler reconciliation [@setup_automation].
Interactive onboarding explains Sync and Garden where automatic wiki maintenance is selected. On the Product updates step, it also shows a choice-sensitive macOS heads-up before installation: automatic maintenance contributes Sync and Garden, and automatic product updates contributes Update. The notice tells the user that macOS may show one “Background Items Added” notification per enabled task and identifies every item as CodeAlmanac-owned. The completion screen repeats the enabled task count and names after setup [@setup_wizard][@setup_background_items].
Scheduled Jobs#
AutomationService reconciles one task, removes all tasks during full uninstall, and reports status through a SchedulerAdapter port [@automation_service]. Reconciliation installs a job when enabled and uninstalls it when disabled. AutomationJobFactory turns saved task policy into a ScheduledJob [@automation_service][@automation_jobs].
The job factory gives each task concrete local execution details. The resolved codealmanac executable runs sync, update --scheduled, or __garden-scheduler [@automation_jobs]. Intervals come from saved user configuration; defaults are 5 hours for sync and 24 hours for both Garden and update [@automation_jobs].
The macOS implementation writes launchd plists under ~/Library/LaunchAgents, creates stdout and stderr log directories, bootouts any existing job, bootstraps the new job, and reads status back from launchd [@launchd]. The generated plist contains the label, a Program key set to the resolved codealmanac executable, program arguments, start interval, environment variables, RunAtLoad, and log paths [@launchd]. This keeps the service boundary scheduler-neutral while the adapter owns launchd mechanics.
launchctl print output backs richer status than install/uninstall alone. The adapter parses that output into a ScheduledJobState of running, idle, or unknown, plus an optional run count, last exit code, and current PID [@launchd]. automation status renders those fields when present: whether launchd loaded the job, its running/idle state, how many times it has run, whether the last run succeeded or failed with its exit code, and its PID while active [@launchd]. An idle state between scheduled intervals is normal; a nonzero last exit code means the last run failed and its log under ~/.codealmanac/logs/ is the next thing to check.
The default application wiring is launchd-backed. create_services constructs AutomationService with LaunchdSchedulerAdapter, then injects it into the config service. The adapter shells out to launchctl for install, uninstall, and status checks [@app][@launchd]. Config reconciliation is macOS-specific until another scheduler adapter is wired.
Update Safety#
Manual update and scheduled update share the same planning logic. plan_update refuses editable installs, maps uv installs to uv tool upgrade codealmanac, maps pip installs to python -m pip install --upgrade codealmanac, and refuses unknown installers with a suggested manual command [@updates].
Scheduled update adds safety checks before running the package command. It skips if the install is not ready, if an update lock is already held, or if the local database shows active CodeAlmanac jobs [@updates]. After a successful scheduled package update, it runs two smoke checks: codealmanac --version and codealmanac doctor --json [@updates]. Tests cover the uv and pip plans, editable-install refusal, active-job skip, held-lock skip, smoke success, and smoke failure paths [@update_tests].
The lock only guards the scheduled path. UpdatesService.run calls run_scheduled(...), which acquires the lock, only when request.scheduled is set; a manual codealmanac update calls run_plan(...) directly and never touches UpdateLockStore [@updates]. So a manually invoked update can run at the same time as the launchd-triggered update --scheduled job, even though two scheduled invocations cannot overlap under a fresh lock. This asymmetry is unfixed; the lock protects background automation from itself, not manual invocations from automation.
The editable-install guard does not cover every PEP 610 direct-url install. The metadata reader records source_url from direct_url.json, but update_method only uses the editable flag before falling through to the installer-based uv or pip plan [@update_metadata][@updates]. A non-editable local path, VCS, or direct archive install with INSTALLER set to uv or pip can therefore still be upgraded through the normal package command.
This shape matches the active agreement: scheduled auto-update is an explicit local automation task, not a sync or Garden side effect. The agreement also states that scheduled update should skip editable installs, skip active lifecycle jobs, use a global lock, support uv tool and pip installs, and run the same two smoke checks [@live_agreement].
Uninstall#
SetupService.uninstall removes setup-owned instructions, uninstalls automation through the automation service, removes global CodeAlmanac state, and asks the package uninstaller to remove the installed binary when supported [@setup_service]. The live agreement says uninstall is full uninstall and should not expose partial flags such as keeping automation or instructions; it also says uninstall never deletes a repository's almanac/ tree [@live_agreement].
The consequence is a clear ownership line. Machine-level setup can be removed, but repo-owned wiki source stays with the repository. That line supports Only Almanac Root, SQLite store boundaries, and the local-only product decision.