Config-First Automation Settings
Date: 2026-07-10
Status: Revised — this is the agreed direction from the config discussion.
Replaces: The earlier project-config and automation install design.
Decision#
CodeAlmanac will have:
one config file ~/.codealmanac/config.toml
one mutation command codealmanac config set ...
one manual apply codealmanac config apply
one scheduler view codealmanac automation statusWe will remove:
almanac/config.toml
codealmanac automation install
codealmanac automation uninstallReason: nobody has asked for repository-level settings, and automation settings should not bypass the normal config path.
The Problem Today#
config set ...
-> writes ~/.codealmanac/config.toml
automation install/uninstall ...
-> changes launchd directly
-> does not record the choice in config.tomlFor example:
codealmanac automation install sync --every 8hThis updates the macOS schedule, but user config still has no 8h setting.
The New User Experience#
# Change a schedule
codealmanac config set automation.sync.every 8h
# Disable or enable a task
codealmanac config set automation.garden.enabled false
codealmanac config set automation.update.enabled true
# Inspect desired configuration
codealmanac config get automation.sync.every
codealmanac config list
# Inspect actual macOS scheduler state
codealmanac automation statusThe TOML becomes:
auto_commit = true
[harness]
default = "codex"
model = "gpt-5.5"
[automation.sync]
enabled = true
every = "8h"
[automation.garden]
enabled = false
every = "4h"
[automation.update]
enabled = true
every = "24h"Big Picture#
Config CLI / SetupService
|
v
ConfigService --------------------+
| |
v v
ConfigStore AutomationService
(user TOML) |
v
SchedulerAdapter
|
v
LaunchdSchedulerAdapter| Part | Owns |
|---|---|
| Config CLI | Parses list/get/set/apply. |
ConfigService |
Config meaning, validation, and deciding whether an automation effect is required. |
ConfigStore |
Reading and updating the one TOML file. |
AutomationService |
Turning explicit task settings into install/remove operations. |
LaunchdSchedulerAdapter |
Plists and launchctl. |
SetupService |
Collecting onboarding choices and sending one config update. |
No watcher, daemon, second scheduler, or event framework is added.
Flow 1: config set#
codealmanac config set automation.sync.every 8h1. ConfigService parses 8h.
2. ConfigStore writes it to config.toml.
3. ConfigService reloads and validates the complete UserConfig.
4. ConfigService sees this is a Sync automation key.
5. AutomationService reconciles Sync.
6. launchd is rewritten and reloaded with an 8-hour interval.Pseudocode:
def set(request):
value = parse_config_value(request.key, request.value)
store.set_value(request.key, value)
config = load_user() # complete validation
task = automation_task_for_key(request.key)
if task is not None:
automation.reconcile_task(
task=task,
settings=config.automation.for_task(task),
)For a normal key such as auto_commit, the last step is absent:
store.set_value("auto_commit", False)
load_user()
# no launchd effectFlow 2: Enable And Disable#
codealmanac config set automation.garden.enabled falsedef reconcile_task(task, settings):
job = jobs.job_for_task(task, settings.every)
if settings.enabled:
scheduler.install(job)
else:
scheduler.uninstall(job)There is no hidden “empty tasks means all tasks” behavior. Every request names one task and supplies its complete settings.
Flow 3: Someone Edits TOML Directly#
Saving the file alone cannot reload launchd without a watcher. We do not want a watcher.
The person or agent runs:
codealmanac config applydef apply():
config = load_user()
for task in (SYNC, GARDEN, UPDATE):
automation.reconcile_task(
task=task,
settings=config.automation.for_task(task),
)Documentation and agent instructions will say:
Prefer `config set` for automation changes.
If you edit config.toml directly, run `config apply` afterward.Flow 4: Setup#
Setup currently writes config and installs automation through separate paths. That duplication will be removed.
def setup(request):
config.update(
UserConfigUpdate(
auto_commit=request.auto_commit,
harness=HarnessConfig(request.harness, request.model),
automation=AutomationConfig(
sync=TaskConfig(not request.sync_off, request.sync_every),
garden=TaskConfig(not request.garden_off, request.garden_every),
update=TaskConfig(request.auto_update, request.update_every),
),
)
)ConfigService.update(...) performs one TOML write, validates once, and
reconciles all three tasks. Setup no longer constructs a separate automation
install request.
Flow 5: Uninstall#
Full uninstall ignores stored enabled values:
def uninstall():
automation.remove_all() # Sync, Garden, Update
instructions.remove()
global_state.remove() # includes user config
package.remove()Repository almanac/ wiki content is never deleted.
Why Remove Repository Config#
Current precedence is unnecessarily complicated:
defaults -> user config -> project config -> CLINew precedence:
defaults -> user config -> CLIRemoving almanac/config.toml also removes:
- repository selection during config loading;
- uncertainty about which file
config setchanges; - the possibility of a repository selecting an unavailable local runner;
- the need for separate
UserConfigandProjectConfigmodels; - the risk of repository config controlling machine-wide automation.
One-off repository choices remain:
codealmanac garden --using claudeIf a real persistent repository setting is requested later, design it from that use case instead of preserving speculative machinery.
Existing repository config files#
This is a clean breaking removal:
existing almanac/config.toml files are no longer readDo not add detection, migration, warnings, fallback reads, or automatic
deletion. The release changelog will state that repository config was removed
and that persistent settings now belong only in
~/.codealmanac/config.toml.
Desired State Versus Actual State#
config.toml desired configuration
launchd actual installed stateNormal config set keeps them aligned.
If a direct edit is invalid:
config apply fails
launchd remains unchangedIf TOML is valid but launchd fails:
the desired TOML remains saved
launchd retains its previous actual state
the command reports that apply failed
the user retries `codealmanac config apply`Do not fake a transaction or rollback across a file and macOS launchd.
Models We Add#
class TaskAutomationConfig:
enabled: bool
every: timedelta
class AutomationConfig:
sync: TaskAutomationConfig
garden: TaskAutomationConfig
update: TaskAutomationConfig
class UserConfig:
auto_commit: bool
harness: HarnessConfig
automation: AutomationConfigDefaults:
Sync enabled, 5h
Garden enabled, 4h
Update enabled, 24hNew keys:
automation.sync.enabled
automation.sync.every
automation.garden.enabled
automation.garden.every
automation.update.enabled
automation.update.everyAutomation receives a config-independent request:
class ReconcileAutomationTaskRequest:
task: AutomationTask
enabled: bool
every: timedeltaAutomationService does not import TOML or UserConfig.
Exact File Changes#
| File | Change |
|---|---|
services/config/models.py |
Add automation models, defaults, validation, six keys, and apply results. |
services/config/requests.py |
Remove LoadConfigRequest; add apply and batch user-update requests. |
services/config/store.py |
Load one user file; support nested automation tables and one-write batch updates. |
services/config/service.py |
Remove repository/project merging; add set, update, and apply reconciliation. |
services/automation/requests.py |
Replace overloaded install request with explicit task reconciliation. |
services/automation/service.py |
Add reconcile_task, keep status, add full remove_all. |
services/automation/jobs.py |
Build a job from an explicit task and interval; remove interval special cases. |
services/automation/selection.py |
Delete empty-means-all and Garden-only disable policy; delete if no longer needed. |
cli/parser/config.py |
Add six keys and config apply. |
cli/dispatch/config_command.py |
Route list/get/set/apply only through ConfigService. |
cli/render/config.py |
Render durations and apply outcomes in human/JSON formats. |
cli/parser/automation.py |
Remove install/uninstall; retain status. |
cli/dispatch/automation.py |
Remove mutation dispatch; retain status. |
services/setup/* |
Build one complete user-config update; remove parallel automation-install policy. |
cli/dispatch/setup*.py |
Use existing user values as defaults and pass one resolved setup request. |
app.py |
Wire AutomationService into ConfigService; remove repositories from config wiring. |
| lifecycle CLI dispatch | Load only user config; retain --using command overrides. |
Delete all helpers, imports, tests, and docs for:
PROJECT_CONFIG_NAME
project_config_path(...)
config_source_paths(...)
almanac/config.toml
project-over-user precedenceDo not leave compatibility readers or aliases.
Tests#
Config#
- defaults and all six TOML keys;
- duration parsing and rejection of invalid/zero/negative values;
setreconciles the affected automation task;- non-automation
setdoes not call automation; applyreconciles all tasks;- invalid direct TOML makes no scheduler calls;
- batch setup update writes once and reconciles once;
- launchd failure keeps desired TOML and returns a clear error;
- remove project-precedence tests.
Automation#
- enabled installs with the explicit interval;
- disabled uninstalls;
- no empty-means-all behavior;
- status remains read-only;
- full uninstall removes all three jobs;
- launchd plist/
launchctlbehavior is preserved.
CLI and setup#
- human and JSON forms of list/get/set/apply;
- removed install/uninstall commands are absent and rejected;
- setup uses existing user values as defaults;
- setup flags override them and persist the final values;
- setup uses the config path, not a second automation path;
- tests use isolated HOME and a fake scheduler.
Architecture#
- TOML imports stay inside
services/config; - CLI never calls launchd;
ConfigServiceknowsAutomationService, not launchd;AutomationServiceknowsSchedulerAdapter, not config;- no project-config or empty-task fallback remains.
README And Documentation#
Update README.md to:
- name
~/.codealmanac/config.tomlas the only config file; - show the automation TOML;
- replace automation install/uninstall examples with
config set; - document
config applyafter direct edits; - retain
automation statusfor actual state; - explain that setup writes config and applies launchd;
- clearly state that saving TOML alone does not reload launchd.
Add the breaking change to the release changelog:
Removed repository-level almanac/config.toml support. CodeAlmanac now reads
configuration only from ~/.codealmanac/config.toml.There is no changelog file in the repository today. Create CHANGELOG.md as
part of the release/documentation work if one has not been established before
this slice lands.
Also update:
almanac/reference/config-keys.md
almanac/reference/local-state-layout.md
almanac/architecture/setup/automation-and-update.md
almanac/guides/setup-local-automation.md
almanac/decisions/auto-commit-is-prompt-policy.md
shipped prompts/manual resources mentioning almanac/config.tomlTeach agents to use config set, or config apply after a direct edit.
Update docs/python-port-live-agreement.md only after implementation and review
confirm the final contract.
Scope Guard#
Do not add:
- project config;
- watchers or daemons;
- automatic apply during unrelated commands;
- non-macOS scheduler work;
- persistent unrelated CLI preferences;
- config/launchd rollback machinery;
- compatibility aliases or legacy config paths;
- project-config detection or migration warnings;
- silent recovery from invalid TOML.
Verification#
uv run pytest \
tests/test_config_service.py \
tests/test_automation_service.py \
tests/test_setup_service.py \
tests/test_cli.py \
tests/test_architecture.py -q
uv run pytest
uv run ruff check .
codealmanac validateNever touch the real user config or LaunchAgents in tests.
Review Focus#
- Does every persistent automation mutation go through config?
- Does direct editing require an explicit, documented
config apply? - Is all project-config machinery gone?
- Is the removal documented only as a breaking changelog entry, without a runtime migration guard?
- Are automation requests explicit?
- Are CLI, config, automation, and launchd boundaries obvious?
- Did any watcher, fallback, or compatibility path slip in?
Write must-fix, should-fix, and consider findings in the required separate review-fix plan.