ACT-ADR-005 accepted · accepted-1 activity-core reviewed 2026-08-03generated from canonical source — do not edit

Ops runs vs development work records — claim queue and plane split

Source: activity-core · docs/adr/adr-005-ops-runs-vs-dev-work-records.md · 41a3fb8b81bd521a5fa21af114975c54532df3ad

Review due: 2027-02-03

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:

PathFailure
Emit activity_task_spawn progress onlyAppend-only; not claimable; schedule can “succeed” while nothing runs
ISSUE_SINK_TYPE=rest → issue-core → ForgejoSpam + 503s; issue-core INTENT forbids origin of fleet work
Host systemd timers dual-clocked with TemporalShadow 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

PlaneArtefactHomeLifecycle
A — Development / coordinationWork records (workplan, task, intake, …)Repo files + State Hub (coulombcore)File-backed, reviewable, multi-day
B — Recurrence controlActivity definitions + Temporal schedulesactivity-core (railiance)Git definitions; DB schedule state
C — Ops executionops_run (runtime claim object)activity-core DB + claim APIopen → claimed → succeeded \failed \expired
D — External collabTracker issuesissue-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 familyApproach
mail / mail-intakedeterministic adapter (+ optional triage)
research-brief / fi-dailystructured llm-connect (fi-research-brief)
binky rhythmstructured llm-connect (brief-daily)
agent-sessionpersona + tool profile session
unknownrefuse; 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

WorkplanOwnerRole
ACTIVITY-WP-0026activity-coreops_run schema, claim API, emit path, dual-write
REIN-A-0002rein-aharnessclaim loop, approach table, FI/Binky cutover
ISSUE-WP-0006issue-coreForgejo-only language; projection boundary; no ops queue
STATE-WP-0078state-hubRead 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)