# Discord is the daemon's sync index; polling is a stateless reconcile loop

## Status

accepted (2026-07-21)

## Context

The Hub Daemon must know, for every Request, which Bot post represents it and which
State thread that post currently lives in — and its GitHub polling needs some notion
of "what have I already seen". The obvious shape is a local store (sqlite/json) on
the daemon's node holding message ids and an `updated_at` cursor.

But ADR-0001 makes GitHub Issues the system of record, and CONTEXT.md keeps Seeds
Discord-only *specifically so there is no second sync surface to maintain*. A local
daemon database would quietly become that second surface: it can drift from both
GitHub and Discord, it dies with the node, and every recovery path becomes "rebuild
the store" anyway. Phase 2 already established the reconcile idiom — `sweep` and
`release` are idempotent verbs that compare truth against labels and converge.

## Decision

The daemon keeps **no durable local state**.

1. Every Bot post carries its Request ref (e.g. `spt-core#42`) in a machine-parseable
   embed field. On boot the daemon scans each Project's State threads and rebuilds an
   in-memory index (issue ⇄ message id ⇄ thread).
2. GitHub polling is a **reconcile loop**, not an event stream: each cycle lists
   recently-updated issues and compares GitHub truth (state label, title, flags,
   comment count) against the indexed Bot post, correcting Discord to match. No
   durable cursor; a restart just reconciles from scratch.

Rejected: the local store (second sync surface, node-bound, drift-prone) and storing
Discord message ids in the GitHub issue (writes to the record on every relocation).

## Consequences

- Daemon restarts are free: boot scan + first reconcile fully self-heal, including
  changes missed while down. Crash recovery and cold start are the same code path.
- Reconcile must be idempotent (re-running corrects nothing twice) — same bar the
  Phase 2 sweeps already meet, and testable the same way.
- Boot cost grows with thread count/history; acceptable at current scale, and the
  scan is bounded by active (non-archived) State threads per Project channel.
- The Bot post ref field becomes load-bearing surface: its format is a contract
  (REQ-tagged), and edits must never strip it.
- Anything the daemon cannot rebuild from GitHub + Discord it must not promise —
  a constraint future features (e.g. the Phase 4 Watch registry) must design against
  or explicitly supersede.
