5.2 KiB
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:
- one bounded turn (same conductor
harness onceuses; sequential, one at a time — v0, no concurrency knob yet); - REPORT file lands in
reports/(as before); - REPORT body is POSTed back onto the issue as a Redmine journal note (the previously-stubbed note writeback is now implemented);
- status transitions per
[redmine.status_map](default shipped:"In Progress" = "Done"; names resolved via/issue_statuses.json); - optional Gitea REPORT commit (
[gitea] commit_reports, OFF by default); - 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=<key> HARNESS_LITELLM_KEY=<vertical virtual 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=<key> HARNESS_LITELLM_KEY=<key> \
./dev.sh loop --dry-run # prints "would dispatch <id>" 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)
/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).~/.coordinate/scripts/queue-next.sh,queue-after.sh,pmo-heartbeat.sh→ replaced by the loop + its own wake discipline (poll interval;--oncefor cron). The heartbeat screen (pmo-heartbeat, PID 2924085) can be killed whenever you say so.dispatch-turn.shdoorbellscreen -S reachableceo-PMO -X stuff— note: ALREADY dead. The screen is namedRCEO-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)
./dev.sh loop --dry-runwith the real Redmine — should list the released-scope issues it would dispatch, zero LLM calls.- Pick one low-stakes issue, drop it to "In Progress" in scope, run
./dev.sh loop --oncewith keys: expect turn → REPORT file → journal note on the issue → status → Done. - Check
state/loop/loop.jsonlfor the one-line-per-action trail.
— Work stack, 2026-08-28 ~22:30