diff --git a/SCOPE.md b/SCOPE.md index 4d51250..cec0918 100644 --- a/SCOPE.md +++ b/SCOPE.md @@ -99,7 +99,7 @@ Git holds briefs, schemas, policies, and catalog metadata. Model weight blobs st | Inventory | R verified; **Kimi K3** S1 approved+deferred (~1454 GiB); other S deferred; W candidate | | Reserve policy | Capability-first — not gated by current VRAM | | Activity-core | Definition **enabled on railiance**; Temporal schedule weekdays 07:30 Berlin | -| Consumer | Workstation cron `consume-fi-brief-spawn.sh` 07:40 weekdays | +| Executor | rein-aharness `fi-research-brief` via railiance user timer 07:35 (not workstation cron) | | State Hub | Repo under **agents**; workplans finished + hub-indexed | | Workplans | FI-WP-0001…0003 **finished** | diff --git a/activity-definitions/fi-daily-research-brief.md b/activity-definitions/fi-daily-research-brief.md index ace1c6d..9425929 100644 --- a/activity-definitions/fi-daily-research-brief.md +++ b/activity-definitions/fi-daily-research-brief.md @@ -55,15 +55,12 @@ action: ## Enable checklist -1. [x] FI-WP-0002-T03 resolver live (`fi_brief_status` in activity-core) -2. [x] FI-WP-0002-T04 external definition path registered - - Local: `ACTIVITY_DEFINITION_DIRS` includes `/home/worsch/freedom-intelligence` - - Railiance: ConfigMap `actcore-external-activity-definitions` key - `fi-daily-research-brief.md` (**applied live 2026-08-03**) -3. [x] Completion event proven (`fi_daily_brief` 2026-07-24, 07-28, 08-03) +1. [x] Resolver `fi_brief_status` in activity-core +2. [x] ConfigMap projection + Temporal schedule (weekdays 07:30 Europe/Berlin) +3. [x] Completion event `fi_daily_brief` proven 4. [x] `enabled: true` / `status: active` -5. [x] Temporal schedule live: `activity-schedule-3169ab1f-882b-59c7-9763-d014dc96f4fc` - (weekdays 07:30 Europe/Berlin; not paused; next fires from 2026-08-04) -6. [x] Workstation consumer cron: `scripts/consume-fi-brief-spawn.sh` at 07:40 +5. [x] Executor: `rein-aharness fi-research-brief` + `scripts/railiance-rhythm/` + (install on railiance01; not workstation cron) -Ops: `docs/recurrence-ops.md` +Layers: activity-core (when/what/where) → rein-aharness (execute) → this repo. +See `docs/recurrence-ops.md` and `activity-core/docs/recurring-automations-playbook.md`. diff --git a/docs/daily-brief-playbook.md b/docs/daily-brief-playbook.md index b5259b7..b128945 100644 --- a/docs/daily-brief-playbook.md +++ b/docs/daily-brief-playbook.md @@ -74,38 +74,41 @@ full resurveys. --- -## Automation consumer +## Automation consumer (activity-core design) + +**Organizer:** activity-core (when / what / where) — not workstation cron. +**Executor:** rein-aharness on railiance01 (`fi-research-brief`). +**Org playbook:** `activity-core/docs/recurring-automations-playbook.md`. When activity-core emits `activity_task_spawn` with `target_repo: freedom-intelligence` and labels including `research-brief`: -1. Checkout this repo on the execution host -2. Run this playbook end-to-end -3. Prefer **llm-connect / OpenRouter** for any LLM drafting (Binky lesson: do - not install coding-agent CLIs on railiance01 solely for this) -4. Always post `fi_daily_brief` even if the brief is “no material delta” -5. Idempotency key: do not open a second brief file for the same `date` +1. Railiance host timer (or manual) runs + `rein-aharness fi-research-brief --target-repo ~/freedom-intelligence` +2. llm-connect drafts the brief; harness commits and posts **`fi_daily_brief`** +3. Idempotency: one brief file per date; resolver clears `due` on completion event -Activity definition: `activity-definitions/fi-daily-research-brief.md` -Workplan: `workplans/FI-WP-0002-activity-core-daily-research.md` -Resolver: activity-core state-hub query `fi_brief_status` (implemented 2026-07-24) +Do **not** install Claude Code / Codex on railiance01 for this rhythm. + +| Piece | Location | +| ----- | -------- | +| Definition | `activity-definitions/fi-daily-research-brief.md` | +| Ops | `docs/recurrence-ops.md` | +| Executor install | `scripts/railiance-rhythm/install-on-railiance.sh` | +| Workplan | FI-WP-0002 (finished) | +| Resolver | `fi_brief_status` in activity-core | ### Ops: load this repo’s definitions -Dev / workstation worker: - ```bash -export ACTIVITY_DEFINITION_DIRS=/home/worsch/freedom-intelligence -# then: make sync-activity-definitions (with ACTCORE_DB_URL + stack) +# Dev +export ACTIVITY_DEFINITION_DIRS=.../freedom-intelligence +make -C ~/activity-core sync-activity-definitions + +# Production: ConfigMap actcore-external-activity-definitions +# then POST /admin/sync?definitions=true&schedules=true ``` -Production (railiance): projected into ConfigMap -`actcore-external-activity-definitions` key `fi-daily-research-brief.md` -(**enabled 2026-08-03**; Temporal schedule weekdays 07:30 Europe/Berlin). - -Ops runbook: `docs/recurrence-ops.md` -Workstation due-notice cron: `scripts/consume-fi-brief-spawn.sh` (07:40 weekdays). - --- ## Quality bar diff --git a/docs/recurrence-ops.md b/docs/recurrence-ops.md index 60e6d81..032f534 100644 --- a/docs/recurrence-ops.md +++ b/docs/recurrence-ops.md @@ -1,93 +1,87 @@ -# Freedom Intelligence daily brief — recurrence ops +# Freedom Intelligence daily brief — activity-core recurrence -**Status:** production schedule wired 2026-08-03 -**Definition:** `activity-definitions/fi-daily-research-brief.md` -**Playbook:** `docs/daily-brief-playbook.md` +**Status:** activity-core schedule live; rein-aharness executor on Railiance +**Do not** use workstation crontab as the primary path (retired anti-pattern). + +Canonical org playbook: `activity-core/docs/recurring-automations-playbook.md`. --- -## Architecture (live) +## Design (correct layers) ```text -Temporal schedule (railiance activity-core) - cron: 30 7 * * 1-5 Europe/Berlin - → fi_brief_status (state-hub) - → if due: activity_task_spawn (state-hub sink) - → workstation consumer notice (optional cron) - → operator/agent executes playbook in freedom-intelligence - → progress event fi_daily_brief → next day due=false +activity-core (when / what / where) + Temporal: weekdays 07:30 Europe/Berlin + context: fi_brief_status → due? + rule: emit activity_task_spawn target_repo=freedom-intelligence + │ + ▼ +rein-aharness on railiance01 (does the work) + host timer 07:35 → fi-research-brief + llm-connect drafts brief → commit → fi_daily_brief + │ + ▼ +freedom-intelligence repo + briefs/YYYY/MM/YYYY-MM-DD.md + catalog / RESERVE-STATUS updates as needed ``` -| Piece | Where | -| ----- | ----- | -| Source of truth (def) | `freedom-intelligence/activity-definitions/fi-daily-research-brief.md` | -| Production projection | ConfigMap `actcore-external-activity-definitions` key `fi-daily-research-brief.md` | -| GitOps copy | `activity-core/k8s/railiance/20-runtime.yaml` (keep in sync) | +| Piece | Location | +| ----- | -------- | +| Definition (source of truth) | `activity-definitions/fi-daily-research-brief.md` | +| Cluster projection | ConfigMap `actcore-external-activity-definitions` | +| GitOps CM copy | `activity-core/k8s/railiance/20-runtime.yaml` | | Resolver | activity-core `fi_brief_status` | | Sink | `ISSUE_SINK_TYPE=state-hub` → `activity_task_spawn` | -| Consumer (workstation) | `scripts/consume-fi-brief-spawn.sh` (due notice; not auto-LLM) | +| Executor | `rein-aharness fi-research-brief` | +| Host timer | `scripts/railiance-rhythm/` (interim until spawn poller) | --- -## Verify schedule +## Verify activity-core (organizer) ```bash -# Definition present + enabled on API -kubectl -n activity-core exec deploy/actcore-api -- \ - python3 -c "import urllib.request,json; d=json.loads(urllib.request.urlopen('http://localhost:8010/activity-definitions/').read()); -print([x for x in d if 'Freedom Intelligence' in (x.get('name') or '')])" +# Definition enabled +kubectl -n activity-core exec deploy/actcore-api -- python3 -c \ + 'import urllib.request,json; d=json.loads(urllib.request.urlopen( + "http://localhost:8010/activity-definitions/").read()); +print([x for x in d if "Freedom Intelligence" in (x.get("name") or "")])' -# After def change: re-project ConfigMap then -kubectl -n activity-core exec deploy/actcore-api -- \ - python3 -c "import urllib.request; print(urllib.request.urlopen(urllib.request.Request('http://localhost:8010/admin/sync?definitions=true&schedules=true', method='POST')).read().decode())" +# Schedule (example id — re-check after re-sync) +# activity-schedule-3169ab1f-882b-59c7-9763-d014dc96f4fc +# paused=False, 07:30 Mon–Fri Europe/Berlin + +# Ops UI: activity.coulomb.social → automations +# Manual fire: POST /activity-definitions//trigger ``` -Schedule id pattern: `activity-schedule-`. +After definition edits: + +1. Update ConfigMap key from this repo’s definition file +2. Wait for volume refresh +3. `POST /admin/sync?definitions=true&schedules=true` --- -## Workstation consumer (optional but recommended) - -After activity-core fires at 07:30, run a due-check on the workstation: - -```cron -# crontab -e (Europe/Berlin host clock or TZ=Europe/Berlin) -40 7 * * 1-5 /home/worsch/freedom-intelligence/scripts/consume-fi-brief-spawn.sh -``` - -This writes `.local/consumer-logs/DUE-YYYY-MM-DD.txt` when a brief is still due. -An agent session or operator then runs the playbook and posts `fi_daily_brief`. - ---- - -## Manual one-shot (if schedule missed) +## Install executor on railiance01 ```bash -# Resolve activity id from API list, then: -kubectl -n activity-core exec deploy/actcore-api -- \ - python3 -c "import urllib.request; print(urllib.request.urlopen(urllib.request.Request('http://localhost:8010/activity-definitions//trigger', method='POST')).read().decode())" +# On railiance01 +git clone forgejo-remote:coulomb/freedom-intelligence.git ~/freedom-intelligence +cd ~/freedom-intelligence && ./scripts/railiance-rhythm/install-on-railiance.sh + +# rein-aharness must expose fi-research-brief (deploy/pull rein-aharness) +# Logs: ~/.cache/freedom-intelligence/rhythm/ ``` -Then execute playbook + post progress event. +Timer is **07:35** Berlin — five minutes after activity-core’s 07:30 schedule. --- -## Update definition - -1. Edit `activity-definitions/fi-daily-research-brief.md` in this repo -2. Copy content into ConfigMap key (or re-apply from `activity-core` 20-runtime.yaml) -3. Wait for volume refresh (~30–90s) -4. `POST /admin/sync?definitions=true&schedules=true` -5. Commit both freedom-intelligence and activity-core if 20-runtime.yaml changed - ---- - -## Completion event (required) +## Completion event (required for due=false) ```yaml event_type: fi_daily_brief -author: -summary: "FI daily brief YYYY-MM-DD" detail: repo: freedom-intelligence date: "YYYY-MM-DD" @@ -95,4 +89,13 @@ detail: collection_candidates: ``` -API: `POST http://127.0.0.1:8000/progress/` +Posted automatically by `rein-aharness fi-research-brief` on success. + +--- + +## What not to do + +- Workstation `crontab` that writes the brief (bypasses activity-core) +- Installing Claude Code / Codex on railiance01 for this rhythm +- Treating host timers as the scheduler of record +- Skipping the completion event (leaves `due=true` forever) diff --git a/scripts/consume-fi-brief-spawn.sh b/scripts/consume-fi-brief-spawn.sh index 6978ece..53ca0dd 100755 --- a/scripts/consume-fi-brief-spawn.sh +++ b/scripts/consume-fi-brief-spawn.sh @@ -1,14 +1,13 @@ #!/usr/bin/env bash -# Workstation consumer for FI daily research brief spawns. +# BREAK-GLASS only — not the primary recurrence path. # -# activity-core emits activity_task_spawn (state-hub sink) with -# target_repo=freedom-intelligence. This script: -# 1) checks fi_brief_status due (or open spawn) -# 2) reminds operator / runs playbook path -# 3) does NOT invent brief content — open a session with the playbook +# Primary design: +# activity-core (schedule + fi_brief_status + spawn) +# → rein-aharness fi-research-brief on railiance01 +# See docs/recurrence-ops.md and activity-core/docs/recurring-automations-playbook.md # -# Schedule suggestion (Europe/Berlin, after 07:30 spawn): -# 40 7 * * 1-5 /home/worsch/freedom-intelligence/scripts/consume-fi-brief-spawn.sh +# This script only prints due status for operators debugging from a workstation. +# Do NOT install it as the main crontab for FI briefs. # # Env: # STATE_HUB_URL default http://127.0.0.1:8000 diff --git a/scripts/railiance-rhythm/install-on-railiance.sh b/scripts/railiance-rhythm/install-on-railiance.sh new file mode 100755 index 0000000..1727008 --- /dev/null +++ b/scripts/railiance-rhythm/install-on-railiance.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +# Install host-side FI research brief timer on railiance01 (user systemd). +# Run ON railiance01 from a freedom-intelligence checkout: +# ./scripts/railiance-rhythm/install-on-railiance.sh +# +# Prerequisites: +# - activity-core definition fi-daily-research-brief enabled (cluster) +# - rein-aharness installed with fi-research-brief command +# - freedom-intelligence clone with write access for brief commits +set -euo pipefail +ROOT="$(cd "$(dirname "$0")/../.." && pwd)" +UNIT_DIR="${HOME}/.config/systemd/user" +mkdir -p "${UNIT_DIR}" +chmod +x "${ROOT}/scripts/railiance-rhythm/"*.sh + +cp -f "${ROOT}/scripts/railiance-rhythm/systemd/fi-research-brief-daily.service" "${UNIT_DIR}/" +cp -f "${ROOT}/scripts/railiance-rhythm/systemd/fi-research-brief-daily.timer" "${UNIT_DIR}/" + +systemctl --user daemon-reload +systemctl --user enable --now fi-research-brief-daily.timer +systemctl --user list-timers --all | grep fi-research || true +echo "Installed. Logs: ~/.cache/freedom-intelligence/rhythm/" +echo "Cadence truth: activity-core (07:30). Executor: this timer (07:35) → rein-aharness." diff --git a/scripts/railiance-rhythm/run-common.sh b/scripts/railiance-rhythm/run-common.sh new file mode 100755 index 0000000..9aa4486 --- /dev/null +++ b/scripts/railiance-rhythm/run-common.sh @@ -0,0 +1,86 @@ +#!/usr/bin/env bash +# Shared env for Railiance host-side Freedom Intelligence research brief. +# +# activity-core owns cadence (Temporal + fi_brief_status + activity_task_spawn). +# This host timer is the *executor* path (same pattern as binky-control): +# rein-aharness consumes the work and posts fi_daily_brief. +# +# ISSUE_SINK_TYPE=state-hub writes progress only (not claimable issues), so a +# host timer remains the interim executor until rein-aharness poll/claim of +# activity_task_spawn is fully wired. Cadence truth still lives in activity-core. +set -euo pipefail + +: "${HOME:=/home/tegwick}" + +# Prefer rein-aharness venv (current name); fall back to legacy agent-harness path. +if [[ -x "${HOME}/.local/rein-aharness/venv/bin/rein-aharness" ]]; then + export PATH="${HOME}/.local/rein-aharness/venv/bin:${HOME}/.local/bin:${PATH}" + # shellcheck disable=SC1091 + [[ -f "${HOME}/.local/rein-aharness/env" ]] && source "${HOME}/.local/rein-aharness/env" +elif [[ -x "${HOME}/.local/agent-harness/venv/bin/rein-aharness" ]]; then + export PATH="${HOME}/.local/agent-harness/venv/bin:${HOME}/.local/bin:${PATH}" + # shellcheck disable=SC1091 + [[ -f "${HOME}/.local/agent-harness/env" ]] && source "${HOME}/.local/agent-harness/env" +else + export PATH="${HOME}/.local/bin:${PATH}" +fi + +export STATE_HUB_URL="${STATE_HUB_URL:-http://127.0.0.1:18000}" +export PYTHONPATH="${HOME}/rein-aharness:${HOME}/llm-connect${PYTHONPATH:+:$PYTHONPATH}" +export FI_REPO="${FI_REPO:-${HOME}/freedom-intelligence}" +export LOG_DIR="${LOG_DIR:-${HOME}/.cache/freedom-intelligence/rhythm}" +mkdir -p "${LOG_DIR}" + +log() { echo "$(date -Iseconds) $*" | tee -a "${LOG_DIR}/railiance-rhythm.log"; } + +require_repo() { + if [[ ! -d "${FI_REPO}/.git" ]]; then + log "ERROR: freedom-intelligence missing at ${FI_REPO}; clone with deploy key" + exit 1 + fi + git -C "${FI_REPO}" fetch --quiet origin main || true + git -C "${FI_REPO}" checkout --quiet main + git -C "${FI_REPO}" pull --ff-only --quiet origin main || log "WARN: pull failed (continuing on local)" +} + +require_llm_connect() { + if [[ -z "${LLM_CONNECT_URL:-}" ]]; then + local candidates=() + candidates+=("http://127.0.0.1:18080") + if command -v kubectl >/dev/null 2>&1; then + local cip + cip="$(kubectl -n activity-core get svc llm-connect \ + -o jsonpath='{.spec.clusterIP}' 2>/dev/null || true)" + if [[ -n "${cip}" ]]; then + candidates+=("http://${cip}:8080") + fi + fi + candidates+=("http://llm-connect.activity-core.svc.cluster.local:8080") + local u + for u in "${candidates[@]}"; do + if curl -sf -m 2 "${u}/health" >/dev/null 2>&1; then + export LLM_CONNECT_URL="${u}" + break + fi + done + if [[ -z "${LLM_CONNECT_URL:-}" ]]; then + export LLM_CONNECT_URL="${candidates[-1]}" + log "WARN: no llm-connect health probe succeeded; using ${LLM_CONNECT_URL}" + fi + fi + log "LLM_CONNECT_URL=${LLM_CONNECT_URL}" +} + +ensure_git_identity() { + git -C "${FI_REPO}" config user.email >/dev/null 2>&1 \ + || git -C "${FI_REPO}" config user.email "rein-aharness@railiance.local" + git -C "${FI_REPO}" config user.name >/dev/null 2>&1 \ + || git -C "${FI_REPO}" config user.name "rein-aharness" +} + +require_harness() { + if ! command -v rein-aharness >/dev/null 2>&1; then + log "ERROR: rein-aharness not on PATH. Install rein-aharness venv on this host." + exit 2 + fi +} diff --git a/scripts/railiance-rhythm/run-daily.sh b/scripts/railiance-rhythm/run-daily.sh new file mode 100755 index 0000000..5c201ed --- /dev/null +++ b/scripts/railiance-rhythm/run-daily.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +# Weekday 07:35 Europe/Berlin — FI research brief executor (rein-aharness). +# +# Cadence authority: activity-core definition fi-daily-research-brief +# (Temporal 07:30 Europe/Berlin, fi_brief_status due check, activity_task_spawn). +# This timer is the host executor, 5 minutes after the schedule fire window. +set -euo pipefail +# shellcheck disable=SC1091 +source "$(dirname "$0")/run-common.sh" +require_repo +ensure_git_identity +require_harness +require_llm_connect +log "START fi-research-brief (rein-aharness / llm-connect)" +rein-aharness fi-research-brief --target-repo "${FI_REPO}" \ + 2>&1 | tee -a "${LOG_DIR}/daily.log" +log "END fi-research-brief rc=${PIPESTATUS[0]}" diff --git a/scripts/railiance-rhythm/systemd/fi-research-brief-daily.service b/scripts/railiance-rhythm/systemd/fi-research-brief-daily.service new file mode 100644 index 0000000..727ba94 --- /dev/null +++ b/scripts/railiance-rhythm/systemd/fi-research-brief-daily.service @@ -0,0 +1,10 @@ +[Unit] +Description=Freedom Intelligence daily research brief (rein-aharness on Railiance) +After=network-online.target + +[Service] +Type=oneshot +Environment=HOME=%h +WorkingDirectory=%h/freedom-intelligence +ExecStart=%h/freedom-intelligence/scripts/railiance-rhythm/run-daily.sh +Nice=10 diff --git a/scripts/railiance-rhythm/systemd/fi-research-brief-daily.timer b/scripts/railiance-rhythm/systemd/fi-research-brief-daily.timer new file mode 100644 index 0000000..a816be5 --- /dev/null +++ b/scripts/railiance-rhythm/systemd/fi-research-brief-daily.timer @@ -0,0 +1,10 @@ +[Unit] +Description=Timer: FI research brief weekdays 07:35 Europe/Berlin + +[Timer] +OnCalendar=Mon..Fri *-*-* 07:35:00 Europe/Berlin +Persistent=true +Unit=fi-research-brief-daily.service + +[Install] +WantedBy=timers.target