Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions .almanac/pages/almanac-doctor.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,16 @@ files:
- src/commands/doctor-checks/wiki.ts
- src/commands/doctor-checks/updates.ts
- src/commands/doctor-checks/probes.ts
- src/install/ephemeral.ts
- src/commands/setup.ts
- src/abi-guard.ts
- test/doctor.test.ts
sources:
- docs/plans/2026-04-30-doctor-refactor.md
- docs/bugs/codealmanac-known-bugs.md
- docs/plans/2026-05-14-windows-support.md
- /Users/kushagrachitkara/.codex/sessions/2026/05/12/rollout-2026-05-12T00-52-10-019e1b2c-0679-7bb0-a926-b8643aa710c1.jsonl
verified: 2026-05-13
verified: 2026-05-14
status: active
---

Expand All @@ -41,12 +43,16 @@ The install section currently reports:
- install-path detection, including whether the current binary is running from an ephemeral `npx`-style location
- `better-sqlite3` native-binding readiness
- Claude authentication state
- whether scheduled capture automation is installed
- whether scheduled capture automation is installed for the active platform
- whether Claude guide files exist under `~/.claude/`
- whether `~/.claude/CLAUDE.md` contains the Almanac import line

[[src/commands/doctor-checks/install.ts]] expresses repairs as `fix: "run: ..."` strings. The command prints those hints, but it does not execute them.

The automation check is platform-aware as of the Windows support work. macOS checks the launchd capture plist at `~/Library/LaunchAgents/com.codealmanac.capture-sweep.plist`; Windows validates the Task Scheduler manifest at `~/.almanac/automation/windows-capture-sweep.json` and then confirms the recorded task name exists with `schtasks /Query`. A stale or malformed manifest is reported as a repairable problem, because otherwise doctor can say automation is healthy after a partial install or manual Task Scheduler deletion.

Install-path classification is shared with setup through [[src/install/ephemeral.ts]]. That helper normalizes slashes and case before checking npm npx, pnpm dlx, `/tmp`, `/var/folders`, and Windows temp directories such as `%TEMP%`. Without that shared helper, setup and doctor could disagree about whether a Windows `npx` install is durable enough for scheduler installation.

## Relationship to the SQLite ABI guard

[[install-time-node-launcher]] now reduces the most common mismatch path by pinning published bins to the installing Node executable. [[src/abi-guard.ts]] still fails early when `better-sqlite3` cannot load under the current Node ABI and prints an exact rebuild command. Doctor surfaces the same failure class as structured install state: `install.sqlite` reports whether the binding loads and points users at `npm rebuild better-sqlite3` when it does not.
Expand Down
27 changes: 22 additions & 5 deletions .almanac/pages/automation.md
Original file line number Diff line number Diff line change
@@ -1,32 +1,37 @@
---
title: Automation
summary: Automation is the macOS launchd layer that schedules `almanac capture sweep` and `almanac garden`, while capture eligibility and dedupe stay inside Almanac-owned state.
summary: Automation is the platform scheduler layer that wakes `almanac capture sweep` and `almanac garden`, while capture eligibility and dedupe stay inside Almanac-owned state.
topics: [automation, cli, flows]
files:
- src/commands/automation.ts
- src/commands/automation/windows.ts
- src/install/ephemeral.ts
- src/commands/setup.ts
- src/commands/uninstall.ts
- src/cli.ts
- src/cli/register-setup-commands.ts
- src/cli/register-wiki-lifecycle-commands.ts
- .github/workflows/ci.yml
- src/commands/capture-sweep.ts
- src/update/config.ts
- test/automation.test.ts
- test/codex-harness-provider.test.ts
- test/cli.test.ts
- test/uninstall.test.ts
sources:
- docs/plans/2026-05-11-scheduled-quiet-session-capture.md
- docs/plans/2026-05-14-windows-support.md
status: active
verified: 2026-05-13
verified: 2026-05-14
---

# Automation

Automation is the scheduler layer around Almanac's recurring maintenance work. In the current product shape, that means two launchd jobs on macOS: one wakes `almanac capture sweep`, and the other wakes `almanac garden`. The scheduler decides when Almanac starts. Almanac still decides what to capture, whether a wiki needs gardening, and how job state is recorded.
Automation is the scheduler layer around Almanac's recurring maintenance work. The current implementation has two platform adapters: macOS writes launchd jobs, and Windows writes Task Scheduler tasks through `schtasks`. In both cases one scheduler entry wakes `almanac capture sweep`, and the other wakes `almanac garden`. The scheduler decides when Almanac starts. Almanac still decides what to capture, whether a wiki needs gardening, and how job state is recorded.

## Public command surface

`almanac automation install|status|uninstall` is the explicit scheduler-management surface. `install` writes launchd plists, bootstraps them with `launchctl`, and prints the effective capture interval, quiet window, activation timestamp, commands, and plist paths. `status` reads the plist files back and reports whether capture and Garden automation are installed. `uninstall` unloads and removes whichever CodeAlmanac plists exist.
`almanac automation install|status|uninstall` is the explicit scheduler-management surface. On macOS, `install` writes launchd plists, bootstraps them with `launchctl`, and prints the effective capture interval, quiet window, activation timestamp, commands, and plist paths. On Windows, `install` calls `schtasks /Create` and writes local manifests under `~/.almanac/automation/` so status and doctor can report the installed task names without shelling out. `status` reads the platform-owned record, and `uninstall` removes the scheduled capture and Garden entries for the active platform.

`almanac setup` is the onboarding entry point for the same automation surface. Setup installs scheduled capture and scheduled Garden by default unless the user passes `--skip-automation` or `--garden-off`. That makes automation a first-run product behavior rather than a hidden expert-only command.

Expand All @@ -40,6 +45,18 @@ Both jobs get an explicit `PATH` assembled for launchd from the current environm

There are two command-path modes. Direct `almanac automation install` writes absolute `ProgramArguments` for the current Node executable and resolved `dist/codealmanac.js` entrypoint. Setup uses a stricter rule when it was launched from ephemeral `npx`: it installs automation only after a durable global install succeeds, then writes `/usr/bin/env almanac ...` commands instead of pinning launchd to the transient cache path.

## Windows Task Scheduler contract

On Windows, capture uses the task name `\CodeAlmanac\CaptureSweep`, and Garden uses `\CodeAlmanac\Garden`. `runAutomationInstall({ platform: "win32" })` maps minute-sized intervals to `schtasks /Create /SC MINUTE /MO <minutes>` and whole-day intervals to `/SC DAILY /MO <days>`. The default capture cadence (`5h`) is therefore a 300-minute task, and the default Garden cadence (`2d`) is a two-day task.

The Windows adapter stores manifests at `~/.almanac/automation/windows-capture-sweep.json` and `~/.almanac/automation/windows-garden.json`. Those files are local scheduler metadata, not capture state. They record the task name, command, interval seconds, quiet window where applicable, and the Garden working directory. Doctor validates the capture manifest and checks the Task Scheduler task with `schtasks /Query`; it no longer checks a launchd plist on that platform.

Garden needs a scheduler working directory because `almanac garden` resolves the target wiki by walking upward from `cwd`. Task Scheduler does not have the launchd-style `WorkingDirectory` field in the simple `schtasks /Create` CLI surface, so Windows Garden wraps the command with `cmd.exe /d /s /c "cd /d <wiki-root> && <almanac garden command>"`. Without that wrapper, scheduled Garden starts from Task Scheduler's default directory and fails to find `.almanac/`.

Setup also changes the durable-global command shape on Windows. After an ephemeral `npx` setup successfully installs the package globally, scheduled commands use npm's Windows command shim (`almanac.cmd ...`) instead of `/usr/bin/env almanac ...`. Both setup's global install helper and the bare `codealmanac` bootstrap use `cmd.exe /d /s /c npm.cmd ...` on Windows because Node cannot directly launch `.cmd` shims without a shell or explicit `cmd.exe`.

The repository verifies this path in GitHub Actions with a matrix over `ubuntu-latest` and `windows-latest` on Node 20 and Node 22. Keep platform-specific scheduler tests explicit about `platform: "darwin"` or `platform: "win32"` rather than inheriting `process.platform`; otherwise a Windows runner will correctly take the Task Scheduler branch while a macOS-oriented test is still asserting launchd plist behavior. Fake command-line binaries in tests need the same split: extensionless executable scripts work on Unix-like runners, while Windows needs a `.cmd` shim on the executable search path. When a test mutates the path on Windows, update and restore the `Path` key as well as `PATH`; spawned child processes may ignore a newly-added uppercase `PATH` when the original environment uses `Path`.

## What the scheduler owns and what it does not

The scheduler owns wakeup cadence and command invocation. It does not own transcript eligibility, cursor state, or capture dedupe. Those remain inside Almanac and are described by [[capture-flow]], [[capture-automation]], and [[capture-ledger]].
Expand All @@ -56,4 +73,4 @@ The install path validates its duration flags instead of silently falling back t

Current automation is scheduler-first, but setup and uninstall still run private cleanup for older provider hook installs. `cleanupLegacyHooks()` removes CodeAlmanac-owned `almanac-capture.sh` commands from observed Claude, Codex, and Cursor hook files and deletes the old Claude shell script path when present. [[sessionend-hook]] keeps the historical shapes and rationale for that migration boundary.

`almanac uninstall` removes both launchd jobs unless the user passes `--keep-automation`. That keeps automation cleanup aligned with the broader global-install cleanup described in [[global-agent-instructions]].
`almanac uninstall` removes both platform scheduler jobs unless the user passes `--keep-automation`. That keeps automation cleanup aligned with the broader global-install cleanup described in [[global-agent-instructions]].
15 changes: 10 additions & 5 deletions .almanac/pages/capture-automation.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ summary: CodeAlmanac's auto-capture contract is scheduler-backed quiet-session c
topics: [flows, agents, cli, automation]
files:
- docs/plans/2026-05-11-scheduled-quiet-session-capture.md
- docs/plans/2026-05-14-windows-support.md
- src/commands/capture-sweep.ts
- src/commands/automation.ts
- src/commands/automation/windows.ts
- src/update/config.ts
- src/commands/session-transcripts.ts
- src/commands/operations.ts
Expand All @@ -17,8 +19,9 @@ files:
- test/automation.test.ts
sources:
- /Users/kushagrachitkara/.codex/sessions/2026/05/11/rollout-2026-05-11T14-32-08-019e18f4-5e73-7790-ba49-73cc02544a58.jsonl
- docs/plans/2026-05-14-windows-support.md
status: implemented
verified: 2026-05-13
verified: 2026-05-14
---

# Capture Automation
Expand Down Expand Up @@ -224,7 +227,7 @@ The session explicitly rejected the idea that CodeAlmanac itself should keep a s

- macOS: `launchd`, likely via `~/Library/LaunchAgents/com.codealmanac.capture-sweep.plist`
- Linux: `systemd --user` timer, with cron as fallback
- Windows: Task Scheduler later if Windows support is added
- Windows: Task Scheduler tasks created with `schtasks`

The key implementation invariant is that the scheduler only invokes the CLI. It should not embed transcript-discovery or capture logic itself.

Expand All @@ -236,7 +239,7 @@ That keeps the system debuggable:

The same session tightened that separation one step further: the scheduler entry should only need wakeup-level state such as interval, command path, and log paths. Sweep behavior such as enabled apps, quiet window, and other capture defaults should live in CodeAlmanac-owned config that `almanac capture sweep` reads when it starts. For the first version, even that split can stay minimal: the wakeup cadence may still be the only scheduler-owned knob, and changing it would rewrite or reload the platform scheduler entry.

The current macOS implementation now follows that stronger shape, but with one setup-time distinction. Direct installs still write launchd `ProgramArguments` as the absolute Node executable plus the resolved `dist/codealmanac.js` entrypoint, then append `capture sweep`. Setup switches to `/usr/bin/env almanac ...` only after it has first converted an ephemeral `npx` launch into a durable global install. If that durable install does not happen, setup leaves automation uninstalled instead of writing a launchd entry that points into the temporary `npx` cache.
The current platform implementations now follow that stronger shape, with one setup-time distinction. Direct macOS installs still write launchd `ProgramArguments` as the absolute Node executable plus the resolved `dist/codealmanac.js` entrypoint, then append `capture sweep`. Direct Windows installs create Task Scheduler tasks and record manifests under `~/.almanac/automation/`. Setup switches to the platform's durable global command only after it has first converted an ephemeral `npx` launch into a durable global install: `/usr/bin/env almanac ...` on Unix-like shells, and `almanac.cmd ...` on Windows. If that durable install does not happen, setup leaves automation uninstalled instead of writing a scheduler entry that points into the temporary `npx` cache.

That direct-install shape has one operational consequence that surfaced during the 2026-05-13 Garden smoke test. If launchd is pinned to an absolute Node path such as `~/.nvm/versions/node/v24.15.0/bin/node`, rebuilding `better-sqlite3` from a shell running a different Node version repairs the shell runtime, not the scheduled job. The reliable fix is to rebuild with the exact `node` or `npm` from the plist command path, or to prepend that Node version's bin directory to `PATH` before running `npm rebuild better-sqlite3`.

Expand Down Expand Up @@ -343,7 +346,7 @@ The scheduler mechanism is platform-owned:

- macOS: `launchd` agent under `~/Library/LaunchAgents/`
- Linux: `systemd --user` timer, with cron as a weaker fallback
- Windows: Task Scheduler if Windows support is added later
- Windows: Task Scheduler tasks via `schtasks`

The agent-facing command discussed in the session was conceptually a sweep such as `almanac capture sweep --quiet 45m`, run by the platform scheduler on a configurable cadence. Early in the discussion that cadence was imagined as every few minutes; later turns settled on "default around five hours, user-configurable" for the first implementation.

Expand Down Expand Up @@ -435,7 +438,9 @@ The Garden plist owns graph-maintenance cadence, using `StartInterval = 172800`
- working directory: the nearest wiki root found from the install command's current directory
- stdout/stderr: user-visible log files under the same CodeAlmanac-owned log directory

`launchd` is the thing that stays resident. When the timer fires, macOS starts a new `almanac` process, waits for it to exit, and then returns to sleeping until the next interval. The same separation should hold on Linux with a `systemd --user` timer and later on any Windows scheduler support.
On Windows, scheduler install creates Task Scheduler tasks named `\CodeAlmanac\CaptureSweep` and `\CodeAlmanac\Garden`. The capture default maps to `/SC MINUTE /MO 300`, while the Garden default maps to `/SC DAILY /MO 2`. CodeAlmanac stores task manifests under `~/.almanac/automation/` for local status and doctor output; those manifests are not capture ledger state.

`launchd` or Task Scheduler is the thing that stays resident. When the timer fires, the OS starts a new `almanac` process, waits for it to exit, and then returns to sleeping until the next interval. The same separation should hold on Linux with a `systemd --user` timer.

This separation is part of the product contract, not just an implementation convenience:

Expand Down
2 changes: 1 addition & 1 deletion .almanac/pages/capture-flow.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,4 +164,4 @@ Raw provider events are normalized and written to `.almanac/runs/<run-id>.jsonl`

## Scheduled automation

Automatic capture is scheduler-driven. `almanac automation install` writes a macOS launchd plist that runs `almanac capture sweep` every 5h by default, alongside the separate scheduled Garden plist described in [[capture-automation]]. Sweep applies quiet-window and ledger rules in-process, so launchd is only the wakeup mechanism; it does not own capture state.
Automatic capture is scheduler-driven. `almanac automation install` writes a platform scheduler entry that runs `almanac capture sweep` every 5h by default, alongside separate scheduled Garden automation described in [[capture-automation]]. macOS uses launchd plists; Windows uses Task Scheduler tasks plus manifests under `~/.almanac/automation/`. Sweep applies quiet-window and ledger rules in-process, so the OS scheduler is only the wakeup mechanism; it does not own capture state.
5 changes: 3 additions & 2 deletions .almanac/pages/global-agent-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ files:
- src/commands/uninstall.ts
- src/commands/doctor-checks/install.ts
- src/agent/providers/codex-instructions.ts
- src/harness/providers/codex.ts
- test/setup.test.ts
- test/uninstall.test.ts
- test/doctor.test.ts
Expand All @@ -16,7 +17,7 @@ sources:
- /Users/kushagrachitkara/.codex/sessions/2026/05/12/rollout-2026-05-12T20-25-14-019e1f5d-ff59-7ee1-a73b-836277d8092b.jsonl
- /Users/kushagrachitkara/.codex/sessions/2026/05/13/rollout-2026-05-13T13-34-26-019e230c-4437-7422-9e8d-b7caa9b592fc.jsonl
status: active
verified: 2026-05-13
verified: 2026-05-14
---

# Global Agent Instructions
Expand Down Expand Up @@ -81,4 +82,4 @@ The same session also confirmed the reinstall path from a markdown-only reset: a

[[src/commands/doctor-checks/install.ts]] currently verifies only the Claude-side artifacts through `install.guides` and `install.import`. There is no Codex-specific doctor check yet, so debugging "Codex is not seeing Almanac guidance" still requires reading `~/.codex/AGENTS.override.md` and `~/.codex/AGENTS.md` directly and checking which file is active.

The provider-status path adds one more practical split for Codex debugging. [[src/harness/providers/codex.ts]] treats Codex as installed only when the `codex` executable is visible on `PATH`; otherwise it reports `codex not found on PATH` before any AGENTS-file logic matters. A support triage for "Codex works in one place but Almanac cannot see it" should therefore start with `which codex` and `codex --version`, then move on to which of `~/.codex/AGENTS.override.md` or `~/.codex/AGENTS.md` is active.
The provider-status path adds one more practical split for Codex debugging. [[src/harness/providers/codex.ts]] treats Codex as installed only when the `codex` executable is visible on `PATH`; otherwise it reports `codex not found on PATH` before any AGENTS-file logic matters. On Unix-like systems, status checks use `command -v codex`; on Windows they use `where codex`, and actual Codex process launches run through the Windows shell so npm command shims such as `codex.cmd` are executable. A support triage for "Codex works in one place but Almanac cannot see it" should therefore start with `which codex`/`where codex` and `codex --version`, then move on to which of `~/.codex/AGENTS.override.md` or `~/.codex/AGENTS.md` is active.
Loading
Loading