109 lines
5.2 KiB
Markdown
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
|