conversation operations
The exact fields each conversation.* operation takes and returns.
Every op is POST /v1/<op> over HTTP and the MCP tool loom_<op>. The identity fields (org, namespace, agent, user_id, session_id) are filled from the token and the MCP session when a transport can; for an HTTP call with no token on a single-user server, the install's org on every op but code.* and tracker.* (which keep their plane's), and on some ops its namespace and agent; see identities and namespaces. The operations index explains role, kind and the MCP all listing. Unknown input fields are served and named in ignored_args.
conversation.append
Log one chat turn (role + content) to a thread.
POST /v1/conversation.append · MCP loom_conversation_append · role writer · idempotent
| Input field | Type | Default | Meaning |
|---|---|---|---|
org |
str |
required | The tenant (first element of the identity tuple). |
namespace |
str |
required | The namespace the op reads or writes. |
agent |
str |
required | The writing or reading agent; '-' or '*' means any agent where the op allows it. |
thread_id |
str |
required | The thread to append to; it need not have been created first. |
msg_id |
str |
'' |
optional; server derives a content hash when omitted |
role |
Literal['user', 'assistant', 'system', 'tool'] |
required | Who spoke: user, assistant, system or tool. |
content |
str |
required | The message text, stored verbatim; a user or assistant turn is also embedded for conversation.search, unless its text reads as high. |
reasoning |
str |
'' |
The model's reasoning for this turn, stored beside content; empty = none. |
user_id |
str |
'-' |
The end user the row belongs to; '-' for none. |
run_id |
str |
'-' |
The agent run (task scope) the call belongs to; '-' for none. |
metadata |
dict[str, Any] |
(computed) | Free-form JSON stored on the message; read back by get and list. |
tokens |
int |
0 |
Token count the caller reports for the message, stored as given; 0 = not reported. |
| Output field | Type | Meaning |
|---|---|---|
msg_id |
str |
The message id: the one sent, or the content hash the server derived. |
ts |
datetime |
Server time of this call; the message's timestamp when it was written. |
stored |
bool |
False when recording is off or the turn was removed while embedding ran. |
reason |
Literal['', 'duplicate', 'logging_disabled', 'deleted'] |
"" (written), "duplicate" (this msg_id already landed; stored=True), "logging_disabled" or "deleted" (stored=False). |
conversation.compact
Summarize old turns into one embedded system turn and tombstone the originals.
POST /v1/conversation.compact · MCP loom_conversation_compact · role writer · idempotent · audited · not listed under the MCP all toolset
| Input field | Type | Default | Meaning |
|---|---|---|---|
org |
str |
required | The tenant (first element of the identity tuple). |
namespace |
str |
required | The namespace the op reads or writes. |
agent |
str |
required | The writing or reading agent; '-' or '*' means any agent where the op allows it. |
thread_id |
str |
required | The thread to compact. |
keep_recent |
int |
10 |
The newest this many messages stay verbatim; older ones are summarized, as many as fit the summarizer's prompt. |
min_to_compact |
int |
4 |
Skip when fewer new turns than this precede the last keep_recent; an earlier summary does not count. |
| Output field | Type | Meaning |
|---|---|---|
compacted |
bool |
True when a summary was written and the originals tombstoned; reason says why not. |
messages_summarized |
int |
How many older messages the summary replaced, not counting an earlier summary; 0 when not compacted. |
summary_msg_id |
str |
Id of the synthetic system summary message; empty when not compacted. |
tokens_in |
int |
Prompt tokens the summarizer LLM call used; 0 when no call was made. |
tokens_out |
int |
Completion tokens of the summary; 0 when no call was made. |
reason |
str |
Why compacted=False: nothing-to-compact, below-min-to-compact, already-compacted, summarizer-empty, embed-failed (retry once the embedder answers), summary-high-sensitivity (do not retry: the same window gets the same summary). |
conversation.create
Open a thread container.
POST /v1/conversation.create · MCP loom_conversation_create · role writer · idempotent · not listed under the MCP all toolset
| Input field | Type | Default | Meaning |
|---|---|---|---|
org |
str |
required | The tenant (first element of the identity tuple). |
namespace |
str |
required | The namespace the op reads or writes. |
agent |
str |
required | The writing or reading agent; '-' or '*' means any agent where the op allows it. |
thread_id |
str |
required | The caller's id for the thread; re-creating the same id replaces its title, metadata and run_id. |
title |
str |
'' |
Display title shown in thread lists; empty = untitled. |
metadata |
dict[str, Any] |
(computed) | Free-form JSON stored on the thread row; conversation.get returns it. |
run_id |
str |
'-' |
The agent run (task scope) the call belongs to; '-' for none. |
user_id |
str |
'-' |
The end user the row belongs to; '-' for none. |
| Output field | Type | Meaning |
|---|---|---|
thread_id |
str |
Echo of the input thread_id. |
created_at |
datetime |
When this call wrote the thread row. |
conversation.delete
Soft-delete one thread: its messages, thread row and embeddings no other agent holds.
POST /v1/conversation.delete · MCP loom_conversation_delete · role admin · idempotent · audited · not listed under the MCP all toolset
| Input field | Type | Default | Meaning |
|---|---|---|---|
org |
str |
required | The tenant (first element of the identity tuple). |
namespace |
str |
required | The namespace the op reads or writes. |
agent |
str |
required | The writing or reading agent; '-' or '*' means any agent where the op allows it. |
thread_id |
str |
required | The thread to soft-delete. |
confirm_thread_id |
str |
required | Must equal thread_id, or the call fails with 400 and nothing is deleted. |
| Output field | Type | Meaning |
|---|---|---|
tombstoned_at |
datetime |
The tombstone time stamped on every affected row. |
thread_id |
str |
Echo of the input thread_id. |
rows_affected |
dict[str, int] |
{table_name: rows tombstoned}; for loom.conversation_embeddings, turns (one per turn however many embedding models hold it). |
total |
int |
Sum of rows_affected across tables. |
conversation.get
Fetch one thread by id: thread metadata + optional messages.
POST /v1/conversation.get · MCP loom_conversation_get · role viewer · read · not listed under the MCP all toolset
| Input field | Type | Default | Meaning |
|---|---|---|---|
org |
str |
required | The tenant (first element of the identity tuple). |
namespace |
str |
required | The namespace the op reads or writes. |
agent |
str |
required | "" or "-" reads every agent's turns in the thread (a turn two agents both logged once), for a caller that has only its id, as conversation.search reads "" and "-"; any other value reads that one agent's. |
thread_id |
str |
required | The thread to fetch. |
include_messages |
bool |
False |
Default off: keeps the metadata lookup to two point reads. |
limit |
int |
100 |
At most this many messages when include_messages is set (1-1000). |
offset |
int |
0 |
Messages to skip from the end order counts from. |
order |
Literal['asc', 'desc'] |
'asc' |
Which end of the thread the page is counted from. |
| Output field | Type | Meaning |
|---|---|---|
found |
bool |
False when the thread has neither a thread row nor any live message. |
thread_id |
str |
Echo of the input thread_id. |
title |
str |
Title from conversation.create or update; empty when none was set. |
metadata |
dict[str, Any] |
Thread metadata JSON; {} when none was set, apart from the titler's title_refused mark. |
run_id |
str |
The agent run (task scope) the call belongs to; '-' for none. |
user_id |
str |
The end user the row belongs to; '-' for none. |
agent |
str |
The thread's owner (the agent of its thread row); for a thread built by appends only, the latest writer among the messages read, so empty without include_messages. |
created_at |
datetime \| None |
When the thread row was first written; None for a thread built by appends only. |
updated_at |
datetime \| None |
When the thread row last changed; None for a thread built by appends only. |
msg_count |
int |
Live (not tombstoned) messages in the thread, returned even without messages. |
messages |
list[_ConversationEntry] |
Populated only when include_messages=True; simple limit/offset slicing (cursor-paginated walks belong on conversation.list). |
next_cursor |
str \| None |
With order="asc", the conversation.list cursor for the turns after this page; None when the page reaches the end, or the order is desc. |
conversation.list
Enumerate stored conversation threads for an agent / namespace.
POST /v1/conversation.list · MCP loom_conversation_list · role viewer · read · not listed under the MCP all toolset
| Input field | Type | Default | Meaning |
|---|---|---|---|
org |
str |
required | The tenant (first element of the identity tuple). |
namespace |
str |
required | The namespace the op reads or writes. |
agent |
str |
required | "" or "-" reads every agent's turns in the thread (a turn two agents both logged once), for a caller that has only its id, as conversation.search reads "" and "-"; any other value reads that one agent's. |
thread_id |
str |
required | The thread to page through, oldest message first. |
run_id |
str \| None |
None |
optional filter; None or "-" = every run |
limit |
int |
100 |
At most this many messages per page (1-1000). |
cursor |
str \| None |
None |
opaque (ts, msg_id) |
| Output field | Type | Meaning |
|---|---|---|
rows |
list[_ConversationEntry] |
The page of messages in chronological order. |
next_cursor |
str \| None |
Pass as cursor for the next page; None when this is the last page. |
conversation.list_saved_messages
Page through the messages a user saved, newest first, with no query: the first view of a "Saved" panel before anything is typed.
POST /v1/conversation.list_saved_messages · MCP loom_conversation_list_saved_messages · role viewer · read · not listed under the MCP all toolset
| Input field | Type | Default | Meaning |
|---|---|---|---|
org |
str |
required | The tenant (first element of the identity tuple). |
namespace |
str |
required | The namespace the op reads or writes. |
agent |
str |
required | The writing or reading agent; '-' or '*' means any agent where the op allows it. |
user_id |
str |
required | Only this user's saved messages are listed. |
conversation_id |
str \| None |
None |
Optional: only the messages saved from one conversation. |
limit |
int |
50 |
At most this many messages per page (1-200). |
cursor |
str \| None |
None |
Opaque; pass the previous page's next_cursor. |
fields |
list[str] \| None |
None |
The output fields the caller reads (a whitelist resolved against the row model; an unknown name is a 422); unset, every field. |
| Output field | Type | Meaning |
|---|---|---|
rows |
list[_SavedMessageHit] |
The page, most recently saved first. |
next_cursor |
str \| None |
Pass as cursor for the next page; None when this is the last page. |
conversation.save_message
Save one chat message a user picked for search, whether or not the namespace records transcripts.
POST /v1/conversation.save_message · MCP loom_conversation_save_message · role writer · idempotent · not listed under the MCP all toolset
| Input field | Type | Default | Meaning |
|---|---|---|---|
org |
str |
required | The tenant (first element of the identity tuple). |
namespace |
str |
required | The namespace the op reads or writes. |
agent |
str |
required | The writing or reading agent; '-' or '*' means any agent where the op allows it. |
conversation_id |
str |
required | The client's conversation id; this remains the thread id in Loom. |
message_id |
str |
required | The client's stable message id. |
role |
Literal['user', 'assistant'] |
required | Only selectable user and assistant messages are embedded. |
content |
str |
required | The message text; ignored when the transcript already holds the message. |
user_id |
str |
required | The owner is a hard filter on the paired search operation. |
| Output field | Type | Meaning |
|---|---|---|
conversation_id |
str |
|
message_id |
str |
|
indexed |
bool |
True when the turn is saved and has a vector the saved search ranks: a new write, a re-save (finishing what an interrupted save left behind), and a transcript turn that is now flagged saved. |
reason |
Literal['', 'duplicate', 'owner_mismatch', 'deleted', 'unsupported_role', 'embed_failed', 'high_sensitivity'] |
"duplicate": already saved, nothing changed. "owner_mismatch": the stored turn belongs to another user. "deleted": the stored turn was tombstoned. "unsupported_role": the stored turn is a system or tool turn, which is not embedded.… |
ts |
datetime |
The stored turn's timestamp; the time of the refusal when one is refused. |
conversation.saved_status
Which of a conversation's messages a user has saved, in one call: a chat client draws every message's saved state when it opens the conversation.
POST /v1/conversation.saved_status · MCP loom_conversation_saved_status · role viewer · read · not listed under the MCP all toolset
| Input field | Type | Default | Meaning |
|---|---|---|---|
org |
str |
required | The tenant (first element of the identity tuple). |
namespace |
str |
required | The namespace the op reads or writes. |
agent |
str |
required | The writing or reading agent; '-' or '*' means any agent where the op allows it. |
user_id |
str |
required | The end user the row belongs to; '-' for none. |
conversation_id |
str |
required | The conversation the messages belong to; message ids are unique within it. |
message_ids |
list[str] |
required | The messages to check (1-500). |
| Output field | Type | Meaning |
|---|---|---|
saved |
list[str] |
The subset of message_ids this user has saved, in the order asked. |
conversation.search
Rank conversation messages by embedding similarity to query.
POST /v1/conversation.search · MCP loom_conversation_search · role viewer · read · not listed under the MCP all toolset
| Input field | Type | Default | Meaning |
|---|---|---|---|
org |
str |
required | The tenant (first element of the identity tuple). |
namespace |
str |
required | The namespace the op reads or writes. |
agent |
str |
required | "*" or "-" searches every agent's turns; any other value, that one agent's. |
query |
str |
required | Text matched against message content, by embedding similarity, or by substring when the embedder is unavailable. |
thread_id |
str \| None |
None |
optional: scope to one thread |
run_id |
str \| None |
None |
optional: filter by run_id; None or "-" = every run |
role |
Literal['user', 'assistant', 'system', 'tool'] \| None |
None |
Filter by speaker; system turns other than compaction summaries, and tool turns, are not embedded, so they match only on the substring fallback. |
user_id |
str \| None |
None |
Optional owner filter; None or "-" = every user. |
top_k |
int |
20 |
At most this many hits (1-200). |
namespace_scope |
Literal['this', 'all_mine'] |
'all_mine' |
"all_mine" also reads the caller's own namespaces, up to memory_search_fanout_max_namespaces, as knowledge.search does. |
| Output field | Type | Meaning |
|---|---|---|
hits |
list[_ConversationHit] |
Matching messages, most similar first and empty when none clears the similarity floor; newest first, with no floor, on the substring fallback. |
namespaces_skipped |
list[str] |
Namespaces of the caller an all_mine search did not read because it stops at memory_search_fanout_max_namespaces; address one by name to search it. |
conversation.search_saved_messages
Hybrid search over only the messages one user saved with conversation.save_message, not the whole transcript.
POST /v1/conversation.search_saved_messages · MCP loom_conversation_search_saved_messages · role viewer · read · not listed under the MCP all toolset
| Input field | Type | Default | Meaning |
|---|---|---|---|
org |
str |
required | The tenant (first element of the identity tuple). |
namespace |
str |
required | The namespace the op reads or writes. |
agent |
str |
required | The writing or reading agent; '-' or '*' means any agent where the op allows it. |
user_id |
str |
required | The end user the row belongs to; '-' for none. |
query |
str |
required | |
conversation_id |
str \| None |
None |
|
role |
Literal['user', 'assistant'] \| None |
None |
|
top_k |
int |
20 |
|
search_mode |
Literal['hybrid', 'semantic', 'lexical'] |
'hybrid' |
hybrid: the vector leg and a BM25 leg over each message's whole text, fused by reciprocal rank; semantic: vector only; lexical: BM25 only. |
lexical_weight |
float |
1.0 |
Weight of the BM25 leg in the fusion (hybrid mode); 1.0 = equal weight. |
| Output field | Type | Meaning |
|---|---|---|
hits |
list[_SavedMessageHit] |
conversation.set_logging
Toggle whether this namespace records conversation transcripts.
POST /v1/conversation.set_logging · MCP loom_conversation_set_logging · role admin · idempotent · audited · not listed under the MCP all toolset
| Input field | Type | Default | Meaning |
|---|---|---|---|
org |
str |
required | The tenant (first element of the identity tuple). |
namespace |
str |
required | The namespace the op reads or writes. |
enabled |
bool |
required | True records transcripts in this namespace; False makes append store nothing. |
| Output field | Type | Meaning |
|---|---|---|
namespace |
str |
The namespace the op reads or writes. |
enabled |
bool |
The logging switch as it now stands. |
conversation.threads
Recent conversation threads of a namespace, newest first.
POST /v1/conversation.threads · MCP loom_conversation_threads · role viewer · read
| Input field | Type | Default | Meaning |
|---|---|---|---|
org |
str |
required | The tenant (first element of the identity tuple). |
namespace |
str |
required | The namespace the op reads or writes. |
agent |
str |
'*' |
"*" or "-" lists every agent's threads |
window_days |
int |
30 |
Only threads with a live message in the last this many days (1-365). |
limit |
int |
30 |
At most this many threads (1-200). |
offset |
int |
0 |
Threads to skip before the page (0-100,000); default 0, the first page. |
fields |
list[str] \| None |
None |
The output fields the caller reads (a whitelist resolved against the row model; an unknown name is a 422); unset, every field. |
| Output field | Type | Meaning |
|---|---|---|
threads |
list[ConversationThreadSummary] |
Threads, most recently active first, each with its count and last message excerpt. |
conversation.unsave_message
Withdraw one message from a user's saved-message search.
POST /v1/conversation.unsave_message · MCP loom_conversation_unsave_message · role writer · idempotent · not listed under the MCP all toolset
| Input field | Type | Default | Meaning |
|---|---|---|---|
org |
str |
required | The tenant (first element of the identity tuple). |
namespace |
str |
required | The namespace the op reads or writes. |
agent |
str |
required | The writing or reading agent; '-' or '*' means any agent where the op allows it. |
conversation_id |
str |
required | The client's conversation id, as given to conversation.save_message. |
message_id |
str |
required | The client's message id, as given to conversation.save_message. |
user_id |
str |
required | Only this user's saved row is withdrawn. |
| Output field | Type | Meaning |
|---|---|---|
conversation_id |
str |
|
message_id |
str |
|
found |
bool |
False when no saved row of this user exists: never saved, already withdrawn, or saved by another user (which is not confirmed). |
removed |
bool |
True when the turn itself was removed: the namespace does not record transcripts, so the row existed only because of the save. |
ts |
datetime |
conversation.update
Update one thread's title/metadata/run_id.
POST /v1/conversation.update · MCP loom_conversation_update · role writer · idempotent · not listed under the MCP all toolset
| Input field | Type | Default | Meaning |
|---|---|---|---|
org |
str |
required | The tenant (first element of the identity tuple). |
namespace |
str |
required | The namespace the op reads or writes. |
agent |
str |
required | The writing or reading agent; '-' or '*' means any agent where the op allows it. |
thread_id |
str |
required | The thread to update; an unknown thread returns updated=False. |
title |
str \| None |
None |
Handler raises 400 if all three are absent (no-op signals a caller bug). |
metadata |
dict[str, Any] \| None |
None |
New metadata JSON, replacing the old object whole; None leaves it unchanged. |
run_id |
str \| None |
None |
The thread's new run; None or "-" leaves it unchanged, "" detaches it from its run. |
| Output field | Type | Meaning |
|---|---|---|
updated |
bool |
False when the thread has no thread row (never created); nothing is written then. |
thread_id |
str |
Echo of the input thread_id. |
updated_at |
datetime \| None |
The new updated_at of the thread; None when nothing was updated. |