diff --git a/OperatingRhythm.md b/OperatingRhythm.md index c25cca6..a1a0f2c 100644 --- a/OperatingRhythm.md +++ b/OperatingRhythm.md @@ -58,11 +58,11 @@ all `enabled: true`). Execution on Railiance: `23 8 * * 1-5 /home/worsch/binky-control/scripts/rhythm-session.sh # binky rhythm bridge` and pause railiance timers. Script kept for one month as break-glass. -**Known residual (BINKY-WP-0006):** activity-core `ISSUE_SINK_TYPE=state-hub` -emits progress events, not claimable issue-core tasks; agentic daily/review -on Railiance need `claude` CLI on that host (mail-scan is deterministic and -already runs there). Until Claude is installed on railiance01, agentic slots -may still be run from a laptop harness session with the same task files. +**Known residual (BINKY-WP-0006):** finish **llm-connect / OpenRouter** path +for mail triage + briefs on Railiance (structured LLM output + deterministic +apply). **Do not** install Claude Code or another coding agent on railiance01. +Mail-scan is already deterministic and proven there; see +`integrations/railiance-llm-rhythm.md`. ## Weekly review (founder, ~15 min) diff --git a/integrations/executor-cutover-runbook.md b/integrations/executor-cutover-runbook.md index 57454d6..fe3cd15 100644 --- a/integrations/executor-cutover-runbook.md +++ b/integrations/executor-cutover-runbook.md @@ -42,9 +42,10 @@ cd ~/binky-control && git pull | Gap | Follow-up | | --- | --- | -| No continuous harness consumer of state-hub sink emissions | BINKY-WP-0006 / harness intake | -| No `claude` CLI on railiance01 (agentic daily/review fail-closed until installed) | BINKY-WP-0006 | -| Host timers are interim dual of activity-core cron (not pure sink-driven) | retire timers when poller live | +| Post-scan **triage** still assumes coding-agent session (Claude) | BINKY-WP-0006: **llm-connect → OpenRouter** + JSON apply (see `railiance-llm-rhythm.md`) | +| Daily/review unattended without host coding agent | Same pattern via llm-connect | +| Host timers dual activity-core schedules | Simplify once llm path is the only executor | +| **Out of scope / rejected:** install Claude Code on railiance01 | — | ## Rollback diff --git a/integrations/railiance-llm-rhythm.md b/integrations/railiance-llm-rhythm.md new file mode 100644 index 0000000..a96d089 --- /dev/null +++ b/integrations/railiance-llm-rhythm.md @@ -0,0 +1,106 @@ +# Railiance rhythm via llm-connect (not a host coding agent) + +> Status: design note 2026-07-21 (BINKY-WP-0006). +> Supersedes the residual “install Claude on railiance01” idea from the +> BINKY-WP-0004 cutover notes. + +## Principle + +**Railiance executes infrastructure + hosted LLM inference. It does not host +Claude Code, Codex, or another interactive coding harness.** + +| Layer | On Railiance | Not on Railiance | +| --- | --- | --- | +| Secrets | OpenBao AppRole / in-cluster Secret | Paste into chat | +| Deterministic I/O | email-connect scan, git commit/push with deploy key | — | +| LLM | **llm-connect → OpenRouter** (open-weights / cheap models) | Claude CLI agentic session | +| Policy | Allow-listed apply steps (Blue/Green) | Model free-form shell | + +This matches agent-harness INTENT (session policy shell) and llm-connect +INTENT (provider abstraction): the **server profile** uses llm-connect HTTP; +laptop coding agents remain optional for interactive work only. + +## Mail path (canonical) + +```text +timer / CronJob + → agent-harness mail-scan # deterministic, AppRole IMAP + → agent-harness mail-triage # NEW: llm-connect JSON + apply + → hub progress + local commit +``` + +### Phase 1 — scan (exists, proven) + +- Module: `agent_harness/mailscan.py` +- Creds: `tenants/binky/company-email/imap` via AppRole +- Output: `mailmeta/reports/email-channel-evidence-report-*.csv` +- Hub: `binky_mail_intake` with counts only + +### Phase 2 — triage (to build) + +1. **Prompt** (metadata only): newest CSV rows + current mail-log tail + + queue templates. +2. **llm-connect** `POST` (same contract as activity-core `llm_client.py`): + - URL: `http://llm-connect.activity-core.svc.cluster.local:8080` + - Provider: OpenRouter (already configured on railiance) + - Model: cost-efficient; **prefer open-weights** (pick at T03; examples to + evaluate: Llama/Qwen/Mistral class on OpenRouter — measure quality on + fixture CSV before locking). +3. **Response**: strict JSON, e.g. + +```json +{ + "log_entries": [ + {"date": "2026-07-21", "from": "…", "subject": "…", "action": "ignore|queue|suspicious"} + ], + "queue_items": [], + "notes": "one-line summary" +} +``` + +4. **Apply** (deterministic Python): append mail-log; add queue YAML only if + schema-valid; **suspicious** → log only; `git commit`; hub event. +5. **Never** pass message bodies to the model if the scanner can omit them; + never execute model-suggested shell. + +## Daily / weekly (same shape) + +| Slot | Deterministic inputs | LLM output | Apply | +| --- | --- | --- | --- | +| Daily brief | queues, open risks, yesterday brief | structured sections | write `briefs/YYYY-MM-DD-daily-brief.md` + commit | +| Weekly review | briefs week, SuccessMilestones | structured prep | write weekly prep file + commit | + +No tool-using coding agent required if apply is code-owned. + +## What already runs on railiance01 + +| Component | Status (2026-07-21) | +| --- | --- | +| `llm-connect` Deployment in `activity-core` | Running; OpenRouter; ConfigMap model `google/gemini-2.5-flash` (may change) | +| OpenRouter API key Secret | Present (operator custody) | +| `agent-harness mail-scan` on host | Proven | +| Host systemd timers | Interim cadence; must call llm path, not `require_claude` | +| Claude CLI on host | **Must not** become a dependency | + +## Anti-patterns + +- Installing Claude Code / npm agent stacks on railiance for rhythm. +- Defaulting `runner.run_task` to `AgenticClaudeCodeAdapter` for server Jobs. +- Sending full email bodies or secrets to OpenRouter. +- Dual “coding agent policy” vs “server policy” that drifts (same JSON schema + and apply code for all environments). + +## Implementation homes + +| Work | Repo | +| --- | --- | +| HTTP adapter + mail-triage command + tests | **agent-harness** | +| Model/profile ConfigMap, smoke | **llm-connect** / activity-core overlay | +| Timers, binky task prompts, dogfood | **binky-control** (this doc + BINKY-WP-0006) | + +## See also + +- `integrations/executor-cutover-runbook.md` — cutover status +- `workplans/BINKY-WP-0006-…` — tasks +- llm-connect `docs/activity-core-llm-endpoint.md` +- agent-harness `mailscan.py` two-phase header comment diff --git a/scripts/railiance-rhythm/install-on-railiance.sh b/scripts/railiance-rhythm/install-on-railiance.sh index ff0a652..10b55bb 100755 --- a/scripts/railiance-rhythm/install-on-railiance.sh +++ b/scripts/railiance-rhythm/install-on-railiance.sh @@ -19,4 +19,5 @@ systemctl --user enable --now binky-rhythm-mail.timer systemctl --user enable --now binky-rhythm-review.timer systemctl --user list-timers --all | grep binky-rhythm || true echo "Installed. Logs: ~/.cache/binky-control/rhythm/" -echo "Agentic daily/review require: claude CLI on PATH. mail-scan does not." +echo "Execution model: llm-connect/OpenRouter on cluster — NOT Claude CLI on host." +echo "See integrations/railiance-llm-rhythm.md (BINKY-WP-0006)." diff --git a/scripts/railiance-rhythm/run-common.sh b/scripts/railiance-rhythm/run-common.sh index c8abb3b..6a60728 100755 --- a/scripts/railiance-rhythm/run-common.sh +++ b/scripts/railiance-rhythm/run-common.sh @@ -28,10 +28,18 @@ require_repo() { git -C "${BINKY_REPO}" pull --ff-only --quiet origin main || log "WARN: pull failed (continuing on local)" } -require_claude() { - if ! command -v claude >/dev/null 2>&1; then - log "ERROR: claude CLI not on PATH — agentic rhythm cannot run on this host" - log " residual: install Claude Code (or swap in a hosted agentic adapter) on railiance01" - exit 2 +require_llm_connect() { + # Server-side rhythm uses llm-connect HTTP (OpenRouter), never Claude CLI. + if [[ -z "${LLM_CONNECT_URL:-}" ]]; then + # In-cluster default when running inside k8s; host timers may override. + export LLM_CONNECT_URL="${LLM_CONNECT_URL:-http://llm-connect.activity-core.svc.cluster.local:8080}" fi + log "LLM_CONNECT_URL=${LLM_CONNECT_URL}" +} + +require_claude() { + # Deprecated for Railiance. Kept only so old docs fail loudly with the right message. + log "ERROR: Claude CLI is not the Railiance execution path (BINKY-WP-0006)." + log "Use llm-connect/OpenRouter via agent-harness hosted adapters — see integrations/railiance-llm-rhythm.md" + exit 2 } diff --git a/scripts/railiance-rhythm/run-daily.sh b/scripts/railiance-rhythm/run-daily.sh index 88fe4b3..0a09248 100755 --- a/scripts/railiance-rhythm/run-daily.sh +++ b/scripts/railiance-rhythm/run-daily.sh @@ -4,9 +4,14 @@ set -euo pipefail # shellcheck disable=SC1091 source "$(dirname "$0")/run-common.sh" require_repo -require_claude -log "START daily-rhythm" -agent-harness run \ - --task-file "${BINKY_REPO}/integrations/harness-tasks/daily-rhythm.json" \ - 2>&1 | tee -a "${LOG_DIR}/daily.log" +require_llm_connect +log "START daily-rhythm (llm-connect path when available; else fail until BINKY-WP-0006)" +# Prefer dedicated command once implemented; fall back message if only Claude runner exists. +if agent-harness brief-daily --help >/dev/null 2>&1; then + agent-harness brief-daily --target-repo "${BINKY_REPO}" \ + 2>&1 | tee -a "${LOG_DIR}/daily.log" +else + log "ERROR: brief-daily not implemented yet (needs llm-connect structured apply — BINKY-WP-0006-T05)" + exit 3 +fi log "END daily-rhythm rc=${PIPESTATUS[0]}" diff --git a/scripts/railiance-rhythm/run-mail.sh b/scripts/railiance-rhythm/run-mail.sh index bde2965..396d1e4 100755 --- a/scripts/railiance-rhythm/run-mail.sh +++ b/scripts/railiance-rhythm/run-mail.sh @@ -15,13 +15,14 @@ log "mail-scan rc=${scan_rc}" if [[ "${scan_rc}" -ne 0 ]]; then exit "${scan_rc}" fi -# Agentic triage if Claude present; otherwise scan+hub event is enough for intake idempotence. -if command -v claude >/dev/null 2>&1; then - log "START mail-triage session" - agent-harness run \ - --task-file "${BINKY_REPO}/integrations/harness-tasks/mail-intake.json" \ - 2>&1 | tee -a "${LOG_DIR}/mail.log" || log "WARN: mail-triage session failed" +# Triage must use llm-connect (OpenRouter), not a host coding agent. +# Until agent-harness mail-triage exists (BINKY-WP-0006), scan-only is OK. +if agent-harness mail-triage --help >/dev/null 2>&1; then + log "START mail-triage (llm-connect)" + agent-harness mail-triage \ + --target-repo "${BINKY_REPO}" \ + 2>&1 | tee -a "${LOG_DIR}/mail.log" || log "WARN: mail-triage failed" else - log "SKIP mail-triage session (no claude CLI); mail-scan hub event stands" + log "SKIP mail-triage: command not available yet (BINKY-WP-0006); scan-only" fi log "END mail-intake" diff --git a/scripts/railiance-rhythm/run-review.sh b/scripts/railiance-rhythm/run-review.sh index d49be14..545fb1e 100755 --- a/scripts/railiance-rhythm/run-review.sh +++ b/scripts/railiance-rhythm/run-review.sh @@ -4,9 +4,13 @@ set -euo pipefail # shellcheck disable=SC1091 source "$(dirname "$0")/run-common.sh" require_repo -require_claude -log "START review-prep" -agent-harness run \ - --task-file "${BINKY_REPO}/integrations/harness-tasks/review-prep.json" \ - 2>&1 | tee -a "${LOG_DIR}/review.log" +require_llm_connect +log "START review-prep (llm-connect path)" +if agent-harness brief-weekly --help >/dev/null 2>&1; then + agent-harness brief-weekly --target-repo "${BINKY_REPO}" \ + 2>&1 | tee -a "${LOG_DIR}/review.log" +else + log "ERROR: brief-weekly not implemented yet (BINKY-WP-0006-T05)" + exit 3 +fi log "END review-prep rc=${PIPESTATUS[0]}" diff --git a/workplans/BINKY-WP-0006-railiance-rhythm-continuous-intake.md b/workplans/BINKY-WP-0006-railiance-rhythm-continuous-intake.md index b739020..7535dfa 100644 --- a/workplans/BINKY-WP-0006-railiance-rhythm-continuous-intake.md +++ b/workplans/BINKY-WP-0006-railiance-rhythm-continuous-intake.md @@ -1,7 +1,7 @@ --- id: BINKY-WP-0006 type: workplan -title: "Railiance rhythm: continuous intake + agentic runtime on host" +title: "Railiance rhythm: llm-connect execution (no host coding agent)" domain: infotech repo: binky-control status: ready @@ -12,19 +12,44 @@ updated: "2026-07-21" state_hub_workstream_id: "1c068209-c3f7-4445-82ed-f762a095120c" --- -Close the residual gaps after BINKY-WP-0004 cutover so rhythm is fully -unattended on Railiance without host-timer dual scheduling. +Close residual gaps after BINKY-WP-0004 cutover so Binky rhythm runs +**unattended on Railiance without installing Claude Code or any coding +agent/harness shell on the host.** -## Context +## Architectural decision (founder direction 2026-07-21) -- activity-core `ISSUE_SINK_TYPE=state-hub` posts **progress** events on emit - (not claimable issue-core issues). agent-harness intake still polls issue-core. -- agent-harness k8s deploy is `sleep infinity` (placeholder). -- railiance01 host timers call task files directly (interim). -- Agentic daily/review need `claude` CLI on railiance01 (absent as of 2026-07-21); - mail-scan works via AppRole without Claude. +| Wrong | Right | +| --- | --- | +| Install Claude CLI / coding agent on railiance01 | Use **llm-connect** (already in `activity-core` ns on railiance) via **OpenRouter** | +| AgenticClaudeCodeAdapter as default on server | **Hosted text adapter** + **deterministic apply** of structured results | +| Re-implement policy in every client | Keep Green/Blue rules in harness apply layer; model only classifies/drafts | -## Task: Choose emission → execution contract +### Two-phase mail path (already half-built) + +1. **Deterministic scan** (`agent-harness mail-scan`) — IMAP via AppRole, CSV + under `mailmeta/reports/`, hub `binky_mail_intake` counts. **No LLM.** + Proven on railiance01 2026-07-21. +2. **Triage** — today wrongly defaults to Claude Code agentic session. + **Target:** call in-cluster + `http://llm-connect.activity-core.svc.cluster.local:8080` (OpenRouter, + cost-efficient open-weights or cheap hosted model) → **JSON triage plan** → + deterministic applier commits metadata-only queue/log updates. + +Daily brief / weekly review follow the same pattern (structured draft → +apply → commit), not a full IDE agent on the node. + +### Existing platform assets + +- llm-connect Deployment live on railiance01 (`LLM_CONNECT_PROVIDER=openrouter`, + model currently `google/gemini-2.5-flash` in ConfigMap — may retarget to + open-weights for cost). +- OpenBao lane for OpenRouter key already used by that service + (`llm-connect-provider-secrets` / CCR path under activity-core). +- activity-core `llm_client.py` is the HTTP client pattern to reuse. +- agent-harness `runner.py` already accepts an injected `adapter=` (swap + default off Claude Code for server profiles). + +## Task: Spec llm-connect mail-triage contract ```task id: BINKY-WP-0006-T01 @@ -33,17 +58,21 @@ priority: high state_hub_task_id: "912a8802-e80f-4c04-aecd-9586c3b7a4b0" ``` -Decide with activity-core / harness owners: +Write `integrations/railiance-llm-rhythm.md` (or extend cutover runbook): -A) Restore opt-in `ISSUE_SINK_TYPE=rest` for Binky labels only, or -B) Teach agent-harness to claim work from a state-hub work-record / task queue, or -C) Keep host timers as the durable executor and demote activity-core schedules - to observability-only for Binky. +- Input: newest `mailmeta/reports/*.csv` (metadata columns only; no bodies). +- Output JSON schema: log entries, queue candidates, suspicious flags + (sender/subject/date only). +- Model profile: OpenRouter **cost-efficient** default (prefer open-weights; + name candidates + fallback); max tokens; temperature low. +- Apply rules: Blue lane, no push, no network from apply step; suspicious + mail never acted on. +- Where it runs: railiance Job/CronJob or host timer calling + `agent-harness mail-triage` (new subcommand), **not** Claude. -Record decision (DEC if founder-relevant) and update -`integrations/executor-cutover-runbook.md`. +Done when: doc reviewed against AutonomyPolicy + mailscan two-phase design. -## Task: Continuous consumer on Railiance +## Task: Implement hosted triage path in agent-harness ```task id: BINKY-WP-0006-T02 @@ -52,10 +81,18 @@ priority: high state_hub_task_id: "00439d4e-d6ee-4cbc-98e2-431eca4651fe" ``` -Implement the chosen path: systemd/k8s poller loop, not `sleep infinity`. -Health metrics + logs under `~/.cache/binky-control/rhythm/`. +In **agent-harness** (implementation repo; this WP tracks binky integration): -## Task: Agentic runtime on railiance01 +1. `LlmConnectHttpAdapter` (or client matching activity-core `llm_client.py`) + using `LLM_CONNECT_URL`. +2. `mail-triage` command: read report → prompt → parse JSON → apply to + `mailmeta/mail-log.md` + queues → local commit → hub event. +3. Server profile default: **never** require `claude` CLI. +4. Tests with fixture CSV + mock HTTP llm-connect. + +Done when: unit tests green; dry-run docs for railiance. + +## Task: Wire OpenRouter model profile on railiance llm-connect ```task id: BINKY-WP-0006-T03 @@ -64,11 +101,21 @@ priority: high state_hub_task_id: "1f82c8e0-945e-44b6-a5dc-22dd055e751d" ``` -Install and auth Claude Code (or approved hosted agentic adapter) on -railiance01 so `run-daily.sh` / `run-review.sh` exit 0 unattended. Document -in `integrations/executor-worker-secrets.md` if new secret lanes appear. +On railiance01 activity-core llm-connect ConfigMap (and optional dedicated +profile env for rhythm): -## Task: Retire dual scheduling +- Confirm OpenRouter secret still healthy (no values in git/chat). +- Choose cost-efficient model (open-weights preferred; document choice and + $/MTok rationale). +- Smoke: in-cluster request from a Job to llm-connect with a tiny prompt. +- Expose URL to host timers if host-run: + `LLM_CONNECT_URL=http://…` via cluster DNS or port-forward service policy + (prefer **in-cluster CronJob** over host if possible). + +Done when: smoke OK; ConfigMap change committed in llm-connect/activity-core +overlays as appropriate. + +## Task: Railiance schedule uses llm path; drop Claude residual ```task id: BINKY-WP-0006-T04 @@ -77,17 +124,33 @@ priority: medium state_hub_task_id: "2093bf62-20ef-4d3e-9b91-be77a38603f7" ``` -Once T01–T03 proven for three business days: remove host timers **or** -disable redundant activity-core schedules so only one cadence owner remains. -Update OperatingRhythm.md. +Update `scripts/railiance-rhythm/run-mail.sh` to call `mail-scan` then +`mail-triage` (llm-connect), remove `require_claude` for mail. Prefer +migrating daily/review similarly or keep them laptop-optional until T05. -## Task: Closure +Update OperatingRhythm.md + executor-cutover-runbook residual section: +**no coding agent on railiance**. + +## Task: Daily/review via llm-connect (optional same WP if small) ```task id: BINKY-WP-0006-T05 status: todo -priority: low +priority: medium state_hub_task_id: "503ff56b-ac38-44b0-911e-937e0c2fc089" ``` -Mark finished; log milestone; `statehub fix-consistency`. +Extend the same pattern to daily brief + weekly review prep: structured +generation via llm-connect + deterministic markdown apply + commit. If scope +grows, spawn BINKY-WP-0007 and mark this task done with handoff. + +## Task: Closure + +```task +id: BINKY-WP-0006-T06 +status: todo +priority: low +``` + +Three clean unattended mail cycles on railiance (scan+triage); milestone; +`statehub fix-consistency`.