| 1 | # Community Assistant Agent |
| 2 | |
| 3 | The community assistant is a set of Cloudflare Cron Triggers that call `deepseek-v4-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 | Usage logged to: |
| 30 | usage:<YYYY-MM-DD> |
| 31 | ``` |
| 32 | |
| 33 | ## Cron schedule |
| 34 | |
| 35 | | Expression | Frequency | Tasks | |
| 36 | |---|---|---| |
| 37 | | `0 */6 * * *` | Every 6 hours | Today's Dispatch (curate) | |
| 38 | | `*/30 * * * *` | Every 30 min | Issue triage + PR review | |
| 39 | | `0 0 * * *` | Daily 00:00 UTC | Stale issue nudges + duplicate detection | |
| 40 | | `0 9 * * 1` | Monday 09:00 UTC | Weekly digest | |
| 41 | |
| 42 | ## Voice constraints |
| 43 | |
| 44 | All drafts follow these rules: |
| 45 | |
| 46 | - Calm, factual, never breathless. |
| 47 | - Never uses first person plural ("we"/"我们") — the maintainer is one person. |
| 48 | - Never commits to timing, prioritisation, or merge intent. |
| 49 | - Never apologises on the maintainer's behalf. |
| 50 | - Cites specific files / line numbers / linked issues when discussing code. |
| 51 | - Ends with: "— drafted by community assistant, pending maintainer review" |
| 52 | - Chinese drafts end with: "— 由社区助理草拟,待维护者审阅" |
| 53 | - Chinese output is rewritten in zh-CN, not machine-translated. |
| 54 | |
| 55 | ## Cost guardrails |
| 56 | |
| 57 | - Each cron invocation caps at ~30k input tokens and ~2k output tokens. |
| 58 | - Issue/PR bodies are truncated to 1000–4000 chars before sending to the model. |
| 59 | - Deduplication: `hasFreshDraft` checks if a draft already exists that's newer than the item's `updated_at`. Skips if so. |
| 60 | - Token usage is logged to `usage:<YYYY-MM-DD>` KV keys (retained 90 days). |
| 61 | - If `DEEPSEEK_API_KEY` is missing or the API errors, the cron returns 200 with `{ skipped: true, reason }` — never crashes, never retry-loops. |
| 62 | |
| 63 | ## Maintainer review surface |
| 64 | |
| 65 | Access at `/admin?token=<MAINTAINER_TOKEN>`. |
| 66 | |
| 67 | - Lists all pending drafts with source link, draft body, and three actions: |
| 68 | - **Post as comment** — calls GitHub REST API using `MAINTAINER_GITHUB_PAT` |
| 69 | - **Edit & post** — opens a textarea for editing before posting |
| 70 | - **Discard** — removes the draft from KV |
| 71 | - The auth token is set via `MAINTAINER_TOKEN` env var. Access sets an `mt` cookie for the session. |
| 72 | - **Nothing posts to GitHub without an explicit maintainer click.** |
| 73 | |
| 74 | ## Environment variables |
| 75 | |
| 76 | | Variable | Required | Purpose | |
| 77 | |---|---|---| |
| 78 | | `DEEPSEEK_API_KEY` | Yes | DeepSeek API key for the community agent | |
| 79 | | `GITHUB_TOKEN` | Optional | Fine-grained PAT for GitHub API (raises rate limit) | |
| 80 | | `CRON_SECRET` | Optional | Shared secret for manual cron invocation | |
| 81 | | `MAINTAINER_TOKEN` | Optional | Auth token for /admin panel | |
| 82 | | `MAINTAINER_GITHUB_PAT` | Optional | GitHub PAT with `issues:write` scope for posting comments | |
| 83 | |
| 84 | ## Initial deployment |
| 85 | |
| 86 | One-time setup before the first `npm run deploy`: |
| 87 | |
| 88 | 1. **Create the KV namespaces:** |
| 89 | ```bash |
| 90 | npx wrangler kv namespace create CURATED_KV |
| 91 | npx wrangler kv namespace create NEXT_INC_CACHE_KV |
| 92 | ``` |
| 93 | Copy the returned `id` values and paste them into the matching |
| 94 | `wrangler.jsonc` bindings, replacing each `"REPLACE_WITH_KV_ID"`. |
| 95 | |
| 96 | 2. **Set secrets:** |
| 97 | ```bash |
| 98 | npx wrangler secret put DEEPSEEK_API_KEY |
| 99 | npx wrangler secret put MAINTAINER_TOKEN |
| 100 | npx wrangler secret put MAINTAINER_GITHUB_PAT |
| 101 | npx wrangler secret put CRON_SECRET |
| 102 | ``` |
| 103 | |
| 104 | 3. **(Optional) Raise GitHub rate limit:** |
| 105 | ```bash |
| 106 | npx wrangler secret put GITHUB_TOKEN |
| 107 | ``` |
| 108 | |
| 109 | 4. **Verify:** |
| 110 | ```bash |
| 111 | npm run predeploy # checks KV ID is set |
| 112 | npm run deploy # builds + deploys |
| 113 | ``` |
| 114 | |
| 115 | ## Kill switch |
| 116 | |
| 117 | To disable the community agent entirely: |
| 118 | |
| 119 | 1. Remove all cron triggers from `wrangler.jsonc` except the original `0 */6 * * *` (curate). |
| 120 | 2. Redeploy: `npm run deploy`. |
| 121 | |
| 122 | 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. |
| 123 | |
| 124 | To disable a specific cron task, remove its cron expression from `wrangler.jsonc` and redeploy. |
| 125 | |
| 126 | ## Bilingual output |
| 127 | |
| 128 | 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. |
| 129 |