192 lines
7.8 KiB
Markdown
192 lines
7.8 KiB
Markdown
|
|
---
|
|||
|
|
id: ACT-ADR-005
|
|||
|
|
type: architecture-decision-record
|
|||
|
|
title: "Ops runs vs development work records — claim queue and plane split"
|
|||
|
|
status: accepted
|
|||
|
|
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**.
|
|||
|
|
|
|||
|
|
## 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 (sketch; implement in ACTIVITY-WP-0026):
|
|||
|
|
|
|||
|
|
- `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
|
|||
|
|
|
|||
|
|
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)
|