--- id: ACT-ADR-005 type: architecture-decision-record title: "Ops runs vs development work records — claim queue and plane split" status: accepted owner: activity-core revision: "accepted-1" last_reviewed: "2026-08-03" review_interval: 6m decided_by: Bernd Worsch date: "2026-08-03" scope: cross-repo affects: - activity-core - rein-aharness - state-hub - issue-core - domain repos (Binky, freedom-intelligence, …) tags: - architecture - activity-core - ops-run - work-records - rein-aharness - issue-core - forgejo supersedes_notes: > Clarifies ACTIVITY-WP-0022 sink practice and G2 executor gap from ACTIVITY-WP-0023. Does not reverse WP-0022 (no default external tracker issues). --- # ACT-ADR-005: Ops runs vs development work records ## Status **Accepted** (2026-08-03). ## Context The fleet has two healthy patterns that were forced into one vague “task” idea: 1. **Development / coordination work** — structured workplans and tasks as **repo files**, indexed by **State Hub** on **coulombcore** (`fix-consistency`, UUIDv7 write-back). This matches ADR-001 and `work-record-types_v0.1.md` and is how humans and coding agents ship product. 2. **Operations / scheduled automation** — activity-core Temporal schedules (when / what / where), rein-aharness execution (how), completion events (idempotence). Instances of “today’s FI brief” or “this prune fire” are **ephemeral runs**, not multi-day workplan bodies. Practice collapsed (2) into broken paths: | Path | Failure | | ---- | ------- | | Emit `activity_task_spawn` progress only | Append-only; **not claimable**; schedule can “succeed” while nothing runs | | `ISSUE_SINK_TYPE=rest` → issue-core → Forgejo | Spam + 503s; issue-core INTENT forbids origin of fleet work | | Host systemd timers dual-clocked with Temporal | Shadow scheduler; weak self-healing | issue-core’s **correct** role is a **connector facade** over external trackers (Forgejo, GitHub, Jira, …) so agents need not know each backend. It is **not** the origin of work records and **not** the default internal ops queue. **Gitea is out of scope** for this fleet; the self-hosted forge is **Forgejo**. Canon already allows a DB-only exception for “runtime operations data (logs, metrics, run histories, token events)” (`work-record-types_v0.1.md`). This ADR names that exception for **ops runs**. This is also the precise meaning of INTENT's no-task-lifecycle boundary: `ops_run` claim/lease/outcome is durable runtime delivery state, not work-item state. It may not grow assignment, commitments, dependencies, project phases, or manually managed task status. ## Decision ### 1. Two planes for “work,” one vocabulary for people | Plane | Artefact | Home | Lifecycle | | ----- | -------- | ---- | --------- | | **A — Development / coordination** | Work records (`workplan`, `task`, `intake`, …) | Repo files + State Hub (coulombcore) | File-backed, reviewable, multi-day | | **B — Recurrence control** | Activity definitions + Temporal schedules | activity-core (railiance) | Git definitions; DB schedule state | | **C — Ops execution** | **`ops_run`** (runtime claim object) | **activity-core DB + claim API** | open → claimed → succeeded \| failed \| expired | | **D — External collab** | Tracker issues | **issue-core** optional projection to **Forgejo** (and others) | Never default for internal automation | Coding agents orient on **Plane A** via State Hub. Automation orients on **Planes B+C**. Humans outside the fleet may see **Plane D** only when deliberately projected. ### 2. activity-core owns the ops_run claim queue When a definition emits a TaskSpec for internal fleet work: 1. Write **`ops_run`** row (durable, claimable) with idempotency key (e.g. `fi-daily:2026-08-04` for once-per-day briefs). 2. Optionally dual-write `activity_task_spawn` progress for existing observers. 3. Do **not** open a Forgejo issue. 4. Do **not** create a workplan task file for that day’s fire. Claim API (implemented in ACTIVITY-WP-0026 and hardened in ACTIVITY-WP-0036): - `POST /ops-runs/claim` — lease next open run matching labels / worker id - `POST /ops-runs/{id}/complete` — succeeded + completion metadata - `POST /ops-runs/{id}/fail` — failed + retry policy - `GET /ops-runs?state=open` — operator visibility Worker mutation credentials are bound to one configured queue `worker_id`; the request body cannot assert a different claim owner. Heartbeat, completion, and failure lock the row and require its lease deadline to remain strictly in the future. Operator/SSO credentials provide visibility and explicit administration, but do not act as a normal worker identity. activity-core remains **when / what / where** only: it does **not** run domain LLM sessions or hold tenant git credentials. ### 3. rein-aharness owns how (approach selection + execute) - Continuous **claim loop** (service / Deployment), not dual wall-clock timers as source of truth. - Host systemd timers become **break-glass** after cutover. - **Approach selection** at claim time (minimal table v1): | Labels / definition family | Approach | | -------------------------- | -------- | | mail / mail-intake | deterministic adapter (+ optional triage) | | research-brief / fi-daily | structured llm-connect (`fi-research-brief`) | | binky rhythm | structured llm-connect (`brief-daily`) | | agent-session | persona + tool profile session | | unknown | refuse; do not invent | - Success posts **domain completion event** (e.g. `fi_daily_brief`) so resolvers set `due=false`, and closes the `ops_run`. ### 4. State Hub remains the work-record read model - Stays on **coulombcore**; railiance peers via edge relay / ops-bridge. - May **project** open/failed ops runs for fleet ops UI (read-only), but is **not** the claim authority. - Development workplans/tasks continue file + `fix-consistency` only. ### 5. issue-core is Forgejo (and multi-tracker) facade only - **Forgejo** is the self-hosted forge; do not plan for **Gitea** as a product. - Optional **project / link** of an existing work-record UUID to an external issue when collaboration needs it. - Authenticated `POST /issues/` means “create/link **external** tracker work,” never “spawn fleet automation.” - Internal automation **must not** use issue-core as the ops claim queue unless a future design adds an **internal-only** backend with projection **hard-off** for automation labels — out of scope unless a later ADR says otherwise. ### 6. Promotion path ops → dev When an ops run discovers multi-day product work (e.g. “collect Kimi K3 under new quota”), the executor or human **promotes** an `intake` / workplan in the domain repo (Plane A). Ops runs never become permanent fake workplans. ## Consequences ### Positive - Self-healing recurrence: schedule → claimable row → claim → complete/fail. - Aligns issue-core INTENT with practice; stops Forgejo spam path. - Keeps activity-core thin; keeps rein-aharness as sole session/credential shell. - Preserves the good dev loop (files + State Hub). ### Negative / cost - New schema + API + migrator in activity-core. - rein-aharness must leave issue-core-only poll as primary for ops. - Temporary dual-write and dual timers until cutover proven. ### Non-goals - Replacing workplans for development. - Running domain briefs inside activity-core workers. - Global `ISSUE_SINK_TYPE=rest` to Forgejo. - Gitea support or migration paths. ## Topology ```text coulombcore: State Hub + work-record registry railiance: activity-core (Temporal + ops_run queue) rein-aharness (claim loop + approach + llm-connect) domain checkouts forgejo: optional external collab via issue-core projection only ``` ## Implementation stack | Workplan | Owner | Role | | -------- | ----- | ---- | | ACTIVITY-WP-0026 | activity-core | ops_run schema, claim API, emit path, dual-write | | REIN-A-0002 | rein-aharness | claim loop, approach table, FI/Binky cutover | | ISSUE-WP-0006 | issue-core | Forgejo-only language; projection boundary; no ops queue | | STATE-WP-0078 | state-hub | Read projection of ops_run for ops UI (optional consume) | ## References - `docs/recurring-automations-playbook.md` - `docs/task-emission-consumer-contract.md` - `docs/issue-core-emission-boundary.md` - ACTIVITY-WP-0022, ACTIVITY-WP-0023 (G2) - `the-custodian/canon/standards/work-record-types_v0.1.md` - `issue-core/INTENT.md` (work-record boundary) - `state-hub/docs/cluster-operating-model.md` (coulombcore primary)