From c40872e476efb1a50fc8462f3603fa92b0eaf392 Mon Sep 17 00:00:00 2001 From: autocommit Date: Tue, 9 Jun 2026 03:25:53 -0700 Subject: [PATCH] =?UTF-8?q?docs(docs):=20=F0=9F=93=9D=20Add=20detailed=20a?= =?UTF-8?q?rchitecture=20documentation=20for=20multi-host=20sync=20between?= =?UTF-8?q?=20plum=20and=20apricot=20hosts,=20explaining=20roles,=20workfl?= =?UTF-8?q?ow,=20and=20technical=20specifics?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Lilith Autocommit --- CLAUDE.md | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/CLAUDE.md b/CLAUDE.md index f9bd151..e032110 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -81,6 +81,71 @@ All inference routes through **model-boss coordinator** (port 8210). No direct m - **rag-retrieval** (optional): Contextual retrieval for commit analysis - **git**: All operations via `asyncio.create_subprocess_exec` (no shell) +## Multi-host sync (plum ↔ apricot) + +The same ~68 repos are checked out on two machines. ACS keeps them in sync **and +up to date** in both directions. Internalize this model before touching anything +that pushes, pulls, or discovers repos. + +**Forgejo is the hub — there is no direct host-to-host git.** Both hosts push to +and pull from the same `origin` (`forge.nasty.sh:2222` / `forge.black.local`). +plum talks to apricot only over HTTP (port 8200) for LLM message generation and +commit recording — never git-to-git. + +### Roles + +| Host | What runs | LLM | How | +|------|-----------|-----|-----| +| **apricot** (Fedora, primary) | ACS daemon, `auto-commit-applications.service` (port 8200) | local (model-boss :8210) | Full 11-stage pipeline per repo each cycle | +| **plum** (macOS MacBook) | `commits-tray --commit-local` LaunchAgent | none — forwards to apricot | Scans local repos, gets messages from apricot's `/generate-message`, commits+pushes locally, reports via `/record-commit` | + +### Stay-up-to-date (the pull half) — runs every cycle, even with nothing to commit + +- **apricot**: `pre_cycle_sync()` (`git/operations.py`) per repo — orphan-recover → + `fetch` → if behind **and clean** → `git pull --rebase`. **Dirty trees are never + pulled or stashed** (other agents may be mid-edit); the dirty changes commit this + cycle, push, and the *next* cycle pulls clean. Gated by `pre_cycle_pull=True` + (`config.py`, default on). +- **plum**: `_push_if_safe()` (`tray/local_agent.py`) — `git fetch --quiet` first, + then if clean-but-behind → `git merge --ff-only`. This path runs even when a repo + has no local changes, so plum's clean checkouts self-heal toward `origin`. + +### Stay-in-sync (the push half) + +- **apricot**: pipeline COMMIT → PUSH; on rejection, `git pull --rebase` then retry + (`pipeline/stages/push.py`). +- **plum**: secret **prefilter** strips denylisted files (`tray/prefilter.py`) → + stage *allowed paths only* (never blanket `git add -A`) → message from apricot → + commit → `_push_if_safe` push. Repos with a non-empty staging index are skipped + (don't clobber in-progress manual work). + +### Divergence (both ahead and behind) + +Neither host force-anything. Both hand off to Claude Code recovery — apricot via +`recovery/claude_fallback.py`, plum via `_invoke_claude_recovery` (with a stall +cooldown so a stuck repo isn't retried every cycle). Recovery commands are +allowlisted: no `--force`, `--hard`, `--no-verify`. + +### Branch/remote are config-driven, not hardcoded + +`git_remote` / `git_branch` come from settings/per-directory overrides; each repo +syncs whatever branch its checkout tracks (e.g. this repo: `origin/main`). Don't +assume `master`. + +### Known failure mode — keep these distinct + +1. **Data repos** (the ~68 monitored checkouts): self-heal via the pull/push halves + above. Drift only if `pre_cycle_pull` is off (apricot) or `--commit-local` is + absent from plum's LaunchAgent — **`--commit-local` is OFF by default for + safety**, so a plum tray launched without it neither commits nor fast-forwards, + and its checkouts silently fall behind. +2. **The ACS tooling itself on plum** (the `commits-tray` code plum executes): this + is plum's *local checkout of this repo*, which can drift far behind apricot + (the source of the past "~1000-commit-stale" tray). It does **not** self-heal + like a data repo, because the stale code is what would do the healing. Mitigation + pattern: have plum run apricot's current tray code over SSH rather than its own + local copy. Don't conflate this with data-repo sync. + ## Configuration All settings via env vars prefixed `AUTO_COMMIT_` or `~/.config/commits/startup-config.json`.