Skip to content

Conversation Management

Arbitex organises chat sessions into conversations. Each conversation has a title, an operating mode (Single / Compare / Summarize), a snapshot of the models it was created with, and optional organisational attributes (folder, pin, archive, project).

When a new conversation’s first message is sent, Arbitex automatically generates a concise title in a background task. The auto-title service runs independently of the response stream so it never adds latency to the chat.

  1. The first user role message is sent to a lightweight LLM (default: claude-haiku-3-20240307 via the Anthropic provider).
  2. The model receives the system prompt: “Generate a concise 3-5 word title for this conversation. Respond with only the title, no quotes or punctuation.”
  3. Only the first 500 characters of the message are forwarded to the title model (token cost optimisation). max_tokens=30, temperature=0.3.
  4. The raw response is sanitised: surrounding quotes stripped, trailing punctuation removed, truncated to 100 characters.

If the LLM is disabled, unavailable, or returns an empty response, a fallback title is derived from the first 50 characters of the message text, truncated at the nearest word boundary with "..." appended.

Examples:

First message (excerpt)Generated title
"How do I configure SCIM in Okta?"Configure SCIM Okta
"Write a Python function that..."Python Function Writing
(LLM unavailable) "Explain the difference between..."Explain the difference...

Auto-titling is controlled by two SystemConfig keys:

KeyDescriptionDefault
auto_title_enabledEnable/disable auto-titling globallytrue
auto_title_modelModel to use for title generation, format "provider/model_id""anthropic/claude-haiku-3-20240307"

Admins can change these via PUT /api/admin/config/{key}.

  • If a conversation already has a manually-set title (even an empty string set intentionally), the auto-title service returns immediately without overwriting it.
  • If auto_title_enabled is false in SystemConfig, the first-N-chars fallback title is used instead.

Users can rename any conversation they own at any time.

PATCH /api/conversations/{conversation_id}
Authorization: Bearer <token>
Content-Type: application/json
{
"title": "SCIM Okta Integration Questions"
}

Response 200 OK — the full conversation object with the updated title.

The new title replaces the existing one and persists. After a manual rename, the auto-title service will not overwrite it (it only acts when title is null or empty at the time of the first message).

Setting title to an empty string "" is allowed and results in the conversation appearing as “Untitled Conversation” in the UI.

FieldTypeDescription
idUUIDUnique conversation identifier
titlestring | nullConversation title (null until auto-titled)
mode"single" | "compare" | "summarize"Operating mode
model_config_snapshotJSONBSnapshot of models selected at creation
model_instructionsstring | nullCustom system prompt for this conversation
is_pinnedboolPinned to top of sidebar
is_archivedboolSoft-archived (hidden from default view)
folder_idUUID | nullFolder assignment
project_idUUID | nullProject cost-tagging
share_tokenstring | nullToken for public share link
parent_conversation_idUUID | nullFork source (null if not a fork)
created_atISO-8601Creation timestamp
updated_atISO-8601Last modification timestamp

Archived conversations are hidden from the default sidebar list but remain accessible via the archive view or direct URL.

PATCH /api/conversations/{conversation_id}
Authorization: Bearer <token>
Content-Type: application/json
{
"is_archived": true
}

To unarchive, send "is_archived": false.

The PATCH endpoint accepts both title and is_archived in the same request.

Pinned conversations float to the top of the sidebar regardless of recency. Pin state is toggled via the same PATCH endpoint:

PATCH /api/conversations/{conversation_id}
Content-Type: application/json
{
"is_pinned": true
}

Each message in a conversation carries a model_id field. The frontend renders a model badge alongside assistant messages to identify which model produced each response. In Compare and Summarize modes (multiple models active), each response column displays the appropriate model badge.

  • Single mode: one badge, matching the model selected for the conversation.
  • Compare mode: N badges (one per model column), rendered side-by-side.
  • Summarize mode: individual model responses are labelled with their model_id; the synthesis message carries the synthesizer model badge.

The model_config_snapshot on the conversation records which models were active at the time the conversation was created. If models are later renamed or deprecated, the badge still shows the original model_id string from the snapshot.

Each Message in the API response includes:

{
"role": "assistant",
"content": "Here is your answer...",
"model_id": "gpt-4o",
"created_at": "2026-03-12T14:22:00Z"
}

For summarizer synthesis messages, role is "summary" and model_id is the configured synthesizer model.

The conversation list endpoint returns conversations ordered by most recently updated, with pagination support.

GET /api/conversations?page=1&page_size=20&search=SCIM
Authorization: Bearer <token>

Query parameters:

ParameterTypeDefaultDescription
pageint1Page number (1-indexed)
page_sizeint20Results per page
searchstringFull-text search against title and message content (case-insensitive ILIKE)
is_archivedboolfalseInclude/exclude archived conversations
folder_idUUIDFilter to a specific folder

Search behaviour: The search parameter performs a PostgreSQL ILIKE match against the conversation title column and any message content in the conversation (CAST(content AS text)). This is a substring match — no tokenisation or stemming.

A fork creates an independent copy of a conversation from a chosen message point. The fork title is automatically prefixed with (Fork).

POST /api/conversations/{conversation_id}/fork
Authorization: Bearer <token>
Content-Type: application/json
{
"fork_message_id": "msg-uuid-here"
}

The forked conversation has parent_conversation_id set to the source conversation’s ID and fork_message_id set to the branch point. Messages up to and including the fork point are copied.

Conversations are soft-deleted: the deleted_at timestamp is set. Soft-deleted conversations do not appear in list or search results.

DELETE /api/conversations/{conversation_id}
Authorization: Bearer <token>

Response 204 No Content

Permanent deletion is not available via the user API. Admins can perform hard deletes via GDPR erasure endpoints (see Conversation Export).