rein-aharness/docs/ops-run-claim-loop.md
tegwick 0f01c3e421 feat: route profiled ops runs through Glas
Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a02b6f-7db1-7222-918b-e813a6bda38d
2026-08-22 23:55:29 +02:00

157 lines
5.4 KiB
Markdown

# Ops run claim loop (REIN-A-0002)
**Primary intake** for activity-core scheduled automation.
**Architecture:** ACT-ADR-005 · activity-core [ops-run-queue.md](../../activity-core/docs/ops-run-queue.md)
Does **not** require issue-core or Forgejo for FI / Binky-style ops.
## Flow
```text
activity-core emit_tasks
→ INSERT ops_run (open, optional harness_profile_ref) + activity_task_spawn
rein-aharness claim-loop
POST /ops-runs/claim
harness_profile_ref present? ─ yes → Glas ExecutionRequest → gateway
│ no
└─→ select_approach(labels, definition, hint) → legacy executor
POST complete | fail(+reopen)
```
`harness_profile_ref` is authoritative under ACT-ADR-006. A profiled row never
consults `approach_hint`, `select_approach`, or a legacy executor, including
after profile resolution refusal. The claim worker sends the complete
`GatewayResult` to Activity Core; Activity Core persists its allowlisted
`ExecutionEvidence` and drops direct tool output/error. Profile-absent rows keep
the legacy coexistence path until the migration flag is enabled upstream.
## Environment
| Variable | Default | Meaning |
| -------- | ------- | ------- |
| `ACTIVITY_CORE_URL` | `http://127.0.0.1:8010` | actcore-api base |
| `ACTIVITY_CORE_WORKER_TOKEN` | unset | `X-Worker-Token` / Bearer |
| `AGENT_HARNESS_WORKER_ID` | `rein-aharness@hostname` | claim owner |
| `AGENT_HARNESS_OPS_LABELS` | `automated` | claim label filter |
| `AGENT_HARNESS_OPS_LABELS_MODE` | `any` | `any` or `all` |
| `AGENT_HARNESS_OPS_LEASE_SECONDS` | `900` | claim lease |
| `AGENT_HARNESS_CLAIM_INTERVAL` | `30` | empty-queue poll interval |
| `AGENT_HARNESS_REPO_MAP` | `{}` | JSON slug→checkout path |
| `AGENT_HARNESS_REPO_ROOTS` | `~:~/work` | slug search roots |
Example map on railiance01:
```bash
export AGENT_HARNESS_REPO_MAP='{
"freedom-intelligence":"/home/tegwick/freedom-intelligence",
"binky-control":"/home/tegwick/binky-control"
}'
export ACTIVITY_CORE_URL=http://127.0.0.1:8010 # or ClusterIP via tunnel
export ACTIVITY_CORE_WORKER_TOKEN=# from actcore-runtime-secret
```
## Approach registry
| Match | Approach |
| ----- | -------- |
| labels `research-brief` / `freedom-intelligence` or fi-daily* | `fi-research-brief` |
| labels `rhythm` / `binky-daily` or binky-daily* | `brief-daily` |
| labels `binky` + `weekly-review` or binky-weekly-review* | `brief-weekly` |
| labels `mail-intake` | `mail-scan` then `mail-triage` |
| labels `agent-session` | agentic `run` (TaskSpec) |
| none | fail ops_run (no silent drop) |
For a profile-absent legacy row, optional `approach_hint` overrides matching.
It has no effect when `harness_profile_ref` is present.
Add a row: edit `rein_aharness/approaches.py` (`APPROACH_RULES` + `execute_approach`)
and tests in `tests/test_approaches.py`. Prefer cheapest correct path
(structured llm-connect before open-ended agent session).
## CLI
```bash
# Peek open ops_runs + selected approach
rein-aharness poll --source=ops-run --no-claim
# Claim one, execute approach, complete/fail
rein-aharness run --from-ops-run
# or
rein-aharness poll --source=ops-run
# Continuous worker (systemd)
rein-aharness claim-loop
rein-aharness claim-loop --once --verbose
# Dry-run: claim then fail+reopen without domain work
rein-aharness poll --source=ops-run --dry-run
```
Legacy issue-core (external tickets only):
```bash
rein-aharness poll --source=issue-core
rein-aharness run --from-issue-core
```
## Install claim-loop on railiance01 (user systemd)
```bash
# From rein-aharness checkout on railiance01
python3 -m venv .venv
.venv/bin/pip install -e . -e ../llm-connect -e ../sand-boxer -e ../glas-harness
./deploy/scripts/install-claim-loop-user.sh
# Or manually:
mkdir -p ~/.config/systemd/user
cp deploy/systemd/rein-aharness-claim-loop.service ~/.config/systemd/user/
# Edit Environment= paths / token in the unit or env file
systemctl --user daemon-reload
systemctl --user enable --now rein-aharness-claim-loop.service
journalctl --user -u rein-aharness-claim-loop -f
```
### Host access to cluster services (no port-forward)
On railiance01 (single-node k3s), set **k8s://** pseudo-URLs in
`claim-loop.env` (see `deploy/systemd/claim-loop.env.example`). The
`~/bin/rein-aharness-claim` wrapper resolves them to Service ClusterIPs at
start. Canon: activity-core `docs/llm-connect-host-access.md`.
```bash
# migrate existing env
./deploy/scripts/refresh-claim-loop-k8s-urls.sh
# reinstall wrapper
./deploy/scripts/install-claim-loop-user.sh
systemctl --user restart rein-aharness-claim-loop.service
```
**Deprecated:** long-lived `kubectl port-forward` to 127.0.0.1:8010 / :8080.
Legacy note (break-glass only) if ClusterIP routing is broken:
```bash
kubectl -n activity-core port-forward svc/actcore-api 8010:8010
export ACTIVITY_CORE_URL=http://127.0.0.1:8010
```
## Cutover from host timers (T05)
Host oneshot timers (FI 07:35, Binky rhythm) remain **break-glass** until:
1. claim-loop is running and healthy
2. **5 clean weekday cycles** (or 5 forced triggers) with open→claimed→succeeded
3. Then disable or demote timers — see `deploy/docs/timer-cutover.md`
Until then dual-path is intentional: schedule truth is activity-core; timers are
a safety net if claim-loop is down.
## Labels required on definitions
| Domain | Labels (min) |
| ------ | ------------ |
| Freedom Intelligence | `automated`, `research-brief` (already on FI rule) |
| Binky daily | `automated`, `rhythm` |
| Binky mail | `automated`, `mail-intake` |