@3ngram/sdk is a thin TypeScript client over the REST /api/v1 surface. It takes explicit configuration and sends the configured API key as X-API-Key on every request.
The SDK is workspace-only before v1.0. Npm publishing is tracked as part of the v1 launch gate; until then, examples describe the public package API rather than an install command.
Client setup
import { ThreengramClient } from '@3ngram/sdk'
const apiKey = process.env.THREENGRAM_API_KEY
if (apiKey === undefined) throw new Error('missing THREENGRAM_API_KEY')
const client = new ThreengramClient({
baseUrl: 'https://3ngram-server-production.up.railway.app',
apiKey,
})
| Config | Description |
|---|---|
baseUrl | REST origin without /api/v1. Trailing slashes are trimmed. |
apiKey | API key sent as the X-API-Key header. The SDK has no env fallback. |
Methods
remember
Append a typed memory. Commitment outputs include a commitmentId. Signature:remember(input: RememberToolArgs): Promise<RememberToolOutput>REST route:
POST /api/v1/memories
Example
await client.remember({
memoryType: 'decision',
topic: 'search backend',
content: 'Use Postgres full-text search for v1.',
scope: 'work',
project: '3ngram',
})
Input schema
{
"type": "object",
"properties": {
"memoryType": {
"type": "string",
"enum": [
"decision",
"commitment",
"blocker",
"fact",
"preference",
"pattern",
"note",
"event"
]
},
"topic": {
"type": "string",
"minLength": 1,
"maxLength": 256
},
"content": {
"type": "string",
"minLength": 1,
"maxLength": 2000
},
"scope": {
"default": "personal",
"type": "string",
"minLength": 1,
"maxLength": 64,
"pattern": "^[a-z0-9][a-z0-9-]*$"
},
"project": {
"type": "string",
"minLength": 1,
"maxLength": 256
},
"tags": {
"default": [],
"maxItems": 32,
"type": "array",
"items": {
"type": "string",
"minLength": 1,
"maxLength": 64
}
}
},
"required": [
"memoryType",
"topic",
"content"
],
"additionalProperties": false
}
Output schema
{
"type": "object",
"properties": {
"memory": {
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
},
"memoryType": {
"type": "string",
"enum": [
"decision",
"commitment",
"blocker",
"fact",
"preference",
"pattern",
"note",
"event"
]
},
"topic": {
"type": "string"
},
"scope": {
"type": "string",
"minLength": 1,
"maxLength": 64,
"pattern": "^[a-z0-9][a-z0-9-]*$"
},
"project": {
"anyOf": [
{
"type": "string",
"minLength": 1,
"maxLength": 256
},
{
"type": "null"
}
]
}
},
"required": [
"id",
"memoryType",
"topic",
"scope",
"project"
],
"additionalProperties": false
},
"embedded": {
"type": "string",
"enum": [
"pending",
"done",
"failed",
"off"
]
},
"commitmentId": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
}
},
"required": [
"memory",
"embedded"
],
"additionalProperties": false
}
search
Run semantic and keyword retrieval with optional pre-fusion filters. Signature:search(query: string, opts?: SearchOptions): Promise<SearchToolOutput>REST route:
POST /api/v1/search
Example
await client.search('oauth refresh token', {
limit: 5,
scope: 'work',
project: '3ngram',
})
Input schema
{
"type": "object",
"properties": {
"query": {
"type": "string",
"minLength": 1
},
"limit": {
"default": 5,
"type": "integer",
"minimum": 1,
"maximum": 25
},
"memoryType": {
"type": "string",
"enum": [
"decision",
"commitment",
"blocker",
"fact",
"preference",
"pattern",
"note",
"event"
]
},
"scope": {
"type": "string",
"minLength": 1,
"maxLength": 64,
"pattern": "^[a-z0-9][a-z0-9-]*$"
},
"project": {
"type": "string",
"minLength": 1,
"maxLength": 256
},
"status": {
"type": "string",
"enum": [
"active",
"archived"
]
},
"asOf": {
"type": "object",
"properties": {
"validAt": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
},
"asKnownAt": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
}
},
"additionalProperties": false
}
},
"required": [
"query"
],
"additionalProperties": false
}
Output schema
{
"type": "object",
"properties": {
"hits": {
"maxItems": 25,
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
},
"memoryType": {
"type": "string"
},
"topic": {
"type": "string"
},
"content": {
"type": "string",
"maxLength": 600
},
"contentLength": {
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"truncated": {
"type": "boolean"
},
"score": {
"type": "number"
}
},
"required": [
"id",
"memoryType",
"topic",
"content",
"contentLength",
"truncated",
"score"
],
"additionalProperties": false
}
},
"count": {
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
}
},
"required": [
"hits",
"count"
],
"additionalProperties": false
}
getFacts
Read currently-valid facts, with optional bi-temporal filters. Signature:getFacts(filters?: FactsQueryArgs): Promise<FactsToolOutput>REST route:
GET /api/v1/facts
Example
await client.getFacts({
subject: '3ngram',
predicate: 'status',
limit: 20,
})
Input schema
{
"type": "object",
"properties": {
"subject": {
"type": "string",
"minLength": 1
},
"predicate": {
"type": "string",
"minLength": 1
},
"asOf": {
"type": "object",
"properties": {
"validAt": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
},
"asKnownAt": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
}
},
"additionalProperties": false
},
"limit": {
"default": 50,
"type": "integer",
"minimum": 1,
"maximum": 200
}
},
"additionalProperties": false
}
Output schema
{
"type": "object",
"properties": {
"facts": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
},
"subject": {
"type": "string"
},
"predicate": {
"type": "string"
},
"value": {
"type": "string"
},
"confidence": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
]
},
"validFrom": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
},
"validTo": {
"anyOf": [
{
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
},
{
"type": "null"
}
]
}
},
"required": [
"id",
"subject",
"predicate",
"value",
"confidence",
"validFrom",
"validTo"
],
"additionalProperties": false
}
},
"count": {
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
}
},
"required": [
"facts",
"count"
],
"additionalProperties": false
}
revise
Create a corrected successor memory and link it to the predecessor. Signature:revise(predecessorId: string, input: ReviseBody): Promise<ReviseToolOutput>REST route:
POST /api/v1/memories/:id/revise
Example
await client.revise(memoryId, {
memoryType: 'decision',
topic: 'search backend',
content: 'Use Postgres full-text search plus embeddings for v1.',
edgeIntent: 'updates',
})
Input schema
{
"type": "object",
"properties": {
"memoryType": {
"type": "string",
"enum": [
"decision",
"commitment",
"blocker",
"fact",
"preference",
"pattern",
"note",
"event"
]
},
"topic": {
"type": "string",
"minLength": 1,
"maxLength": 256
},
"content": {
"type": "string",
"minLength": 1,
"maxLength": 2000
},
"scope": {
"default": "personal",
"type": "string",
"minLength": 1,
"maxLength": 64,
"pattern": "^[a-z0-9][a-z0-9-]*$"
},
"project": {
"type": "string",
"minLength": 1,
"maxLength": 256
},
"tags": {
"default": [],
"maxItems": 32,
"type": "array",
"items": {
"type": "string",
"minLength": 1,
"maxLength": 64
}
},
"edgeIntent": {
"default": "supersedes",
"type": "string",
"enum": [
"supersedes",
"updates"
]
}
},
"required": [
"memoryType",
"topic",
"content"
],
"additionalProperties": false
}
Output schema
{
"type": "object",
"properties": {
"memory": {
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
},
"memoryType": {
"type": "string",
"enum": [
"decision",
"commitment",
"blocker",
"fact",
"preference",
"pattern",
"note",
"event"
]
},
"topic": {
"type": "string"
},
"scope": {
"type": "string",
"minLength": 1,
"maxLength": 64,
"pattern": "^[a-z0-9][a-z0-9-]*$"
},
"project": {
"anyOf": [
{
"type": "string",
"minLength": 1,
"maxLength": 256
},
{
"type": "null"
}
]
}
},
"required": [
"id",
"memoryType",
"topic",
"scope",
"project"
],
"additionalProperties": false
},
"embedded": {
"type": "string",
"enum": [
"pending",
"done",
"failed",
"off"
]
}
},
"required": [
"memory",
"embedded"
],
"additionalProperties": false
}
resolve
Transition the commitment riding a memory, or archive an active blocker. Signature:resolve(memoryId: string, status: CommitmentStatus): Promise<ResolveToolOutput>REST route:
POST /api/v1/memories/:id/resolve
Example
await client.resolve(memoryId, 'resolved')
Input schema
{
"type": "object",
"properties": {
"status": {
"type": "string",
"enum": [
"open",
"waiting",
"resolved",
"expired"
]
}
},
"required": [
"status"
],
"additionalProperties": false
}
Output schema
{
"type": "object",
"properties": {
"commitmentId": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
},
"status": {
"anyOf": [
{
"type": "string",
"enum": [
"open",
"waiting",
"resolved",
"expired"
]
},
{
"type": "string",
"enum": [
"archived"
]
}
]
}
},
"required": [
"commitmentId",
"status"
],
"additionalProperties": false
}
Errors
| Error | Description |
|---|---|
ThreengramApiError | Thrown for non-2xx REST responses. Carries status and reason from the response body. |
ThreengramNetworkError | Thrown when fetch rejects before a server response is available. |