Skip to Content
TypeScript SDK

TypeScript SDK

The @khwan/client package is a thin wrapper over the REST API for Node and the browser. It mirrors the Python client one-to-one, with async methods.

npm install @khwan/client
import { Khwan, KhwanError } from '@khwan/client'

Every method returns a Promiseprepare, record, sync, memory, cores, and metrics are all async.

new Khwan(options)

const kw = new Khwan({ apiKey: 'kwk_live_xxx', // required userId: 'alice', // required — sets X-Khwan-User core: 'client1', // optional — sets X-Khwan-Core (isolated brain) baseUrl: 'https://api.khwan.ai', // optional; override for on-prem model: undefined, // optional session model hint constitution: undefined, // optional named constitution profile timeout: 60000, // optional request timeout (ms) })
OptionTypeDefaultNotes
apiKeystringRequired. Your Khwan key (kwk_...).
userIdstringRequired. End-user id; sent as X-Khwan-User. Identifies the user, not a separate brain.
corestringundefinedCore slug; sent as X-Khwan-Core. Selects an isolated brain. Omit for the default core.
baseUrlstringhttps://api.khwan.aiOverride for an on-prem instance.
modelstringundefinedForwarded on prepare.
constitutionstringundefinedNamed constitution profile reference.
timeoutnumber60000Per-request timeout in ms.

memory and embedder are not configurable — they are server-managed and only exist in the on-prem engine.

Isolated cores

Each named core is a fully separate brain (own memory, identity, learning). Omit core for the account’s default core. Quota is pooled at the account level.

const client1 = new Khwan({ apiKey: 'kwk_live_xxx', userId: 'alice', core: 'client1' })

Methods

prepare(input): Promise<Turn>

Builds the turn context (memory + constitution + coherence). No LLM call. POST /prepare.

const turn = await kw.prepare('remember I prefer short answers in Thai')

record(turn, answer): Promise<Record<string, unknown>>

Hands your model’s answer back to persist and learn. POST /record.

await kw.record(turn, answer)

sync(): Promise<Record<string, unknown>>

Triggers a learning / consolidation pass. POST /sync.

memory(limit = 20): Promise<Record<string, unknown>>

Recent memory entries for the selected core. GET /memory?limit=....

cores(): Promise<Record<string, unknown>>

Lists the account’s cores. GET /cores.

metrics(): Promise<Record<string, unknown>>

Coherence / learning / usage metrics for the selected core. GET /metrics.

Turn

Returned by prepare().

PropertyTypeNotes
messagesArray<{ role: string; content: string }>Brief for your model.
coherencenumber | nullCoherence signal.
sourcesunknown[]Sources used.
allowedbooleanfalse if the turn was gated.
reasonstring | nullWhy it was gated.
turnTokenstring | nullOpaque handle used by record().
rawRecord<string, unknown>Full server payload.

KhwanError

Thrown on any non-2xx response. Has a .status number (the HTTP code).

Full example (BYOM with the Anthropic SDK)

import { Khwan, KhwanError } from '@khwan/client' import Anthropic from '@anthropic-ai/sdk' const kw = new Khwan({ apiKey: 'kwk_live_xxx', userId: 'alice', core: 'client1' }) const llm = new Anthropic({ apiKey: 'sk-ant-...' }) // your key, Khwan never sees it async function myOwnLlm(messages: Array<{ role: string; content: string }>) { const system = messages.find((m) => m.role === 'system')?.content ?? '' const chat = messages .filter((m) => m.role !== 'system') .map((m) => ({ role: m.role as 'user' | 'assistant', content: m.content })) const r = await llm.messages.create({ model: 'claude-sonnet-4-6', max_tokens: 1024, system, messages: chat, }) const block = r.content[0] return block.type === 'text' ? block.text : '' } try { const turn = await kw.prepare('remember I prefer short answers in Thai') if (turn.allowed) { const answer = await myOwnLlm(turn.messages) await kw.record(turn, answer) console.log(answer) } else { console.log('blocked:', turn.reason) } } catch (e) { if (e instanceof KhwanError) console.error('Khwan error', e.status, e.message) else throw e }
Last updated on