| 1 | # Community Assistant Agent |
| 2 | |
| 3 | The community assistant is a set of Cloudflare Cron Triggers that call `deepseek-flash` to draft triage comments, PR reviews, stale-issue nudges, duplicate suggestions, and weekly digests. **It never posts to GitHub directly.** Every output is a draft staged in Workers KV for maintainer review. |
| 4 | |
| 5 | ## Architecture |
| 6 | |
| 7 | ``` |
| 8 | Cloudflare Cron Triggers |
| 9 | └─ worker.ts scheduled() handler |
| 10 | ├─ */30 min → triage (new issues) + pr-review (new PRs) |
| 11 | ├─ daily → stale (30d inactive) + dupes (embed-similarity scan) |
| 12 | ├─ weekly → digest (Mon 09:00 UTC) |
| 13 | └─ 6h → curate (Today's Dispatch — pre-existing) |
| 14 | |
| 15 | Drafts stored in Workers KV (keys always derived via `draftStorageKey` in `lib/community-agent.ts`): |
| 16 | draft:triage:<issue-number> |
| 17 | draft:pr-review:<pr-number> |
| 18 | draft:stale:<issue-number> |
| 19 | draft:dupes:<issue-number> |
| 20 | draft:digest:<year>-W<week> |
| 21 | draft:linkcheck:<slug>-<sha256-16> (identity: full broken URL) |
| 22 | draft:semantic-drift:<slug>-<sha256-16> (identity: page+claim+evidence+replacement) |
| 23 | |
| 24 | Watcher draft IDs are deterministic — readable slug plus a 64-bit SHA-256 |
| 25 | suffix over the finding's full identity, max 80 chars — so unchanged findings |
| 26 | dedup and changed findings land as new drafts. Semantic-drift model output is |
| 27 | validated and capped (10 drafts/run) before any KV writes. |
| 28 | |
| 29 | Before sending a GitHub post, the action records its target/text identity in the |
| 30 | existing durable claim object and KV. Read or write failures post nothing. |
| 31 | The durable receipt survives lease expiry; retries with changed text or target |
| 32 | are refused while unresolved. Reconciliation searches up to ten pages under |
| 33 | one 30-second deadline and a per-page byte cap; an incomplete search posts |
| 34 | nothing. The KV fallback has eventual-consistency limits; deployment of the |
| 35 | existing durable binding still needs its own receipt. |
| 36 | |
| 37 | A post whose GitHub outcome was unknown (network error, 5xx, 408, 429) leaves |
| 38 | draft-post-unknown:<type>:<id> (until resolved; the next post of that |
| 39 | draft first looks on GitHub for |
| 40 | the earlier attempt's post) |
| 41 | |
| 42 | Usage logged to (one record per model call; sum the day's prefix): |
| 43 | usage:<YYYY-MM-DD>:<timestamp>:<uuid> |
| 44 | ``` |
| 45 | |
| 46 | ## Cron schedule |
| 47 | |
| 48 | | Expression | Frequency | Tasks | |
| 49 | |---|---|---| |
| 50 | | `0 */6 * * *` | Every 6 hours | Today's Dispatch (curate) | |
| 51 | | `*/30 * * * *` | Every 30 min | Issue triage + PR review | |
| 52 | | `0 0 * * *` | Daily 00:00 UTC | Stale issue nudges + duplicate detection | |
| 53 | | `0 9 * * 1` | Monday 09:00 UTC | Weekly digest | |
| 54 | |
| 55 | ## Voice constraints |
| 56 | |
| 57 | All drafts follow these rules: |
| 58 | |
| 59 | - Calm, factual, never breathless. |
| 60 | - Never uses first person plural ("we"/"我们") — the maintainer is one person. |
| 61 | - Never commits to timing, prioritisation, or merge intent. |
| 62 | - Never apologises on the maintainer's behalf. |
| 63 | - Cites specific files / line numbers / linked issues when discussing code. |
| 64 | - Ends with: "— drafted by community assistant, pending maintainer review" |
| 65 | - Chinese drafts end with: "— 由社区助理草拟,待维护者审阅" |
| 66 | - Chinese output is rewritten in zh-CN, not machine-translated. |
| 67 | |
| 68 | ## Cost guardrails |
| 69 | |
| 70 | - Each cron invocation caps at ~30k input tokens and ~2k output tokens. |
| 71 | - Issue/PR bodies are truncated to 1000–4000 chars before sending to the model. |
| 72 | - Deduplication: the existing `DRAFT_CLAIM_LOCK` authority serializes each cron task before source reads or model calls (45-minute crash lease, then a two-minute propagation hold). Missing KV or lock bindings skip generation before spending. Inside the claim, `hasFreshDraft` skips drafts newer than the item's `updated_at`. |
| 73 | - Token usage is logged as one `usage:<YYYY-MM-DD>:…` KV record per model call |
| 74 | (retained 90 days); list the day's prefix and sum `calls`/`inputTokens`/ |
| 75 | `outputTokens`. Records are append-only because KV has no atomic increment. |
| 76 | - If `DEEPSEEK_API_KEY` is missing or the API errors, the cron returns 200 with `{ skipped: true, reason }` — never crashes, never retry-loops. |
| 77 | |
| 78 | ## Maintainer review surface |
| 79 | |
| 80 | Open `/en/admin` (or `/zh/admin`) and enter `MAINTAINER_TOKEN` in the login |
| 81 | form. The form posts to `/api/admin/login`; the token never goes in the URL. |
| 82 | |
| 83 | - Lists all pending drafts with source link, draft body, and three actions: |
| 84 | - **Post as comment** — calls GitHub REST API using `MAINTAINER_GITHUB_PAT` |
| 85 | - **Edit & post** — opens a textarea for editing before posting |
| 86 | - **Discard** — removes the draft from KV |
| 87 | - The auth token is set via `MAINTAINER_TOKEN` env var. A successful login sets an httpOnly `mt_sid` session cookie that lasts 24 hours. |
| 88 | - **Nothing posts to GitHub without an explicit maintainer click.** |
| 89 | |
| 90 | ## Environment variables |
| 91 | |
| 92 | | Variable | Required | Purpose | |
| 93 | |---|---|---| |
| 94 | | `DEEPSEEK_API_KEY` | Yes | DeepSeek API key for the community agent | |
| 95 | | `GITHUB_TOKEN` | Optional | Fine-grained PAT for GitHub API (raises rate limit) | |
| 96 | | `CRON_SECRET` | Optional | Shared secret for manual cron invocation | |
| 97 | | `MAINTAINER_TOKEN` | Optional | Auth token for /admin panel | |
| 98 | | `MAINTAINER_GITHUB_PAT` | Optional | GitHub PAT with `issues:write` scope for posting comments | |
| 99 | |
| 100 | ## Initial deployment |
| 101 | |
| 102 | One-time setup before the first `npm run deploy`: |
| 103 | |
| 104 | 1. **Create the KV namespaces:** |
| 105 | ```bash |
| 106 | npx wrangler kv namespace create CURATED_KV |
| 107 | npx wrangler kv namespace create NEXT_INC_CACHE_KV |
| 108 | ``` |
| 109 | Copy the returned `id` values and paste them into the matching |
| 110 | `wrangler.jsonc` bindings, replacing each `"REPLACE_WITH_KV_ID"`. |
| 111 | |
| 112 | 2. **Set secrets:** |
| 113 | ```bash |
| 114 | npx wrangler secret put DEEPSEEK_API_KEY |
| 115 | npx wrangler secret put MAINTAINER_TOKEN |
| 116 | npx wrangler secret put MAINTAINER_GITHUB_PAT |
| 117 | npx wrangler secret put CRON_SECRET |
| 118 | ``` |
| 119 | |
| 120 | 3. **(Optional) Raise GitHub rate limit:** |
| 121 | ```bash |
| 122 | npx wrangler secret put GITHUB_TOKEN |
| 123 | ``` |
| 124 | |
| 125 | 4. **Verify:** |
| 126 | ```bash |
| 127 | npm run predeploy # checks KV ID is set |
| 128 | npm run deploy # builds + deploys |
| 129 | ``` |
| 130 | |
| 131 | ## Kill switch |
| 132 | |
| 133 | To disable the community agent entirely: |
| 134 | |
| 135 | 1. Remove all cron triggers from `wrangler.jsonc` except the original `0 */6 * * *` (curate). |
| 136 | 2. Redeploy: `npm run deploy`. |
| 137 | |
| 138 | The curate cron (Today's Dispatch) continues working independently. Individual tasks remain callable manually for testing through `/api/cron?task=triage`, `/api/cron?task=pr-review`, etc. |
| 139 | |
| 140 | To disable a specific cron task, remove its cron expression from `wrangler.jsonc` and redeploy. |
| 141 | |
| 142 | ## Bilingual output |
| 143 | |
| 144 | Every draft contains both `bodyEn` (English) and `bodyZh` (Chinese zh-CN). The admin panel shows the version matching the current locale. The zh version is rewritten natively by the model, not translated from English. |
| 145 |