TypeScript SDK for app.memmesh.ai — a managed memory + behavioral-pattern engine for AI agents.
Two resources:
memory— admin + project memory CRUD, semantic search, promote/confirm/reject workflow, feedbacklattice— behavioral pattern intelligence (extract, monitor, retrieve, search, observe)
Runs anywhere with a modern fetch: Node 18+, Bun, Deno, browsers, Cloudflare Workers.
npm install @thinkfleet/memory-sdk
# or: pnpm add @thinkfleet/memory-sdk
# or: bun add @thinkfleet/memory-sdkimport { ThinkFleetMemory } from '@thinkfleet/memory-sdk'
const tf = new ThinkFleetMemory({
apiKey: 'sk-...', // Platform Admin → API Keys
projectId: 'proj_...', // Default project for all calls
})
// Dashboard stats
const stats = await tf.memory.admin.stats()
console.log(`${stats.total} memories, ${stats.pendingReview} pending review`)
// Seed a memory the agent should know
await tf.memory.admin.create({
content: 'Customer prefers email over phone.',
type: 'preference',
scope: 'project',
})
// Semantic search
const hits = await tf.memory.admin.search({
query: 'communication preferences',
limit: 5,
})const tf = new ThinkFleetMemory({
apiKey: 'sk-...', // Required
projectId: 'proj_...', // Required default
baseUrl: 'https://app.memmesh.ai', // Default
maxRetries: 2, // Retries 429/5xx with backoff
timeout: 30_000, // ms
fetch: globalThis.fetch, // BYO fetch
requestInterceptors: [...], // Mutate RequestInit before send
responseInterceptors: [...], // Inspect Response before JSON parse
})await tf.memory.admin.list({ scope: 'project' }, { projectId: 'proj_other' })Pass a request interceptor that swaps the Authorization header on each call:
const tf = new ThinkFleetMemory({
apiKey: 'unused', // still required to be non-empty, but interceptor wins
projectId: 'proj_...',
requestInterceptors: [
async (_url, init) => {
const jwt = await getCognitoJwt()
return {
...init,
headers: {
...(init.headers as Record<string, string>),
Authorization: `Bearer ${jwt}`,
},
}
},
],
})| Method | Endpoint |
|---|---|
mine(params?) |
GET /projects/:id/memory/mine |
delete(memoryId) |
DELETE /projects/:id/memory/:memId |
submitFeedback(body) |
POST /projects/:id/memory/feedback |
| Method | Endpoint |
|---|---|
list(params?) |
GET /projects/:id/admin/memory |
listPlatform(params?) |
GET /projects/:id/admin/memory/platform |
listPendingReview(params?) |
GET /projects/:id/admin/memory/review |
stats() |
GET /projects/:id/admin/memory/stats |
create(body) |
POST /projects/:id/admin/memory |
createProcedure(body) |
POST /projects/:id/admin/memory (type=procedure) |
update(memId, body) |
PATCH /projects/:id/admin/memory/:memId |
confirm(memId, body) |
POST /projects/:id/admin/memory/:memId/confirm |
promote(memId, body) |
POST /projects/:id/admin/memory/:memId/promote |
search(body) |
POST /projects/:id/admin/memory/search |
getPrecedence() |
GET /projects/:id/admin/memory/precedence |
setPrecedence(policy) |
PUT /projects/:id/admin/memory/precedence |
delete(memId) |
DELETE /projects/:id/admin/memory/:memId |
listFeedback(memId) |
GET /projects/:id/admin/memory/:memId/feedback |
| Method | Endpoint |
|---|---|
extractPatterns(body?) |
POST /projects/:id/lattice/patterns/extract |
getPattern(patternId) |
GET /projects/:id/lattice/patterns/:patternId |
listPatterns(contactId, params?) |
GET /projects/:id/lattice/contacts/:cid/patterns |
getContext(contactId, params?) |
GET /projects/:id/lattice/contacts/:cid/context |
runMonitorTick() |
POST /projects/:id/lattice/monitor/tick |
getMonitorStatus() |
GET /projects/:id/lattice/monitor/status |
Register, version, and manage the brains a project publishes (a brain = a Brain
Card manifest + a stable externalId slug). Once a brain is PUBLISHED +
PUBLIC, any caller consumes it over the hosted MCP endpoint
(/brains/:brainId/mcp-server/http) — an MCP connection, not a REST call, so it
lives outside this resource.
| Method | Endpoint |
|---|---|
create(body) |
POST /projects/:id/brains |
list(params?) |
GET /projects/:id/brains |
get(brainId) |
GET /projects/:id/brains/:brainId |
update(brainId, body) |
PATCH /projects/:id/brains/:brainId |
delete(brainId) |
DELETE /projects/:id/brains/:brainId |
const brain = await tf.brains.create({
externalId: 'sec-edgar-financials',
name: 'SEC EDGAR Financials',
domain: 'finance',
card: { provenance: [{ source: 'SEC EDGAR', license: 'public-domain' }] },
})
await tf.brains.update(brain.id, { visibility: 'PUBLIC', status: 'PUBLISHED' })
const page = await tf.brains.list({ limit: 20 })
for (const b of page.data) console.log(b.externalId, b.status)Don't pick a model. Declare what to predict — a churn event, a next-order amount, a next-visit time, an anomaly — and the engine predicts it from the subject's history, calibrated, with provenance, and abstaining when there isn't enough signal.
const p = await tf.lattice.predictTarget(
{ kind: 'customer', externalId: 'acct-42' },
{ kind: 'event_occurrence', eventType: 'subscription_cancelled' },
{ horizonDays: 90 },
)
if (p.abstained) {
// The whole trust story: "unknown" is a first-class answer. Never treat an
// abstention as low risk.
console.log('not enough signal —', p.abstentionReason)
} else {
console.log(`churn risk ${(p.probability * 100).toFixed(0)}% `
+ `[${(p.probabilityLower * 100).toFixed(0)}–${(p.probabilityUpper * 100).toFixed(0)}%]`)
console.log('why:', p.explanation, '| evidence:', p.evidenceMemoryIds)
}target.kind is one of event_occurrence | numeric | event_time |
anomaly, and the kind selects the result fields (probability* / value* /
expectedAt* / anomalyScore). A target is just a question — adding a row to
your registry adds a prediction, with no SDK or engine change. That's how a
whole vertical (Shopify churn, health risk, fraud) is one registry over one
engine.
See examples/predict-anything.ts for all four
kinds end-to-end. The lower-level tf.lattice.predict({ subject, target })
returns the full PredictResult (targetPrediction + top-level abstained).
import { MemoryScope } from '@thinkfleet/memory-sdk'
MemoryScope.PLATFORM // visible to every project on the platform
MemoryScope.PROJECT // visible to every user in this project
MemoryScope.AGENT // tied to a specific chatbotId
MemoryScope.USER // tied to a specific chatIdentityId
MemoryScope.SESSION // tied to a specific sessionKeyAll non-2xx responses throw a typed error. Catch the base type or the specific one:
import {
ThinkFleetMemoryError,
AuthenticationError,
AuthorizationError,
NotFoundError,
ValidationError,
RateLimitError,
ServerError,
TimeoutError,
} from '@thinkfleet/memory-sdk'
try {
await tf.memory.admin.create({ content: '' })
} catch (err) {
if (err instanceof ValidationError) {
console.error('Bad request:', err.message, err.params)
} else if (err instanceof RateLimitError) {
console.warn('Throttled; retry after', err.retryAfterMs, 'ms')
} else if (err instanceof ThinkFleetMemoryError) {
console.error('API error:', err.code, err.statusCode, err.message)
} else {
throw err
}
}MIT