Add durable claimable ops_runs table, emit dual-write on TaskSpec, REST claim/lease/complete/fail API, ops status visibility, and consumer docs aligned with ACT-ADR-005. T07 railiance rollout remains deploy-side.
8 KiB
Recurring automations playbook — activity-core as the organizer
Audience: operators and coding agents adding or fixing recurring work
Principle: activity-core answers when / what / where only. It does not execute.
Architecture decision: ACT-ADR-005 —
ops runs (claimable DB) vs development work records (repo files + State Hub).
Implementation stack: ACTIVITY-WP-0026 · REIN-A-0002 · ISSUE-WP-0006 · STATE-WP-0078.
This is the org standard. Prefer this path over workstation cron, ad-hoc shell timers, or “I’ll remember to run it.”
Self-hosted forge: Forgejo only (not Gitea). External tickets via issue-core projection — never as the default ops claim queue.
The three layers (do not collapse them)
┌─────────────────────────────────────────────────────────────┐
│ 1. activity-core — WHEN / WHAT / WHERE │
│ Temporal schedule · context resolvers · rules │
│ → INSERT ops_run (open) + dual-write activity_task_spawn │
│ Claim API: POST /ops-runs/claim (docs/ops-run-queue.md) │
└────────────────────────────┬────────────────────────────────┘
│
┌────────────────────────────▼────────────────────────────────┐
│ 2. rein-aharness (or domain executor) — DOES THE WORK │
│ claim ops_run · llm-connect · checkout · complete/fail │
│ Host timers on railiance are *interim* until REIN-A-0002 │
│ claim loop is live (state-hub spawn alone is not claimable)│
└────────────────────────────┬────────────────────────────────┘
│
┌────────────────────────────▼────────────────────────────────┐
│ 3. domain repo — INSTANCE STATE │
│ activity-definitions/ · playbooks · briefs/ · catalogs │
│ completion event types (fi_daily_brief, binky_daily_brief)│
└─────────────────────────────────────────────────────────────┘
| Layer | Owns | Must not own |
|---|---|---|
| activity-core | Triggers, due checks, task specs, audit of spawns | Brief writing, git commits, LLM prompts for domain content |
| rein-aharness | Execution session, credentials at runtime, hub completion | Inventing schedules, domain policy |
| domain repo | Definition markdown, playbook, artifacts | Scattered crons that bypass activity-core |
Anti-pattern: workstation crontab that “does the daily brief” while
activity-core also schedules it — dual cadence and forgotten design.
Correct interim (Binky / FI): activity-core schedule is authority for when due; railiance user-systemd timer invokes rein-aharness a few minutes later as executor. Document both; never pretend the host timer is the scheduler.
How to add a recurring automation (checklist)
A. Domain definition (source of truth)
- Create
{domain-repo}/activity-definitions/{slug}.mdtrigger.type: cron+ timezone +misfire_policycontext_sourceswith a due resolver when work is once-per-dayrulewithtarget_repo, labels, description pointing at the playbook- Start with
enabled: falseuntil dry-run
- Implement completion event shape in domain playbook
(e.g.fi_daily_briefwithdetail.repo+detail.date) - If needed, add resolver in activity-core (
context_resolvers/) + tests
B. Register with activity-core
| Environment | How |
|---|---|
| Dev | ACTIVITY_DEFINITION_DIRS=/path/to/domain-repo + make sync-activity-definitions |
| Railiance | Project definition into ConfigMap actcore-external-activity-definitions (and keep k8s/railiance/20-runtime.yaml in git) |
Then:
# after ConfigMap / files update
kubectl -n activity-core exec deploy/actcore-api -- python3 -c \
'import urllib.request as u; print(u.urlopen(u.Request(
"http://localhost:8010/admin/sync?definitions=true&schedules=true",
method="POST")).read().decode())'
Verify:
# definition enabled + cron
# Temporal schedule: activity-schedule-<definition-uuid> not paused
kubectl -n activity-core exec deploy/actcore-worker -- python3 -c '...' # describe schedule
Ops UI: activity.coulomb.social → automations status / trigger (SSO).
C. Executor (rein-aharness) — target vs interim
Target (ACT-ADR-005 / REIN-A-0002): continuous claim loop on
ops_run rows from activity-core; approach table selects adapter
(fi-research-brief, brief-daily, mail-scan, …).
Interim (until WP-0026 + REIN-A-0002 cutover): railiance user-systemd timer may call rein-aharness wall-clock — break-glass / dual-clock, not cadence authority.
- Prefer a named rein-aharness command + llm-connect, not Claude CLI on railiance01
- On success, post the domain completion event so the resolver sets
due=false - Close the ops_run (when claim queue is live)
D. Prove the loop
POST /activity-definitions/<id>/trigger(ops UI or API)- Confirm
activity_task_spawnprogress event (when sink=state-hub) - Run executor once (timer or manual rein-aharness)
- Confirm domain completion event → resolver
due=falsefor that day - Set
enabled: trueif still false; commit domain + activity-core CM projection
Where definitions live
| Kind | Location |
|---|---|
| Org-wide (SBOM, consistency, inventory) | activity-core/activity-definitions/ and/or ConfigMap |
| Domain-specific (Binky, FI, …) | {domain}/activity-definitions/ + ConfigMap projection |
ADR-002: domain repos own domain automations; activity-core discovers them via
ACTIVITY_DEFINITION_DIRS or the external ConfigMap mount.
Sinks (ACTIVITY-WP-0022)
ISSUE_SINK_TYPE |
Use |
|---|---|
state-hub (default) |
Internal fleet work → activity_task_spawn progress |
null |
Dry-run |
rest |
Explicit external issue-core / Forgejo only |
Do not flip production to rest globally.
Reference implementations
| Domain | Definition | Executor |
|---|---|---|
| Binky | binky-daily-rhythm in activity-core / binky |
rein-aharness brief-daily + binky-control/scripts/railiance-rhythm/ |
| Freedom Intelligence | fi-daily-research-brief |
rein-aharness fi-research-brief + freedom-intelligence/scripts/railiance-rhythm/ |
Operator daily habit
- Ops console: did automations run? (
/ops/automations/status) — includesops_runs.countsandstuck_open_or_claimed(SLA, default 1h) - Claim queue:
GET /ops-runs?state=open(or failed) — harness backlog - State Hub:
activity_task_spawn+ domain completion events - Domain repo: brief/artifact files + git history
- If something is missing, fix activity-core first (schedule paused? definition disabled? resolver due stuck? open ops_run with no claim?) — do not invent a new cron
Related
INTENT.md— when/what/where boundarydocs/adr/adr-005-ops-runs-vs-dev-work-records.md— ops vs dev workdocs/ops-run-queue.md— claim APIdocs/task-emission-consumer-contract.md— spawn payload + consumer dutiesdocs/runbook.md— sync, trigger, ops UIdocs/adr/adr-002-definition-format.md— definition files