Conversations
List, open, rename and delete threads, and generate titles and follow-up suggestions.
A thread is one conversation. Everything a chat sidebar needs takes a handful of calls, each automatically limited to the user the client acts for.
| Task | Call |
|---|---|
| Start a conversation | threads.create() |
| List the user's conversations | threads.search(...) |
| Load a transcript | threads.get_state(id) / getState(id) |
| Rename | threads.update(id, metadata=...) |
| Delete | threads.delete(id) |
| Title and follow-ups after a turn | suggestions.create_for_thread(id) / createForThread(id) |
| Home-screen starter prompts | suggestions.starters() |
List conversations
threads = client.threads.search(limit=30, sort_by="updated_at", sort_order="desc")
for t in threads:
print(t["metadata"].get("thread_name") or "New conversation", t["thread_id"])- Titles come back with the list, so a sidebar costs one request.
- Sort by
updated_aton purpose. The default iscreated_at, which buries the conversation the user just replied in. - There's no cursor and no total. Page with
limitandoffset: ask for one more row than you show, and if it comes back there's another page.
Load a transcript
state = client.threads.get_state(thread_id)
for m in state["values"]["messages"]:
print(m["type"], m["content"]) # "human", "ai" or "tool"type | Render as |
|---|---|
human | the user's message |
ai | the assistant's text; or, with empty content and a populated tool_calls, a tool call |
tool | a tool's result, paired with the call by tool_call_id. Usually best collapsed |
Use get_state, not get_history. get_history returns one checkpoint per internal step, so a two-message exchange comes back as about ten entries. It exists for debugging.
Titles and follow-ups
After each turn finishes, make one call. It generates the thread's title and three follow-up suggestions in a single model call:
result = client.suggestions.create_for_thread(thread_id)
# {"thread_name": "Capital of Kenya",
# "suggestions": [{"title": "Population?", "prompt": "What is the population of Nairobi?"}],
# "generated": True}- It's the only trigger. No background job does this. The service can't tell a finished turn from a dropped connection, so your code has to say so.
- Call it once, after the stream ends normally. Not per chunk, and not when the stream failed.
titleandpromptare different. Showtitleon the chip and sendpromptwhen it's tapped.generated: falseisn't an error. The model produced nothing usable and the stored title and suggestions were left alone.- Treat failure as cosmetic. The reply already rendered, so don't show an error toast for a missing title.
| Status | Meaning |
|---|---|
404 | the thread doesn't exist, or isn't this user's |
409 | the thread has no turns yet. Call it after a turn, not on creation |
503 | suggestions are switched off for this deployment |
Stop the title changing every turn
Each call rewrites the title unless the user renamed it. To keep refreshing suggestions while leaving the title alone, send retitle=false. The SDK method doesn't take query parameters, so make that call directly:
curl -X POST "$AICE_AGENT_URL/threads/$THREAD_ID/postturn?retitle=false" \
-H "Authorization: Bearer $AICE_API_KEY" -H "X-User-Id: $USER_ID" \
-H "Content-Type: application/json" -d '{}'Home-screen starters
Starter prompts come from the user's ten most recent thread titles, and are regenerated only when the saved set is more than six hours old:
starters = client.suggestions.starters()The response is {items: [{title, prompt}], generated_at, source, generated}. An empty items is normal for a new user. Show the empty state without chips.
Rename
There's no rename endpoint. A rename is a metadata update, and it must write titled_by: "user" in the same call:
client.threads.update(
thread_id,
metadata={"thread_name": "Quarterly review", "titled_by": "user"},
)Leave out titled_by and it looks right in testing, then the next turn's title generation overwrites the user's name. The marker is what tells it to leave the title alone.
Delete
client.threads.delete(thread_id)Deletion is permanent and takes the thread's runs and history with it. There's no trash. Ask the user to confirm first.