Skip to main content

Conversation threads

A thread is a person's conversation with one agent. It outlives any single session: the chat client keeps one thread while the agent's sessions start and end. Threads live on the host, under threads/<id>/ in the runtime's state directory (thread.json plus an append-only messages.jsonl), never inside a cell.

Status: the store, the HTTP API and the AG-UI endpoint are built and tested. POST /v1/agui keeps every conversation as a thread, continues it after the agent's session ended, and sends the stored messages to a reconnecting client (AGUI.md). The chat page (scripts/keep-chat.py) lists, reopens and forgets them and reopens your last conversation after a reload.

RouteWhoWhat
GET /v1/threadsuser (own) or operator (all)Newest first.
POST /v1/threadsuser or operator{"agent", "title"?, "client_thread_id"?}. A user token is always its own owner; the operator names user_id. The same client_thread_id returns the same thread for the same user.
GET /v1/threads/{id}owner or operatorThe thread.
GET /v1/threads/{id}/messages?after=<seq>&limit=<n>owner or operatorMessages with seq > after, oldest first, at most 500.
DELETE /v1/threads/{id}owner or operatorForgets the thread and every message on disk.

Isolation. Another user's thread looks like one that does not exist (404). The journal records that a thread was deleted (thread id, user, message count), never what was said. Tests: threads::tests and tenancy_tests::threads_are_private_to_their_owner.

Limits. 500 threads per user, 32 KiB per message (refused, not cut), 120-character titles, 500 messages per page.

Retention. Off by default: nothing is removed unless the operator sets a period, and threads are otherwise kept until the user forgets them. Five settings, each a whole number of days (1 to 3650; unset or 0 keeps everything):

VariableWhat it removes
ZYVOR_AGENT_THREAD_RETENTION_DAYSThreads (and their messages) untouched for that long. A thread whose session is still running is never removed.
ZYVOR_AGENT_EVENT_RETENTION_DAYSThe event log (events.jsonl) of sessions that ended that long ago. The session record, artifacts and approvals stay; reading the events of such a session returns none.
ZYVOR_AGENT_RECEIPT_RETENTION_DAYSAction receipts made that long ago. A forgotten receipt no longer answers a repeat of its idempotency key (that key can be used again), so keep this longer than any agent retries.
ZYVOR_AGENT_SUGGESTION_RETENTION_DAYSSuggestions nobody decided for that long, and decided ones that long after the decision. A forgotten dismissal can be proposed again.
ZYVOR_AGENT_MEMORY_PROPOSAL_RETENTION_DAYSMemory proposals an agent made that nobody accepted or rejected for that long. Accepted entries are never removed by this.

Separately, and with no setting: memory entries whose expiry the person set have passed are now deleted from disk (they were only hidden before). An hourly sweep applies all of this (the first one a minute after start). A sweep that removes something is journaled as keep.retention.sweep with counts only, never names or text. Tests: retention::tests, including that a running session's thread survives, that nothing is removed when no period is set, that a forgotten receipt's key is free again while a recent one still answers, that expired memory (and a stale proposal only when a period is set) is deleted from the file while accepted entries stay, and that the journal holds counts only. This is deletion from the host's files, not secure erasure: copies in backups, snapshots or the disk's free space are not touched.

Not built yet. Search and attachments in the chat page. Like the vault, the operator of the host can read this data; it is not encrypted to the user.