# Session 0025.0 — Transcript > Date: 2026-05-28 > Goal: Pick one roadmap item disjoint from v0.21.0's polished surfaces > and from #26/#29, build it on a manual worktree while deploys are > frozen. Recommended fits from the 0022 handoff: #19 (CONTRIBUTING > docs) or #27 (Haiku tag suggestions). > > Outcome: **#19 turned out to be already shipped (Session 0013.1) but > never struck in the ROADMAP — which is exactly what misdirected this > session to it. Pivoted to #27. Built Claude Haiku tag suggestions on > the propose-RFC modal, tagged rfc-app [v0.24.0](https://git.wiggleverse.org/ben.stull/rfc-app/src/tag/v0.24.0), > pushed to origin + benstull. 351 backend tests green, frontend builds > clean. NOT deployed — deploys frozen behind the #1 VM-rename blocker > (Session 0022); pin held at 0.22.0. ROADMAP bookkeeping corrected > (#19 struck, #21 Part B noted shipped, #27 recorded as built+staged).** > > Highlights: > - The §9.1 "Slice 2" AI-suggested tag chips, deferred since the > propose modal first landed, are now wired. > - Reused the existing §18 provider abstraction (`AnthropicProvider`, > `_CLAUDE_VARIANTS["claude-haiku"]`) — added one small factory > `providers.construct_haiku()`. `ANTHROPIC_API_KEY` was already a > config var (the chat stack reads it), so #27's "new secret" may > already be bound on OHM — flagged for the operator to check. > - Held the pin and staged the deploy, consistent with how v0.23.0's > pin is being held during the freeze. --- ## Pre-session state | repo | sha | state | | --- | --- | --- | | rfc-app | `daebb54` | `main`; last tag v0.23.0 (#29). Backend 340 tests green. | | ohm-rfc | `9f0c4e7` | `main` clean; pin `.rfc-app-version` = 0.22.0 | | ohm-rfc-app-flotilla | `85f01f8` | session anchored here; `main` clean except untracked `.claude/` | | OHM live | running v0.21.0 (per the 0019.0 / 0024.0 ledger), healthy | — | **Deploy freeze in force.** The 0022 handoff: flotilla deploy of v0.22.0 failed preflight (install dir `/opt/ohm-app` missing on the VM; clean failure, no lock held, no partial deploy). Session 0022 is investigating whether the #1 VM rename (`ohm-app` → `ohm-rfc-app`) was started on the VM, leaving the §13 deployment record pointing at the old path. Deploys blocked until 0022 clears it; OHM still healthy at v0.21.0. Deploy order when it unblocks: v0.22.0 → v0.23.0 → mine. Sessions 0022 (deploy-blocker, INPROGRESS), 0023, 0024 (roadmap-capture #33/#34, done) preceded this. None were building #27. --- ## Turn-by-turn arc ### Arc 1 — Claim, orient, pick an item Claimed session ID **0025** via `claim-session-id.sh --start 2026-05-28T13-06` (pushed the placeholder; the push is the atomic claim). Read SESSION-PROTOCOL.md §7 + the session-start gesture, and the full ROADMAP (PULL'd ohm-rfc first — already up to date). Reasoned to **#19** over #27 initially: deploy-free (ships as PRs the operator merges), zero code-surface collision with the avoid-list, fully completable this session. #27 by contrast needs a new secret, a counsel pass, and a deploy — none closeable during the freeze. ### Arc 2 — #19 is already done (the ledger lied) Before touching anything, checked the repos for existing artifacts. **Both `CONTRIBUTING.md` files already existed and were committed:** - rfc-app `CONTRIBUTING.md` + SPEC §21 analytics chapter landed on `main` via `213f686` (merge `ac3513a`, *"Session 0013.1"*) — the commit message literally reads *"Lands roadmap item #19 ... and #21 Part B ... as a single docs commit."* Verified `213f686` is an ancestor of `origin/main` and the file is in the `main` tree. - ohm-rfc `CONTRIBUTING.md` committed (`90965d4` + later edits). So **#19 + #21 Part B shipped back in Session 0013.1** and were simply never struck in the ROADMAP version-target table. The 0022 handoff recommended #19 not realizing it was done — the un-struck row is what misdirected this session. This is the session's main friction finding (see §19.2 candidates). Pivoted to the other named fit, **#27**. Confirmed via local transcripts + rfc-app tags/branches that no live session was building it (0024 only *lists* #27 as a future candidate; no `v0.24.0` tag, no `#27` branch). ### Arc 3 — Map the surfaces Mapped before building (all reads, no subagents — #27 is one cohesive feature, so the driver built it directly; no §5 subsession transcripts warranted): - **Existing LLM infra (§18 chat stack).** `backend/app/providers.py` already has a multi-provider abstraction with `AnthropicProvider` and `_CLAUDE_VARIANTS["claude-haiku"] = ("claude-haiku-4-5-20251001", "Claude Haiku")`. `ANTHROPIC_API_KEY` is already a config var (`config.py:44/91`) and `anthropic>=0.39` is already a dep. The `models_resolver` docstring even anticipates *"the §9.1 tag suggestions"*. So #27's "new secret" is the same key the chat stack uses — it may already be bound on OHM. - **Tags are free-form chip input** ("free-form for slice 1; the AI-suggested chips of §9.1 are deferred to Slice 2"). No curated taxonomy. So "the existing tag taxonomy" #27 references = the de-facto set of distinct tags across `cached_rfcs` (`tags_json`). - **Propose endpoint** is in `api.py`'s `make_router(config, gitea, bot, providers)` closure, gated by `auth.require_contributor`. - **Provider-call convention** (`api_prs._draft_with_provider`): async endpoints call `provider.send()` directly (no threadpool), wrapped in try/except → graceful stub fallback. Matched it. - **Test harness**: no shared conftest; tests self-bootstrap via `app_with_fake_gitea` / `sign_in_as` / `provision_user_row` from `test_propose_vertical.py`. Runner `./.venv/bin/python -m pytest` from `backend/` (340 tests pre-change). ### Arc 4 — Build #27 (worktree) Manual worktree per the handoff (Agent worktree isolation is broken): `git worktree add -b feature/v0.24.0-haiku-tags /Users/benstull/git/rfc-app-v0.24.0-haiku-tags main`, then symlinked `frontend/node_modules` + `backend/.venv` in. (`.gitignore` ignores `.venv/`/`node_modules/` as directories; the symlinks showed as untracked, so every commit staged explicit files — never `-A`.) Backend: - **`backend/app/tag_suggest.py`** (new) — `gather_tag_universe()` (distinct corpus tags, most-common first, capped 200); `Draft` dataclass; `haiku_provider(config)` seam (monkeypatchable; `None` when no key); `build_messages` + tolerant `parse_reply` (drops invented tags, dedupes case-insensitively to canonical spelling, clamps confidence, caps); `suggest()` orchestration; in-process per-user sliding-window rate limit (`TAG_SUGGEST_RATE_MAX` / `_WINDOW_SECONDS`, defaults 30/60). - **`providers.construct_haiku(api_key)`** — dedicated Haiku provider, model id sourced from `_CLAUDE_VARIANTS`. Independent of `ENABLED_MODELS`; no RFC slug at propose time so the §6.7 funder path doesn't apply. - **`POST /api/rfcs/suggest-tags`** in `api.py` — `require_contributor`, rate-limit → 429, `SuggestTagsBody {title, pitch, use_case}`. Returns `{suggestions:[{tag,confidence}]}`. Degrades to `[]` (200, never an error) for no key / empty corpus / empty draft / parse failure. - `backend/.env.example` — documented that `ANTHROPIC_API_KEY` also powers tag suggestions + the two optional rate-limit vars. Frontend: - `ProposeModal.jsx` — debounced (700 ms, stale-response-guarded) suggestion fetch on `[title, pitch, useCase]`; clickable suggestion chips that add to the tag list (nothing auto-applies); the required inline disclosure ("Suggestions are generated by Claude (Anthropic). The text you've entered above is sent to Anthropic to produce them."). - `api.suggestTags()` — forgiving; any non-OK → `[]`. Tests: **`test_tag_suggest_vertical.py`** (11) — anon→401, contributor gets universe-filtered suggestions (invented tag dropped), no-key→[], empty-corpus→[] (model never called), rate-limit→429, plus units for gather/parser-tolerance/max-cap/empty-draft-short-circuit/provider- failure. One initial failure (the gather unit test needed the db initialized, which happens in the TestClient lifespan) — fixed by wrapping in a `TestClient(app)` context. Full suite **351 green**; frontend `npm run build` clean (needed the deployment `.env` copied in from the main checkout — gitignored, not committed). ### Arc 5 — Release (tag), staged deploy, bookkeeping Followed the established release shape: feature commit on the branch (`fb9b4fa`) → `--no-ff` merge to `main` (`376a6da`) → release commit bumping `VERSION` + `frontend/package.json` + `CHANGELOG.md` (`28015ed`) → tag **`v0.24.0`** → pushed `main` + branch + tag to **origin** and **benstull**. Re-ran the full suite on `main` post-merge: 351 green. CHANGELOG `Upgrade steps:` (RFC 2119) names the **MUST** `flotilla secret set ohm-rfc-app ANTHROPIC_API_KEY` gesture (stdin pbpaste form; never paste bytes), notes the chat stack may already have it bound, the optional rate-limit knobs, and a **SHOULD** counsel pass on the disclosure wording (per #22). **Pin deliberately NOT bumped** — held at 0.22.0, consistent with how v0.23.0's pin is being held during the freeze. Bumping to 0.24.0 would have jumped ahead of un-deployed 0.22.0/0.23.0 and scheduled a frozen deploy. The deploy + pin bump are staged for the operator. ROADMAP corrected + pushed (`f62cb90`): struck **#19** (with the "misdirected 0025" note), annotated **#21 Part B** as shipped in 0013.1, recorded **#27** as built+tagged v0.24.0 with the full operator-pending-gesture list. Did **not** touch #26/#29 rows — those are tagged-but-not-struck pending deploy and belong to the still-open Session 0022. Removed the build worktree (branch preserved on both remotes). ### Arc 6 — Publish-script bug, fixed in passing Publishing this transcript first **failed**: `publish-transcript.sh` `mkdir -p`s the `NNNN/` folder early, then `git rm`s the INPROGRESS placeholder — and because the placeholder was the *only* file in `0025/`, git pruned the now-empty dir from the worktree, so the subsequent `cp` died with "No such file or directory." This bites any session whose folder holds nothing but its own placeholder (i.e. most non-subagent sessions). Fixed by re-ensuring `mkdir -p "$(dirname "$DEST")"` immediately before the `cp`. `~/git/ohm-infra` is a plain local working dir (no VCS — see #33), so the fix simply lives in the script file; re-ran publish and it succeeded. --- ## Cut state (end of session) | | | | --- | --- | | rfc-app | `28015ed` `main`, tag **v0.24.0** (#27) — pushed origin + benstull. 351 tests green, frontend builds clean. | | ohm-rfc | `f62cb90` `main` — ROADMAP corrected; **pin held at 0.22.0** | | OHM live | unchanged — v0.21.0, healthy. **No deploy** (frozen). | | flotilla | untouched | | Roadmap ledger | Status | | --- | --- | | #19 CONTRIBUTING + onboarding | ✅ was already shipped (0013.1); struck this session | | #21 Part B analytics discipline | ✅ was already shipped (0013.1) with #19; noted | | #27 Haiku tag suggestions (v0.24.0) | 🟡 built + tagged + pushed; ⏸ deploy staged (freeze) | --- ## §19.2 candidates surfaced 1. **The ROADMAP version-target ledger lags reality and it cost a session's worth of misdirection.** #19 + #21 Part B shipped in 0013.1 but were never struck; #26 (v0.22.0) and #29 (v0.23.0) are tagged-but-not-struck pending deploy. A future session reading the table can't tell "available" from "shipped-but-unbooked." Worth a convention call: either (a) strike on *tag* (not deploy) with a separate "deployed?" column, or (b) a fast `git tag` vs. table reconciliation pass at session start. The first gesture of an implementer session should be "is this already done?" — this session only avoided wasted work by checking the tree before building. 2. **`ANTHROPIC_API_KEY` is overloaded across two surfaces** (the §18 chat stack and now #27 tag suggestions). Fine today (same provider), but if a deployment wants a *cheaper/separate* key for the high-volume suggestion path vs. interactive chat, that's a future `TAG_SUGGEST_ANTHROPIC_KEY`-style split. Noted, not built. 3. **Tag suggestion quality is bounded by corpus size.** Constrained to existing tags (per #27's explicit scope), a near-empty corpus yields near-empty suggestions. The roadmap already names taxonomy-extension (letting the model propose genuinely new tags) as the deferred follow-up; this is the data point that it'll matter early for OHM. --- ## What lands on the operator's plate 1. **Clear the deploy freeze** (Session 0022's `/opt/ohm-app` blocker), then deploy in order: v0.22.0 → v0.23.0 → **v0.24.0**, bumping the ohm-rfc pin at each step and verifying. 2. **`ANTHROPIC_API_KEY` — DONE.** The operator bound it during this session; `flotilla secret list ohm-rfc-app` confirms `ANTHROPIC_API_KEY → wiggleverse-ohm/ohm-rfc-app-anthropic-api-key@latest`. It waits in Secret Manager until v0.24.0 deploys. (No bytes ever touched the conversation — operator ran the stdin gesture.) 3. **Counsel pass** on the propose-modal disclosure wording before exposing the surface, per #22 discipline. The copy is honest as written. 4. End-to-end verification of the suggestion surface happens post-deploy (couldn't be done this session — no deploy, and no key should ever touch the model). --- ## Prompt the operator can paste into the next Claude Code session ``` You are an OHM roadmap driver session. Claim your session ID first (~/git/ohm-infra/scripts/claim-session-id.sh --start ); your number is not assigned here. Read SESSION-PROTOCOL.md §7 + ohm-rfc/ ROADMAP.md (PULL ohm-rfc first). State at handoff (from session 0025): - rfc-app main @ 28015ed: v0.24.0 tagged + pushed (origin + benstull) — roadmap #27, Claude Haiku tag suggestions on the propose-RFC modal. 351 backend tests green, frontend builds clean. NOT deployed. - THREE rfc-app releases now sit un-deployed behind the freeze: v0.22.0 (#26), v0.23.0 (#29), v0.24.0 (#27). ohm-rfc pin still 0.22.0 (all three pin-bumps held for deploy). - ⛔ DEPLOYS STILL BLOCKED pending Session 0022's /opt/ohm-app VM-rename investigation. Do NOT run flotilla deploy until that clears — coordinate with the operator. Deploy order: v0.22.0 → v0.23.0 → v0.24.0, bump pin + verify at each. - #19 + #21 Part B were already shipped (Session 0013.1) — struck/noted in the ROADMAP this session. The ledger had lagged; check the rfc-app git tags against the version-target table before assuming a row is available (#26/#29 are tagged-but-not-struck pending deploy). Operator gestures pending for v0.24.0 (#27), at deploy time: - ANTHROPIC_API_KEY is ALREADY BOUND (operator set it in session 0025; `flotilla secret list ohm-rfc-app` shows ohm-rfc-app-anthropic-api-key@latest). Nothing to do — it waits in Secret Manager until the deploy. - Counsel pass on the propose-modal disclosure copy (per #22) — still pending before exposing the surface to users. Your job: pick one roadmap item that's shippable and disjoint from the un-deployed surfaces (#26 ProposeModal/PRModal/RFCView/PRView, #29 App.jsx sign-in region, #27 ProposeModal suggestion row + tag_suggest.py + the suggest-tags endpoint) and from v0.21.0's polished CSS. Build on a manual git worktree (git worktree add -b main; symlink frontend/node_modules + backend/.venv). Good deploy-free fits while frozen: #21 Part A (Amplitude audit — but it wants real-world data) or another Track-A surface that can build + tag + stage. Avoid touching the three un-deployed features' code regions. Hard rules: never ask for secret bytes; subagents write their own §5 subsession transcripts; finalize + publish your transcript last, delivering the next handoff prompt to the operator before publish. Claim your ID, then go. ```