# Cloud Sync Push Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Backend-mode users get a dashboard that stays current (periodic push) and a way to backfill their full history into it (one-shot `backfill_cloud`), so the hosted dashboard can be truthful.

**Architecture:** Two small additions to the existing `cloud_sync` path in the `heylead` MCP client. No behavior change for direct-mode users — everything is gated on `is_backend_mode()`. TDD against the existing suite (`python3.12 -m pytest tests/`, 699 passing — must stay green).

**Tech Stack:** Python 3.12, httpx, pytest. Traps: `get_db()` refuses the event loop — tests stay sync and use `asyncio.run(...)`; prompts/JSON gotchas don't apply here.

---

### Task 1: Periodic push in the cloud sync loop

**Files:** Modify `src/heylead/server.py` (`_cloud_sync_loop`, ~368-408); Test: `tests/test_cloud_sync_push.py` (new)

Today the loop only PULLS; pushes happen only on show_status/retry/enable events, so a hosted dashboard goes stale.

- [ ] **Step 1: Failing test** — monkeypatch `heylead.services.cloud_sync.sync_to_cloud` with a recorder and `is_backend_mode` → True; drive one iteration of the loop body (extract the per-iteration body into `_cloud_sync_tick()` if needed for testability); assert `sync_to_cloud` was awaited. Companion test: `is_backend_mode` → False ⇒ recorder NOT called.
- [ ] **Step 2: FAIL.**
- [ ] **Step 3: Implement** — inside the loop, when `config.is_backend_mode()`, call `await sync_to_cloud()` no more than once per `CLOUD_PUSH_INTERVAL_SECONDS = 900` (module-level constant, 15 min), wrapped in try/except (a failed push logs at debug and never breaks the loop). Keep the existing pull logic untouched.
- [ ] **Step 4: PASS + full suite.**
- [ ] **Step 5: Commit** `feat: periodic cloud push keeps the hosted dashboard current in backend mode`

### Task 2: One-shot backfill — scheduler(action="backfill_cloud")

**Files:** Modify `src/heylead/services/cloud_sync.py` (`sync_to_cloud`), `src/heylead/tools/scheduler.py` (or the module handling the scheduler tool's actions — locate the action dispatcher), `src/heylead/server.py` (scheduler tool docstring mentions the new action); Test: `tests/test_backfill_cloud.py` (new)

`sync_to_cloud()` filters to `mode=='autopilot'` AND status in (active, paused) (`cloud_sync.py:73-76`) — completed/manual campaigns can never reach the dashboard. Backfill must push everything once, without changing periodic-sync semantics.

- [ ] **Step 1: Failing tests** —
  1. `sync_to_cloud(include_all=True)` includes completed and non-autopilot campaigns in the POSTed payload (seed 3 campaigns: active autopilot, completed autopilot, paused manual; capture the httpx POST body via monkeypatched client or by patching the internal gather; assert 3 campaigns with `include_all=True`, 1 without).
  2. The scheduler tool action `backfill_cloud`: in direct mode returns the message "Backend account not linked — sign in at https://heylead.dev/auth/login-url and run setup_profile with your token first." without POSTing; in backend mode (monkeypatched) calls `sync_to_cloud(include_all=True)` and reports the counts from the backend response.
- [ ] **Step 2: FAIL.**
- [ ] **Step 3: Implement** — add `include_all: bool = False` parameter; when True skip the mode/status filter (and the matching outreach/contact scoping so their related rows go too). Wire a `backfill_cloud` action into the scheduler tool dispatcher returning a human-readable summary (`"Pushed N campaigns, M outreaches to your hosted workspace — heylead.dev/dashboard"`). Update the `scheduler` tool docstring action list in server.py.
- [ ] **Step 4: PASS + full suite (699+).**
- [ ] **Step 5: Commit** `feat: scheduler(action='backfill_cloud') pushes full local history to the hosted dashboard`

### Task 3: Gate

- [ ] `python3.12 -m pytest tests/ -q` fully green. Note in the final report: this ships in the NEXT PyPI release; 0.10.185 does not include it.
