Skip to main content

Motivation / problem

A knowledge base does not fail loudly. It rots quietly: a decision is superseded but the old doc still ranks; a runbook hasn’t been touched in nine months; the same three questions get asked every week and never get a canonical answer; the two people who write everything burn out while everyone else only reads. None of this shows up in a 200 OK. Stack Overflow for Teams, Zendesk and Notion each surface a slice of this — a thin “low activity this week” email, a ticket backlog, an edit history. AskMyDocs already computes far richer raw signals (canonical health scores, decision-debt, search-failure gaps, citation impact, promotion events). The Engagement & Intelligence Suite is the packaging, delivery and feedback layer that makes those signals act on people: it tells the right person what changed, what is decaying, what is unanswered — across email, Discord, Slack, Teams and an in-app feed — and reflects each contributor’s impact back to them on a dashboard.

Theory & background

The suite rests on one append-only primitive — the contribution event — and two derived layers built on top of it.
  • Contribution events (kb_contribution_events) are an immutable per-user log: who did what (created | modified | promoted | reviewed | answered | cited) to which document, with a weight. They are written from the existing ingest / promotion / citation paths — never a new write path — so the log is a faithful projection of real activity (the canonical markdown remains the source of truth; this table is rebuildable).
  • Engagement metrics aggregate those events in SQL (R3 — never in PHP) into contributor stats, citation impact, coverage %, answer rate and trends. A daily snapshot (kb_engagement_snapshots) freezes the rollup so dashboards and digests read O(1) instead of re-scanning the log.
  • Gamification (opt-in) derives badges from the same metrics. It computes nothing new — it is a view over engagement, gated off by default.
Everything composes into a digest: a tenant- and user-scoped bundle of typed sections (newly created/promoted docs · stale/obsolete review queue · top unanswered questions · KB-health trend · “your attention needed”) rendered per channel and optionally narrated by an LLM. Modified-doc activity is reflected in the headline metrics rather than as a separate per-doc list.

Design

Three service clusters sit over the existing signal sources and converge on the digest pipeline and the dashboards.
  • EngagementMetricsService — DB-aggregated contributor stats, leaderboard, coverage, answer rate, trend series. Tenant-scoped (R30).
  • DigestComposer assembles a typed DigestPayload from the existing services — no signal is recomputed.
  • AiDigestNarrator adds an optional “what changed & why it matters” summary via a dedicated digest model, and degrades to deterministic copy when disabled or unreachable (R14/R43).
  • DigestRendererRegistry is an R23 strategy registry — FQCN validated at boot, non-overlapping supports() — with one renderer per channel reusing the existing notification channel adapters for transport.
  • GamificationService derives badges from the same metrics; default-off.
The core capabilities — engagement metrics, the digest, and gamification badges — are tri-surface (R44): an Artisan command, an HTTP endpoint, and an MCP tool, all delegating to the same core service. Two surfaces are intentionally single-surface (R44’s documented exception): digest preferences and the user dashboard are me-context UI reads with no command/agent caller, so they ship HTTP + FE only.

Data model / contract

All five tables are tenant-aware (tenant_id + composite uniques starting with it, BelongsToTenant; R30/R31).
TablePurpose
kb_contribution_eventsAppend-only per-user activity log (event, document_id, user_id, weight, created_at).
kb_engagement_snapshotsDaily per-tenant rollup (contributors, activity, coverage, answer rate, leaderboard, health).
digest_preferencesPer-user frequency (weekly/monthly/off) + per-section toggles.
engagement_digest_feedIn-app digest entries (retention-pruned).
kb_user_badgesAwarded gamification badges (opt-in).

Tri-surface map

CapabilityCommandHTTPMCP
Engagement metricsengagement:computeGET /api/admin/engagement/{summary,leaderboard,series}KbEngagementSummaryTool
Digestdigest:send, digest:prune-feedGET /api/admin/digest/preview, GET /api/me/digest/latestKbDigestPreviewTool
Digest preferencesGET /api/me/digest-preferences + PUT /api/me/digest-preferences
User dashboardGET /api/me/dashboard
Gamification badgesgamification:recomputeGET /api/me/badgesKbUserBadgesTool

Decision rationale (ADR-style)

  • An append-only event log, not mutable counters. Counters drift and can’t be re-derived; an immutable log is rebuildable from the canonical markdown + git and supports any future metric without a migration. Aligns with the “DB is a projection” principle (see Architecture → Decisions).
  • Daily snapshot over live aggregation. Dashboards and digests must be fast and cheap; a nightly snapshot trades freshness (≤24h) for O(1) reads. The live path remains available as a fallback when no snapshot exists yet (fresh install) or a snapshot’s metrics are null (partial-compute).
  • Reuse the notification channel adapters, add only renderers. The transport (Discord webhook, Slack Block Kit, Teams Adaptive Card, email) already exists and is hardened; the suite adds presentation (a renderer per channel) behind an R23 registry, not new transport.
  • A dedicated, free digest model. Digests are summary prose, not latency/quality-critical, so the narrator defaults to a free OpenRouter model — it never competes with the primary chat model and costs ≈$0. See Digests.
  • Gamification is off by default and computes nothing new. It is a view over engagement metrics, gated by KB_GAMIFICATION_ENABLED and tested in both states (R43). Teams that want quiet analytics get them; teams that want badges opt in. See Gamification.

Worked example

Compute today’s snapshot for one tenant, preview the weekly digest without sending, then award badges:
# 1. Freeze today's engagement metrics for a tenant (7-day activity window).
php artisan engagement:compute --tenant=acme --days=7

# 2. Compose + render the weekly digest for every channel — print, don't send.
php artisan digest:send --frequency=weekly --tenant=acme --preview

# 3. (opt-in) Award gamification badges across contributors.
KB_GAMIFICATION_ENABLED=true php artisan gamification:recompute --tenant=acme
Read the same metrics over HTTP (admin) or MCP (agent):
curl -s -H "Authorization: Bearer $TOKEN" \
  "https://kb.example.com/api/admin/engagement/summary?days=7"

Gotchas & operations

The contribution log is written from the existing ingest/promote/citation paths. If you add a new write path to the KB, hook it into the contribution recorder or its activity will be invisible to engagement, digests and gamification.
  • The daily snapshot runs at 05:15 (engagement_compute) and the gamification recompute at 05:20, so both read fresh metrics; the rich digests are sent later in the morning — digest_weekly Mon 07:15 and digest_monthly the 1st at 07:30 — well after the snapshot. Every slot’s cron + kill-switch is env-tunable. See Scheduler & Maintenance.
  • Every new knob is documented in Configuration and defaults to a safe value (narrative on with a free model; gamification off; feed retention 120 days).
  • All reads are tenant-scoped; two tenants sharing a project_key never see each other’s contributors. See Multi-tenant isolation.

Digests

Multi-channel rich digests + AI narrative.

Dashboards

Admin + user metrics, the “wow” catalog.

Gamification

Opt-in badges over engagement metrics.