docs + dev.sh: loop in README/REPORT, ./dev.sh loop runner, scan-failure exit codes
README gains the self-host loop section (flow diagram, config reference
rows for [loop]/[redmine.status_map]/[keyproxy]/[gitea], CLI flags,
status table); REPORT.md records phase 3a including the die list for the
bash stack it replaces. dev.sh grows a `loop` runner (repo bind-mounted,
key env passthrough). `harness loop --once` now exits 2 when the intake
scan itself fails so cron setups alert loudly; daemon mode still logs
and retries.
💘 Generated with Crush
Assisted-by: Crush:glm-5.2
This commit is contained in:
@@ -63,7 +63,6 @@ without a key, so the live run needs exactly one thing:
|
||||
|
||||
- Event → turn dispatch: `harness events` stores + maps events and hands
|
||||
them to `Conductor.DispatchEvent`, which is a printing stub (phase 3).
|
||||
- Redmine issue-note writeback (SoR note after the REPORT) — file only.
|
||||
- Budget/semaphore GATE (LiteLLM spend APIs, class-aware slots) and cost
|
||||
in REPORT (tokens only today).
|
||||
- `bw:` key refs error until the bitwarden wrapper (phase 3).
|
||||
@@ -71,8 +70,8 @@ without a key, so the live run needs exactly one thing:
|
||||
- Session persistence + read-time repair (crush notes §2) — not started.
|
||||
- Write confinement to declared roots + symlink-aware canonicalization;
|
||||
redirects currently match by segment text (phase 3 permission layer).
|
||||
- Task selection is first-in-scope; no P1-first ordering or status
|
||||
transitions, so chained `once` re-picks the same issue (use --task-id).
|
||||
- `once` task selection is first-in-scope (`--task-id` to chain); the
|
||||
loop's dedup + status map supersede this for self-hosted runs.
|
||||
- Local inbox intake (DESIGN core-loop step 1, second half) — not started.
|
||||
|
||||
## Build phase 2b — events receiver (`mopac events`) — 2026-08-28
|
||||
@@ -127,13 +126,78 @@ Stubbed:
|
||||
action; real wiring lands in phase 3 per plan).
|
||||
- Cross-host/event forwarding (T1 replication later; JSONL is local-only).
|
||||
|
||||
## Build phase 3a — self-host core (`harness loop`) — 2026-08-28 ~22:00
|
||||
|
||||
Charles (21:30): "get to be self hosting asap — smallest core that can
|
||||
bootstrap itself (redmine/gitea interaction fine, discourse later)". The
|
||||
harness now drives ITSELF: Redmine is the SoR, the loop is the worker, no
|
||||
bash middle layer.
|
||||
|
||||
Works:
|
||||
|
||||
- **`harness loop` CLI**: the self-host daemon. Polls the `/issues.json`
|
||||
intake every `[loop] poll_interval_secs` (default 120; `-interval`
|
||||
override; `--once` = single scan for cron-style setups; `--dry-run` =
|
||||
scan + print, zero LLM calls, zero state writes — test-asserted).
|
||||
- **Dispatch discipline**: one bounded turn per issue not yet processed at
|
||||
its current `updated_on`, strictly sequential (v0 — the concurrency
|
||||
slot-file cap of the old stack is replaced by "one at a time"; a knob
|
||||
can come later). The dedup marker is written BEFORE the turn, so a
|
||||
failed turn is recorded and never retried (update the issue to
|
||||
re-release it) — a down proxy cannot hot-loop the poll.
|
||||
- **State**: append-only `state/loop/loop.jsonl` (0600, dir 0700), one
|
||||
JSON line per action (dispatch / report / note / status / commit /
|
||||
refresh / error), index rebuilt at startup (torn tail skipped). Restart
|
||||
keeps exactly-once-reaction semantics — test-asserted across a new
|
||||
Conductor instance.
|
||||
- **Redmine note writeback** (was the phase-2 stub): after the turn, the
|
||||
REPORT body is POSTed onto the issue as a journal note
|
||||
(PUT `/issues/{id}.json`), so the SoR carries the result, not just the
|
||||
filesystem.
|
||||
- **Status transitions**: `[redmine.status_map]` (quoted TOML keys, e.g.
|
||||
`"In Progress" = "Done"`); names resolve to ids via
|
||||
`/issue_statuses.json` (fetched once, cached, case-insensitive);
|
||||
unmapped statuses are left alone.
|
||||
- **Self-retrigger prevention**: the loop's own note/status writes bump
|
||||
`updated_on`; after writeback the loop re-fetches the issue and
|
||||
advances its dedup marker to the post-writeback `updated_on`
|
||||
(`refresh` event). If that refresh call fails, the JSONL records the
|
||||
error and one redundant re-dispatch may follow (visible, bounded).
|
||||
- **`mpk:` key refs** (`[keyproxy]`): resolved through the
|
||||
ukrrs/mopac-keyproxy hop — POST `/v1/resolve`, bearer token from a
|
||||
LOCAL `token_ref` (no recursion), 60s in-memory cache, transport
|
||||
errors query-string-stripped so a misconfigured hop cannot echo the
|
||||
token. `env:`/`file:`/`literal:` refs untouched: the loop runs with or
|
||||
without keyproxy up. All hosts in harness.toml, none in code.
|
||||
- **Gitea REPORT commit** (optional, off by default): `[gitea]
|
||||
commit_reports = true` commits each REPORT file right after it lands
|
||||
(contents API: GET for sha → POST create / PUT update, `token` auth,
|
||||
branch from config).
|
||||
- **Module rename**: `git.knownelement.com/reachableceo/MOPAC/harness` →
|
||||
`ukrrs.com/mopac/harness` (the doc-rot finding folded in; build + vet
|
||||
+ test verified in the Docker builder).
|
||||
- **Tests**: loop e2e against a fake Redmine (mutable state: notes bump
|
||||
updated_on like the real SoR) + fake OpenAI server — dispatch/note/
|
||||
status happy path, dedup on rescan, re-dispatch on PMO issue update,
|
||||
failed turn recorded without hot-loop, dry-run zero-trace, restart
|
||||
dedup survival, status-map miss, gitea create/update/auth shapes,
|
||||
keyproxy resolver (cache hit, auto-prefix, 401/404, local refs
|
||||
untouched), writeback unit tests for PUT body shapes.
|
||||
|
||||
Replaced by this turn (die list for the PMO report; deletion pending
|
||||
Charles' sign-off): `/home/_crossfeed/tooling/agent-stack/semaphore.sh` +
|
||||
slot-file cap; `~/.coordinate/scripts/queue-next.sh`, `queue-after.sh`,
|
||||
`pmo-heartbeat.sh`; the dispatch-turn.sh screen doorbell (already dead —
|
||||
it targets screen `reachableceo-PMO`, the live screen is `RCEO-PMO`, so
|
||||
the message was silently dropped).
|
||||
|
||||
## Next chunk (phase 3)
|
||||
|
||||
1. bitwarden-go wrapper + `bw:` refs (unblocks secret posture).
|
||||
2. Full permission layer: tree-sitter bash scopes, write roots, symlink
|
||||
checks, per-vertical allow/deny presets.
|
||||
3. Redmine note writeback + status transition so chaining advances scope.
|
||||
4. Event → turn dispatch wiring: stored actionable events actually chain
|
||||
3. Event → turn dispatch wiring: stored actionable events actually chain
|
||||
conductor iterations for the affected stack.
|
||||
5. Budget gate via LiteLLM spend APIs; cost line in REPORT.
|
||||
6. Streaming with truncation retry + turn resume.
|
||||
4. Budget gate via LiteLLM spend APIs; cost line in REPORT.
|
||||
5. Streaming with truncation retry + turn resume.
|
||||
6. Loop concurrency knob (in-process limit) once load justifies it.
|
||||
|
||||
Reference in New Issue
Block a user