diff --git a/work/plans/executor-orchestrator-redesign.md b/work/plans/executor-orchestrator-redesign.md new file mode 100644 index 00000000..433a25d6 --- /dev/null +++ b/work/plans/executor-orchestrator-redesign.md @@ -0,0 +1,115 @@ +# Plan: Executor as Spawned Worker — Orchestrator Pattern + +**Date**: 2026-05-14 +**Status**: Planning +**Parent**: [[executor-v2-redesign]] + +## Problem + +Currently Eagle (орёл) handles executor tasks inline in the conversation: +- Takes on the task directly in the discussion thread +- Asks Alex for permissions mid-task, blocking the flow +- Creates tight coupling between conversational layer and execution layer + +## Desired Architecture + +``` +Alex → Eagle (orchestrator, in Zulip topic) + ↓ spawn + Executor (worker process, separate Hermes session) + ↓ writes to Zulip #executor topic + Eagle monitors #executor + ↓ reads recent output (not full thread) + ↓ auto-approve obvious decisions + ↓ escalate ambiguous → @mention Alex +``` + +## Roles + +### Eagle (Orchestrator) +- Receives task request in any Zulip topic +- Queues task to `executor_queue` DB table +- Spawns Executor worker via `hermes cron run` or similar +- Monitors `#executor` topic for Executor messages +- Reads only recent N messages (sliding window, not full thread) +- Auto-approval logic: + - "create worktree" → approve + - "run tests" → approve + - "open draft PR" → approve + - "push to branch" → approve + - "modify unrelated file" → DENY + escalate + - "post Asana comment" → escalate to Alex + - Any scope expansion → escalate to Alex +- @mention Alex only when ambiguous or high-risk + +### Executor (Worker) +- Separate Hermes session/process +- Bot identity: "Executor" or "Исполнитель" (not Eagle) +- Communicates via `#executor` Zulip topic +- Posts structured messages: `[REQUEST: ]`, `[STATUS: ]`, `[DONE: ]` +- Does NOT ask Alex directly — all escalations go to Eagle +- On permission denied → explain why, ask what to do differently + +## Message Protocol (Executor → Eagle) + +``` +[STATUS: investigation] Analysing bug GID 123456... +[REQUEST: create_worktree] Branch: fix/tab-preview-stuck +[REQUEST: run_tests] Scheme: macOS UI Tests CI +[STATUS: pr_open] Draft PR: https://github.com/... +[DONE: pr_ready] PR #1234 opened, CI green, ready for review +[ESCALATE: scope_expansion] Found unrelated issue in TabBar.swift — should I fix it? +``` + +## Auto-approval Rules (Eagle) + +| Message type | Auto action | +|---|---| +| `[REQUEST: create_worktree]` | ✅ approve silently | +| `[REQUEST: run_build]` | ✅ approve silently | +| `[REQUEST: run_tests]` | ✅ approve silently | +| `[REQUEST: open_draft_pr]` | ✅ approve silently | +| `[REQUEST: push_branch]` | ✅ approve silently | +| `[REQUEST: post_asana_comment]` | ⚠️ show Alex, wait | +| `[REQUEST: merge_pr]` | ❌ always escalate | +| `[ESCALATE: *]` | ⚠️ always escalate | +| `[ESCALATE: scope_expansion]` | ❌ deny by default + notify Alex | + +## Eagle Monitoring Loop + +Eagle does NOT run a continuous monitoring process. Instead: +- Option A: Hermes cron (every 2 min) → check `#executor` for unprocessed `[REQUEST]` or `[ESCALATE]` messages → process them +- Option B: Webhook trigger on Zulip `#executor` stream → Eagle session wakes up + +**Recommendation: Option A** (simpler, uses existing cron infra) + +## Context Window Management + +Eagle reads Executor's thread with sliding window: +- Last 20 messages from `#executor` for the current `executor_run_id` +- Filter by `run_id` tag in messages to handle concurrent runs +- Structured messages allow O(1) parsing without LLM + +## Relation to Executor v2 + +This is a UI/coordination layer ON TOP of executor-v2 bash daemons: +- executor-runner.sh still does the heavy lifting +- Eagle becomes the gatekeeper/orchestrator that monitors and approves +- Executor worker sends structured messages via Zulip API (or hermes platform API) + +## Implementation Steps + +1. Define message protocol (structured tags) +2. Add Zulip message sender to executor worker prompts +3. Create Eagle monitoring cron (2 min interval, `#executor` stream) +4. Implement auto-approval logic in Eagle monitoring prompt +5. Add escalation → `@mention Alex` in monitoring prompt +6. Test with a real executor run in dry-run mode + +## Open Questions + +- How does Executor "spawn" as a separate bot persona? + - Option A: Same Hermes instance, different SOUL/persona config + - Option B: Separate Hermes gateway with "Executor" identity + - Option C: `hermes run -p executor-worker.md` as subprocess (non-interactive) +- Concurrent runs: Eagle needs to track run_id per conversation thread diff --git a/work/plans/gha-ci-executor.md b/work/plans/gha-ci-executor.md new file mode 100644 index 00000000..f90f2d30 --- /dev/null +++ b/work/plans/gha-ci-executor.md @@ -0,0 +1,151 @@ +# Plan: GHA CI Executor — macOS Build + Peekaboo + +**Date**: 2026-05-14 +**Status**: Planning + +## Goal + +Migrate executor workers from Eagle's local Mac to GitHub Actions macOS runners: +- Each task spawns a GHA job +- Job installs Claude Code + Peekaboo + builds the app +- Executor (Claude Code) runs the fix, opens PR, posts report to Asana +- Eagle orchestrates: queues task → triggers GHA → monitors job → handles result + +## Architecture + +``` +Eagle (orchestrator) → triggers GHA workflow via gh CLI + ↓ + GHA macOS runner (macos-15-xlarge or self-hosted) + ↓ + Install: Xcode, Claude Code, Peekaboo, Simulator + ↓ + claude -p executor-worker.md (non-interactive) + ↓ + Claude Code: fix → build → UI test via Peekaboo → PR + ↓ + Post result to Asana (gh pr comment + asana-bot) +``` + +## Analysis Agent (Cloud-side) + +Separate from the executor worker: +- Runs in Claude Cloud (or Eagle's Mac) +- Picks task tagged "обработать" from Asana +- Reads full task context (description, stories, user reports) +- Generates detailed executor prompt (investigation plan + acceptance criteria) +- Triggers GHA workflow with this prompt as input + +## GHA Workflow Design + +```yaml +name: Executor Worker +on: + workflow_dispatch: + inputs: + task_gid: + description: 'Asana task GID' + executor_prompt: + description: 'Base64-encoded executor prompt' + branch_name: + description: 'Git branch to create' + +jobs: + executor: + runs-on: macos-15 # or self-hosted M-series + timeout-minutes: 120 + steps: + - uses: actions/checkout@v4 + - name: Install Claude Code + run: npm install -g @anthropic-ai/claude-code + - name: Install Peekaboo + run: # brew install or download binary + - name: Bootstrap Xcode + run: # select Xcode version, accept license + - name: Run Executor + env: + ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_CI_KEY }} + ASANA_TOKEN: ${{ secrets.ASANA_CI_TOKEN }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + echo "${{ inputs.executor_prompt }}" | base64 -d > /tmp/prompt.md + claude -p /tmp/prompt.md \ + --branch "${{ inputs.branch_name }}" \ + --no-interactive + - name: Post Result + if: always() + run: # Post summary to Asana task + open PR if not already +``` + +## Key Questions to Investigate + +1. **macOS runner availability**: GitHub-hosted `macos-15` or `macos-15-xlarge`? + - Large runners cost more but needed for Xcode + Simulator + - Self-hosted M4 Mac runner on Eagle itself? (no cloud cost but uses Eagle) + +2. **Peekaboo on GHA**: Does Peekaboo work on GHA macOS runners? + - Needs Screen Recording permission → headless runner may not grant it + - Alternative: use `xcrun simctl` directly for UI tests + - Or: spin up VM via ddg-vm (if GHA runner has the VM tool) + +3. **Build time**: Full prod build on GHA macos-15? + - Estimated 15-30 min cold, 5-10 min with cache + - DerivedData caching via `actions/cache` is critical + +4. **Claude Code non-interactive**: Does `claude -p` (non-interactive) work for complex fix tasks? + - Yes — `claude -p` is the standard non-interactive mode + - Needs `--dangerously-skip-permissions` or pre-approved tool config + +5. **Secrets**: Need in GHA: + - `ANTHROPIC_CI_KEY` — separate key for CI (not user key) + - `ASANA_CI_TOKEN` — for posting results + - `GITHUB_TOKEN` — auto-provided + +## Validation Steps + +Before full implementation: +1. Test prod build on `macos-15` runner: does it succeed without signing? +2. Test Peekaboo install + basic screenshot on GHA runner +3. Test `claude -p` with a simple prompt on GHA runner +4. Verify DerivedData caching works (build time < 10 min cached) + +## Phased Implementation + +### Phase 1: Validation (1-2 days) +- Create a test GHA workflow that: checks out repo, builds, runs basic UI test +- Verify Peekaboo accessibility permissions on headless runner +- Document what works / what doesn't + +### Phase 2: Claude Code Integration (2-3 days) +- Add Claude Code step to workflow +- Test with a trivial fix prompt +- Verify PR creation from GHA bot context + +### Phase 3: Analysis Agent (3-5 days) +- Cloud-side agent that picks Asana tasks + generates prompts +- Triggers GHA workflow via `gh workflow run` +- Monitors job, posts result to Asana + +### Phase 4: Eagle Integration (1 day) +- Eagle can trigger Phase 3 analysis agent on demand +- `#executor` topic shows GHA job status +- Auto-approval for GHA-spawned actions + +## Relation to Sentry Bot + +Sentry bot pattern (already working): +- Cloud agent monitors Sentry errors +- Generates structured report +- Posts to Asana/Slack + +GHA executor follows same pattern: +- Cloud agent picks Asana bug tasks +- Generates fix prompt +- GHA runner executes fix + validation + +## Notes + +- GHA macos-15 runners do NOT have Xcode pre-cached — each run installs from scratch +- Self-hosted runner on Eagle avoids this but ties up the main Mac +- DuckDuckGo likely has existing GHA macOS setup — check existing workflows first +- Peekaboo may need `tccutil` or System Preferences pre-grant on runner diff --git a/work/plans/no-topic-routing.md b/work/plans/no-topic-routing.md new file mode 100644 index 00000000..fcc48f4d --- /dev/null +++ b/work/plans/no-topic-routing.md @@ -0,0 +1,78 @@ +# Plan: (no topic) Thread Routing + +**Date**: 2026-05-14 +**Status**: Planning +**Priority**: Medium + +## Problem + +When Alex sends a message in Zulip's `(no topic)` thread (the default unnamed thread), it usually means one of two things: +1. He missed the correct topic — it should go to the most recent active thread in the same stream +2. He's starting a genuinely new discussion + +Eagle needs to automatically detect which case it is and act accordingly. + +## Design + +### Detection Logic + +**Step 1: Find most recent active thread in stream** +- Query Zulip API for recent messages in the current stream (last N hours) +- Exclude `(no topic)` thread itself +- Get the thread with the most recent message → `candidate_thread` + +**Step 2: Relevance check** +- Take first ~200 chars of `(no topic)` message +- Take thread name + last 2-3 messages from `candidate_thread` as context +- Ask LLM (cheap/fast call): "Is this message topically related to this thread? yes/no + confidence" + +**Step 3: Action** + +If **related** (confidence > 0.7): +- Quote Alex's message in `candidate_thread` with attribution: `[Cross-posted from (no topic) — @Alex]` +- Reply in `(no topic)`: `Переношу в тред «{thread_name}» ↗` +- Continue discussion in `candidate_thread` +- ⚠️ Coordination: check if another Eagle instance is active in `candidate_thread` (see below) + +If **not related** (new topic): +- Rename `(no topic)` thread to a descriptive topic name (2-5 words, inferred from message) +- Reply normally in the renamed thread + +### Multi-instance Coordination + +Eagle may have multiple instances running in different Zulip topics simultaneously. To avoid conflicts when routing to an existing thread: + +**Option A: DB mutex** (preferred) +- `zulip_thread_locks` table: `(stream, topic, locked_by_session, locked_at)` +- Before posting in another thread → INSERT lock (fail on conflict) +- Release lock after posting +- Stale lock (>5 min) → auto-expire + +**Option B: Message-based handoff** +- Before cross-posting → send a "silent" message to `candidate_thread` (deletable): `[Eagle routing incoming from (no topic)]` +- If another instance replies with `[ACK]` → let it handle +- If no reply in 30s → proceed + +**Recommendation: Option A** — simpler, no timing issues. + +### Multi-instance Awareness + +Each Eagle instance should know its own Zulip topic context. When spawned in a topic, it registers itself in `zulip_thread_locks`. Eagle reads this to avoid double-posting. + +## Implementation Steps + +1. Add Zulip stream message history query to Eagle's toolkit (REST API call via hermes) +2. Create `zulip_thread_locks` table in `personal_os` DB +3. Write topic routing logic as a function in the Eagle prompt or as a cron-triggered script +4. Test: send message in `(no topic)`, verify routing + rename behavior + +## Questions + +- Does Eagle get invoked *automatically* for every (no topic) message, or only when mentioned? +- Is Eagle a single process or multiple instances per stream? (affects coordination complexity) +- What's the stream scope? Only `master`? Or all streams? + +## Notes + +- Current session: this `(no topic)` thread in `master` stream should be renamed to something like `planning: system improvements 2026-05` +- Zulip topic rename API: `PATCH /api/v1/messages/{message_id}` with `topic` param (requires first message ID of the thread) diff --git a/work/plans/time-machine-tunnel-backup.md b/work/plans/time-machine-tunnel-backup.md new file mode 100644 index 00000000..f3795ce7 --- /dev/null +++ b/work/plans/time-machine-tunnel-backup.md @@ -0,0 +1,83 @@ +# Plan: Time Machine Backup via SSH Tunnel + +**Date**: 2026-05-14 +**Status**: Planning + +## Problem + +Kraken (RPi5) is in a different network (VPS via tunnel, IP 91.207.28.205:2223). +Time Machine is configured for `smb://timemachine@kraken._smb._tcp.local./TimeMachine` via mDNS — not reachable when Kraken is on a remote network. + +Last backup: May 12, 2026. Backup destination is 3.98 TB quota. + +## Options + +### Option A: SSH Tunnel → SMB Port Forward (complex, fragile) +```bash +ssh -L 445:localhost:445 kraken@91.207.28.205 -p 2223 +``` +Then add TM destination: `smb://timemachine@localhost/TimeMachine` + +**Problems:** +- SMB over SSH tunnel is slow for large backups +- macOS blocks port 445 forwarding without SIP disabled +- TM doesn't handle SMB reconnects well → corrupted sparsebundles +- Port 445 requires root privileges on Mac side + +### Option B: Netatalk (AFP over TCP) via Tunnel (legacy, not recommended) +AFP support was removed from TM in macOS Ventura+. Not viable. + +### Option C: restic/rclone to remote (not TM, but works) +Replace TM for Kraken backup with `restic`: +```bash +# On Eagle: +restic -r sftp:kraken@91.207.28.205:2223/backups/eagle backup ~/ +``` +Works over SSH, incremental, encrypted. But not native TM UX. + +### Option D: Wireguard VPN between Eagle and Kraken (best) +- Kraken runs Wireguard server → Eagle connects as peer +- Eagle sees Kraken's SMB share at VPN IP (e.g. 10.0.0.2) +- TM configured to use VPN IP instead of mDNS +- Survives network changes, works from any location + +**Setup:** +1. Install Wireguard on Kraken (Docker or host) +2. Configure Eagle as Wireguard peer +3. Expose Samba port on Kraken's VPN interface +4. Update TM destination to VPN IP + +### Option E: Keep WD12-TimeMachine local + periodic rsync to Kraken +- WD12-TimeMachine (6.01 TB) is LOCAL — backups fine +- Rsync the TM sparsebundle to Kraken weekly +- Kraken = offsite copy, not real-time TM + +## Recommendation + +**Short term**: Do nothing — WD12 local TM is working fine (last backup May 12). + +**Medium term (1-2 weeks)**: Option D (Wireguard VPN) +- Best reliability, proper TM over SMB +- Kraken already has Docker — easy to add Wireguard container + +**Alternatively**: Option E (rsync TM bundle to Kraken) — simpler, less real-time + +## Current Status + +- WD12-TimeMachine: local, 6.01 TB, working ✅ +- Kraken SMB TM: 3.98 TB, last backup May 12, NOT reachable from Eagle currently ⚠️ +- Kraken network: accessible only via SSH tunnel (VPS 91.207.28.205:2223) + +## Next Steps (when ready to implement) + +1. Decision: Wireguard vs rsync-only +2. If Wireguard: deploy wg-easy Docker container on Kraken +3. Configure Eagle as peer (macOS Wireguard app or CLI) +4. Test SMB reach over VPN +5. Update TM destination + +## Notes + +- Don't try SMB over port-forward — port 445 blocked by macOS SIP on loopback +- Kraken SSH tunnel is stable (CF tunnel via cloudflared) +- TrueNAS (Taiga) also available for backup — might be better option if Taiga on local net