Nox / Knox Admin
Audience: Ops · Super-admins · Support · Engineering · Where in app: /admin/knox/* · Access: Super-admin only (verify — these are admin-portal routes behind the admin layout; the ms-ai knowledge/model/feedback endpoints are the super-admin management surface).
Naming note: Nox is the product name shown to users; Knox is the internal/code name. The admin routes and ms-ai modules use
knox, the UI labels say "Nox". They are the same assistant.
These screens are the back office for the Nox assistant: read every conversation, triage thumbs-down feedback, pick which AI model powers each Nox role, author the knowledge documents that feed Nox's RAG retrieval, register the portal pages Nox can navigate users to, watch/cancel per-visitor auto-engage directives, and sync Nox's tool registry to the shared ElevenLabs voice agent.
What it does
- Conversations browser — cross-tenant list of Nox threads with per-turn tool-call audit logs and voice-call playback.
- Feedback review — paginated rated turns (defaults to
not_helpful) for debugging answer quality. - AI Models — map each Nox role (chat / narration / embedding / transcript enrichment) to a catalog
(provider, model_id); exactly one default per role. - Knowledge authoring — collections + markdown documents that get chunked and embedded into the RAG store (
knox_knowledge_chunks). - Page Registry — the catalog of portal pages Nox knows about, used by the navigation agent.
- Visitor Watches — cross-company view + cancel of per-visitor notify / auto-engage directives, now with fire diagnostics.
- Voice Tools — idempotent sync of the Nox tool registry into the shared ElevenLabs agent.
- Nox Funnel audit — cross-company funnel audit-log browser + per-contact trace, plus backfill triggers (see Nox Funnel).
- Beta gates —
nox_funnel_beta_enabledandagent_k_beta_enabledtoggles live on the company detail screen (views/admin/companies/Detail.vue), not under/admin/knox.
How it works
The Vue views live in frontend/src/views/admin/knox/ and the visitor-watch view in frontend/src/views/admin/visitor-watches/. Most knox screens call ms-ai directly via config.getAiApiUrl() (repository frontend/src/repositories/admin/knox.js), under /knox-agent/* and /knox-voice/*. Visitor Watches instead talk to the Node backend admin routes (frontend/src/repositories/admin/visitor-watches.js → /admin/visitor-watches*).
ms-ai controllers:
knox-admin.controller.ts—crm-backfill, models CRUD/default, conversations list+thread, feedback list (underknox-agent/admin/…).knox-management.controller.ts— knowledge chunks, sources, pages, ingest, reembed (underknox-agent/knowledge/…).knox-document.controller.ts— collections + documents +page-options(underknox-agent/documents/…).knox-voice.controller.ts— voice call metadata/audio (knox-voice/calls/:thread_id) and the tool sync.
Knowledge documents are the authored source of truth; on save the document service (ms-ai/.../services/knox-document.service.ts) deletes the doc's old chunks, splits the markdown into semantic chunks, generates contextual embeddings, and stores them as knox_knowledge_chunks — that chunk table is what Nox actually retrieves at answer time.
Screens & fields
Conversations — /admin/knox/conversations
Source: KnoxConversations.vue → getKnoxConversations (GET /knox-agent/admin/conversations). One row per thread_id. Defaults to the last 7 days server-side; max window 90 days.
| Filter | Notes |
|---|---|
| From / To | date window |
| Company ID | optional — omit for cross-tenant browsing |
| Column | Meaning |
|---|---|
| Latest | most recent turn time |
| Company | tenant |
| Last question | the visitor's latest question |
| Agent | routed sub-agent |
| Intent | primary intent |
| Turns | turn count |
| Tools | tool-call count |
| Tokens | token usage |
| Latency | response latency |
Clicking a row opens KnoxConversationModal.vue → getKnoxConversationThread (…/conversations/threads/:thread_id), which renders the whole thread chronologically with each turn's tool_calls (input / output / duration) inline, plus per-turn feedback notes. Each tool call also shows "Model saw (markdown)" — the curated markdown the LLM actually read (model_markdown, rendered by ms-ai knox-tool-markdown.ts and persisted per turn), with an honest truncation marker when the stored output was capped at 24KB; the copy-to-markdown export includes it fenced. For voice threads it also calls getKnoxVoiceCall (GET /knox-voice/calls/:thread_id) for the ElevenLabs summary, termination reason, duration, and audio_available; if audio exists, fetchKnoxVoiceAudioBlob pulls the recording as a blob (auth header can't ride a bare <audio src>) for playback. Audio returns 404 if deleted from ElevenLabs retention, 403 if the admin doesn't own the call.
Feedback — /admin/knox/feedback
Source: KnoxFeedback.vue → getKnoxFeedback (GET /knox-agent/admin/feedback). Paginated rated turns; the debug case (not_helpful) is the default, but helpful is also supported. Filters: From/To, Company ID, sentiment. Each row shows: question, answer, the feedback badge (Helpful / Not helpful), routed_agent, primary_intent, feedback_note, thread_id, company_id, created_at.
AI Models — /admin/knox/models
Source: KnoxModels.vue → getKnoxModels / createKnoxModel / updateKnoxModel / setKnoxModelDefault / deleteKnoxModel (/knox-agent/admin/models). Maps Knox roles to a (provider, model_id) in the shared ai_models catalog. Exactly one model per role is the active default. This page only attaches a catalog model to a role — pricing/catalog itself lives elsewhere (Cost Tracking → Models).
Roles (create dropdown): Chat (answers, ingestion, context), Narration (recording analysis), Embedding, Transcript enrichment.
| Column | Field |
|---|---|
| Model | display_name / model id |
| Provider | provider |
| Selected | is_default (the active model for the role) |
| Actions | edit / set default / delete |
Create flow: pick role → pick a catalog model from the dropdown (eligibleCatalogModels, filtered by role) → provider/model_id auto-fill → optional display name. Backing entity: knox_ai_models (provider, model_id, display_name, role, is_active, is_default); a unique constraint enforces one (provider, model_id, role) and a partial index enforces one default per role.
Knowledge — /admin/knox (collections + documents)
Source: KnoxKnowledge.vue → getKnoxCollectionTree / getKnoxDocuments / createKnoxCollection etc. (/knox-agent/documents/…). Left rail is the collection tree; main pane lists documents in the selected collection (or "All Documents") with a title/content/tag search and Bulk Import (KnoxBulkImportModal.vue) + New Document.
Collections (knox_knowledge_collections): name, slug (unique), description, icon, parent_id (nestable), sort_order, document_count, is_active. A collection with documents can't be hard-deleted (FK RESTRICT); soft-delete via is_active=false.
Document editor — /admin/knox/doc/new, /admin/knox/doc/:id
Source: KnoxDocEditor.vue → getKnoxDocument / saveKnoxDocument / deleteKnoxDocument (/knox-agent/documents). Markdown authoring surface. Editing/saving a document re-chunks and re-embeds it into the RAG store.
Document entity (knox_knowledge_documents):
| Field | Notes |
|---|---|
title | |
markdown_content | full markdown source |
content_type | feature_guide (default) · how_to · troubleshooting · policy · api_reference · pricing · glossary |
product_area | integrations, billing, chat, … |
plan_scope | all (default) · starter · growth · pro · enterprise |
tags[] | |
related_urls[] | portal page URLs this doc covers (picker fed by getKnoxPageOptions → /knox-agent/documents/page-options) — rendered as inline links in answers |
collection | required FK — every doc belongs to one collection, no orphans |
status | draft · processing · published · failed |
chunk_count | how many chunks were generated |
processing_error, last_processed_at, author_id, version, is_active | processing/authoring metadata |
The resulting chunks (knox_knowledge_chunks) carry content, context, feature_area, content_type, keywords[], requires_role, difficulty, two 1536-dim vector embeddings, and is_active. Admins can also browse/edit chunks directly via getKnoxChunks / updateKnoxChunk and trigger reembedAllKnoxChunks (POST /knox-agent/knowledge/reembed).
Page Registry — /admin/knox/pages, …/pages/create, …/pages/:id/edit
Source: knox/pages/List.vue + knox/pages/Edit.vue → getKnoxPages / createKnoxPage / updateKnoxPage / deleteKnoxPage / seedKnoxPages (/knox-agent/knowledge/pages). The catalog of portal pages Nox's navigation agent can route users to.
List filters: search (URL/title/section/role), feature area, status (All / Active only / Inactive only). List columns: Page, URL Pattern, Feature Area, Role, Status, Actions.
Edit form (entity knox_page_registry):
| Field | Notes |
|---|---|
url_pattern | unique — e.g. /settings/integrations/shopify |
title | |
feature_area | pick or type |
Breadcrumb / parent_url | e.g. Settings > Integrations > Shopify / /settings/integrations |
requires_role | public · user · admin (default) · agency |
| description | |
| keywords / aliases | search/intent matching for navigation |
| UI Elements | jsonb list of {id, label, type} where type ∈ input / button / toggle / select / link / section |
Visitor Watches — /admin/knox/visitor-watches
Source: visitor-watches/Index.vue → getVisitorWatches / cancelVisitorWatch / getVisitorWatchEvents (Node backend /admin/visitor-watches*). Cross-company view of per-visitor Nox watch / auto-engage directives, plus a fired-events history sub-tab.
Directives filters: Status (active / done / expired / All), Type (notify / auto_engage / All), Company ID. Directive columns: Company, Person, Type, Recurrence, Confidence, Set by, Status, Last fired, Created, Actions (Cancel → DELETE /admin/visitor-watches/:id).
A Diagnostics column (admin-only — not rendered on the tenant list) explains why an active directive keeps not-firing: last_attempt_at, attempt_count, and last_skip_reason (tooltip "Matched N× — last match skipped: …"). Fields live on KnoxVisitorDirectiveModel and are stamped by PersonWatchService on every recognition attempt. Tenants have their own list/cancel surface (Settings → Nox, GET/DELETE /knox/visitor-watches).
Events history (survives the directive): filter by Company ID, Type (notify / auto_engage), Outcome (fired / fire_failed).
Nox Funnel audit — /admin/nox-funnel/*
Source: views/admin/nox-funnel/AuditLogs.vue + ContactTrace.vue (repository repositories/admin/noxFunnel.js) → Node backend GET /admin/nox-funnel/audit-logs and GET /admin/nox-funnel/contacts/trace (AdminNoxFunnelController). Browses the 120-day nox_funnel_audit_logs trail (signals, dedupes, gates, stage flips, intercept throttles) cross-company, and traces one contact's full funnel history. Manual triggers: POST /admin/nox-funnel/backfill (seed a newly-gated tenant), /brief-send-hour-backfill (9→5 cohort), /reason-text-backfill.
Voice Tools — /admin/knox/voice-tools
Source: KnoxVoiceTools.vue. Sync to ElevenLabs reads every tool registered in KnoxToolRegistry on ms-ai and mirrors it into the ElevenLabs workspace: new tools are created, existing tools updated (latest schema + webhook URL), removed tools deleted, and the shared "Knox" agent is upserted to point at the resulting tool set. Idempotent — safe to re-run, intended after editing the tool registry or a fresh deploy. The result panel reports the agent action + id, attached tool count, and created/updated/deleted counts (or a failure message). This is a registry-sync view, not a per-tool editor.
Behaviors & edge cases
- Nox vs Knox is a naming split, not two systems.
- Saving a knowledge document re-chunks and re-embeds it — old chunks for that doc are deleted first, so a save is a full reprocess (status moves through
processing;failed+processing_erroron error). - Exactly one default model per role —
setKnoxModelDefaultflips the active model; deleting the default for a role needs another model promoted. - Conversations default to 7 days, max 90 — widen From/To to look further back.
- Voice audio is retention-bound —
404once ElevenLabs has purged the recording;403if the admin doesn't own the call. - CRM-link backfill (
backfillCrmLinks→POST /knox-agent/admin/crm-backfill) re-syncsknox_visitor_profiles.ghl_contact_idfrom the backend source of truth, fixing CRM-linked / pipeline-stage metrics that undercount because the mirror only learns links reactively from GHL stage webhooks.
Technical implementation
- Frontend:
frontend/src/repositories/admin/knox.js,frontend/src/repositories/admin/visitor-watches.js; views underfrontend/src/views/admin/knox/andfrontend/src/views/admin/visitor-watches/. - ms-ai controllers:
knox-admin.controller.ts,knox-management.controller.ts,knox-document.controller.ts,knox-voice.controller.ts. - ms-ai entities:
knox-knowledge-document.entity.ts,knox-knowledge-collection.entity.ts,knox-knowledge-chunk.entity.ts,knox-page-registry.entity.ts,knox-ai-model.entity.ts. - Doc → chunk pipeline:
ms-ai/src/knox-agent/services/knox-document.service.ts; retrieval:knox-knowledge.service.ts. - Voice tool sync:
ms-ai/src/knox-voice/knox-agent-sync.service.ts,knox-voice-tools.service.ts,knox-voice-registry.store.ts. - Backend visitor-watch routes:
backend/app/routes/adminVisitorWatchRoutes.js; funnel admin routes:backend/app/routes/adminNoxFunnelRoutes.js. - Beta toggles:
PUT /company/:_id/nox-funnel-beta(also setsknox.daily_brief.send_hour9→5) andPUT /company/:_id/agent-k-beta(CompanyController), surfaced onviews/admin/companies/Detail.vue.