Set up Threadless AI

Exact steps for an agent adding Threadless AI to a codebase that calls OpenAI, from install to one verified call.

/setup.md

What this is, in five lines

  1. Threadless AI is hosted memory for an agent that calls OpenAI (Chat Completions or Responses).
  2. Before every model call, decorate takes what is new in the conversation and returns the request body to submit.
  3. After the reply, report hands over the response; it returns at once and never throws.
  4. Old history leaves the model's view by time and use, its facts are kept, and recall brings them back when relevant.
  5. The OpenAI call stays in this codebase, with its own key; it never passes through Threadless AI.

Follow the steps in order. Each code block can be copied as is. Replace only what is marked your….

Step 1: find the model call

Find where this codebase calls chat.completions.create or responses.create, and the loop around it that handles tool calls. You will wrap that call. Note which API it uses; the steps below show both.

Step 2: install the SDK

TypeScript or JavaScript:

Shell
npm install @threadlessai/sdk

Python:

Shell
pip install threadlessai

Step 3: get a project key

Either:

Use a test key (tl_test_…) while developing. Put it in the environment, next to the OpenAI key, never in source:

Shell
THREADLESS_API_KEY=tl_test_your-key

Step 4: create a memory per conversation

One memory per conversation. Create it when the conversation is created, and store its id with the conversation (a database column, or wherever the conversation id lives).

TypeScript
import { Threadless } from '@threadlessai/sdk';

export const memory = new Threadless(); // reads THREADLESS_API_KEY

const { id: memoryId } = await memory.memories.create({ metadata: { conversation: yourConversationId } });
Python
from threadlessai import Threadless

memory = Threadless()  # reads THREADLESS_API_KEY

memory_id = memory.memories.create(metadata={"conversation": your_conversation_id})["id"]

If the user can edit a past message or branch the conversation, the edited or branched conversation gets a new memory.

Step 5: wrap the model call

Replace the call so that every request goes through decorate, and every reply through report. Pass only what is new since the last call: the user's message, or the model's reply plus the tool results. Keep the system message in the first call; it is stored and leads every request.

Chat Completions, TypeScript:

TypeScript
const request = await memory.chat.decorate(memoryId, newMessages, { model: yourModel, tools });
const reply = await openai.chat.completions.create(
  request as unknown as OpenAI.ChatCompletionCreateParamsNonStreaming,
);
memory.chat.report(memoryId, reply);

Responses, TypeScript:

TypeScript
const request = await memory.responses.decorate(memoryId, newItems, { model: yourModel, tools });
const reply = await openai.responses.create(request as OpenAI.Responses.ResponseCreateParamsNonStreaming);
memory.responses.report(memoryId, reply);

Chat Completions, Python:

Python
request = memory.chat.decorate(memory_id, new_messages, {"model": your_model, "tools": tools})
reply = openai.chat.completions.create(**request)
memory.chat.report(memory_id, reply)

Responses, Python:

Python
request = memory.responses.decorate(memory_id, new_items, {"model": your_model, "tools": tools})
reply = openai.responses.create(**request)
memory.responses.report(memory_id, reply)

Do not keep sending the full history yourself. decorate returns it.

Step 6: register the note tool

Add the note tool to the tools list of every request:

TypeScript
const tools = [...yourTools, memory.chat.noteTool]; // Responses: memory.responses.noteTool
Python
tools = [*your_tools, memory.chat.note_tool]  # Responses: memory.responses.note_tool

Step 7: answer note calls with NOTE_RESULT

In the tool-call handler, answer every call to the note tool with NOTE_RESULT (the string noted). Do not run anything for it. Then continue the loop as after any tool call.

TypeScript
import { isNoteCall, NOTE_RESULT } from '@threadlessai/sdk';

// Chat Completions
const output = isNoteCall(call.function.name) ? NOTE_RESULT : await yourRunTool(call);
newMessages.push({ role: 'tool', tool_call_id: call.id, content: output });

// Responses
const output = isNoteCall(call.name) ? NOTE_RESULT : await yourRunTool(call);
newItems.push({ type: 'function_call_output', call_id: call.call_id, output });
Python
from threadlessai import NOTE_RESULT, is_note_call

# Chat Completions
output = NOTE_RESULT if is_note_call(call.function.name) else your_run_tool(call)
new_messages.append({"role": "tool", "tool_call_id": call.id, "content": output})

# Responses
output = NOTE_RESULT if is_note_call(call.name) else your_run_tool(call)
new_items.append({"type": "function_call_output", "call_id": call.call_id, "output": output})

A complete loop for each API and language is in the quickstart.

Step 8: verify with one call

Run this with the key from step 3. It creates a memory and decorates one turn.

Shell
MEMORY_ID=$(curl -s https://api.threadlessai.com/v1/memories -X POST \
  -H "Authorization: Bearer $THREADLESS_API_KEY" -H 'content-type: application/json' -d '{}' \
  | sed -E 's/.*"id":"([^"]+)".*/\1/')

curl -s https://api.threadlessai.com/v1/memories/$MEMORY_ID/decorate \
  -H "Authorization: Bearer $THREADLESS_API_KEY" -H 'content-type: application/json' \
  -d '{"format":"chat.completions","messages":[{"role":"system","content":"You are terse."},{"role":"user","content":"Hello"}],"request":{"model":"gpt-5"}}'

Success looks like this: memory.turn is 1, and request is your request echoed back, with the history you sent. Numbers marked … vary.

JSON
{
  "request": {
    "model": "gpt-5",
    "messages": [
      { "role": "system", "content": "You are terse." },
      { "role": "user", "content": "Hello" }
    ]
  },
  "memory": {
    "id": "mem_…",
    "revision": …,
    "turn": 1,
    "notes_requested": false,
    "recalled": 0,
    "facts_in_view": 0,
    "tokens": …
  }
}

If it fails, the response says why:

  • 401 unauthorized: the key is missing, revoked, or an admin key (tla_…) rather than a project key.
  • 404 not_found: the memory id is not in this key's project.
  • 400 invalid_request: the body does not match; the message names the field.

Then run the codebase's own agent once and check that it still answers. If it does, setup is done.

Reference