Files
moreminimore-chat/HANDOFF.md
Moreminimore fb55e08a9f
Some checks failed
Auto-assign PR to Author / auto-assign (pull_request) Has been cancelled
Deploy Check / Check Deployment (pull_request) Has been cancelled
Frontend Lint & Test / test (pull_request) Has been cancelled
Log Lines Percentage Check / log_lines_check (pull_request) Has been cancelled
Run Chatwoot CE spec / lint-backend (pull_request) Has been cancelled
Run Chatwoot CE spec / security-scan (pull_request) Has been cancelled
Run Chatwoot CE spec / lint-frontend (pull_request) Has been cancelled
Run Chatwoot CE spec / frontend-tests (pull_request) Has been cancelled
Run Chatwoot CE spec / backend-tests (0, 16) (pull_request) Has been cancelled
Run Chatwoot CE spec / backend-tests (1, 16) (pull_request) Has been cancelled
Run Chatwoot CE spec / backend-tests (10, 16) (pull_request) Has been cancelled
Run Chatwoot CE spec / backend-tests (11, 16) (pull_request) Has been cancelled
Run Chatwoot CE spec / backend-tests (12, 16) (pull_request) Has been cancelled
Run Chatwoot CE spec / backend-tests (13, 16) (pull_request) Has been cancelled
Run Chatwoot CE spec / backend-tests (14, 16) (pull_request) Has been cancelled
Run Chatwoot CE spec / backend-tests (15, 16) (pull_request) Has been cancelled
Run Chatwoot CE spec / backend-tests (2, 16) (pull_request) Has been cancelled
Run Chatwoot CE spec / backend-tests (3, 16) (pull_request) Has been cancelled
Run Chatwoot CE spec / backend-tests (4, 16) (pull_request) Has been cancelled
Run Chatwoot CE spec / backend-tests (5, 16) (pull_request) Has been cancelled
Run Chatwoot CE spec / backend-tests (6, 16) (pull_request) Has been cancelled
Run Chatwoot CE spec / backend-tests (7, 16) (pull_request) Has been cancelled
Run Chatwoot CE spec / backend-tests (8, 16) (pull_request) Has been cancelled
Run Chatwoot CE spec / backend-tests (9, 16) (pull_request) Has been cancelled
Run MFA Tests / test (pull_request) Has been cancelled
Run Size Limit Check / test (pull_request) Has been cancelled
Test Docker Build / test-build (linux/amd64, ubuntu-latest) (pull_request) Has been cancelled
Test Docker Build / test-build (linux/arm64, ubuntu-22.04-arm) (pull_request) Has been cancelled
Lint PR / Validate PR title (pull_request_target) Has been cancelled
docs: session engineering log + handoff for M2 + OSS chatbot
2026-08-25 16:09:53 +07:00

20 KiB
Raw Blame History

HANDOFF

═══════ SESSION 2026-08-22 — Final decision: external n8n (Option 3) ═══════

  • User selected Option 3: n8n remains a separate service for the LLM chatbot/workflow path.
  • Do not integrate Hermes, embed n8n, or change Chatwoot production code for this decision.
  • Keep the existing AgentBot async webhook boundary: Chatwoot → external n8n → configured LLM/external services → Chatwoot API reply.
  • The Gateway/Adapter design is deferred; implement it only if later requirements justify centralized tenant isolation, idempotency, DLQ, observability, or provider routing.
  • Only this handoff/log decision is updated; production source remains unchanged.

═══════ SESSION 2026-08-22 — LLM chatbot runtime architecture discovery ═══════

  • Read-only architecture review for /Users/kunthawat/Gitea/Chatwoot; no production code changed.
  • Chatwoot's existing AgentBot path is already the intended boundary: async signed webhook out, external runtime processes, then external runtime posts an outgoing message through Chatwoot API. outgoing_url is not an LLM request/response endpoint.
  • Hermes is technically feasible only behind a bridge. Hermes API server is the preferred execution interface; Hermes webhook adapter is not plug-compatible because signature headers and response delivery contract differ.
  • Recommended target: Chatwoot AgentBot → dedicated Hermes bridge → Hermes API → Chatwoot API; optional external n8n remains for visual workflows, CRM/LINE/ERP side effects, and integrations. Do not embed n8n or give a customer-facing Hermes profile unrestricted terminal/file tools.
  • Tenant rules: keep Chatwoot as transcript/source of truth; namespace every request by account/inbox/conversation; do not treat Hermes personal memory/profile files as a multi-tenant knowledge base. Use allow-listed tools, sandboxing, idempotency, HMAC verification, secret rotation, and human handoff.
  • Next implementation gate: P0 one-account bridge proof (or n8n→Hermes proof), message-created only, no loop, signed webhook verification, reply via bot token, duplicate/retry tests, cross-tenant isolation test, and latency/error telemetry.
  • Working tree was already dirty with unrelated M1/M2 changes; future bridge work must use a separate worktree/branch.

═══════ SESSION 2026-08-19 — Moreminimore Chat: rebrand + M1 + M2 work ═══════

Current state (2026-08-19)

Repo /Users/kunthawat/Gitea/Chatwoot, branch develop, pushed to Gitea. Remote origin = ssh://git@moreminimoreapps-gitea.ahkhwd.easypanel.host:2222/kunthawat/moreminimore-chat (NOT github). SSH push via port 2222 (the HTTPS path git.moreminimore.com gets nginx 413; SSH port 22 is not Gitea SSH). HEAD = ccff2dfca. Worktree clean except untracked .hermes/plans/ (never commit).

Completed this session (all through independent five-key reviews, deployed by user)

  • Rebrand → Moreminimore Chat complete (phases 1-3 + Dockerfile + brand DB fix): f17ce89, d925ab1, 18e6fc28a, bee28c32e, d77d60443, 4b35130ae.
  • Docker/build: root Dockerfile added (4b35130ae) so EasyPanel 'Dockerfile' builder works instead of Nixpacks rbenv stack. nixpacks.toml added then removed (wrong approach).
  • M1 — per-account OpenAI custom base URL (the /app/accounts/{id}/settings/integrations/openai page): commit f9628db0e. Adds optional custom OpenAI-compatible base_url per account (apps.yml schema+form, llm_base_service api_base priority hook.base_url→CAPTAIN_OPEN_AI_ENDPOINT→api.openai.com, key_validator base_url kwarg, hook https validation). NOTE: an earlier wrong-scope Captain-level provider switch (46fe70398) was reverted (961ecd388) — the Captain CAPTAIN_OPEN_AI_ENDPOINT already supports custom URL as original behavior, so no change needed there.
  • M2 — analytics foundation (self-improving chatbot, admin-only):
    • 9fbb0da9b conversation_daily_metrics (per account/day; tags, deal outcomes, peak hours, agent/team/channel/inbox breakdown)
    • 0fad9ce6b customer_daily_metrics (per customer/day; agent_ids)
    • 161e4210b product_catalog_entries (per-account hierarchical product reference: group>subgroup>product+aliases)
    • ccff2dfca lib/llm/resolver.rb (LLM cascade: per-account openai hook → Captain → nil; https-only api_base)
  • DB brand fix done by user on prod (login no longer shows Chatwoot / logo loads).

M2 design decisions (approved by user — IMPORTANT for remaining work)

  • Analyze EVERY chat of EVERY account daily (high LLM cost accepted — selling point is self-improving chatbot).
  • LLM cascade: per-account openai → fallback Captain config → if neither, feature disabled (LLM integration effectively always on unless Captain unset).
  • Use Chatwoot's ORIGINAL conversation tags (ActsAsTaggableOn tag_list) — do NOT add many tag fields. Product/sale tag goes into the same tag list, prefixed (e.g. lines like group>subgroup>product joined, or สินค้า:.../กลุ่ม:...). Daily metric top_tags aggregates from these.
  • No tagged_at (user decided to skip time-tracking to avoid DB complexity).
  • Per-day stats are IMMUTABLE snapshots; conversation live tags get re-tagged on later analysis (e.g. session now about product B → product A tag removed). The daily snapshot preserves the earlier state.
  • Report page admin-role ONLY (agent must NOT see deep analytics). Filter by agent/team/inbox/channel/tag/sale-tag/time + cross-analysis. Break down per CUSTOMER and per AGENT.
  • Weekly (Mon 10:00) LLM summarizes 7 days → persona improvement recommendation (summary only, NOT full prompt). Delivered via preferred channel: Line (has quick-replies) / Telegram (inline keyboard) / webhook (3rd party). Admin chooses approve / view-full-prompt / reject; viewing full prompt → re-approve; on approve → webhook with prompt (admin sets URL+secret).
  • Product list: per-account, admin-only menu (like report), import via copy/paste or CSV/XLSX. Multi-level hierarchy (big group>subgroup>product>display) — LLM matches from lowest level first, tags ancestors; supports multiple products/groups per chat.

NEXT TO DO (M2 remaining — biggest remaining work)

  1. part2b-ii — LLM classifier service: DONE (2026-08-19). New lib/llm/analytics_classifier.rb — reads conversation, classifies via Llm::Resolver cascade + with_schema JSON output → Result (topics, products via tag_hierarchy path mapping, deal won/lost/undecided). Fail-closed on no-credential (returns disabled, never calls LLM / sends content). Also fixed: Account missing has_many :product_catalog_entries (foundation 161e4210b wired model+migration but not the reverse association → would NoMethodError). Verified: ruby -c clean (2.6/3.4), isolated smoke 8/8 under Ruby 3.4.10 (found /opt/homebrew/opt/ruby@3.4/bin/ruby — real 3.4.10 available, .ruby-version=3.4.4), static scan clean, independent reviewer deleg_47c1a2cb = complete 5-key PASS (non-blocking em-dash escape fix applied). Plan doc: .hermes/plans/2026-08-19-m2-self-improving-analytics.md.
  2. part2b-iii — daily batch job: DONE (2026-08-19). New app/services/analytics/account_daily_processor.rb (per-account/date: UTC day window from account.reporting_timezone, selects active conversations, classifies via Llm::AnalyticsClassifier fail-closed, re-tags replacing M2-managed tags + preserving manual labels, upserts ConversationDailyMetric unique account+date + CustomerDailyMetric unique account+contact+date) + app/jobs/analytics/daily_metrics_job.rb (per-account rescue, yesterday in account TZ) + config/schedule.yml entry analytics_daily_metrics_job 30 2 * * * timezone Asia/Bangkok. Verified: ruby -c clean (3.4.10), smoke 15/15, static scan clean, schedule no dup keys, reviewer deleg_fecf7c61 = complete 5-key PASS (4 non-blocking suggestions: add specs, N+1 in chat_message_count, deal_outcomes dual structure, apply_tags rescue scope).
  3. phase 2 — rollup + report: DONE (2026-08-19). Backend: Analytics::ReportService (summary/timeseries/customers/agents from immutable daily-metric tables; deal_outcomes normalization for 'totals'-nested + flat) + Analytics::DrilldownService (deep filterable drilldown over live conversations: since/until/agent/team/inbox/channel/tag/deal + pagination; fail-closed label-join) + Api::V2::Accounts::AnalyticsReportsController (admin-only via ReportPolicy#view?=administrator; summary + drilldown) + routes analytics_reports (get :summary/:drilldown). AccountDailyProcessor#apply_tags now writes deal:<outcome> tag for deal filtering. Frontend: api/analyticsReports.js + AnalyticsReports.vue (summary cards + filter bar + drilldown table) + route analytics_reports under reports section + ANALYTICS_REPORTS i18n keys in en/report.json. Verified: backend smoke 12/12, reviewers deleg_6800b012 (backend) + deleg_15f8447c (frontend) = complete 5-key PASS (non-blocking i18n suggestions applied). NOTE: frontend full build (eslint/vite/vitest) cannot run locally (no node_modules) — run pnpm install && pnpm test before ship.
  4. phase 3 — import + persona eval + approval (2026-08-20):
    • Weekly persona eval : Analytics::WeeklyPersonaEvaluator (7d via ReportService → LLM summary+recommendations; summary-only, no PII) + persona_evaluation endpoint + route. Reviewer deleg_b7d63fd7 PASS.
    • Frontend product-catalog import UI : api/productCatalog.js + productCatalog/Index.vue (paste + xlsx/csv upload + result + table) + admin-only route. Reviewer deleg_0e9f9291 PASS (i18n suggestions applied).
    • Approval flow (user chose LINE primary + webhook fallback, then added Telegram; precedence LINE → Telegram → webhook): Analytics::PersonaApprovalService (deliver LINE quick-reply / Telegram inline keyboard / webhook fallback; notify_approval webhook-only w/ decision) + Analytics::WeeklyPersonaEvaluationJob + controller (persona_evaluation_deliver, persona_approval_settings [line_user_id + telegram_chat_id + webhook_url, conditional], persona_decision) + routes + schedule.yml analytics_weekly_persona_evaluation_job (Mon 10:00 Asia/Bangkok). First reviewer deleg_1fe19871 FAIL-CLOSED (1 medium logic error: :approve path LINE-first + decision not passed) → FIXED (webhook-only notify_approval(decision)) → re-review deleg_3052f7a4 PASS; Telegram deleg_34116d1a PASS. Config: persona_line_user_id / persona_telegram_chat_id / persona_webhook_url / persona_webhook_secret in Account#custom_attributes.
    • Docs — via HANDOFF.md/plan doc; no separate docs/ convention.
    • POST-MVP design note (prompt webhook handshake) — user clarified, NOT built, awaiting LLM-server details: a SEPARATE LLM server holds/applies the actual prompt; this app connects via webhook — (1) connect-first: pull current prompt (GET) + store snapshot; (2) approve weekly recommendation → POST new prompt to LLM server to apply. Analytics here ONLY surfaces "what customers asked" + recommendations; knowledge-prep/prompt-authoring is admin/LLM-server side. Delivery-webhook design (REST 2-endpoint vs generic action-body) DEFERRED — user wants document/design only for now. Verified: smoke tests (eval 8/8, import 10/10 + xlsx 5/5, approval 10/10 incl. Telegram), ruby -c clean, node --check on .js, schedule.yml valid + unique keys, static scan clean across all.

Verification notes / blockers

  • Ruby env active is 2.6.10; target 3.4.4. Rails/RSpec/RuboCop cannot be reliably claimed passed. Ruby ruby -c syntax + YAML validity + isolated smoke tests used instead.
  • Full Vitest baseline is clean under TZ=UTC: 414 files / 4176 tests, 0 failed.
  • Enterprise (EE) files still read CAPTAIN_OPEN_AI_ENDPOINT directly and won't honor per-account base_url gating — intentional (out of OSS scope), note as follow-up.
  • No secrets committed. Temporary test helpers under /tmp only.

───────── historical (2026-08-16, before this session) ─────────

Latest handoff — 2026-08-16 (updated after rebrand + push)

  • Rebrand to Moreminimore Chat complete across all phases (committed): f17ce89 (phase 1 configurable brand core: logo/config/mailer defaults/views), d925ab1 (phase 2a cleanup + 512px thumbnail), 18e6fc28a (phase 2b color theme: black #333333 primary + Moreminimore yellow #FED400 accent), bee28c32e (phase 3 visible surfaces: app.json + mailers + 56 locales + prompts). All phases passed independent five-key reviews (deleg_a733cdf4, deleg_37455fbd, deleg_9b054593, deleg_892d115f).
  • PUSHED to Gitea via SSH port 2222: branch develop (HEAD bee28c32e) now on kunthawat/moreminimore-chat. Remote origin = ssh://git@moreminimoreapps-gitea.ahkhwd.easypanel.host:2222/kunthawat/moreminimore-chat. HTTPS push to git.moreminimore.com is blocked by nginx 413 (client_max_body_size < ~237MB pack); SSH port 22 is NOT Gitea SSH — use 2222. upstream (github.com/chatwoot) removed; Gitea rejects shallow push, so unshallow first. SSH key moreminimore-local-ed25519 added via Gitea API.
  • Earlier checkpoints: 19dc449 SM-07, 2ef6fa5 SM-07 remediation (approved deleg_087e4a3f), 364e72f SM-08, 8101395 residual Hub removal (approved deleg_49d6ee2e + deleg_58a576e1).

Verified checkpoints

  • 8ebb320 — privacy/branding audit harness.
  • 832a7fd — Hub sync/registration/telemetry removal.
  • 1a3697f — restored only the compatibility APIs required by direct push/billing callers.
  • 8b1a033 — removed the Chatwoot Hub push relay; exact post-commit independent review passed.
  • 3458272 — removed cwctl telemetry from the installer; exact post-commit independent review passed.
  • d9bf4c4 — removed community signup/onboarding website-branding enrichment; split exact post-commit reviews deleg_5e2578c5 (backend) and deleg_ee3e217f (frontend) passed with empty blocking arrays.
  • ced77af — SM-06 Product Analytics removal and dashboard config allowlist remediation; pre-commit review deleg_f9644984 and exact post-commit review deleg_9b0a2f63 passed with empty security/logic arrays.

Current work / uncheckpointed work

  • SM-05 community signup enrichment removal is checkpointed at d9bf4c4; pre-commit review, commit hooks, and split exact post-commit reviews passed. The enterprise service/spec remain unchanged and fail with NameError: uninitialized constant WebsiteBrandingService. This remains an explicit architecture/legal blocker; do not add a dummy service or restore remote enrichment.
  • SM-06 is verified at ced77af. Its follow-up fix applies .slice(*GLOBAL_CONFIG_KEYS) before merging app_config, preventing stale/unrequested values such as CLOUD_ANALYTICS_TOKEN from reaching serialized window.globalConfig. SM-07 local-only observability and the remaining SM-08 dashboard/config script-injection work are still uncheckpointed. The community Help Center SM-08 correction is committed as 5619cc3: remove GTM remote-container execution, reject/remove legacy GTM config, and retain only fixed allow-listed provider snippets for explicit admin configuration. Exact post-commit review deleg_dd3db5b7 passed with empty blocking arrays. Enterprise marketing conversion tracking remains outside this community scope pending legal/compatibility review.

Evidence and blockers

  • SM-03 exact post-commit review passed for 1a3697f..8b1a033; reviewer suggestions are to add explicit missing/blank/partial Firebase negative coverage and clarify legacy FCM configuration names in .env.example.
  • SM-04 exact post-commit review passed for 1a3697f..3458272; Bash 3.2 compatibility and fail-closed privacy-test behavior were verified.
  • SM-05 exact committed range 3458272..d9bf4c4 passed split independent reviews: backend deleg_5e2578c5 and frontend deleg_ee3e217f; both returned complete five-key verdicts with empty security and logic arrays. Frontend targeted run passed 9 suites/8 tests. Backend RSpec rerun is environment-blocked because Ruby 3.4.4 is required but only 3.4.10 is installed and the bundle has no usable rspec executable.
  • SM-06 exact committed range a96b977..ced77af changed one controller line and passed fresh pre-commit/post-commit independent review. Raw merge leaked a stubbed analytics token; the allowlist slice now removes unrequested keys before layout serialization. Committed Ruby syntax, diff check, and security scan passed. Rails RSpec/RuboCop remain blocked because the active Ruby is 2.6.10 and Bundler 2.5.16 is unavailable.
  • SM-08 exact post-commit review deleg_dd3db5b7 passed for d9bf4c4..5619cc3; 57 portal model/controller examples, GTM regression 3/3, Ruby/ERB syntax, RuboCop, ESLint/Prettier, Vite build and privacy-audit harness passed. Full Vitest with TZ=UTC passes 414 files / 4176 tests, 0 failed.
  • No push or deploy has occurred.
  • Approved product name, domains, logos/icons and sender/legal values are still missing; stop before SM-09/10.
  • Full Rails/frontend suites, production-like DB checks, HTTP/DNS egress capture, git fetch --unshallow, and upstream merge rehearsal remain outstanding. The full Vitest frontend baseline is clean under TZ=UTC: 414 files / 4176 tests passed, 0 failed (the earlier "407/15 failed" record was a non-UTC run artifact; timezone-sensitive tests all pass under UTC).
  • Temporary test services/configuration stay under /tmp; never commit them or secrets.

Repository baseline

  • Path: /Users/kunthawat/Gitea/Chatwoot
  • Origin: https://github.com/chatwoot/chatwoot.git
  • Branch: develop
  • Initial baseline commit: 9a73c1473ffa0ae6a9c7725046b8ca17922dcc83
  • Latest checkpoint: ced77af (SM-06 Product Analytics plus dashboard config allowlist remediation; exact post-commit review deleg_9b0a2f63 passed). SM-08 Help Center remains separately verified at 5619cc3.
  • Clone is shallow; fetch full upstream history before merge/rebase rehearsal.

Primary deliverable

  • .hermes/plans/2026-08-15_092534-chatwoot-private-rebrand.md
  • Small-model execution index: .hermes/plans/chatwoot-private/README.md
  • The execution package contains 00-execution-contract.md plus runbooks 0112 with 58 unique SM-* tasks. Send only one task at a time and review its exact diff/test evidence before advancing.

Most important finding

DISABLE_TELEMETRY=true is not a complete opt-out. lib/chatwoot_hub.rb still posts instance_config to the Hub; it only omits instance_metrics. The corresponding behavior is asserted in spec/lib/chatwoot_hub_spec.rb.

Other unsolicited/vendor-controlled egress to remove

  • Chatwoot Hub daily sync and onboarding registration
  • Chatwoot Hub /send_push relay fallback
  • Hub-hosted changelog fetch
  • silent cwctl command reporting to the Hub
  • automatic signup email-domain website/DNS branding enrichment
  • Amplitude browser analytics
  • Sentry/frontend/backend remote error reporting
  • optional remote APM agents
  • arbitrary dashboard scripts and remote GTM container execution
  • enterprise marketing conversion tracking (proprietary/cloud-only; pending separate legal/compatibility decision)

Implementation order

  1. Follow dependency order in .hermes/plans/chatwoot-private/README.md.
  2. Establish the baseline and privacy audit harness before behavior changes.
  3. Remove Hub sync/registration/manual refresh, push relay, cwctl telemetry, signup-domain enrichment and remote changelog.
  4. Neutralize/remove analytics, remote error reporting/APM and script injection.
  5. Stop at the Runbook 09 input gate until approved brand values/assets are supplied.
  6. Add central brand defaults and replace visible assets/copy without renaming compatibility APIs.
  7. Run static and isolated dynamic egress gates, positive feature-traffic tests, full review and visual QA.
  8. Complete upstream-history and merge-rehearsal workflow; never mix upstream merge with customization commits.

User decisions required before implementation

  • Product name, canonical/support/docs domains, logos/icons and email sender name
  • Private Gitea repository name
  • Whether to keep deprecated widget API aliases (recommended: yes)
  • Whether local-only logs are sufficient or a self-hosted observability endpoint is required
  • Push notification plan and own FCM/VAPID credentials

Safety/legal notes

  • Root is MIT, but enterprise/ is proprietary; do not ship enterprise features without a separate license.
  • No remote repository was created, no files were pushed and no deployment was performed.