setup · diff

git:20260626.d9fb956 to git:20260705.5a5efb9

56 added, 522 removed. Audit C to A.

---
name: setup
- description: "Use when configuring a new machine to run the Valor Telegram bridge. Installs all dependencies, authentication, and service startup. Triggered by 'setup', 'configure this machine', or 'new machine setup'."
+ description: "Use when configuring a new machine to run the Valor Telegram bridge. Triggered by 'setup', 'configure this machine', or 'new machine setup'."
disable-model-invocation: true
---
# Setup - New Machine Configuration
Configure this machine to run the Valor Telegram bridge. You do everything except the interactive Telegram login step.
## Prerequisites
**PREREQUISITE: Must be on latest main branch before running.**
```bash
cd ~/src/ai && git checkout main && git pull
```
Before starting, confirm the user has:
- Python 3.12+ installed
- The `ai` repo cloned at `~/src/ai` **on the main branch with latest changes pulled**
- Telegram API credentials (api_id and api_hash from https://my.telegram.org). If they don't have these, pause and explain how to get them before continuing.
- ### Ensure bare `python` resolves to Python 3.12+
-
- Claude Code hooks invoke bare `python` under `/bin/sh`, which does not honor zsh aliases. macOS does not ship a `python` binary by default — only `python3`. Without this symlink every hook that uses `python` silently fails with `command not found`, surfacing errors in the UI and disabling validators (no-raw-redis-delete, plan-section checks, SDLC reminders, etc.).
-
- ```bash
- # Verify python3 is 3.12+
- python3 --version
-
- # Create the symlink in a user-writable PATH dir (no sudo)
- ln -sf "$(command -v python3)" /opt/homebrew/bin/python
-
- # Confirm /bin/sh resolves it
- /bin/sh -c 'python --version' # expected: Python 3.12.x or newer
- ```
-
- The update orchestrator (`scripts/update/run.py`) verifies this via `check_python_alias()` and fails loudly if missing.
-
- ### Bootstrap cross-machine shell env loader
-
- Cross-machine secrets and shell config live in `~/Desktop/Valor/` (iCloud-synced). `~/.zshenv` itself does NOT sync (it's in `$HOME`), so each new machine needs a one-line bootstrap that sources the vault loader. The update script self-heals this on every run via `scripts/update/zshenv_sync.py`, but on a fresh machine the easiest path is to run that module directly before the first `/update`:
-
- ```bash
- cd ~/src/ai
- .venv/bin/python -c "from scripts.update.zshenv_sync import sync_zshenv; r = sync_zshenv(); print(r)"
- ```
-
- That:
- - Seeds `~/Desktop/Valor/zshenv.sh` with a default loader if missing (only the very first machine ever does this — subsequent machines inherit the file via iCloud).
- - Appends a `[ -f ~/Desktop/Valor/zshenv.sh ] && source ...` guard to `~/.zshenv` if missing.
-
- After it runs, open a fresh shell and confirm a shared secret is loaded (e.g., `echo "${SENTRY_PERSONAL_TOKEN:+set}"` — should print `set` if the vault `.env` defines it). If the vault hasn't synced yet, the guard line is still safe (it's `[ -f ]`-gated) and will activate as soon as iCloud lands the file.
-
- If you need to add new cross-machine shell config later (PATH tweaks shared across all Valor machines, shell functions, etc.), edit `~/Desktop/Valor/zshenv.sh` directly — it syncs everywhere automatically. Keep host-specific config in the local `~/.zshenv` or `~/.zshrc`.
-
- ## Step 1: Install uv Package Manager
-
- We use `uv` for fast, reliable Python package management (much faster than pip).
-
- ```bash
- # Check if uv is already installed
- if ! command -v uv &> /dev/null; then
- echo "Installing uv package manager..."
- curl -LsSf https://astral.sh/uv/install.sh | sh
- export PATH="$HOME/.local/bin:$PATH"
- fi
-
- # Verify installation
- uv --version
- ```
-
- ## Step 2: Virtual Environment & Dependencies
-
- ```bash
- cd ~/src/ai
-
- # Create virtual environment with uv (auto-creates with pip support)
- uv venv
-
- # Sync all dependencies including dev tools from pyproject.toml
- uv sync --all-extras
-
- # Install package in editable mode (registers CLI tools)
- uv pip install -e .
- ```
-
- This will:
- - Create `.venv/` with Python 3.12 (or latest)
- - Install all dependencies including:
- - Telegram bridge (telethon, httpx)
- - Claude SDK integration (anthropic, claude-agent-sdk)
- - Google Calendar (google-auth-oauthlib, google-api-python-client)
- - Job queue (popoto, redis)
- - Summarization (ollama)
- - Dev tools (pytest, ruff, mypy)
- - Register CLI tools (`valor-calendar`, `valor-telegram`)
-
- Verify key imports work:
-
- ```bash
- .venv/bin/python -c "import telethon; import httpx; import dotenv; import anthropic; import google_auth_oauthlib; print('Dependencies OK')"
- ```
-
- If this fails, debug before continuing.
-
- ## Step 3: Environment File (.env)
-
- Check if `.env` exists. If not:
-
- ```bash
- cp .env.example .env
- ```
-
- **Ask the user** which project(s) this machine should monitor. The available projects are defined in `~/Desktop/Valor/projects.json` -- check the full list there. Common options:
- - Single project: `ACTIVE_PROJECTS=psyoptimal`
- - Multiple: `ACTIVE_PROJECTS=valor,popoto`
- - All: `ACTIVE_PROJECTS=valor,django-project-template,popoto,psyoptimal,flutter-project-template,cuttlefish,yudame-research`
-
- Edit `.env` and ensure these are set:
-
- | Variable | Required | Notes |
- |----------|----------|-------|
- | `ACTIVE_PROJECTS` | Yes | Comma-separated project keys |
- | `ANTHROPIC_API_KEY` | Yes | Starts with `sk-ant-` |
- | `TELEGRAM_API_ID` | Yes | Numeric, from my.telegram.org |
- | `TELEGRAM_API_HASH` | Yes | Hex string, from my.telegram.org |
- | `TELEGRAM_PHONE` | Yes | With country code, e.g. `+1234567890` |
- | `TELEGRAM_PASSWORD` | If 2FA on | Telegram 2FA password |
- | `TELEGRAM_SESSION_NAME` | No | Defaults to `valor_bridge` |
-
- If any required values are placeholder/missing, ask the user to provide them. The shared API keys file at `~/src/.env` may have `ANTHROPIC_API_KEY` and other keys -- check there first.
-
- ## Step 4: Google Calendar Configuration
-
- Set up Google Calendar integration for work time tracking.
-
- ### 4.1 Get Google OAuth credentials
-
- Check if credentials exist:
-
- ```bash
- ls ~/Desktop/Valor/google_credentials.json
- ```
-
- If missing, ask the user to:
- 1. Go to Google Cloud Console (project: Yudame General)
- 2. Enable Google Calendar API
- 3. Create OAuth 2.0 Client ID (Desktop app)
- 4. Download JSON and save to `~/Desktop/Valor/google_credentials.json`
-
- ### 4.2 Run OAuth consent flow
-
- Check if token already exists:
-
- ```bash
- ls ~/Desktop/Valor/google_token.json 2>/dev/null
- ```
-
- If no token exists, run the OAuth flow:
-
- ```bash
- cd ~/src/ai
-
- # This will open browser for Google OAuth consent
- .venv/bin/valor-calendar --reauth
- ```
-
- The user must complete the OAuth consent in their browser. After completion, verify the token is valid:
-
- ```bash
- .venv/bin/valor-calendar --check
- ```
-
- ### 4.3 Create calendar mappings
-
- The calendar config is auto-generated by the `/update` command. For now, ensure the Google Calendars exist with matching names:
-
- **Required calendars in Google Calendar:**
- - **"Internal Projects"** (default for Claude Code sessions and internal projects)
- - Optional: Create dedicated calendars for client projects that need separate time tracking
- - Example: For "Dev: PsyOPTIMAL" group -> create "PsyOPTIMAL" calendar
- - Internal projects (valor, popoto, flutter-template, etc.) use the default calendar
-
- After setup, run `/update` to auto-generate `config/calendar_config.json`.
-
- ## Step 5: Authentication Configuration
-
- The SDK uses Max subscription OAuth via the Claude Desktop app (no API credits needed).
-
- ```bash
- # Check if Claude Desktop app is running (provides OAuth for CLI)
- if pgrep -f "Claude.app" > /dev/null; then
- echo "Claude Desktop is running (provides subscription auth)"
- else
- echo "Claude Desktop is not running"
- echo "Start /Applications/Claude.app to enable subscription auth"
- echo "Without it, the bridge will fall back to API key billing"
- fi
-
- # Verify API key exists as fallback
- if grep -q 'ANTHROPIC_API_KEY=sk-ant-' .env 2>/dev/null; then
- echo "API key configured (fallback if Desktop auth fails)"
- else
- echo "No API key fallback configured"
- fi
- ```
-
- **How authentication works:**
- - **Primary**: Claude Desktop app (if running) provides OAuth authentication
- - **Fallback**: API key from `.env` (`ANTHROPIC_API_KEY`)
- - **Force API billing**: Set `USE_API_BILLING=true` in `.env`
-
- The SDK spawns Claude Code CLI subprocesses that inherit authentication from the running Claude Desktop app. No separate login command is needed.
-
- ### Sentry CLI Authentication
-
- `sentry-cli` is installed automatically by `/update`. After installation, authenticate:
-
- ```bash
- # Login to Sentry (generates auth token)
- sentry-cli login
-
- # Or set token directly in ~/Desktop/Valor/.env
- # SENTRY_PERSONAL_TOKEN=sntrys_...
- # The SDK automatically injects this as SENTRY_AUTH_TOKEN for Eng (and Teammate) sessions
- ```
-
- The token is stored in `~/Desktop/Valor/.env` as `SENTRY_PERSONAL_TOKEN` and auto-injected into agent sessions by `sdk_client.py`.
-
- ## Step 6: Project Configuration (~/Desktop/Valor/projects.json)
-
- Project configuration lives in `~/Desktop/Valor/projects.json` (iCloud-synced, private). This directory is shared across machines via iCloud.
-
- Check if `~/Desktop/Valor/projects.json` exists. If not, create from the repo example:
-
- ```bash
- mkdir -p ~/Desktop/Valor
- cp config/projects.example.json ~/Desktop/Valor/projects.json
- ```
-
- Edit `~/Desktop/Valor/projects.json` for this machine's projects.
-
- **Critical rules when editing projects.json:**
-
- 1. **Every project MUST have `working_directory`** -- absolute path to the repo on this machine
- 2. **Every project MUST have `machine`** -- the exact `ComputerName` of the single machine that owns it (`scutil --get ComputerName`). This is the source of truth for ownership; whitelists, groups, and email patterns all inherit from it. Two projects on different machines must never share a Telegram group, email contact, or DM whitelist contact id — see [Single-Machine Ownership](../../../docs/features/single-machine-ownership.md).
- 3. **Always include the full `defaults` section** -- copy it from the example if missing
- 4. **DO NOT set `respond_to_all: false`** -- the default is `true`, which is correct. Omit the field entirely from project-level telegram config.
- 5. **Keep project telegram config minimal** -- usually just `"groups": {"Eng: ProjectName": {"persona": "engineer"}}` is sufficient
- 6. **Verify paths exist on disk** -- run `ls` on each `working_directory` to confirm
-
- **No per-contact ownership edits.** When adding this machine, you do not edit `dms.whitelist`, individual `telegram.groups` entries, or `email.contacts/domains` to "exclude" other machines. Just set each project's `machine` field once. The validator (`bridge/config_validation.py`) and the update gate (`scripts/update/run.py` Step 4.6) will enforce that no contact is owned by two machines.
-
- Example minimal project entry:
-
- ```json
- {
- "projects": {
- "myproject": {
- "name": "My Project",
- "working_directory": "~/src/myproject",
- "telegram": {
- "groups": {
- "Eng: My Project": {"persona": "engineer"}
- }
- },
- "github": {
- "org": "orgname",
- "repo": "reponame"
- },
- "context": {
- "tech_stack": ["Python"],
- "description": "What the agent should focus on"
- }
- }
- },
- "defaults": {
- "working_directory": "~/src/ai",
- "telegram": {
- "respond_to_all": true,
- "respond_to_mentions": true,
- "respond_to_dms": true,
- "mention_triggers": ["@valor", "valor", "hey valor"]
- },
- "response": {
- "typing_indicator": true,
- "max_response_length": 4000,
- "timeout_seconds": 300
- }
- }
- }
- ```
-
- ### Persona overlays
-
- Persona overlay files live in `~/Desktop/Valor/personas/`. The loader (`agent.sdk_client.load_persona_prompt`) prefers the private overlay when present and falls back to the in-repo template (`config/personas/<persona>.md`) otherwise. Seeding the private overlays from the in-repo defaults at setup time gives the agent identical behavior on every fresh machine without waiting for iCloud propagation from another box.
+ ## Setup Phases
- The engineer and customer-service personas have in-repo templates that are version-controlled and PR-reviewable:
- - `config/personas/engineer.md` — Engineer SDLC-owner playbook (CRITIQUE/REVIEW gates, Mode 3 parallel orchestrator, `merge_authorized` bypass)
- - `config/personas/customer-service.md` — Customer-service overlay for `customer-service`-persona sessions
+ Work through the phases in order. Each phase's full commands, tables, and rationale live in a sub-file — load it when you reach that phase.
- Seed them into the vault if not already present (do NOT overwrite — existing overlays may carry per-machine customizations):
+ | Phase | Steps | Load |
+ |-------|-------|------|
+ | 1. Environment | Python symlink, zshenv bootstrap, uv, venv + deps, `.env` | `references/environment.md` |
+ | 2. Authentication | Google Calendar OAuth, Claude SDK auth, Sentry CLI | `references/auth.md` |
+ | 3. Project config | `~/Desktop/Valor/projects.json`, persona overlays | `references/projects-config.md` |
+ | 4. Telegram login | Interactive login (USER ACTION — inline below) | (inline) |
+ | 5. Services + optional surfaces | Worker/reflections install (inline below); BYOB, computer-use, generation model | `references/optional-surfaces.md` |
+ | 6. Start + verify | Start bridge, health check, confirm to user | `references/verification.md` |
- ```bash
- mkdir -p ~/Desktop/Valor/personas
+ ## Phase 1: Environment (Steps 0-3)
- for persona in engineer customer-service; do
- src="config/personas/${persona}.md"
- dst="$HOME/Desktop/Valor/personas/${persona}.md"
- if [ ! -f "$dst" ]; then
- cp "$src" "$dst"
- echo "Seeded $dst from $src"
- else
- echo "$dst already exists — leaving in place (run \`diff\` to compare with $src)"
- fi
- done
- ```
+ Load `references/environment.md` and complete, in order:
- The `teammate` persona has no in-repo template — there is no `config/personas/teammate.md`. A teammate overlay is purely operator-authored under `~/Desktop/Valor/personas/teammate.md`; if absent, the loader has no fallback for that persona, so a teammate-using machine must author its own overlay.
+ 1. **Step 0.1** — Ensure bare `python` resolves to Python 3.12+ (hooks invoke bare `python` under `/bin/sh`; without the symlink every hook silently fails)
+ 2. **Step 0.2** — Bootstrap the cross-machine shell env loader (`~/Desktop/Valor/zshenv.sh` via `scripts/update/zshenv_sync.py`)
+ 3. **Step 1** — Install the `uv` package manager
+ 4. **Step 2** — Create `.venv` and sync dependencies (`uv venv && uv sync --all-extras && uv pip install -e .`), then verify key imports
+ 5. **Step 3** — Create `.env` from `.env.example` and fill required variables (`ACTIVE_PROJECTS`, `ANTHROPIC_API_KEY`, Telegram credentials). **Ask the user** which projects this machine should monitor.
- If the machine is already running and you want to inspect drift between the in-repo template and the private overlay:
+ ## Phase 2: Authentication (Steps 4-5)
- ```bash
- diff config/personas/engineer.md ~/Desktop/Valor/personas/engineer.md
- diff config/personas/customer-service.md ~/Desktop/Valor/personas/customer-service.md
- ```
+ Load `references/auth.md` and complete:
- The persona loader emits a WARNING log line if a known load-bearing substring is missing from the private engineer overlay (e.g., `CRITIQUE` for the pipeline gate, `Mode 3` for the parallel orchestrator, `merge_authorized` for the stale-baseline bypass). The `/update` script also runs an engineer-overlay drift check (`scripts/update/persona_drift.py`, Step 4.10). Watch `logs/bridge.log` after the first session for these warnings — they signal that the private overlay has rolled back and should be re-synced.
+ 1. **Step 4** — Google Calendar: OAuth credentials at `~/Desktop/Valor/google_credentials.json`, consent flow via `valor-calendar --reauth`, calendar mappings (auto-generated later by `/update`)
+ 2. **Step 5** — Claude SDK auth (Claude Desktop app OAuth primary, API key fallback) and Sentry CLI token (`SENTRY_PERSONAL_TOKEN` in `~/Desktop/Valor/.env`)
- ### Cross-machine reuse
+ Google OAuth consent and any Claude login are **user actions** — pause for them when the sub-file says so.
- If the project is already defined on another machine's `~/Desktop/Valor/projects.json`, copy its entry rather than writing from scratch (iCloud syncs this file across machines).
+ ## Phase 3: Project Configuration (Step 6)
- After editing, verify all working directories exist:
+ Load `references/projects-config.md` and configure `~/Desktop/Valor/projects.json` (iCloud-synced, private). Key invariants (full detail in the sub-file):
- ```bash
- # For each project's working_directory, confirm it exists
- ls ~/src/<project_dir>
- ```
+ - Every project MUST have `working_directory` and `machine` (exact `ComputerName` — single-machine ownership)
+ - Always include the full `defaults` section; never set `respond_to_all: false`
+ - Seed persona overlays into `~/Desktop/Valor/personas/` without overwriting existing ones
+ - Verify every `working_directory` exists on disk
- ## Step 7: Telegram Login (USER ACTION REQUIRED)
+ ## Phase 4: Telegram Login (Step 7 — USER ACTION REQUIRED)
Check for an existing session:
```bash
ls data/*.session 2>/dev/null
```
- **If a session file exists**: Skip to Step 8.
+ **If a session file exists**: Skip to Phase 5.
**If no session file exists**: The user must complete an interactive login. Tell them:
> I've finished all the automated setup. One step requires your input -- the Telegram login sends a verification code to your phone.
>
> Please run this in a terminal:
> ```
> cd ~/src/ai && source .venv/bin/activate && python scripts/telegram_login.py
> ```
> Let me know when you're done.
**STOP HERE. Do not proceed until the user confirms the login is complete.**
After they confirm, verify the session was created:
```bash
ls data/*.session
```
If no session file appeared, something went wrong. Ask the user what happened and help debug.
- ## Step 8: Install Reflections Scheduler
-
- Install the reflections daily maintenance plist (runs at 6 AM Pacific):
-
- ```bash
- cd ~/src/ai
- ./scripts/install_reflections.sh
- ```
-
- Verify it loaded:
-
- ```bash
- launchctl list | grep com.valor.reflections
- ```
-
- If the output shows the `com.valor.reflections` label, the scheduler is installed. It will run `scripts/reflections.py` daily at 6 AM, performing log review, session analysis, LLM reflection, and memory consolidation.
-
- ## Step 8.5: Optional BYOB + Computer-Use Install (macOS only)
-
- These two surfaces are operator-opt-in. Skip on non-macOS hosts.
+ ## Phase 5: Services + Optional Surfaces (Steps 8-8.6)
- ### BYOB (real-Chrome control)
+ ### Step 8: Install Worker + Reflection Schedules
- BYOB lets the agent read and act on the user's already-logged-in Chrome via MCP tools (`byob_navigate`, `byob_click`, etc.) -- no `state.json` files in the repo, no per-session re-auth.
+ Three launchd services: the standalone worker, the reflection-scheduler subprocess (`python -m reflections` — its own supervised process since issue #1828, no longer inside the worker), and the SDLC reflection schedule:
```bash
- # 1. Install bun if not already present
- command -v bun >/dev/null || curl -fsSL https://bun.sh/install | bash
-
- # 2. Clone BYOB to ~/.byob/ and check out the pinned commit
- PIN=$(python3 -c "import json; print(json.load(open('config/byob_pin.json'))['commit'])")
- if [ ! -d ~/.byob ]; then
- git clone https://github.com/wxtsky/byob ~/.byob
- fi
- git -C ~/.byob fetch
- git -C ~/.byob checkout "$PIN"
-
- # 3. Build + register the native messaging host
- cd ~/.byob && bun install && bun run setup
cd ~/src/ai
-
- # 4. Register the BYOB MCP server in ~/.claude.json (idempotent, self-healing)
- python -c "from scripts.update import mcp_byob; r = mcp_byob.verify_byob_mcp(write=True); print(r.message)"
- ```
-
- After install, the user must:
- 1. Open Chrome → `chrome://extensions` → toggle **Developer mode** ON (top-right) → click **Load unpacked** (top-left) → select `~/.byob/packages/extension/output/chrome-mv3/` (the BYOB extension cannot be auto-installed; this is an operator click-through).
- 2. **Quit Chrome completely** (`⌘Q` on macOS — closing windows is not enough). Reopen Chrome. Chrome only re-reads the Native Messaging config on full restart.
-
- Verify with BYOB's own diagnostic — this is authoritative across BYOB versions and tells you exactly what's wrong if anything's off:
-
- ```bash
- cd ~/.byob && bun run doctor
- ```
-
- Expected output (all green checkmarks):
- - ✓ Native Messaging manifest registered
- - ✓ Launcher script present
- - ✓ Bridge process: pid N, deviceId UUID, uptime Ns
- - ✓ IPC socket: `~/.byob/bridges/<deviceId>.sock`
-
- If any line is red, the message points at the exact fix. The most common case is "no live bridge — extension never connected" which means the user hasn't loaded the extension yet, or loaded it into a different Chrome profile than the one being tested.
-
- Note: the IPC socket path is **per-device** (UUID-keyed under `~/.byob/bridges/`), not a fixed `~/.byob/run/byob.sock`. The MCP server discovers the socket at startup; callers should never hardcode the path.
-
- ### Computer-Use (bcu, native macOS app control)
-
- bcu drives Slack, Notes, Telegram Desktop, etc. via the macOS Accessibility API without moving the user's cursor. **Prompt the user before installing**:
-
- > Do you want to enable computer-use (lets the agent drive native macOS apps -- Slack, Notes, etc. -- without moving your cursor)?
-
- On **yes**:
- ```bash
- # Write the opt-in sentinel
- mkdir -p ~/.config/valor && touch ~/.config/valor/computer-use-enabled
-
- # Resolve the pinned bcu release
- TAG=$(python3 -c "import json; print(json.load(open('config/bcu_pin.json'))['release_tag'])")
-
- # Download + verify SHA + install -- /update handles this on every run too,
- # so the SETUP-time fetch is just bootstrap. See scripts/update/run.py.
- echo "bcu pinned tag: $TAG"
- echo "Run: python scripts/update/run.py --full to fetch + install + permission-prompt."
- ```
-
- After install, the user must grant **two** permissions in System Settings:
- - Privacy & Security -> Accessibility -> add `BackgroundComputerUse.app`
- - Privacy & Security -> Screen Recording -> add `BackgroundComputerUse.app`
-
- These permissions cannot be granted programmatically.
-
- On **no**: skip everything. Don't write the sentinel; `/update` will leave bcu alone.
-
- ## Step 8.6: Generation Model Selection (RAM-based)
-
- Free-text generation (memory titles, the test AI judge, knowledge-doc
- summarization) runs on a larger `gemma4:31b` model. Classification (bridge
- routing, memory-audit, email triage) runs on the resident `granite4.1:3b` and
- needs no choice here. Pick the generation variant from this machine's RAM:
-
- - **RAM ≥ `MIN_LOCAL_GEN_RAM_GB` (48 GB)** → local Apple-Silicon MLX variant
- `gemma4:31b-mlx` (the ~18-20 GB MLX 32B coexists with granite + nomic-embed + OS).
- - **RAM < 48 GB** → Ollama Cloud variant `gemma4:31b-cloud` (a lightweight hosted
- pointer that fits any machine, including a 16 GB host).
-
- Write the choice to `~/.zshenv` — **machine-local**, NOT the iCloud-synced
- `~/Desktop/Valor/.env` (the vault `.env` would propagate one machine's variant to
- every other machine via iCloud and break per-machine semantics). The write is
- grep-before-append idempotent:
-
- ```bash
- RAM_GB=$(( $(sysctl -n hw.memsize) / 1024 / 1024 / 1024 ))
- if [ "$RAM_GB" -ge 48 ]; then
- GEN_MODEL="gemma4:31b-mlx"
- else
- GEN_MODEL="gemma4:31b-cloud"
- fi
- LINE="export MODELS__OLLAMA_GENERATION_MODEL=$GEN_MODEL"
- grep -qxF "$LINE" ~/.zshenv 2>/dev/null || echo "$LINE" >> ~/.zshenv
- echo "Generation model: $GEN_MODEL (RAM=${RAM_GB}GB)"
- ```
-
- Then ensure the chosen tag (the RAM guard inside `ensure_generation_model()`
- re-checks and degrades a misconfigured mlx tag to a soft warning — it never pulls
- 18 GB on a small host):
-
- ```bash
- python -c "from config.models import ensure_generation_model; ok,d=ensure_generation_model('$GEN_MODEL'); print(('OK' if ok else 'WARN'), d)"
- ```
-
- **Cloud-signin warning:** when `GEN_MODEL` ends in `:cloud`, the machine must be
- signed in to Ollama Cloud (`ollama list` shows a `:cloud` entry). If not, warn the
- user to run `ollama signin` — generation is fail-soft, so this does not block setup.
-
- The launchd worker does not read the shell, so `scripts/install_worker.sh` parses
- `MODELS__*` lines from `~/.zshenv` and injects them into the plist
- `EnvironmentVariables` block — no extra action needed here.
-
- ## Step 9: Start the Bridge
-
- Ensure the logs directory exists, then start the bridge as a background process:
-
- ```bash
- mkdir -p logs
- ```
-
- Start the bridge using the service script:
-
- ```bash
- ./scripts/valor-service.sh start
- ```
-
- Wait a few seconds, then verify it started:
-
- ```bash
- sleep 4 && tail -20 logs/bridge.log 2>/dev/null
+ ./scripts/install_worker.sh
+ ./scripts/install_reflection_worker.sh
+ ./scripts/install_sdlc_reflection.sh
```
- Check for these indicators in the logs:
- - `Agent backend: Claude Agent SDK` -- correct backend
- - `Active projects: [...]` -- the projects you configured
- - `Monitored groups: [...]` -- the Telegram groups
- - `Connected to Telegram` -- successful connection
-
- Also verify the process is running:
+ `install_reflection_worker.sh` self-gates on worker role (any project's `machine` matches this host; fail-open), so it installs wherever the worker does. Verify all loaded:
```bash
- pgrep -f telegram_bridge.py
+ launchctl list | grep com.valor
```
- ## Step 10: Final Verification
-
- Run a comprehensive health check:
-
- ```bash
- cd ~/src/ai
+ Expect `com.valor.worker`, `com.valor.reflection-worker`, and `com.valor.sdlc-reflection` labels. The reflection scheduler logs to `logs/reflection_worker.log`; `python -m reflections --dry-run` validates the registry loads.
- echo "=== System Tools ==="
- claude --version
- gh --version
- git --version
- uv --version
+ ### Steps 8.5-8.6: Optional Surfaces (macOS only, operator-opt-in)
- echo ""
- echo "=== Python Environment ==="
- .venv/bin/python --version
- .venv/bin/python -c "import telethon; import anthropic; import google_auth_oauthlib; print('Dependencies OK')"
+ Load `references/optional-surfaces.md` for:
- echo ""
- echo "=== CLI Tools ==="
- .venv/bin/valor-calendar --version 2>/dev/null || echo "valor-calendar: Not found (run 'uv pip install -e .' again)"
- .venv/bin/python -m tools.sms_reader.cli recent --limit 1 | grep -q "rowid" && echo "SMS reader: OK" || echo "SMS reader: FAIL"
+ - **BYOB** (real-Chrome control via MCP) — bun install, pinned clone to `~/.byob/`, extension load (user click-through), `bun run doctor` verification
+ - **Computer-use (bcu)** — **prompt the user first**; opt-in sentinel + pinned release + two System Settings permissions (user actions)
+ - **Generation model selection** — RAM-based `gemma4:31b-mlx` vs `gemma4:31b-cloud` choice written to `~/.zshenv` (machine-local, never the vault `.env`)
- echo ""
- echo "=== Bridge Status ==="
- ./scripts/valor-service.sh status
- ```
+ Skip this entire phase on non-macOS hosts or when the user declines.
- ## Step 11: Confirm to User
+ ## Phase 6: Start the Bridge and Verify (Steps 9-11)
- Report the final status to the user with:
+ Load `references/verification.md` and complete:
- - **Bridge status**: Running (with PID)
- - **Agent backend**: Claude Agent SDK
- - **Active projects**: Which projects are configured
- - **Monitored groups**: Which Telegram groups are being watched
- - **Next steps**:
- 1. Send a test message in the Telegram group to verify it responds
- 2. Run `/update` to generate calendar config
+ 1. **Step 9** — `mkdir -p logs`, start via `./scripts/valor-service.sh start`, confirm `Connected to Telegram` in `logs/bridge.log` and `pgrep -f telegram_bridge.py`
+ 2. **Step 10** — Run the comprehensive health check (system tools, Python env, CLI tools, bridge status)
+ 3. **Step 11** — Report final status to the user (bridge PID, active projects, monitored groups, next steps: test message + `/update`)
## Rules
- **You** do everything: deps, config, starting the bridge, verification
- **User** only does:
- 1. Interactive Telegram login (Step 7)
+ 1. Interactive Telegram login (Phase 4)
2. Claude login (if not already done)
3. Google OAuth consent (if no token exists)
- Never ask the user to start the bridge or check logs -- do it yourself
- Never set `respond_to_all: false` in project configs
- Always include the `defaults` section in `projects.json`
- Always verify `working_directory` paths exist on disk before starting
- The bridge must be confirmed running before you report success
- If anything fails, debug it yourself. Only escalate to the user if it requires their credentials or interactive input.
## Troubleshooting
- ### uv not found after install
- ```bash
- export PATH="$HOME/.local/bin:$PATH"
- echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zshrc
- ```
-
- ### Dependencies won't install
- ```bash
- rm -rf .venv
- uv venv
- uv sync --all-extras
- ```
-
- ### Calendar OAuth fails
- 1. Verify credentials file exists: `ls ~/Desktop/Valor/google_credentials.json`
- 2. Ensure Google Calendar API is enabled in Cloud Console
- 3. Re-run OAuth: `.venv/bin/valor-calendar --reauth`
+ Phase-specific troubleshooting lives at the bottom of each sub-file:
- ### Bridge won't start
- 1. Check logs: `tail -50 logs/bridge.log`
- 2. Verify Telegram session: `ls data/*.session`
- 3. Test imports: `.venv/bin/python -c "import telethon; print('OK')"`
+ - uv / dependency issues → `references/environment.md`
+ - Calendar OAuth failures → `references/auth.md`
+ - Bridge won't start → `references/verification.md`