What this is, in five lines
- Threadless AI is hosted memory for an agent that calls OpenAI (Chat Completions or Responses).
- Before every model call,
decoratetakes what is new in the conversation and returns the request body to submit. - After the reply,
reporthands over the response; it returns at once and never throws. - Old history leaves the model's view by time and use, its facts are kept, and recall brings them back when relevant.
- 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:
npm install @threadlessai/sdkPython:
pip install threadlessaiStep 3: get a project key
Either:
- ask the user to create a key in the console at https://threadlessai.com/app and give it to you; or
- if the admin MCP server is connected (https://mcp.threadlessai.com/admin), create a project and a key with it.
Use a test key (tl_test_…) while developing. Put it in the environment, next to the OpenAI key, never in source:
THREADLESS_API_KEY=tl_test_your-keyStep 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).
import { Threadless } from '@threadlessai/sdk';
export const memory = new Threadless(); // reads THREADLESS_API_KEY
const { id: memoryId } = await memory.memories.create({ metadata: { conversation: yourConversationId } });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:
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:
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:
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:
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:
const tools = [...yourTools, memory.chat.noteTool]; // Responses: memory.responses.noteTooltools = [*your_tools, memory.chat.note_tool] # Responses: memory.responses.note_toolStep 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.
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 });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.
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.
{
"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; themessagenames the field.
Then run the codebase's own agent once and check that it still answers. If it does, setup is done.
Reference
- Docs index for agents: https://threadlessai.com/llms.txt
- All docs in one file: https://threadlessai.com/llms-full.txt
- OpenAPI spec: https://threadlessai.com/openapi/runtime.yaml