Files
mopac-pmo/archive/REPORT-20260828-2131-selfhost-core.md
T

109 lines
5.2 KiB
Markdown

# 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=<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)
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