SDK Module Map & Data Flow
← Back to System Internals Reference
This section is the source-of-truth map of the SDK's internals: how a call flows from a typed method to the right backend, and the routing/data-flow rules README doesn't cover.
Two entry points, one shared core
-
./management(Management,src/management/client.js): the REST control plane. It holds the admin secret, mints tokens, and routes to the two REST hosts (Agentic API and brain API) and OVP. It enforces the two-KS guard viaassertAdmin/assertConversation(assertKindinclient.js) before any network call. Resource namespaces hang off it:sessions,agents,avatars,avatarSessions,catalog,application,intellects,intellectConfig,tools,skills,conversations,threads,messages,feedback,followups,knowledge,lifecycle,insightSettings.toolsandskillsare standalone, partner-level entities. An intellect only references them viatool_ids/skill_ids. One sub-resource mounts onintellects:intellects.secrets. -
./experience(KalturaAvatarSession,src/experience/session.js): the live socket+WHEP runtime from Platform Overview's "Video Runtime Protocol". It takes only a short-lived conversation token. socket.io is INJECTED (socketFactory), never bundled. Two optional plugin subpaths hang off this same live runtime without loading into apps that don't need them:./experience/presenter(thePresenterdeck helper) and./experience/genui(theExperienceRendererGenUI layer). -
src/core/*: the shared leaf layer both fronts depend on.http.js(transport)errors.js(KalturaError, RFC 9457)session.js(Sessionstoken-minter +makeAuditEmitter)stream.js(converse NDJSON/SSE parser +collectConverse/segmentKind/GENUI_RUNTIMES, the closed enum of GenUI runtime names the brain'sunisphere-toolsegments can carry)redact.jssafety.jsids.js(meta()receipts)knowledge-enums.js(CHAPTER_TYPE/STRATEGY/EMBED/MODALITIES/normalizeModality/buildIndexerObjects)
Core never imports from
management/orexperience/. It stays a leaf.
Branch security on the minted
Token, never oninspectKs(realKs).kind. The publicinspectKsexport (@kaltura/intelligent-agents/management,src/management/ks-inspect.js) decodes only a KSv2 token's plaintext header. It reliably returns{partnerId}. But a real encrypted KS has its privileges AES-encrypted, soinspectKsreturnskind:'opaque',disableEntitlement:null,encrypted:true.kind/disableEntitlementare populated only for unencrypted test tokens. To decide what a token may do, read the.kindof the mintedTokenobject instead. It records what it was minted with:admin/conversation/agent/widget. Don't useinspectKsof an opaque production KS for that decision.
Management modules (what each does, where it writes)
Module (src/management/) |
Exposes | Backend the writes hit |
|---|---|---|
intellects.js |
Intellects: DTO CRUD (add/get/update/delete), prompt authoring (setPrompts/previewPrompt/snapshot/restore/diffSnapshots), capabilities (getCapabilities/setCapability/setCapabilities/resolveCapabilities), setClientVariablesEnabled. Mounts secrets (tools are a separate top-level resource; see tools.js). |
the brain v1/intellect/* |
intellect-config.js |
IntellectConfig (mgmt.intellectConfig): the ONE shared patch(configId, patch|fn, ks) primitive + typed field setters incl. setToolIds (the intellect-side tool_ids reference list), setSkillIds, setThreadStartTools, setModelConfiguration, setOpeningPhrase, setAvatarSummaryConfig + describe() (every EDITABLE_FIELDS value). Exports the closed enums SKILL_MODES, MODEL_IDS, THINKING_LEVELS, SUMMARY_CONTENT_TYPES. buildUserPropertiesForms. |
the brain v1/intellect/update (read-modify-write, full-replace dicts; tool_ids is a plain array write) |
set-forced-language.js |
setForcedLanguage (mgmt.setForcedLanguage): forces the reply language by writing force_language on the intellect (via intellectConfig.patch; the backend enforces it at runtime) and the agent's asr.language (via agents.update) in one idempotent call. Strips the marker block older SDK versions appended to base_directive. LANGUAGE_NAMES (ISO 639-1 code → display name). |
the brain v1/intellect/update + Agentic API agent/update |
capabilities.js |
CAPABILITIES/CAPABILITY_STATE/CAPABILITY_DEFAULTS/CAPABILITY_INFO, assertCapability/assertCapabilityState/validateCapabilities, resolveCapabilities (pure layered resolver), mergeCapabilityWrite. Re-exported from BOTH entry points. |
pure, no network |
tools.js |
tools.api/csv/code builders + tools.client (authors a native, silent client-side command tool with no server-side call, requires kaltura_genie_experiences:'off'; see Client-Side Commands) + tools.clientToolReadiness + tools.validate, class Tools (mgmt.tools: add/get/list/update/remove over the standalone Tool entity), applyResponseMapping. |
the brain v1/tool/* (partner-level entity CRUD, not intellect/update; link via intellectConfig.setToolIds's tool_ids) |
skills.js |
Skills (mgmt.skills: add/get/update/list/delete over the standalone, partner-level Skill entity; delete checks for intellects still referencing the id before acting). |
the brain v1/skill/* (partner-level entity CRUD, not intellect/update; link via intellectConfig.setSkillIds's skill_ids) |
secrets.js |
IntellectSecrets (mgmt.intellects.secrets: listNames/has/set/remove/replaceAll/validate), validateSecretRefs. Write-only values; name-only read contract (no redact() reliance). |
the brain v1/intellect/update config.secrets (mask-and-keep merge) |
prompt-lint.js |
pure: lintPrompts/validatePromptVars/lintGlossary/assembleSystemPrompt/SYS_VARS. Client-side prompt-preview replica (author layer only). |
pure, no network |
conversations.js |
Conversations (stream/send, assertRequestVars), Threads/Messages/Feedback/Followups, Knowledge (addRecord + knowledge_ids linkage, ungated; uploadDocument, createCategory/findOrCreateCategory (plain entry containers), corpusStatus, getLinkage, setEnabled, search, isIndexed, entryStatus). |
the brain's assistant/converse (converse); the brain's v1/knowledge/add + intellect knowledge_ids (ungated); OVP category/*+upload (containers) |
lifecycle.js |
Lifecycle (mgmt.lifecycle: create/get/update/delete/list over event-driven rules {eventType, objectType, eventConditions, action}, plus read-only match/listObjects/listEvents/describeFields). A rule's action can trigger an insight extraction, an insight email, or the built-in lead-capture insight. |
the Agentic API's lifecycle/* |
insight-settings.js |
InsightSettings (mgmt.insightSettings: create/get/update/delete/list over reusable {key, title, prompt, valueType, status} insight definitions). Referenced by a lifecycle rule's action.insightSettingsIds, not embedded in the rule itself. |
the Agentic API's insight-settings/* |
provision.js |
provision(): the agent factory; optional knowledge/tools/capabilities blocks layer after the core create (tools creates each Tool entity via mgmt.tools.add, then links the successful ids in one intellectConfig.setToolIds write). |
both hosts |
The top-level headless converse surface lives on the Management class itself: converse(configId, message, opts?, ks?) (AsyncGenerator over conversations.stream) and converseOnce(...) (delegates to conversations.send). Both auto-mint a conversation token from configId when ks is omitted, so the admin secret never leaves the server.
opts carries {threadId, sse, model_type, force_experience, request_vars, capabilities, recoverFromSpiral}. assertRequestVars rejects reserved keys and non-scalar values before the wire.
opts.capabilities is a per-message {name:state} override, validated client-side. But the server-side DISABLED veto still wins: a stored or env-disabled capability cannot be turned on per message. For example, converse(cfg, msg, {capabilities:{use_web_search:'on'}}) is honored only if use_web_search is not disabled by a stored layer.
opts.recoverFromSpiral:true on conversations.send/converseOnce sends one same-thread nudge retry (SPIRAL_RECOVERY_PREFIX) when the first attempt comes back spiralStopped:true with empty text. See stream.js's collectConverse entry above for what it's recovering from.
Scope-guard timing on the streaming path.
conversations.stream(...)is an async generator, so itsassertConversation(ks)scope check fires on the first.next()/iteration, not at call time.const g = k.conversations.stream(opts, adminKs)without iterating gets no guard yet. This is despite the client's "before any network call" framing, which holds for the non-generator methods. For eager scope validation, useconversations.send(...)/converseOnce(...)instead: they assert the token kind synchronously before returning. The non-generator reads (conversations.status, allagents/avatars/catalogcalls) guard at call time, as documented.
resolveCapabilities return shape (the 16 names are nested, not top-level)
resolveCapabilities(layers) (src/management/capabilities.js) returns a two-key object, { capabilities, _meta }, so Object.keys(result).length === 2. The 16 AssistantCapability states live under .capabilities, keyed by name, NOT at the top level:
const { capabilities, _meta } = k.intellects.resolveCapabilities({
partnerConfig: stored, // from intellects.getCapabilities
request: { use_web_search: 'off' }, // per-turn override to model
});
capabilities.avatar.state; // 'on' | 'off' | 'disabled'
capabilities.avatar.resolvedFrom; // which layer won
Each per-name entry is { state, resolvedFrom, vetoed, layers }:
resolvedFromis one ofrequest | partner_config | env | default | disabled_veto.defaultonly appears when you pass an explicit emptyenv: {}. Whenenvis omitted, the resolver suppliesCAPABILITY_DEFAULTSas the env layer. So an unset capability resolvesresolvedFrom:'env'(for example,resolveCapabilities({}).capabilities.avatar.resolvedFrom === 'env').vetoed:true(withresolvedFrom:'disabled_veto',state:'off') whenenvorpartnerConfigmarks the capabilitydisabled. This is a hard override that no per-requestoncan lift.
A freshly created intellect returns an empty capabilities {} from getCapabilities (nothing stored yet), so every name then resolves from the env/default layer until you setCapability/setCapabilities.
Experience GenUI layer
src/experience/genui/ turns brain unisphere-tool segments into framework-agnostic render descriptors:
parse.js: pure.normalizeRuntime(strips the wire-toolsuffix),RUNTIMES(the nine first-class widgets),parseWidget/parseContent(forgiving JSON-then-line parser; unknown keys map to.raw, never throws),isKnownRuntime.segments.js:SegmentAssemblerbuffers deltas and flushes a complete widget onruntimeName/speechIdchange or turn end.renderer.js:ExperienceRenderer, dual-mode. LIVEstart()subscribes tosession.on('brainSegment'). HEADLESSrender(runtime, widget)is fed fromManagement.conversations.stream()(the reliable widget path, since the live runtime hardcodesforce_experience:'avatar_only'). Unknown runtimes yield a safe{kind:'unknown'}fallback plusonUnhandled, never thrown, never faked.renderers/*.js: one default renderer per runtime, each routing untrusted LLM output throughcore/safety.js(safeText/safeUrl, noinnerHTML).
session.js wires the guardrail gate. Each agent_raw_text delta is classified by classifyAgentAction (wire.js, reading seg.metadata.runtimeName/widgetName plus the adapter-normalized seg.type with -tool stripped). When a capability policy or onAgentAction hook is present, the delta runs through _gateAgentAction before emit('brainSegment', d). This is default-allow, so existing nav/GenUI flows are untouched; a veto emits agentActionDenied plus an agent.action.deny audit event. Presenter exposes covered/questions/lastNav ({target, reason, at}). session.micEnabled is a read getter.
Routing rule: the intellect DTO is the whole writable surface
The intellect DTO (v1/intellect/*) is the one real door for every writable field: prompts, base_directive, glossary, capabilities, tool_ids, skill_ids, thread_start_tools, secrets, user_properties_forms, mcp_servers, allow_client_variables, knowledge_ids, avatar_summary_config, force_language, opening_phrase, model_configuration, name/description/tags/status. It's writable with a partner admin KS, ungated.
Knowledge linkage rides this same door. First call POST /v1/knowledge/add on the brain host, which returns an {id,...} record. Then pass the returned id as knowledge_ids in the intellect create/update DTO. Linkage plus use_knowledge_base:'on' persist with no separate linking call.
It is a model_fields_set PATCH, so omitted top-level fields are preserved. But capabilities/secrets are full-replace sub-dicts, so the SDK read-merge-writes them, via mergeCapabilityWrite or the secrets mask-and-keep guard. IntellectConfig.patch is the one place that logic lives.
EDITABLE_FIELDS in intellect-config.js is the exact list of keys v1/intellect/get echoes and v1/intellect/update accepts for a partner admin KS. patch() rejects any other key before the network call, and describe() returns exactly these keys. When adding a field setter, first confirm the key round-trips through intellect/update and intellect/get with a partner admin KS. Then add it to EDITABLE_FIELDS.
Known limits
- External (BYO-LLM) intellects are not supported.
intellects.createrejects a body containingurl/protocolwith a typedbad_request. - Secrets are write-only: values never read back. The no-leak guarantee is the name-only response contract, not
redact(). Client-side encryption and BYOK are server-managed, not buildable. previewPrompt/snapshot/restoreare client-side: a replica of the author layer only (server-injected capability-conditional prompt blocks are not reproducible), plus a browser-local history (the server has no versioning).agent/listfilter keys are narrow.filteris forwarded as-is; the server accepts onlyagentId,adminTagsIn,adminTagsNotIn,searchValue, and 400s on anything else. Tag the agent withadminTagsat create time to group. Avatars carry no tag field, butavatar/createaccepts and stores it silently (never echoed back), whileavatar/update400s on it. SeeAvatars.create's JSDoc.
The SDK's own node:test suite (test/) exercises every one of these surfaces against the real backend and against injected fakes. See README.md for the full command list.
Related docs
| Doc | Covers |
|---|---|
| System Internals Reference · Resilience and Failure Handling | Failure modes, TURN/relay, and the tool-call-spiral breaker touched on above |
| System Internals Reference | Back to the index |