OPEN_DECISIONS.md — Pending Architectural / Product Calls
Tracks decisions discussed but not finalized — questions that need E's input or where both paths are valid and we need to pick one. Each entry: what is open, why it matters, options on the table, and owner / next step. When a decision lands, move it into a numbered ADR (
docs/decisions/NNN-*.md) and remove it from this file.
Last updated: 2026-05-04.
OD-1 — AI Autoreply Threshold UX: who configures it and when?
DECISION 2026-05-02: AM and Experts configure it. Clients never see it. UI: Never/Sometimes/Often/Always selector with tooltips. Internal scale INVERTED: 0=always autoreply, 101=never. Implemented in feat(threshold) commit. See CD-10.
What's open. The Confidence Threshold (0–101, default 101 = always-HITL) gates whether an AI draft auto-sends or routes to the Expert Queue. We currently expose it on the org settings surface so a client could in principle change it. That's almost certainly wrong: clients don't (and shouldn't) reason about agent confidence — they reason about service quality. The AM picks the threshold during onboarding based on the domain (KYC = 101 always-HITL; FAQ-style = 70). The post-onboarding setting on the client side adds a footgun without a real user need.
Why it matters. Wrong placement either (a) leaks operator-only mechanics to clients (confusing, looks unprofessional), or (b) silently makes risky changes that affect every Expert in the pool serving that org.
Options.
- A. Threshold lives only in the AM wizard (Step 1.2) + AM/superadmin org-detail page. Removed from client-facing settings entirely. ← proposed
- B. Read-only display in client settings ("Your account is currently set to 'Always Reviewed by Expert' — contact your AM to change.") so clients see the safety posture without being able to change it.
- C. Keep current behaviour (client-editable). Reject — already breaks the framing.
Owner. AM team + product (E).
Next step. Decide between A and B; if B, write the read-only widget. The recent refactor(portal-admin): simplify to Team/Channels/Billing commit removed the broken setting from the client portal admin nav, so the immediate footgun is closed but the canonical source of truth (AM wizard / superadmin org-detail) needs the field if it isn't already there.
OD-2 — ✅ RESOLVED
CLOSED 2026-05-02: Rename
/portal/*→/client/*. No backwards compat needed (no live clients). See CD-15.
OD-2 — Client portal URL prefix: /portal/* vs. /client/*
CLOSED. Frontend client routes are at /client/chat, /client/settings/*, etc. The rename from /portal/* to /client/* was completed 2026-05-03. See CD-15.
Why it matters.
- Consistency: every other role prefix is correct (
/workspace/*,/ops/*). The misnomer "portal" implies internal staff portal, which is the opposite of what it is. - Link rot: invite emails, OS bookmarks, support docs may point at
/portal/*. A bare rename without redirects breaks them all.
Options.
- A. Keep
/portal/*forever. Revise the doc convention rather than the code. Cheap, but the doc convention exists for a reason and "portal" is genuinely confusing for new contributors. - B. Rename
/portal/*→/client/*with a Next.js redirect rule from old to new (one-line innext.config.tsor middleware). UpdategetRoleRedirect(). Ship over a single deploy. - C. Two-tier: keep
/portal/*as a legacy redirect shell forever, all new code lives at/client/*.
Owner. Frontend (Ethan).
Next step. Pick B vs. C. If B, the work item is well-scoped (~half-day refactor + redirect rule). If C, only new routes need to use /client/*.
OD-3 — ✅ RESOLVED
CLOSED 2026-05-02: Always from Specialist alias (
bob@human.work). No BCC. In-app thread is the audit trail. See CD-16.
OD-3 — Email channel outbound: from bob@human.work or from the Expert's own email?
What's open. When an Expert (e.g. David Kim) replies to an email-sourced conversation, the reply goes from the Specialist routing address (bob@human.work), DKIM-signed via Resend. The Expert's own mailbox (david@ex.human.work) is never the From. This is the right answer for client-facing email — the client only knows Bob — but it has consequences:
- The Expert has no record of the sent email in their own mailbox unless we BCC them.
- Replies-to-replies always come back via the Specialist alias, not direct to the Expert.
- For internal-to-internal handoffs (Expert ↔ AM ↔ AI Operator), email routing is undefined.
Why it matters. The persona model depends on this being right. If we ever blink and let an Expert email come from david@ex.human.work, we leak the team composition and break the client trust contract.
Options.
- A. Status quo. Outbound is always
bob@human.work; Expert maintains audit trail via the in-app conversation thread, not their own inbox. ← current - B. Auto-BCC the Expert who sent the message at
david@ex.human.workso they have a personal record. Trade-off: doubles Resend spend per outbound; client-sideReply-Allcould leak the Expert address if a client ever sees a forwarded email. - C. Add a per-Expert toggle "BCC me on sends" — they opt in.
Owner. Platform + Expert team. Next step. Confirm A is the long-term answer for client-facing email. Decide B/C separately for internal record-keeping.
OD-4 — ✅ RESOLVED
CLOSED 2026-05-02: Shared number pool (recycled across Specialists and Orgs). Seeded from checked-in JSON file on first migration. Routing: from-address + sender whitelist → org → conversation. See CD-17.
OD-4 — One WhatsApp number per Specialist, vs. per Org
What's open. Per ADR-002, we provision one WhatsApp number from the Twilio Number Pool per Specialist persona. A Specialist can serve many client orgs — Bob serves both Acme Financial and Beta Corp on the same number, and we route inbound by sender whitelist.
Why it matters. As we scale, a hot Specialist (one popular persona used across N orgs) could hit Meta's per-number messaging limits. Conversely, a cold Specialist (assigned to only one client) carries a $1/mo number that mostly idles.
Options.
- A. Status quo. One number per Specialist; let the pool grow with personas, not with orgs. Best for cost and brand consistency. ← current per ADR-002
- B. One number per (Specialist × Org) pair. Better isolation; Meta limits are per-number so capacity is easier to reason about. Number pool grows with $orgs, not personas — much more expensive.
- C. Hybrid — Specialist-default until usage on a pair exceeds a threshold, then promote that pair to its own dedicated number.
Owner. Platform + ops (the pool size budget is the lever). Next step. Watch real volume on the first 5–10 paid orgs. If any (Specialist × Org) approaches Meta's daily messaging cap, revisit. Not blocking MVP.
OD-5 — ✅ RESOLVED
CLOSED 2026-05-02: Standardize on thread in all client UI, conversation in code, ticket retired. See CD-11.
OD-5 — Conversation vs. Ticket vs. Thread terminology
What's open. Three near-synonyms in different layers:
conversation— the API/codebase entity (conversationstable,Conversationentity,/conversations/:idendpoints). Authoritative in the data layer.ticket— old portal naming, still appears in some admin surfaces and in agentic-task descriptions. Echoes a helpdesk metaphor that no longer fits.thread— the client UI label ("Your Threads" sidebar group;+ New Threadbutton). Conversational metaphor.
Why it matters. Inconsistency in user-facing copy makes the product feel less cohesive ("Where is my ticket?" vs. "Where is my thread?"). Inconsistency between code and UI is fine if the boundary is documented (it isn't, currently).
Options.
- A. Standardize on thread in all client-facing UI; keep conversation in the API and code; retire ticket entirely (rename
/portal/ticketsand friends — already partially done in the recent admin refactor). - B. Standardize on conversation in UI too, mirroring the API. Loses the lightweight chat metaphor.
- C. Keep all three; add a glossary section explaining the distinction. Doesn't solve the underlying inconsistency.
Owner. Design + product. Next step. Pick A (default proposal). Audit user-facing copy for stray "ticket" references and replace.
OD-6 — ✅ RESOLVED
CLOSED 2026-05-02: Keep "Expert Queue". See CD-12.
OD-6 — Internal queue surface naming: "Expert Queue" vs. "Inbox"
What's open (surfaced during this audit, not previously tracked). The internal-staff surface at /workspace/queue is called "Expert Queue" everywhere — feature doc, code, UI header. But the actual artifact Experts work in is an inbox: messages arrive, get triaged, get sent, get archived. "Queue" implies one-shot work where "inbox" implies an ongoing conversation surface, which is what we actually have.
Why it matters. The naming shapes the mental model. "Queue" makes Experts think "next item in line" rather than "conversation history with this client". Lower-priority but worth knowing.
Options.
- A. Keep "Expert Queue". Familiar; no churn.
- B. Rename to "Inbox" (UI only — code/DB stays as
expert_queue_*). - C. Differentiate: "Inbox" for the conversation list view, "Queue" for the agentic-task approval list.
Owner. Design. Next step. Run by E. Cosmetic, not blocking.
OD-7 — ✅ RESOLVED
CLOSED 2026-05-02: Delete /portal/kai route and onboarding tile. See CD-13.
OD-7 — Stale /portal/kai route and onboarding tile
What's open (surfaced during this audit). /portal/kai/page.tsx still exists in the app router, and /portal/onboarding/page.tsx line 98 still has a tile that pushes users to it. The route was removed from the client portal admin nav in the recent refactor, but the page itself and the cross-link were left. "Kai" was a Phase-2 reporting concept that didn't ship.
Why it matters. Dead code drift; cross-link in onboarding could 404 or show an empty page to a new client during their first session. Bad first impression.
Options.
- A. Delete
/portal/kai/page.tsxand remove the onboarding tile. - B. Replace the route with a redirect to
/portal/chat(low-effort). - C. Repurpose for something else (none of the candidates are funded).
Owner. Frontend. Next step. Pick A or B. Tracked separately when next touching the onboarding page.
OD-8 — ✅ RESOLVED
CLOSED 2026-05-04:
HWORK_DOMAINis the canonical env var for all domain config. Backend:HWORK_DOMAIN; frontend:NEXT_PUBLIC_HWORK_DOMAIN. Staging:h852.work, production:h.work. ReplacesSPECIALIST_EMAIL_DOMAINandNEXT_PUBLIC_APP_DOMAIN. See ARCHITECTURE.md, GLOSSARY.md, docs/ops/railway-env-vars.md.
OD-8 — Domain env var: SPECIALIST_EMAIL_DOMAIN vs HWORK_DOMAIN
CLOSED 2026-05-04 — resolved above.
OD-9 — ✅ RESOLVED
CLOSED 2026-05-04: R2 file storage is planned but not yet implemented. Spec exists at
docs/design/R2_FILE_STORAGE_SPEC.md. No code exists. All references to R2 must be marked "Planned — not yet implemented".
OD-9 — R2 vs S3 for file storage
CLOSED 2026-05-04 — resolved above.
How to add an entry
- Append at the bottom with a stable ID (
OD-N, monotonically increasing). - Use the four-section template: What's open, Why it matters, Options, Owner / Next step.
- Link to the relevant ADR if one exists, or to a GitHub issue.
- When the decision lands, write the canonical ADR (
docs/decisions/NNN-*.md) and delete the entry from this file. Do not leave stale "resolved" entries here — that's what the ADR is for.
Closed Decisions
Decisions that have been made and documented as ADRs. Listed here for historical context. Full rationale in the linked ADR.
| ID | Decision | Date | ADR |
|---|---|---|---|
| CD-1 | Multi-tenant architecture: RLS-enforced Postgres isolation, orgId on every table, JWT-scoped queries | 2026-04-22 | ADR-000 |
| CD-2 | LangGraph runtime deferred; Hermes Agent is active | 2026-05-12 | ADR-001 |
| CD-3 | WhatsApp via Twilio Meta Cloud API + shared number pool per Specialist | 2026-04-30 | ADR-002 |
| CD-4 | Deployment: Railway (API + agent + Postgres + Redis) + Vercel (frontend) | 2026-04-22 | ADR-003 |
| CD-5 | Specialist = client-facing persona (not a user account); Expert = human employee who reviews AI drafts. Non-obvious — will confuse every new contributor. | 2026-05-01 | GLOSSARY.md |
| CD-10 | AI Autoreply Threshold: AM+Expert only (clients never see it). Scale inverted: 0=always autoreply, 101=never. UI: Never/Sometimes/Often/Always. | 2026-05-02 | AutoreplyThreshold.tsx |
| CD-11 | Terminology: "thread" in client UI, "conversation" in API/code, "ticket" retired. | 2026-05-02 | — |
| CD-12 | Portal routing: path-based (/client/chat) is canonical. No subdomain routing (``{slug}.h.work). All docs and invite email URLs use path-based URLs. | 2026-05-07 | OPEN_DECISIONS.md |
| CD-16 | Email outbound always from Specialist alias (bob@human.work), never from Expert's personal address. No BCC. Protects persona integrity. | 2026-05-02 | — |
| CD-17 | WhatsApp inbound: From-first routing (sender → whitelist → org → conversation). Pool number used only for outbound context. Seeded from whatsapp-pool.json. | 2026-05-02 | seed-whatsapp-pool.ts |
| CD-18 | Keep "org"/"orgId" in code; use "client" in all user-facing copy and docs — renaming would touch 50+ files, DB migrations, API contracts. "Workspace" is retired as a user-facing term; "Expert workspace" remains correct for the Expert's surface. Canonical table in GLOSSARY.md. | 2026-05-03 | — |
| CD-19 | Role boundary: SuperAdmin creates/manages Specialists and Experts. AMs manage client orgs only (assign existing Specialists, run onboarding). API guards: POST/PATCH/DELETE /admin/specialists restricted to superadmin; GET accessible to both. | 2026-05-03 | — |
| CD-20 | HWORK_DOMAIN is the canonical env var for all domain config (Specialist email, client subdomains). Backend: HWORK_DOMAIN; frontend: NEXT_PUBLIC_HWORK_DOMAIN. Staging: h852.work, production: h.work. Replaces SPECIALIST_EMAIL_DOMAIN and NEXT_PUBLIC_APP_DOMAIN. | 2026-05-04 | — |
| CD-21 | R2 file storage: planned only. Spec at docs/design/R2_FILE_STORAGE_SPEC.md. No implementation. All references in docs must be marked "Planned — not yet implemented". | 2026-05-04 | — |
| CD-22 | Expert pool model removed. Expert ↔ Org assignment is direct via org_experts. Pool-based routing (ExpertPool, PoolMembership, pool_id on queue items) deleted. Reassign UI disabled pending new model design. | 2026-05-03 | api/SCHEMA.md |
| CD-23 | Specialist email long-term: each bob@h.work will be a real Google Workspace user (not a Cloudflare routing alias). Single GCP service account + domain-wide delegation gives the platform programmatic access to Gmail, Calendar, Drive, Admin SDK — no per-user OAuth or human intervention at runtime. Current Cloudflare + Resend setup is MVP; migration planned post-MVP. See docs/features/channels.md → System 2 long-term plan. | 2026-05-12 | docs/features/channels.md |