client.threads gives you
both from one call.
flush(), and
idle threads are swept in — where they’re distilled and become
pull-able across every conversation.
The contract
appendreturns the turn’sseq. The write is verifiable, not fire-and-forget. Retries are safe: each logical turn carries an idempotency key, so a timeout plus a retry can never store the same turn twice. If your code retries a failed call, pass the sameturn_key.readis instant and always bounded — default 50 turns, max 500, or atoken_budget. A cut window setstruncated: true; it never masquerades as a short conversation.window.flushed_throughis the last turn already promoted to long-term memory.listrecovers your thread ids after a restart. History is server state, not process state — a fresh client on the samethread_idresumes the conversation.flushpromotes now;clearends a conversation. Clear flushes first (nothing is ever silently discarded), wipes the working window, and never reuses a sequence number.- Roles are
userandassistant. Map anything else (system,tool) before appending, or drop it. thread_idis yours: any stable id up to 200 chars of[A-Za-z0-9._:-], usually your own conversation id.
XysqAgent is built on exactly this — if you want the loop run for you too,
see the agent.
Bring your own transcript store
If you already keep the conversation yourself (your own Postgres, your own context window) and only want the long-term half, skipthreads and push
turns directly. Two rules make it work well:
Group the conversation with a session_id — a stable id per
conversation, so repeated pushes append to one document instead of
fragmenting into many:
client.threads, which
formats turns for you):
format: "turns"tells us to read the content as a dialogue. Turns areuser:andagent:lines — those two prefixes exactly. RelabelHuman:/Assistant:or anything else before pushing, or the payload is treated as one block of prose.- Anything before the first
user:/agent:line is not stored. Put dates or session context inside a turn, or push them separately. - Only
user:andagent:are recognised. Asystem:line is not a turn — map or filter it explicitly, or that content is silently absent later.
session_id values starting with
thread: belong to the checkpointer, and a push claiming one is rejected —
so your manual pushes and a thread can never write into the same document.
Which one am I?
- Building an agent and happy for xysq to hold the conversation →
client.threads. Working memory and long-term memory from one call. - Already have a transcript store you trust (your own DB, your own window
assembly) → manual pushes with
session_id+format: "turns"+ non-overlapping deltas. You keep your recent-turn layer; xysq carries everything older and everything cross-conversation.
pull returns ranked hits from across the
vault, current conversation and earlier ones together. Content becomes
pull-able once background processing finishes — the thread window is the
thing that’s instant.