## 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.

1. 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

    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)