Document recurring automations playbook for activity-core as organizer.
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s

Codify when/what/where vs rein-aharness execution vs domain instance state,
and the checklist to register cron definitions without workstation cron
bypasses.
This commit is contained in:
tegwick 2026-08-03 17:53:06 +02:00
parent 2b4eca0220
commit 747d63a993

View file

@ -0,0 +1,157 @@
# 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.
This is the org standard. Prefer this path over workstation cron, ad-hoc shell
timers, or “Ill remember to run it.”
---
## The three layers (do not collapse them)
```text
┌─────────────────────────────────────────────────────────────┐
│ 1. activity-core — WHEN / WHAT / WHERE │
│ Temporal schedule · context resolvers · rules │
│ → emits activity_task_spawn (state-hub sink by default) │
└────────────────────────────┬────────────────────────────────┘
┌────────────────────────────▼────────────────────────────────┐
│ 2. rein-aharness (or domain executor) — DOES THE WORK │
│ llm-connect · repo checkout · commit · completion event │
│ Host timers on railiance are *interim* until poll/claim │
│ of spawns is fully wired (state-hub sink 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)
1. Create `{domain-repo}/activity-definitions/{slug}.md`
- `trigger.type: cron` + timezone + `misfire_policy`
- `context_sources` with a **due resolver** when work is once-per-day
- `rule` with `target_repo`, labels, description pointing at the playbook
- Start with `enabled: false` until dry-run
2. Implement completion event shape in domain playbook
(e.g. `fi_daily_brief` with `detail.repo` + `detail.date`)
3. 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:
```bash
# 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:
```bash
# 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)
1. Prefer a **named rein-aharness command** for the domain work
(e.g. `brief-daily`, `fi-research-brief`) using **llm-connect**, not Claude CLI on railiance01
2. Install **railiance user-systemd timer** in the domain repo
(`scripts/railiance-rhythm/`) that calls rein-aharness
3. Timer should sit **just after** the activity-core cron (due check already
enforced by completion events on re-run)
4. On success, post the **domain completion event** so the resolver sets `due=false`
### D. Prove the loop
1. `POST /activity-definitions/<id>/trigger` (ops UI or API)
2. Confirm `activity_task_spawn` progress event (when sink=state-hub)
3. Run executor once (timer or manual rein-aharness)
4. Confirm domain completion event → resolver `due=false` for that day
5. Set `enabled: true` if 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
1. Ops console: did automations run? (`/ops/automations/status`)
2. State Hub: `activity_task_spawn` + domain completion events
3. Domain repo: brief/artifact files + git history
4. If something is missing, **fix activity-core first** (schedule paused?
definition disabled? resolver due stuck? sink errors?) — do not invent a new cron
---
## Related
- `INTENT.md` — when/what/where boundary
- `docs/task-emission-consumer-contract.md` — spawn payload + consumer duties
- `docs/runbook.md` — sync, trigger, ops UI
- `docs/adr/adr-002-definition-format.md` — definition files