Skip to main content
@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,
})
ConfigDescription
baseUrlREST origin without /api/v1. Trailing slashes are trimmed.
apiKeyAPI 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
}
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

ErrorDescription
ThreengramApiErrorThrown for non-2xx REST responses. Carries status and reason from the response body.
ThreengramNetworkErrorThrown when fetch rejects before a server response is available.