Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: Publish to npm

# Publishes @memmesh/sdk to the npm registry when a version tag is pushed.
# Tag the release commit with `v<version>` (e.g. `v0.9.0`) and push the tag:
# git tag v0.9.0 && git push origin v0.9.0
on:
push:
tags:
- 'v*'
workflow_dispatch:

permissions:
contents: read

jobs:
publish:
name: build + npm publish
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
registry-url: 'https://registry.npmjs.org'
- run: npm ci
- run: npm run typecheck
- run: npm run build
- name: Publish
run: npm publish --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
76 changes: 66 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# @thinkfleet/memory-sdk
# @memmesh/sdk

TypeScript SDK for [app.memmesh.ai](https://app.memmesh.ai) — a managed memory + behavioral-pattern engine for AI agents.

Expand All @@ -13,17 +13,18 @@ Runs anywhere with a modern `fetch`: Node 18+, Bun, Deno, browsers, Cloudflare W
## Install

```bash
npm install @thinkfleet/memory-sdk
# or: pnpm add @thinkfleet/memory-sdk
# or: bun add @thinkfleet/memory-sdk
npm install @memmesh/sdk
# or: pnpm add @memmesh/sdk
# or: bun add @memmesh/sdk
```

## Quick start

```ts
import { ThinkFleetMemory } from '@thinkfleet/memory-sdk'
import { MemMesh } from '@memmesh/sdk'
// `ThinkFleetMemory` is the legacy alias — still exported for back-compat.

const tf = new ThinkFleetMemory({
const tf = new MemMesh({
apiKey: 'sk-...', // Platform Admin → API Keys
projectId: 'proj_...', // Default project for all calls
})
Expand Down Expand Up @@ -51,7 +52,7 @@ const hits = await tf.memory.admin.search({
## Configuration

```ts
const tf = new ThinkFleetMemory({
const tf = new MemMesh({
apiKey: 'sk-...', // Required
projectId: 'proj_...', // Required default
baseUrl: 'https://app.memmesh.ai', // Default
Expand All @@ -74,7 +75,7 @@ await tf.memory.admin.list({ scope: 'project' }, { projectId: 'proj_other' })
Pass a request interceptor that swaps the `Authorization` header on each call:

```ts
const tf = new ThinkFleetMemory({
const tf = new MemMesh({
apiKey: 'unused', // still required to be non-empty, but interceptor wins
projectId: 'proj_...',
requestInterceptors: [
Expand All @@ -100,10 +101,33 @@ const tf = new ThinkFleetMemory({

| Method | Endpoint |
| ------------------------- | ------------------------------------- |
| `observe(body)` | `POST /projects/:id/memory/observe` |
| `mine(params?)` | `GET /projects/:id/memory/mine` |
| `delete(memoryId)` | `DELETE /projects/:id/memory/:memId` |
| `submitFeedback(body)` | `POST /projects/:id/memory/feedback`|

`observe()` is the primary write path. Hand it the raw turn, **verbatim** — the
engine runs extraction, dedupe, graph wiring, and embedding, and keeps only what
is worth remembering. Do not summarize or pre-filter first: extraction is the
thing you are paying for, and a pre-digested input makes it worse, not cheaper.

```ts
const { saved, candidateCount } = await tf.memory.observe({
text: "I just moved to Denver and I'm still vegetarian.",
role: 'user',
userId: 'user-123', // your identifier, recorded as provenance
sessionId: 'thread-456', // keeps a conversation's turns linkable
})
```

`candidateCount` is what extraction proposed; `saved` is what survived dedupe and
the token budget. Filler comes back as `saved: []` — that is the system working.

`userId` is provenance, **not** a tenancy boundary: `admin.search({ chatIdentityId })`
filters permissively (`IS NULL OR = $1`) so project-wide memories stay visible to
every caller. Isolating one end user's memories from another's needs a project
per tenant.

### `tf.memory.admin` — admin / project-wide memory

| Method | Endpoint |
Expand All @@ -123,6 +147,38 @@ const tf = new ThinkFleetMemory({
| `delete(memId)` | `DELETE /projects/:id/admin/memory/:memId` |
| `listFeedback(memId)` | `GET /projects/:id/admin/memory/:memId/feedback`|

### `tf.memory.admin.graph` — the knowledge graph

Observing text doesn't only produce embeddable rows; extraction also resolves
entities and writes typed edges between them. That graph is what answers a
question no single memory states outright.

| Method | Endpoint |
| ------------------------------- | --------------------------------------------------- |
| `stats()` | `GET /projects/:id/admin/memory/graph/stats` |
| `listEntities(params?)` | `GET /projects/:id/admin/memory/entities` |
| `getEntity(entityId, params?)` | `GET /projects/:id/admin/memory/entities/:entityId` |
| `listEdges(params?)` | `GET /projects/:id/admin/memory/graph/edges` |
| `traverse(entityId, params?)` | `POST /projects/:id/admin/memory/graph/traverse` |

```ts
// How much of what you remember made it into the graph?
const { entityCount, edgeCount, memoriesWithEdges } = await tf.memory.admin.graph.stats()

// Multi-hop: who does Sarah ultimately report to?
const [sarah] = await tf.memory.admin.graph.listEntities({ search: 'Sarah', limit: 1 })
const chain = await tf.memory.admin.graph.traverse(sarah.id, {
hops: 2,
predicates: ['member_of', 'led_by'],
})
```

Use `stats()` — not `listEntities().length` — for any "how big is it" question:
the list routes page, so their length is the page size, not the total.

Read-only by design. Entities and edges are written by extraction when you
`observe()`; a hand-maintained graph is the work the engine exists to do for you.

### `tf.lattice` — behavioral patterns

| Method | Endpoint |
Expand Down Expand Up @@ -206,7 +262,7 @@ returns the full `PredictResult` (`targetPrediction` + top-level `abstained`).
## Memory scopes

```ts
import { MemoryScope } from '@thinkfleet/memory-sdk'
import { MemoryScope } from '@memmesh/sdk'

MemoryScope.PLATFORM // visible to every project on the platform
MemoryScope.PROJECT // visible to every user in this project
Expand All @@ -229,7 +285,7 @@ import {
RateLimitError,
ServerError,
TimeoutError,
} from '@thinkfleet/memory-sdk'
} from '@memmesh/sdk'

try {
await tf.memory.admin.create({ content: '' })
Expand Down
2 changes: 1 addition & 1 deletion examples/financial-demo.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env npx tsx
/**
* @thinkfleet/memory-sdk — financial vertical end-to-end demo
* @memmesh/sdk — financial vertical end-to-end demo
*
* A working sample app that pulls REAL data from public, no-API-key sources,
* loads it into ThinkFleet memory, and reads the financial vertical back out:
Expand Down
2 changes: 1 addition & 1 deletion examples/next-best-offer.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env npx tsx
/**
* @thinkfleet/memory-sdk — Next Best Offer, end to end
* @memmesh/sdk — Next Best Offer, end to end
*
* A working sample app for the question: *"which offer is right for this
* contact, and when is the right time to send it?"* — and, crucially, *how
Expand Down
2 changes: 1 addition & 1 deletion examples/predict-anything.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env npx tsx
/**
* @thinkfleet/memory-sdk — v2 "predict anything" + abstention demo
* @memmesh/sdk — v2 "predict anything" + abstention demo
*
* The whole moat in one file: declare ANY target and the engine predicts it
* from a subject's observation history — calibrated, with provenance, and
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 8 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "@thinkfleet/memory-sdk",
"name": "@memmesh/sdk",
"version": "0.9.0",
"description": "TypeScript SDK for app.memmesh.ai — admin + project memory CRUD, semantic search, feedback, and Lattice behavioral patterns",
"type": "module",
Expand Down Expand Up @@ -51,8 +51,15 @@
"lattice"
],
"license": "MIT",
"publishConfig": {
"access": "public"
},
"repository": {
"type": "git",
"url": "https://github.com/ThinkfleetAI/thinkfleet-memory-sdk.git"
},
"homepage": "https://github.com/ThinkfleetAI/thinkfleet-memory-sdk#readme",
"bugs": {
"url": "https://github.com/ThinkfleetAI/thinkfleet-memory-sdk/issues"
}
}
4 changes: 2 additions & 2 deletions src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,9 @@ export interface ThinkFleetMemoryOptions {
*
* @example
* ```ts
* import { ThinkFleetMemory } from '@thinkfleet/memory-sdk'
* import { MemMesh } from '@memmesh/sdk'
*
* const tf = new ThinkFleetMemory({
* const tf = new MemMesh({
* apiKey: 'sk-...',
* projectId: 'proj_...',
* })
Expand Down
16 changes: 16 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
// Client
export { ThinkFleetMemory, type ThinkFleetMemoryOptions } from './client.js'
// Brand-consistent alias (matches the Python SDK's `MemMesh` class).
// `ThinkFleetMemory` is retained as a back-compat legacy alias.
export { ThinkFleetMemory as MemMesh } from './client.js'

// Core
export {
Expand All @@ -23,6 +26,7 @@ export type {
// Resources
export { MemoryResource, AdminMemoryResource } from './resources/memory.js'
export { ConsentResource } from './resources/consent.js'
export { GraphResource } from './resources/graph.js'
export { ContextResource } from './resources/context.js'
export type {
ContextSection,
Expand Down Expand Up @@ -248,3 +252,15 @@ export type {
SubjectProfile,
RiskIndicator,
} from './types/lattice.js'

// Knowledge graph
export type {
MemoryEntity,
MemoryEntityType,
MemoryEdge,
GraphStats,
ListEntitiesParams,
ListEdgesParams,
TraverseParams,
EntityWithEdges,
} from './types/graph.js'
94 changes: 94 additions & 0 deletions src/resources/graph.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import type { HttpClient } from '../core/http-client.js'
import type { RequestOptions } from '../core/types.js'
import type {
EntityWithEdges,
GraphStats,
ListEdgesParams,
ListEntitiesParams,
MemoryEdge,
MemoryEntity,
TraverseParams,
} from '../types/graph.js'

/**
* The knowledge graph built from observed memory.
*
* Reached as `tf.memory.admin.graph` — it lives under the admin surface because
* every route here is admin-tier (`/admin/memory/...`); a project-scoped key
* gets a 403.
*
* Read-only by design. Entity and edge *creation* happens through extraction
* when you `observe()`; the manual create/retire routes exist on the server for
* annotation tooling, and exposing them here would invite hand-maintained
* graphs, which is exactly the work the engine is supposed to do for you.
*/
export class GraphResource {
constructor(private readonly http: HttpClient) {}

/**
* Aggregate counts for the whole graph.
*
* Prefer this over `listEntities().length` for any "how big is it" question:
* these are SQL `COUNT(*)`s over the full table, where the list routes page
* and would silently report the page size as the total.
*/
async stats(options?: RequestOptions): Promise<GraphStats> {
return this.http.get<GraphStats>('/admin/memory/graph/stats', undefined, options)
}

/** Entities, filtered by type/scope or by a substring of name or alias. */
async listEntities(
params?: ListEntitiesParams,
options?: RequestOptions,
): Promise<MemoryEntity[]> {
return this.http.get<MemoryEntity[]>(
'/admin/memory/entities',
params as Record<string, string | number | undefined>,
options,
)
}

/** One entity plus its 1-hop neighbourhood. */
async getEntity(
entityId: string,
params?: { asOf?: string },
options?: RequestOptions,
): Promise<EntityWithEdges> {
return this.http.get<EntityWithEdges>(
`/admin/memory/entities/${entityId}`,
params as Record<string, string | undefined>,
options,
)
}

/**
* Every currently-valid edge. Use for rendering a whole small graph; for a
* large one, seed from an entity and `traverse` instead.
*/
async listEdges(params?: ListEdgesParams, options?: RequestOptions): Promise<MemoryEdge[]> {
return this.http.get<MemoryEdge[]>(
'/admin/memory/graph/edges',
params as Record<string, string | number | undefined>,
options,
)
}

/**
* Walk out from a seed entity.
*
* This is the multi-hop path: the edges returned here connect facts that no
* single memory states together, which is how a query gets answered from a
* chain rather than from one lucky vector hit.
*/
async traverse(
entityId: string,
params?: TraverseParams,
options?: RequestOptions,
): Promise<MemoryEdge[]> {
return this.http.post<MemoryEdge[]>(
'/admin/memory/graph/traverse',
{ entityId, ...params },
options,
)
}
}
Loading
Loading