All docs

Platform

Conversations and threads

How a conversation is stored, encrypted, branched, forked, and threaded.

Conversations and threads

In Platos, every chat is a thread of messages. A thread can branch (Slack-style sub-threads under a single message), fork (clone the prefix and run a different continuation), and rewind (edit a past message and re-run the agent from that point). Messages are encrypted at rest, attributed to an authorAgentId, and tagged so the dashboard can filter, archive, pin, and rename without losing audit history.

What it is

Two tables, one model:

  • PlatosThread: the parent of a conversation. Owns name, tags, pinned, archived, scope, plus pointers to the agent (or cluster) running the thread.
  • PlatosMessage: the individual messages. Carries role (user, assistant, tool, system), content (encrypted), authorAgentId (for cluster attribution), threadReplyToId (for sub-threads), attachments, cost, and timing.

Threads can be:

  • Sub-threaded via threadReplyToId. A reply with threadReplyToId: <messageId> lives in a side panel. The PRA-TC spec defines the semantics; messages can also be cross-posted into the parent stream when the agent decides so.
  • Forked. POST /agent/v1/threads/:threadId/fork clones the message prefix into a new thread; both threads share the same conversation summary but evolve independently.
  • Edited. POST /agent/v1/threads/:threadId/messages/:messageId/edit-and-rerun mutates the message and replays the agent from that point. The original suffix is dropped.

Auto-naming runs after the first user turn. The auto-name background job calls a cheap model with the first three messages and writes a 3-6 word name. You can rename at any time; auto-naming respects manual renames.

Messages are AES-256 encrypted at rest with PLATOS_MESSAGE_ENCRYPTION_KEY. The MessageCryptoService handles the round-trip; fork and edit both decrypt-then-re-encrypt transparently.

Why it matters

Conversations are the source of truth for memory extraction, evals, traces, and cost attribution. If they are lossy or unsafe, every downstream system inherits the problem. Platos's invariants:

  • Encryption at rest. The dashboard, the audit log, and the export endpoint all read decrypted in-memory; the database carries ciphertext only.
  • Audit-stable history. Soft-delete leaves the row but flips a flag; messages stay readable for compliance review.
  • Branching without aliasing. A forked thread is a copy, not a reference. The original is untouched.

That set is what lets you ship "let users edit a past prompt and rerun" without exploding cost accounting or losing the receipt of what was originally said.

How to use it

Reply in a sub-thread

await platos.threads.update({
  threadId,
  messages: [{ role: "user", content: "follow-up", threadReplyToId: parentMessageId }],
});

Or click the reply icon on a message in the chat panel; the right-side ThreadPanel opens. The agent's enableThreading config decides whether sub-threads are surfaced as a tab or inlined.

Fork a conversation

const forked = await platos.threads.fork({ threadId, atMessageId });

The new thread inherits the prefix up to (and including) atMessageId. Memory writes from the forked thread land in the same user scope; the agent does not get amnesia.

Edit and rerun

await platos.threads.edit_and_rerun({
  threadId,
  messageId,
  newContent: "What about Tuesday?",
});

The runtime re-encrypts the edited message, drops the suffix, and dispatches a fresh turn. Cost for the dropped suffix stays in the audit row; ratings stay attached to the (now archived) original message id.

Soft-delete and archive

DELETE /agent/v1/threads/:threadId flips archived: true plus deletedAt. Hard delete (admin-scope) cascades to messages, ratings, and any extracted memory rows. Use it for GDPR; otherwise prefer soft-delete.

Common pitfalls

  • Without PLATOS_MESSAGE_ENCRYPTION_KEY set to a 32-byte ASCII string, every message read returns <encrypted> and every write panics. Boot will exit if the key is missing in non-dev mode.
  • The race-fix invariant in tool-sync-ws.service.ts:130-135, 281-284 applies to streamed messages, not just tool sync. Frames received before auth completes are buffered and replayed; do not bypass.
  • Auto-naming uses a cheap model. If your project has no provider key for that model, threads stay un-named until you wire one. The cost shows up in the auto-name lane on the Costs page.
  • Forking does not duplicate attachments; both threads reference the same MinIO object. Deleting an attachment from one thread silently breaks it in the other.
  • Chat and Postman mode: the playground that reads/writes conversations from the dashboard.
  • Memory: extraction reads thread messages after a turn ends.
  • Encryption and secrets: the message-crypto key and rotation policy.
  • Traces: turn-level tracing keyed off the thread plus message ids.

Talk to Platos

Powered by the Platos runtime

Powered by Platos →