Skip to content

ai-manager

Status: Current | Last reviewed: 2026-10-20

Preview combines authentication and multimodal content in PhoneCustomerExperience. The shared server state controls its temporary verification view, and completed simulation returns to the same customer page without another SMS. See Customer Authentication.

New starter and blank Scenes leave 1.5 seconds before speech. The shared authentication simulation is headed Identity Verification in both Preview and Scenes. See Director for the timing contract.

The Next.js admin UI. Every agent, workflow, integration, tool, catalog, tenant, user, and permission lives here. Most editor surfaces in the product are pages in this app.

Knowledge Management Agents add a separately gated preparation canvas, native RAGFlow builds, evidence tests, publication/restore and a published MCP query endpoint. A dedicated knowledge worker handles durable processing. Their native connection is configured in the agent workspace's Health tab; the existing Knowledge Sources tool remains a separate integration. See Knowledge Agent implementation and qualification.

Copilot Preview offers Suggestions and Workspace views for the same authenticated call. The view switch preserves the conversation and token; a workspace can consume related suggestion surfaces for a composed patient record. See the Surgery Partners demo.

Scene agents in the Agents library open Director, which stores each authored recording in its own project. Scenes combine ElevenLabs caller/agent takes, phone capabilities, pinned native catalog cards and AI Activity. Chromium and FFmpeg render the same stage to H.264/AAC MP4; migration 074_director.sql stores projects, assets and frozen exports. See Director runtime and validation. Salesforce and Headless Agent Assist chapters add configurable records, native Omni-Channel presentation, incoming context and console components. Durable sandboxed Copilot replay generates version-pinned Assist takes; playback uses saved events, shared A2UI rendering and the same audio routing as MP4 export. Visual cues work in desktop and voice chapters. See the Director application API. Personal-assistant chapters add a native phone app, IVR keypad and audio, Polly Standard/Neural voices, uploaded recordings, a standalone browser and Salesforce Messaging Sessions. The same authored session follows the assistant through owner authentication and human escalation. See personal assistant Scenes.

Analytics Agents add a dedicated canvas and maximized previews from the shared Agents library behind the Analytics feature flags. A leased worker prepares bulk transcripts or audio through shared transcription/PII, annotation and embedding stages, then serves complete releases from PostgreSQL. Maximized previews use Studio SSO, reset on tenant switches and share canonical agent URLs. The former Analytics sidebar entry is retired. See preparation, Vanguard demos and validation.

Expanded Telephony mode tests the selected connection's IVR and human-agent routing. Genesys uses the external SIP route with optional ANI and an active-IVR number selector; Amazon Connect uses its existing browser contact-flow API. Defaults are scoped to the user, tenant, and browser tab. See Telephony Preview for transport, caller identity, API, and recording contracts.

Telephony settings live inside the phone: a connection selector at the top, a searchable dropdown showing phone numbers and route/flow names, and a full dial pad. Amazon Connect uses the same flow-search interaction. ANI and other Template Variables stay below the phone and outside recording captures. Connect receives nonempty variables as contact attributes; SIP uses ANI only. The shared keypad now uses 80px keys with larger digits and distributed rows.

Preview's Show Thinking control follows Run tests in the sidebar; expanded Preview hides Run tests. Thinking passively follows transcript and tool events, including running calls and expandable results. Stream connections retry even when an initial 404 closes EventSource before the voice conversation is created; reconnections forward the last-event cursor through the BFF. Recordings include the panel when enabled and otherwise capture the phone alone. Downloads are MP4, using native H.264/AAC when supported or the authenticated FFmpeg conversion endpoint. The application/devcontainer images include FFmpeg and the chart allows 100 MB uploads with 300-second nginx timeouts. See the capture and event contracts and conversion API.

Thinking also reads tenant-scoped lifecycle workflow history, displaying Conversation Start/End context, returned values, and expandable step actions. Its history polling continues after hangup, independently of the terminal transcript stream. The header carries access and persistent history errors; the former readiness/read-only/system-summary strip is removed.

Workflow editors open an existing draft by default. Their canvas Auto layout action arranges measured blocks in flow-ordered rows and branch columns, reserves columns for long connections, fits nested groups, and autosaves one undoable geometry change. Start and End share a column. Square connectors follow the reserved space, and Fit view includes their paths. Saved manual layouts remain available. See workflow layout and the workflow guide.

Edge + insertions automatically reflow the current graph, grow its containers and preserve forward row spacing. They retain note positions and share one undo entry with the structural insertion. The + controls distinguish individual branches from shared work after joins, while insertion before Decision runs before the decision for all routes. The selected scope is explained in the add panel. Conditions stay on the source router, and label pills grow or wrap to fit without moving the button.

Accent normalization

Studio's tenant Accent normalization profile configures the experimental ElevenLabs SCV alternative: Flash v2.5/v2 voice, bounded phrase/playback timing and optional Gemini grammar. Call Settings selects Krisp or ElevenLabs independently per direction; grammar bypasses the first phrase. Provider keys stay in encrypted Integrations and resolve only through the server-only accent:runtime API-token scope. Audio runs through a dedicated connector runtime with browser AudioWorklet/Worker transport. The profile is disabled by default; the connector also requires an explicit tenant/org deployment gate. See the SCV runtime and deployment contract for environment variables, authorization, limits and measured cold/warm timing.

Design import — September 30

Imported agent-studio-design/feature/lynn-wip at b6c21d6 over the previous 5373080 import. Branding moves to the sidebar, Agents moves its title into the header, and Background Agent display labels become Workflow Agent. Home replaces Needs Attention with Notifications derived from the existing live Home summary and repository. Personal done/dismiss/snooze display preferences use a versioned localStorage key scoped to tenant and user. Snooze lasts one hour; changed issue messages reappear. These actions do not resolve the underlying workflow or draft. Workflow-only accounts keep links to the workflow repository. Four-range live conversation counts from the previous import remain intact.

The September import introduced the Agent Test dialog. The October testing workspace now supersedes that default entry: Test in the header and preview opens /agents/{id}:{version}/testing with Overview, Tests, and Results. Draft navigation first drains pending edits and stays in the editor on a save failure. Overview runs the selected version without requiring a comparison point; comparisons are optional. Tests combines scenarios with their success criteria and editable groups. Results uses a full-width run picker and conversation review. The original Performance workspace and legacy APIs remain available. See agent testing workspace for the additive Athens engine, worker isolation and retained historical records.

Editor notices use the imported plain-language formatter while preserving original diagnostics in View details and the existing severity/blocking state. Editor modals restore keyboard focus to their opener when dismissed.

The agent header’s More menu contains Export and, for editors, Delete agent. Share is hidden to match feature/lynn-wip while private-agent sharing is deferred; the existing dialog and access rules remain in place. This supersedes the Share-menu behavior recorded in the earlier imports below.

Design import — September 29

Imported the current agent-studio-design/main at 5373080, including September 28's 58f709c changes and the reconciled Lynn design. This supersedes the September 25 import details below where they differ.

The header uses shared friendly agent labels; renaming marks local edits before saving through the existing settings writer. Version selection moves from More to Activity → Change History and retains the live version-loading callback. Share, Export, Delete, Test, publishing, autosave retries and real phone Preview keep their existing functionality. Input and Output canvas nodes use neutral cards.

Home supports 24-hour, 30-day, 90-day and 12-month conversation windows. The new useConversationCount hook reuses authenticated POST /api/conversations/search with includeTotal: true, a rolling startedAfter timestamp and tests included. The count describes searchable conversations and the destination receives the same date/test filters. Polling pauses in hidden tabs; tenant/user/range changes cancel stale requests. Errors remain visible with retry. Recent Activity stays on its 24-hour window. Template agents are excluded from the portfolio counts. Repository publication labels remain independent of activation; legacy filter bookmarks still resolve.

Properties use friendly language labels, key-term chips and clearer voice controls. Integration options are scoped to the active tenant/user and invalid selections are cleared; voice samples are cancelled when their selection changes. Auxiliary services use collapsible sections, sparse overrides and the existing /api/settings/aux-services defaults. Runtime schemas and saved sample-rate values are unchanged. Prototype-only Polly preview and resolver-backed auxiliary controls were not imported: their required endpoints are absent here. No API, database, runtime provider or migration changes are part of this UI import.

Validation: focused frontend tests and browser checks cover header actions, version navigation, canvas controls, key terms and auxiliary overrides. Full TypeScript checking still reports pre-existing PhoneCallTest avatar-option and duplicate pipeline/workflow type-property errors.

Design import — September 25

Imported UI updates from agent-studio-design main at a5501eb and feature/lynn-wip at 78e1ad3. Lynn's branch includes main and takes visual precedence. Much of main's Home, repository, Activity, Settings, and prompt-library work was already present in this checkout.

Home and Agents share the reusable StudioBrandLockup and a header variant of NotificationBell, backed by the existing notifications contract. The Enterprise pill remains fixed branding, not an entitlement claim. The sidebar starts with navigation; existing permissions, tenant links and feature gates remain intact. Home's Conversations selector uses the existing /api/dashboard/home?range= for 24 hours or 30 days, defaulting to 30 days; activity remains on 24 hours. The prototype-only count endpoint and unsupported 90-day/12-month ranges are omitted. The destination conversation page retains its own filters. Repository Status shows Unpublished Changes while matching the existing Pending Changes value; legacy Active/Inactive workflow URL filters still work.

The editor follows latest Lynn commit 78e1ad3, including its toolbar spacing, text-only Agent Canvas heading and Test link to the existing performance route. Inline draft rename, compact Canvas View and category-colored selection remain. Rename reuses the settings writer with existing settings preserved. More retains version selection and Share alongside Export and Delete. Preview opens from its edge tab; existing run-tests, readiness, detailed notifications and generation handlers remain connected. Variables reuses its existing accordion. The new save spinner retains retry/save-now behavior; unknown or disconnected collaboration state never displays Saved. Missing shared palette exports and orphaned canvas history buttons from the prior merge were corrected; keyboard history remains. No backend routes, runtime implementations, schema, or prototype fixtures changed.

Agent Studio authoring supports Claude Code and OpenAI Agents API through workspace-agent-runner. Settings select the engine for new jobs and conversations; existing sessions retain their integration and provider context. See OpenAI authoring.

Editor/designer turns, including Build draft, allow 30 minutes by default (ASSISTANT_TURN_WALL_MS on both ai-manager and workspace-agent-runner). Their database lease and internal response stream idle timeout allow one extra minute for cleanup. The separate generation-job budget is GENERATION_WALL_MS.

Source

apps/ai-manager

Runtime

Aspect Value
Language TypeScript
Framework Next.js 16 (App Router)
Port 3001
Auth Authentik OIDC + cookie session

OAuth callback/start redirects, Genesys enrollment redirects, workflow OpenAPI server URLs, and uploaded surface image URLs prefer APP_URL / NEXT_PUBLIC_APP_URL for the browser-facing ai-manager origin. If those are unset, public URL generation accepts X-Forwarded-Host / Host only for localhost or hosts in AI_MANAGER_ALLOWED_REDIRECT_HOSTS; PUBLIC_FQDN also derives ai-manager.<domain> and emulator.<domain>.

What it does

  • CRUD for agents, agent_versions, workflows, workflow_versions, integrations, tools, catalogs, tenants, users, permissions.
  • Organizes integrations by high-level source_id plus enabled_capabilities. The add-integration picker exposes only the source catalog (Amazon, Google, Anthropic, OpenAI, Deepgram, ElevenLabs, Five9, Genesys, Krisp, Microsoft, NVIDIA, Salesforce, Maven AGI); type_id is kept as compatibility metadata for existing rows.
  • Renders the FlowEditor canvas for voice/chat pipelines.
  • Custom agents created from the wizard start with an empty pipeline scaffold; provider/service defaults come from templates, Generate with AI baselines, or explicit stage additions.
  • Renders the WorkflowGraphTab canvas for async LangGraph workflows.
  • Loads Bedrock model choices through /api/models/bedrock, signed with the selected AWS integration. The route returns text-capable foundation models plus account-scoped inference profiles, provisioned throughput models, custom models, and imported models when AWS allows those catalogs to be listed.
  • Loads Google Gemini, Vertex AI, and ADK Live model choices through /api/models/google-gemini, /api/models/google-vertex, and /api/models/google-adk. Gemini IDs are sorted by numeric model version descending (3.5 before 3.1 before 3 before 2.5), with Vertex custom models kept after published Gemini models.
  • Adds sub-agents from scratch or from pipeline_templates.kind = 'subagent' only; full agents and normal agent templates are not offered in the subagent picker.
  • Encrypts integration credentials before storing.
  • Lists Salesforce Agentforce agents for the pipeline editor through /api/salesforce/agentforce/agents, preferring Salesforce Connect discovery and falling back to active BotDefinition / BotVersion metadata with PSTN voice hints.
  • Lists Maven AGI agents for Chat Agent and Voice Agent stages through /api/mavenagi/agents, using the selected mavenagi integration profile's org/app credentials and optional discovery agent id.
  • Provides the Assistant Agent Skill Builder for Codex, Claude Code, and Antigravity chat stages. Reusable skills are stored through /api/agent-skills; inline skills live in the node config as inline_skills. Skills can attach shared governed tools or define skill-local inline Python/manual API tools, which are registered through the same runtime path as normal agent inline tools.
  • Provides the Knowledge tool form when the tenant has Knowledge enabled and configured (see Knowledge tools: per-tenant configuration). The shared Tools flow creates and manages Knowledge Sources for websites, uploaded files, S3, Google Cloud Storage, Azure Blob Storage, and Confluence, then stores only non-secret source metadata in tool_configs.config — never the connection (server_url/transport are not stored; the MCP URL is derived from the tenant's live base URL). The agent/workflow tool picker can also create inline Knowledge tools; those store the same non-secret source metadata in config.inline_tools[] and stay local to that agent stage or workflow. Website sources are single- or multi-site: the form's "Add another site" builds a config.sources[] array (each element carrying the same website keys as a single-source config — url, crawl, limit, max_discovery_depth, wait_for, ignore_sitemap, crawl_entire_domain, include_paths, exclude_paths, crawl_options), and when sources is present the top-level url is dropped before the payload is sent. buildKnowledgeSourceCreatePayload in knowledge-tools.ts normalizes each entry through the shared normalizeWebsiteSourceEntry (single-source and every sources[] element go through the same path) and prunes empty path patterns at the API boundary, so the form can keep raw newline-split textarea state.
  • Loads Knowledge tenant model choices, help, ranges, and defaults through the authenticated /api/tools/knowledge/configuration-options proxy. The form renders the common ingestion/retrieval fields directly and groups parsing/enrichment, metadata/filter policy, query bindability, GraphRAG, and RAPTOR under Advanced sections. knowledge-tools.ts allow-lists ingestion and query, forwards them on Knowledge Source create/update, and retains the non-secret settings in both shared and inline tool config; raw model IDs remain administrator-selected and are never exposed as agent arguments.
  • Discovers MCP/OpenAPI/A2A tools through /api/tools/discover. Saved MCP static custom_headers are forwarded during discovery as non-secret metadata, while auth, cookie, host, hop-by-hop, internal service-token, and MCP session headers are stripped and credentials continue to be resolved through AuthEditor server-side. When server-side auth resolution fails (a token-mint error such as a Salesforce JWT invalid_client, a secret-decrypt error, or a target-origin refusal), the route now fails fast with that real reason (resolveIntegrationAuth/resolveInlineToolAuth return it on ResolvedAuth.authError) instead of sending a tokenless request and surfacing the remote server's misleading "no token" reply (e.g. Salesforce Platform MCP's "JWT Token is required"). Note the oauth2 auth type only attaches a statically stored token — Salesforce integrations must use the Integration auth type, which mints via the JWT-Bearer/Client-Credentials flow in sfdc-client.ts.
  • Serves Home at /dashboard (the root route redirects there) from the Agent Studio design: components/studio/StudioDashboard.tsx over hooks/studio/use-repository.ts (one RepositoryItem list built by lib/studio/repository.ts from /api/pipelines + /api/workflows, each read only when the author holds that domain's permission + feature flag), the persisted /api/favorites + /api/recents (both tenant-scoped; RecentAgentTracker in the main layout records every /agents/:id, /pipelines/:id, /workflows/:id visit), and GET /api/dashboard/home?range= — a withAnyAuth(ManageAgents | ManageWorkflows) summary that returns ONLY conversationCount, workflowRunCount, failedWorkflows and recentActivity, each half nulled/emptied unless the caller holds that domain's permission AND (for API tokens) scope. It is exempt from the proxy's /api/dashboard → ManageAgents group and from the agents-disabled 503 so background-only tenants keep a working Home. Brand chrome (Open Sans via --font-brand-sans, teal studio-sidebar, .studio-* classes, --surface/--brand-* tokens, light + dark) is app/studio.css, imported after globals.css; SidebarInset carries studio-app and is keyed on tenant:user so a switch remounts per-user state. The live conversational designer (/studio, "Design with AI") is gated by runtime FEATURE_CONVERSATIONAL_DESIGN plus the user’s enabled agent/workflow permissions. It reuses the existing designer APIs, assistant sessions, scoped generation tools, and workspace-agent-runner; see conversational design.
  • Refreshes Insights (/dashboard/insights, the former Dashboard page, gated on ManageConfiguration in proxy + MainLayout + the sidebar entry) live from /api/dashboard/summary every 15 seconds (always-on polling; no Live toggle). The summary returns kpis (conversations, active agents, workflow runs, workflow errors — the workflow-error count covers both status='error' and status='failed'; there is no conversation-error count because the runtime never writes conversations.status='error'), topAgents, topWorkflows, wordCloud (unigrams + bigrams + trigrams), portfolio, recentActivity (each row carries is_test so the UI flags test rows), and recentChanges (tenant-wide agent/workflow change history sourced from audit_log, each row carrying its id + a summarizable flag so the widget lazily POSTs /api/audit-log/{id}/summarize for visible rows — same one-call-per-row-ever, AI-gated, deterministic-fallback path as the agent Activity view; only the current page is requested and ids are de-duped so the 15s poll doesn't re-fire). Both feeds paginate client-side at 10 rows per page to keep card height bounded; the dashboard has no manual refresh control (it always polls on the interval). Test traffic (emulator/test-chat/test-call conversations and Workflow Test runs) is always included and flagged with a Test pill rather than filtered.
  • Owns the team-wide agent Debug toggle (PATCH /api/agents/:agentId/debug) and central Whisker live debugger (/debug/whisker). The debugger session index (GET /api/debug/whisker/sessions) scans Redis whisker:session:* entries, joins them to tenant-scoped conversations and agents, and signs browser WebSocket URLs for ai-orchestrator's gateway.
  • Verifies tokens for collab-gateway and code-server.
  • Optionally requires CODE_SERVER_VERIFY_TOKEN on code-server auth-proxy verification requests, preventing direct callers from spoofing proxy-only X-Original-* headers.
  • Hosts /emulator, including SIP dialing for Telephony-capable integrations and Amazon Connect WebRTC contact dialing for Amazon integrations with the Telephony capability.
  • Hosts the public /embed/phone shell and /embed/trueserve-phone.js custom element for transparent, responsive phone embeds. External apps load sessions through their own backend so tenant API tokens stay server-side.
  • Uses one shared 436 × 902 iPhone component tree for Preview, recording mode, /emulator, and /embed/phone. AI Manager and outbound embeds start on Home with the server-resolved agent name in a tap-anywhere banner; inbound embeds start on an iPhone-style Contact card with a green Call action. During AI Manager calls, More opens native Transcript, Type to Speak, and configured Avatar options; transcript events stream through /api/phone/events/stream instead of a surface-app iframe. Agent-editor Preview normally sends X-Multimodal-Sms-Delivery: demo_notification plus X-Caller-Phone, so every SMS-capable feature—including Multimodality and Customer Authentication—uses the simulated Messages tray while preserving the real consent/share-code flow. When NEXT_PUBLIC_FEATURE_REAL_SMS_TESTING=true, full-screen Preview exposes SMS mode; enabling it requires an explicit ani Template Variable and changes the shared delivery header to real_sms, so carrier messages go to that ANI while audio stays in the browser phone. Preview never sends X-Suppress-Multimodal-Sms. /api/phone/* authorizes and signs conversation events, catalog, UI-state, capability, and action requests without creating a nested surface-app iframe. Simulated Customer Authentication notifications with metadata.purpose='customer_auth' hand the share URL to the top-level browser, because the auth link owns a Surface App ceremony rather than an A2UI event and WebAuthn must use the real browser authenticator. A failed editor buildability check is shown as a persistent alert above disabled Preview controls with its first actionable error; readiness is rechecked after config changes.
  • Publishes /openapi.json for the least-privilege external integration API: agents:read lists published agents through GET /api/v1/pipelines, and emulator:run creates signed WebRTC phone sessions through POST /api/v1/emulator/sessions. PII-bearing dynamic config stays in short-lived shared Redis storage while the URL/cookie carry only an opaque id. Dynamic sessions accept only caller identity, session variables, STT language, direction, and auto-connect; arbitrary headers and SIP targets remain administrator-managed.
  • Hosts the hidden /agent-simulation control surface. Its session APIs proxy genesys-agent-go; ai-manager does not load the Genesys Cloud WebRTC SDK or vendor headset drivers.
  • Hosts the Customer Authentication authoring surface: the capability card (CustomerAuthenticationCapability.tsx, rendered in NodeCapabilitiesAndBehaviors) writes builtin:customer_authentication + builtin_tool_configs.customer_authentication; publish validation enforces the assurance-composition rules (validate-node-capabilities.ts); the tenant-level provider/SMS settings live at Settings → Tenant Settings → Customer Authentication (customer-auth-settings.ts, tenant_settings key customer_auth, OIDC secret AES-256-GCM). See customer-authentication.md.

Integration sources and capabilities

Integration rows are selected by source_id and enabled_capabilities rather than by a flat type catalog. GET /api/integrations accepts source_id and capability filters, and /api/integrations/by-provider maps provider/service requests to the source plus capability pair the UI and runtimes should use. Legacy type_id values remain in responses so old saved agent/workflow configs can keep resolving during migration.

Runtime and UI code should use the shared helpers in integration-capabilities.ts: integrationSourceIs, integrationHasCapability, sourceForLegacyType, and provider target helpers. Avoid adding new hard checks for legacy values such as amazon_connect, google-pubsub, or azure; new dropdowns should ask for the capability they need.

File layout

For a deeper file map see ai-manager-layout.md.

Top-level Purpose
app/ Pages + API routes (App Router)
components/ React UI
lib/ DB, crypto, types, schemas, integration adapters
hooks/ Custom hooks (autosave, presence, permissions)
contexts/ Auth, theme
proto/ Generated gRPC clients

Preview template variables

Authenticated direct-WebRTC Preview calls carry template variables as a JSON string in custom_headers.X-Template-Variables through /api/proxy/webrtc/offer. This is JSON body metadata, not an HTTP or SIP header. The proxy accepts up to 256 KiB of UTF-8 encoded variable JSON and requires an object; invalid payloads return 400 and oversized payloads return 413 before a call is forwarded. Other client headers retain their 512-character limit and routing/authentication headers remain server-controlled.

AS-135 was caused by applying the generic 512-character limit to the entire variable JSON and silently discarding it. Long values therefore disappeared for both main and sub-agents even though the browser's offer contained them. The limit applies to the combined variables, not each value. Model context windows remain a separate limit; successful transport does not guarantee a model can accept an arbitrarily long prompt.

Rome verification for AS-135 passed retrieval of facts at the start, middle, and end of a 50,196-character pasted value for both Main and an in-conversation specialist. The 100,071-character specialist prompt reached the runtime intact but OpenAI Realtime rejected its session instructions: 18,723 tokens exceeded its 16,384-token instruction limit. This provider rejection is separate from the proxy's transport limit.

Dependencies

Service Purpose
Postgres All CRUD; app.current_tenant set per request
Redis Session, draft autosave debouncing
collab-gateway Multi-user presence avatars
ai-orchestrator (gRPC) Test-call from FlowEditor
ai-orchestrator (HTTP /whisker-ws/{conversationId}) Signed Whisker live-debug gateway
genesys-agent-go Server-side Genesys agent simulation sessions via GENESYS_AGENT_SERVICE_URL
Amazon Connect APIs Contact-flow listing, StartWebRTCContact, and DTMF proxying for standalone emulator Connect calls
Salesforce REST / Tooling APIs Agentforce agent discovery and optional voice-channel metadata hints
Maven AGI APIs Chat/voice agent discovery for the pipeline editor
Knowledge Sources API Knowledge Source creation, sync, status polling, and raw-file upload/delete for shared and inline Knowledge tools
async-agent Manual workflow runs
catalog-builder Source archive + bundle compile
MinIO / S3 Catalog source + bundle storage

Security boundaries

  • lib/ssrf.ts resolves tenant-controlled outbound hosts, rejects non-public/special IPv4 and IPv6 forms, and pins the accepted addresses into an Undici dispatcher. Connections keep the original hostname for Host and TLS SNI; each redirect is resolved, validated, and pinned separately.
  • Genesys mappings and warm-transfer destination policies use bounded RE2 semantics rather than the backtracking JavaScript regex engine. Write paths reject unsupported/oversized patterns, while lookup and transfer paths also validate stored rows before evaluating them.
  • Amazon Connect emulator proxy calls require an active tenant-scoped amazon_connect integration. Static integration credentials are preferred when configured; the runtime IAM/default credential chain remains available only behind that integration boundary.
  • The production container is based on the Alpine 3.24 Node 22 line and removes npm, npx, Corepack, Yarn, and curl after the standalone build. Native dependencies are built on musl in the matching Alpine build stage. Source-mounted Kind development invokes the local Next binary directly, so it does not depend on runtime package-manager tooling.

Vanguard

overmind restart ai-manager

Hot reload is on. Port 3001. Default URL https://ai-manager.<codespace>.app.github.dev (Codespaces) or https://ai-manager.<host> / the environment apex host on EC2. The root route redirects server-side to /dashboard; unauthenticated users are still intercepted by the auth proxy and sent to /login. In local/vanguard dev, next.config.js derives allowedDevOrigins from the ai-manager URL env vars and PUBLIC_FQDN so the dev server accepts requests for ai-manager.<domain> behind nginx.

Whisker live debugging uses these env vars:

Var Purpose
WHISKER_GATEWAY_TOKEN_SECRET Shared HMAC secret used by ai-manager to sign short-lived session-scoped debugger gateway tokens. Must match ai-orchestrator.
WHISKER_PUBLIC_WS_URL Optional browser-facing gateway base. Gateway/cluster deployments usually use wss://ai-manager.<domain>/whisker-ws; vanguard local uses wss://code.<domain>/whisker-ws. Empty means same-origin /whisker-ws.

Live-frame transport is Redis pub/sub, not a pod-local socket

Frames flow browser → /whisker-ws/{conversationId} → ai-orchestrator gateway handler → Redis pub/sub → the observer's loopback socket, in that order backwards from the running call:

  • Each debugged session runs a WhiskerObserver bound to 127.0.0.1:<random port> inside the pod running the call. An in-pod relay connects to it as its single allowed client and republishes every serialized message verbatim to Redis channel whisker:stream:<conversationId>; the first message (the pipeline graph) is also cached at whisker:initial:<conversationId> so a viewer joining mid-session still gets the graph.
  • The public gateway handler (GET /whisker-ws/{conversationId}) subscribes to that channel and streams messages to the browser as binary WebSocket frames. It does not connect to any pod-local socket.

This indirection is load-bearing: ai-orchestrator scales to many replicas (KEDA in KMS dev, and the base chart's default hpa.minReplicas: 3), and the browser's /whisker-ws request round-robins across pods via the ClusterIP Service. A pod-local backend_ws_url (the previous design) was reachable only from the one pod running the call, so any request landing on a different pod showed zero frames for every agent — the QA symptom that motivated the switch. Redis pub/sub decouples the gateway from pod identity, so it works at any replica count with no NetworkPolicy or per-port changes. The browser is receive-only (the observer ignores client input), so the relay is one-directional. See ai-orchestrator.md and whisker.py.

Live mode keeps nothing — opening the debugger after a call ends (session status: closed) always shows zero frames because there is no replay; file mode is the record-and-replay path (trace uploaded to MinIO). The viewer only connects while whisker_status === 'listening'.

Knowledge tools: per-tenant configuration

Knowledge has no env vars — each tenant's admin configures it in the UI, and an unconfigured tenant has no Knowledge tool type at all.

Where it lives. Settings → Tenant Settings → Knowledge (/settings/knowledge) writes tenant_settings key knowledge as {enabled, base_url, api_token_enc} via GET/PUT /api/settings/knowledge. The API token is AES-256-GCM encrypted under MASTER_ENCRYPTION_KEY (same envelope as tenant telemetry headers, so the Python runtimes decrypt it with their existing helpers) and goes to the browser masked; saving a masked/empty value keeps the stored token. The Tenant Settings group (renamed from Telemetry) holds Knowledge + Telemetry and is visible only to tenant admins (role admin) and platform admins — see ai-manager-layout.md.

Availability gate. knowledge-settings.ts is the single source of truth: resolveKnowledgeAvailability returns {available, reason, settings} (tenant_disabled | base_url_not_configured | token_not_configured), and isKnowledgeAvailable gates /api/tools/types, /api/tools, /api/tools/references (the reference cache key folds the flag), and tool-health refresh. Client components read the same answer through GET /api/tools/knowledge/availability + use-knowledge-availability.ts (this is what gates ToolSelector's inline-Knowledge affordance).

Tools reference the live config; they never copy it. resolveKnowledgeConnection(tenantId) returns {baseUrl, token}, and every Knowledge route passes that connection into the knowledge-tools.ts wrappers. A Knowledge tool row stores source_slug (identity) but not server_url or transport — the MCP URL is derived on each use as {base_url}/mcp/{source_slug}, so changing a tenant's base URL or rotating its token immediately affects every existing tool. Updating a legacy row strips any stored server_url/transport.

Migrate an environment that used the retired env vars with npx tsx apps/ai-manager/scripts/backfill-knowledge-settings.ts (writes them into the TrueServe tenant; see Operational backfills).

Data Protection (tenant settings)

The L0 PII/PHI policy plane of data-protection.md, following the Knowledge pattern: Settings → Tenant Settings → Data Protection (/settings/data-protection, admin-only tab) writes tenant_settings key data_protection via GET/PUT /api/settings/data-protection (AIManager:ManageConfiguration at the route boundary, tenant admin role via the layout gate). The stored JSON is EXACTLY the canonical shape parsed by libs/pii/trueserve_pii/tenant_policy.py (enabled, entity_types, detector_tier regex/self_hosted/cloud, cloud_integration_id, per-boundary action map provider/tools/store/telemetry/recordings/notifications → off/flag/redact/block, compliance_profile none/hipaa/pci/gdpr) — no secrets, so no encryption envelope. The value sets live in data-protection-settings-shared.ts (client-safe; strict PUT validation) with the pg read/write in data-protection-settings.ts; they must stay in lockstep with the Python module's tuples. GET returns disabled defaults when the key is absent; the Python resolvers cache reads for ~60 s, so an admin edit lands without a restart.

Model pickers: OpenAI list filtering and tier defaults

GET /api/models/openai returns text-generation models only. The filter lives in lib/openai-chat-models.ts (the route re-exports it) so it is testable without the route's auth/integration dependencies: OpenAI's /v1/models returns every model the account can reach, and the gpt- prefix spans transcription/TTS/audio/image/realtime — a bare startsWith('gpt') admitted 31 of 86 ids that 404 on chat completions, and because the sort is newest-first a brand-new transcription model landed at rank 0, which is what the auto-pick writes into a fresh config. isNonTextGenerationModel matches NON_TEXT_MODEL_FAMILIES on hyphen-delimited tokens, not substrings, so a future chat model merely containing one of those words survives. -codex/-pro (responses-only) and deprecated/unreachable ids deliberately stay visible — they are the wrong endpoint or a lifecycle condition, not the wrong modality, and hiding them would misreport why a call failed.

On the consumer side, selectDefaultModel(options, provider, fallback)'s fallback argument is reached only for an empty list — against any live model list it is dead code. A caller with a tier preference (e.g. CopilotModelPicker's fast vs capable) must check the tier default's presence in the list itself and only then fall back to selectDefaultModel — passing the tier default as fallback silently runs the newest capable model on every utterance, the opposite of what tier="fast" asks for.

Operational backfills

npm run backfill:audit-summaries -- --limit 10 dry-runs the audit_log.details.summary AI summary backfill from apps/ai-manager. Add --apply to call OpenAI and write rows. The script reads <repo>/secret/OpenAIAPIKey.txt by default, skips rows already marked summarySource="ai", reuses snapshot/diff shaping where possible, and leaves failed rows untouched so reruns are idempotent.

npx tsx scripts/backfill-knowledge-settings.ts migrates the retired env-var Knowledge config (KNOWLEDGE_SOURCES_API_BASE_URL + KNOWLEDGE_SOURCES_API_TOKEN/MCP_API_KEY/KNOWLEDGE_SOURCES_API_TOKEN_FILE) into the TrueServe tenant's tenant_settings key knowledge, encrypting the token. Requires MASTER_ENCRYPTION_KEY; idempotent. Override the target with KNOWLEDGE_BACKFILL_TENANT_ID.

Production

Helm chart at charts/ai-manager. Two replicas by default; presence and autosave both rely on Redis so replicas are stateless.

Permission model

Agent Studio has four access levels: Platform Admin (every tenant), Admin (full access within a tenant), Builder (agent/tool authoring and execution), and Viewer (browse and execute saved agents). Workflows are agent types and share the same permission gates as conversational agents.

lib/permissions.ts defines:

Permission Capability API-token scope
AIManager:RunAgents Browse and run agents of every type, including saved drafts, plus conversations and execution results agents:run
AIManager:ManageAgents Create, edit, publish, clone, import, export and delete agent definitions, triggers and saved tests; includes Run Agents agents:manage
AIManager:ManageTools Author tools, catalogs, surfaces and multimodal configuration tools:manage
AIManager:ManageConfiguration Administer tenant configuration configuration:manage

admin receives all four; builder receives Manage Agents and Manage Tools; viewer receives Run Agents. Platform Admin receives all capabilities in every active tenant even if its explicit membership has a lower role. Normal agent reads, writes and collaboration rooms still use the tenant selected in the session; switch tenants before opening another tenant's resources.

Membership permissions come from the selected tenant's named roles. Retired direct grants cannot override those roles, including wildcard grants. Legacy user-wide permissions do not widen tenant access. Ownership and old private-sharing grants cannot elevate a Viewer to an author. Collaboration checks Run Agents to join an agent/workflow room and Manage Agents to write.

Personal tokens track the creator's current tenant permissions. agents:run includes agents:read and emulator:run; agents:manage includes all three. The old workflow token scope normalizes to agent management. Management OAuth uses agents:read, agents:write, agents:publish, agents:debug and studio-ai:run for every agent type; legacy workflow scope names normalize at read time. Viewer receives read/debug access and cannot invoke AI authoring.

Access assignment and migration

Authentik groups tenant:<slug>:<role> become the trueserve_tenants OIDC claim. Use admin, builder or viewer; trueserve-platform-admin supplies the separate platform-admin flag. The callback synchronizes memberships on SSO sign-in. An explicitly empty tenant claim deactivates existing memberships; a legacy provider that omits the claim entirely can provision default-tenant Viewer access. A tenant claim without roles also defaults to Viewer.

Old roles normalize on every session resolution: Developer → Builder, Operator → Admin, Analyst → Viewer. Old view-only permissions normalize to run access, never authoring. New assignments use only current role names.

Preview stored-role cleanup with npx tsx scripts/migrate-access-roles.ts from apps/ai-manager; add --apply to update Authentik groups first and then mirror the membership rows. Runtime aliases keep old groups working during rollout.

Platform Admin and tenant membership screens

The profile menu's Admin console manages tenants, platform users and platform-admin grants. It requires the platform flag and the platformAdmin feature flag. Platform Admin can switch into every active tenant.

Tenant Admins manage members at Settings → Access → Members. The shared membership APIs require requireTenantAdmin(targetTenantId): Platform Admin or the Admin role in the selected target tenant. Builders and Viewers are denied. Only Platform Admin can create platform users or grant the platform flag.

Membership writes go through lib/admin/dual-write.ts: Authentik first, local membership second. Configure AUTHENTIK_BASE_URL and AUTHENTIK_API_TOKEN/AUTHENTIK_API_TOKEN_FILE to enable writes; otherwise mutation requests return 409. The console offers Admin, Builder and Viewer.

Profile Settings remains available to every signed-in user and shows effective roles/permissions plus personal token administration.

See also

Agent evaluation

Performance tests use durable jobs, pinned runtime configurations and shared mock state. See agent evaluation and worker operations for this service’s role, admission limits, authenticated stream ownership and recovery.

The optional scripts/migrate-access-roles.ts reconciliation includes memberships already normalized by SSO, so stale Authentik role groups are replaced even when the local row already says Builder or Viewer. It previews by default; --apply requires Authentik write-back configuration and writes Authentik before the DB.