# Settled design decisions Resolves the "Open items for design session" in [PLAN.md](./PLAN.md). Dated 2026-07-08. Companion docs: [pm-state.md](./pm-state.md) (sidecar formats), [agent-tools.md](./agent-tools.md) (tool schemas). ## D1 — Write path is soft, split by semantics Chat (Reginald) is the write path for **PM-semantic** mutations; gitea-native content stays directly editable. - **Through the agent** — estimates (`est/*`), priority (`p/*`), `deadline/hard`, milestone assignment, dependency edits, and directives. Additive ops act directly; destructive ops go propose-approve (Dialog or the consequence diff on the Directives screen). - **Direct, reconciled** — issue title/description, comments, assignees. Edit them in the CommiTea UI ("composer writes to gitea, as you") or in the gitea web UI; reconcile absorbs out-of-band edits because **gitea is the source of truth** for intent. The agent observes changes on the next reconcile and may object in standup, but never blocks them. Rejected: strict (every write through chat). Hostile to quick edits and fights the reconcile-from-gitea model — an edit made in gitea's own web UI would be un-representable. ## D2 — Sync is poll + reconcile, no live webhooks in v1 `gitea.stephenmann.io` is remote and the desktop app sits behind NAT, so the server cannot POST to a localhost webhook. v1: - **Reconcile on launch** — full read of the work repo(s) + `pm-state` repo into the local SQLite cache. - **Light poll while running** — `since`/conditional-request poll of issues and the issue timeline (target 30–60 s cadence; visible < 2 s is a webhook-era goal, relaxed to the poll interval for v1). - The change-source is an **interface** (`ChangeSource`) with a polling implementation; a webhook implementation can plug in later for a LAN / self-hosted / tunnelled instance without touching the reconcile core. Rejected now: reachability-detection hybrid (moving parts, cleanup-on-quit), outbound tunnel (runtime dependency + public ingress). Both remain future options behind the same interface. ## D3 — Cold-start forecasts use lognormal per-bucket priors Before the team has n ≥ 20 closed issues with estimates, Monte Carlo samples a **lognormal** actual-duration distribution per estimate bucket (`1d/2d/3d/5d/8d`), with a pessimism-skewed median (actuals run long). The priors live in code (`@commitea/core`), not in a data file. At n ≥ 20 the scheduler switches to the team's own empirical fit (see calibration model in [pm-state.md](./pm-state.md)); `byLabel` / `byPerson` bias terms layer on once their own sample sizes clear a floor. Rejected: uniform pessimism multiplier (`actual = est × U[1.3, 2.0]`) — cruder, dishonest tails, no path to per-bucket calibration. ## D4 — Purity test applies to the SQLite cache, not the pm-state repo The plan's invariant — *delete the sidecar → resync → no truth lost* — is about the **local SQLite cache**, which is a rebuildable index over two durable sources: - **Work repo(s) in gitea** — human-authored intent (issues, milestones + due dates, dependencies, assignees, labels, comments). - **`pm-state` repo in gitea** — the sidecar's own durable truth that is *not* regenerable from the work repo: directive log, capacity config, charter, and the (cached-but-committed) calibration model. Delete SQLite → rebuild from both repos → nothing lost. The `pm-state` repo is never the thing you delete; it is versioned and backed up in gitea like any other repo. Regenerable state (issue mirror, inferred lifecycle timestamps, Monte Carlo forecasts, focus snapshot) lives in SQLite only and is recomputed on rebuild. See [pm-state.md](./pm-state.md) for the file/table split.