# REPORT — self-host core: `harness loop` replaces the bash stack — 2026-08-28 21:31 Deliverable for "get to be self hosting asap — smallest core that can bootstrap itself (redmine/gitea interaction fine, discourse later)". Pushed to ukrrs/MOPAC main as `86c39c8` (module rename) → `c869ac1` (keyproxy/config) → `ecc0ee8` (loop daemon) → `2901bb8` (docs + dev.sh loop runner). ## What runs now The MOPAC harness (Go, stdlib only, built in the digest-pinned Docker builder — host stays toolchain-free) drives ITSELF. Redmine is the SoR, the conductor is the worker. Per issue in the released scope whose (id, updated_on) has not been processed: 1. one bounded turn (same conductor `harness once` uses; sequential, one at a time — v0, no concurrency knob yet); 2. REPORT file lands in `reports/` (as before); 3. REPORT body is POSTed back onto the issue as a Redmine journal note (the previously-stubbed note writeback is now implemented); 4. status transitions per `[redmine.status_map]` (default shipped: `"In Progress" = "Done"`; names resolved via `/issue_statuses.json`); 5. optional Gitea REPORT commit (`[gitea] commit_reports`, OFF by default); 6. dedup marker refreshed to the post-writeback `updated_on`, so the loop's own note never re-triggers itself (self-retrigger hazard handled + test-asserted). State: append-only `state/loop/loop.jsonl` — one JSON line per action (dispatch / report / note / status / commit / refresh / error), dedup index rebuilt at startup, restarts keep exactly-once-reaction semantics. One line per action goes to stdout as well. A FAILED turn is recorded, NOT retried (update the issue to re-release it) — a down proxy cannot hot-loop the poll. Secrets: `key_ref = "mpk:mpk-redmine"` style refs resolve through mopac-keyproxy `POST /v1/resolve` (bearer from `[keyproxy] token_ref`, 60s in-memory cache). `env:`/`file:`/`literal:` refs keep working, so the loop runs with or without keyproxy up. No hosts hardcoded — all in harness.toml. Also folded in: module renamed `git.knownelement.com/reachableceo/MOPAC/harness` → `ukrrs.com/mopac/harness` (the doc-rot finding), verified build+vet+test in the builder. Tests: loop end-to-end against a fake Redmine (mutable: notes bump updated_on like the real SoR) + fake OpenAI server — dispatch/note/ status happy path, dedup on rescan, re-dispatch on issue update, failed turn without hot-loop, dry-run zero-trace, restart dedup survival, status-map miss, gitea create/update, keyproxy 401/404/cache. Full `./dev.sh check` (build + vet + test) clean on go1.26. ## Exact command to start the loop From `/home/reachableceo/projects/meta/MOPAC/harness` (harness.toml is already in place; it is gitignored): ./dev.sh build HARNESS_REDMINE_KEY= HARNESS_LITELLM_KEY= \ ./dev.sh loop That runs the daemon in the Docker builder container with the repo bind-mounted (REPORTs + state land in the repo). Verify first with a zero-cost scan: HARNESS_REDMINE_KEY= HARNESS_LITELLM_KEY= \ ./dev.sh loop --dry-run # prints "would dispatch " per new issue, no turns, no state ./dev.sh loop --once # single scan (cron-able), exit 2 if the intake itself fails Stop: Ctrl-C (SIGINT) in the foreground, or `docker stop mopac-loop`. Interval default 120s (`[loop] poll_interval_secs`), override with `-interval 60s`. With keyproxy up instead of env keys: `[keyproxy] url/token_ref` in harness.toml + `mpk:` refs (see harness.toml.example, all commented). ## What it replaces — the die list (NOT deleted yet, pending your sign-off) 1. `/home/_crossfeed/tooling/agent-stack/semaphore.sh` + slot-file concurrency cap → replaced by the loop's in-process discipline (v0: one turn at a time; concurrency knob later if load justifies). 2. `~/.coordinate/scripts/queue-next.sh`, `queue-after.sh`, `pmo-heartbeat.sh` → replaced by the loop + its own wake discipline (poll interval; `--once` for cron). The heartbeat screen (`pmo-heartbeat`, PID 2924085) can be killed whenever you say so. 3. `dispatch-turn.sh` doorbell `screen -S reachableceo-PMO -X stuff` — note: ALREADY dead. The screen is named `RCEO-PMO`, so the message was silently dropped all along. Mentioned for the record; deletion after your sign-off. Nothing in `/home/_crossfeed` or `~/.coordinate/scripts` was touched this turn. `harness once` (manual/chained path) also unchanged in behavior — file-only writeback, no notes. ## Rollback Just stop the loop (`Ctrl-C` / `docker stop mopac-loop`). The bash stack is fully present and untouched — relaunch it exactly as before. The only new artifacts are `state/loop/loop.jsonl` (delete to forget all dedup history) and REPORT files. No config the old stack reads was modified. ## Suggested verification (PMO) 1. `./dev.sh loop --dry-run` with the real Redmine — should list the released-scope issues it would dispatch, zero LLM calls. 2. Pick one low-stakes issue, drop it to "In Progress" in scope, run `./dev.sh loop --once` with keys: expect turn → REPORT file → journal note on the issue → status → Done. 3. Check `state/loop/loop.jsonl` for the one-line-per-action trail. — Work stack, 2026-08-28 ~22:30