143 lines
7.7 KiB
Markdown
143 lines
7.7 KiB
Markdown
# REPORT — 2026-08-29 15:00 — Bootstrap: runtime container + tool-widening (Redmine 521 + 523)
|
|
|
|
- turn: FINAL crush turn on the harness repo (q15), Charles directive
|
|
"alpha asap, then kill the screen/crush stack; bootstrap the container FIRST"
|
|
- commits pushed to `ukrrs/MOPAC` main: `064aed0` (brief 495 landing),
|
|
`e1b519d` (file tools), `e905515` (loop wiring + bound + heartbeat),
|
|
`02013d3` (runtime container), `19d61a0` (docs)
|
|
- the `mopac-loop` compose service is RUNNING and healthy as of writing;
|
|
it processed alpha task 523 autonomously end-to-end (proof below)
|
|
|
|
## 1. Runtime container (Part A)
|
|
|
|
Files: `deploy/Dockerfile.runtime`, `deploy/compose.yaml` (runbook appended
|
|
to `deploy/runbook.md`, README section added).
|
|
|
|
- Image: digest-pinned `alpine@sha256:143583...` (alpine 3.22) + the
|
|
STATIC release binary (`make release`, CGO off, built in the
|
|
digest-pinned golang builder — never on the host) + `bash` (the bash
|
|
tool / dev.sh), `git` (turn commits), `docker-cli` (dev.sh builder
|
|
client). No ENTRYPOINT: SIGINT graceful stop is Go's; zombie reaping is
|
|
docker's built-in init (`init: true` in compose — docker's tini, zero
|
|
extra packages, image stays digest-pure).
|
|
- Same-path bind trick: the repo is mounted at its HOST path
|
|
(`HARNESS_REPO`, default `/home/reachableceo/projects/meta/MOPAC/harness`)
|
|
so a turn's `./dev.sh build` inside the container bind-mounts a path the
|
|
HOST docker daemon understands — builders remain digest-pinned
|
|
siblings; nothing toolchain-shaped touches the host. `harness.toml`
|
|
overlays read-only (turns can edit the repo, not their own gate).
|
|
- Secrets: `~/.coordinate/secrets/mopac-loop/` (0700): `loop.env` (0600,
|
|
HARNESS_REDMINE_KEY + HARNESS_LITELLM_KEY), container `.gitconfig` +
|
|
`git-credentials` (0600, gitea token via credential store — turn pushes
|
|
authenticate; commits attributed to the vertical).
|
|
- Placement knobs: `HARNESS_REPO`, `HARNESS_SECRETS`, `HARNESS_UID/GID`
|
|
(1001 on this host — the first boot caught the 1000 default and was
|
|
fixed by parameterizing).
|
|
|
|
### Healthcheck design (no port)
|
|
|
|
`harness loop --status-file state/loop/status.json` maintains a heartbeat
|
|
JSON — beats on EVERY scan plus a safety ticker at min(poll/3, 30s), so a
|
|
long turn (up to the 1800s bound) never stales it past the threshold. The
|
|
compose healthcheck is pure mtime age: unhealthy when older than 360s
|
|
(= 3x the default 120s poll; keep in sync if the poll changes). Content:
|
|
`{vertical, pid, started_at, running, scans, dispatched, reports,
|
|
last_scan_at, last_scan_error, last_beat}` — `running:false` on clean
|
|
stop, `last_scan_error` set while Redmine is down but the daemon lives
|
|
(that is deliberately NOT unhealthy: scan errors retry next tick).
|
|
|
|
### Container instructions
|
|
|
|
```sh
|
|
cd ~/projects/meta/MOPAC/harness
|
|
make release # static binary
|
|
docker compose -f deploy/compose.yaml up -d --build # up
|
|
docker compose -f deploy/compose.yaml logs -f mopac-loop
|
|
docker inspect mopac-loop --format '{{.State.Health.Status}}'
|
|
docker compose -f deploy/compose.yaml down # KILL SWITCH
|
|
```
|
|
|
|
Verified live: `state=running restart=unless-stopped health=healthy`.
|
|
|
|
## 2. Tool-widening (Part B, Redmine 521)
|
|
|
|
- File tools (`internal/tools/files.go`): `read` (numbered lines,
|
|
offset/limit, binary refusal, byte cap), `edit` (exact-match; unique
|
|
unless `replace_all`; never writes on failure), `write` (NEW files only
|
|
— no clobbering), `ls`, `glob` (`**` support, mtime-sorted, capped),
|
|
`grep` (RE2, mtime-sorted file list, capped). Deny-first like the bash
|
|
gate: anything outside `work_root` — absolute, `..`, or symlink escapes
|
|
(canonicalized via deepest existing ancestor) — returns `ErrDenied`,
|
|
counted and fed back to the model with the same semantics as bash
|
|
denials. Writes are temp+rename atomic. Every call leaves one audit
|
|
line on stdout (tool name + outcome only, never arguments).
|
|
- Bash gate: allow-list extended MINIMALLY to the build surface —
|
|
`./dev.sh *`, `make check`, `make release`, `git add *`, `git commit *`,
|
|
`git push *` (both `harness.toml.example` and live `harness.toml`).
|
|
Enforced by `TestShippedAllowListCoversDevAndGit`, which parses
|
|
`harness.toml.example` itself so the preset cannot drift from the
|
|
acceptance (dev.sh build/vet/test/check + git commit/push pass;
|
|
sudo/curl/docker run/python3/go run still default-deny).
|
|
- Turn budget: `[loop] turn_timeout_secs` (default 1800, 0 = off). An
|
|
expired turn keeps partial content, writes a partial REPORT with
|
|
stop_reason `turn_timeout`, and releases the task (the pre-turn dedup
|
|
marker prevents hot-looping; PMO re-releases by updating the issue).
|
|
|
|
## 3. Test results (`./dev.sh check` — build + vet + test, docker builder)
|
|
|
|
All green: brief, config, events, intake, llm, loop, models, quota,
|
|
serve, tools, writeback. New coverage:
|
|
- file-tool gate denials (absolute/`..`/symlink escapes → ErrDenied;
|
|
missing file = plain error, not denial), edit exact-match semantics
|
|
(not-found no-write, ambiguity count, replace_all, old==new refusal),
|
|
write new-files-only, read offset/limit + truncation, ls/glob/grep
|
|
scoping + include filter + invalid regexp
|
|
- timeout expiry: partial REPORT written with `turn_timeout`, err chain
|
|
carries ErrTurnTimeout, zero disables the bound
|
|
- allow-list acceptance vs the shipped preset (see above)
|
|
- heartbeat: file appears + fields, `running:false` after clean stop,
|
|
mtime keeps refreshing while scans cycle (poll=1s), scan errors
|
|
recorded without flipping running
|
|
- END-TO-END fake-Redmine loop turn: reads a file, EDITS it (verified on
|
|
disk), runs an allow-listed no-op bash command, REPORTs — note lands on
|
|
the issue, status transitions per the map, tool audit lines present;
|
|
a denied write outside the root still completes the turn with the note
|
|
|
|
## 4. Live autonomous proof (the alpha queue)
|
|
|
|
Old dev.sh loop container drained its queue (519 writebacks recovered,
|
|
523 turn completed 12:19Z), then was stopped BY NAME and replaced:
|
|
`docker stop mopac-loop` → `docker compose up -d`. The new container's
|
|
scan 2 dispatched re-queued task **523** (the exit-gate ticket itself) at
|
|
12:22Z and completed it autonomously at 12:31Z on glm-4.7-flash:
|
|
|
|
- tools used live: bash (2 ok), glob (2 ok), read (2 ok) — the new file
|
|
palette in production
|
|
- gate denials live: `docker ps -a` and `screen -ls` refused with
|
|
model-facing feedback (default deny held)
|
|
- REPORT `reports/REPORT-demo-523-20260829-123112.md` (rounds=8,
|
|
tool_calls=8, denied=2, stop=round_limit), Redmine note posted,
|
|
status New → Resolved
|
|
- heartbeat counted it: `{"scans":2,"dispatched":1,"reports":1}`,
|
|
health=healthy throughout
|
|
|
|
## 5. Notes / handoff
|
|
|
|
- The killed 495 turn's completed-but-uncommitted work (brief surface,
|
|
discourse client vendored) was landed first as its own commit so 521
|
|
started from a green tree; LIVE discourse delivery on 495 remains open
|
|
(queue task 519's follow-up belongs to the loop now — it has the tools).
|
|
- 523's flash turn hit the 8-round bound while exploring (stop=
|
|
round_limit, 23.8k tokens). If turns routinely saturate rounds on
|
|
study-class tasks, consider `[loop] max_rounds` 8 → 12 for the alpha
|
|
queue; not changed unilaterally.
|
|
- `harness.toml` (live) updated with `turn_timeout_secs = 1800`,
|
|
`[tools.files] enabled = true`, extended allow-list — file is
|
|
gitignored, mirrored into `harness.toml.example` (committed).
|
|
- Kill switch documented in compose header, README, runbook:
|
|
`docker compose -f deploy/compose.yaml down`. No broad pkill was used
|
|
at any point (`docker stop mopac-loop` by name only).
|
|
- The crush/screen stack can now be retired per the exit gate: the loop
|
|
is containerized, restart-policied, healthchecked, and its turns can
|
|
do build work through the same digest-pinned docker builder discipline.
|