Pingerchips LogoPingerchips
Chat / SessionsReference

Core Concepts

Threads

A thread is the top-level container for a conversation.

FieldTypeDescription
idUUIDUnique identifier
app_idUUIDThe Pingerchips app it belongs to
statusenumopen | bot | pending | active | resolved
titlestring?Optional display title
bot_idUUID?Agent assigned to this thread
assigned_agentstring?Human agent handle (post-handoff)
metadatamapArbitrary key-value pairs

Thread state is stored in the Durable Objects ring and replicated to Postgres. Subscribe via:

chat:v1:app:{appKey}:thread:{threadId}

Thread status flow

Loading diagram…

Messages

Real-time events carry the WAL message shape (camelCase):

FieldTypeDescription
messageIdstringClient-minted UUID
runIdstringThe run that produced this message
ownerClientIdstringclient_id of the human who triggered the run
rolestringuser | assistant
contentmapCodec-defined; { text } by default
parentIdstring?Previous message in the branch
forkOfstring?Message this forks from (an edit)
msgRegeneratestring?Message this regenerates
statusstringstreaming | complete | cancelled | error
createdAtnumberUnix ms

The Chat JSON:API returns the persisted Postgres shape, which additionally has thread_id, app_id, user_id / bot_id, type, reactions, reply_count, edited_at, deleted_at, inserted_at, updated_at.


Runs

A run is one LLM generation cycle.

Loading diagram…

During streaming, token chunks are buffered in a TokenBuffer GenServer and flushed every 40ms to the WAL as a stream:{runId} volatile slot. On run:end the final message is appended to messages and the volatile slot is dropped.


Conversation tree

Messages form a tree, not a list. parent_id links each message to the one it replies to. fork_of links a regenerated message to the original.

Loading diagram…

When a user regenerates, the server creates a new assistant message with fork_of pointing to the original. Both branches exist; the UI decides which to show.


GC compaction

The WAL grows with every write. Chat compacts on every run:end:

Loading diagram…

set_all is atomic — all preceding log entries for messages can be trimmed safely.


Load older / pagination

load_older walks backwards in pages of 50, from a before_message_id cursor.

Loading diagram…

Archive stubs are transparent to the client — it always receives a normal { messages, has_older } page.

On this page