Scope
Implement the ConversationStore — the durable backing store for the /conversations endpoint. This is an internal package (internal/convstore) consumed by both cmd/conversationd (direct) and the proxy (via the convstore client or an embedded store in single-binary mode).
Design decisions
What a conversation is
A conversation is a named container for a sequence of messages. It is a higher-level concept than a response chain: a single conversation can span many response turns, each of which may have been stored in chainstore independently. The conversation store holds:
- Conversation metadata:
id, created_at, updated_at, title (optional), metadata (key-value)
- Message list: ordered append-only log of
Message values. Each message contains at minimum {role, content[]} (mirrors OpenAI Conversations API message objects).
ID allocation
Conversation IDs (conv_*) are minted by cmd/conversationd at POST /conversations time — not by the proxy. This mirrors how response IDs are assigned by the inference server. The proxy may pre-request an ID via POST /conversations before inference, or post-allocate after.
Storage key scheme (Pebble)
| Key prefix |
Value |
conv:meta:{id} |
Serialised ConversationMeta proto/JSON |
conv:msg:{id}:{seq:8} |
Serialised Message (seq is a zero-padded uint64 for lexicographic ordering) |
conv:lru:{bucket}:{id} |
Tombstone for LRU eviction index (mirrors chainstore bucket design) |
Interface
type ConversationStore interface {
Create(ctx context.Context, meta ConversationMeta) (ConversationMeta, error)
Get(ctx context.Context, id string) (ConversationMeta, error)
Delete(ctx context.Context, id string) error
AppendMessages(ctx context.Context, id string, messages []Message) (nextSeq uint64, err error)
ListMessages(ctx context.Context, id string, afterSeq uint64, limit int) ([]Message, error)
Ping(ctx context.Context) error
Close() error
}
Config mirrors chainstore.Config
type Config struct {
MaxConversations int64
MaxMessagesPerConversation int
MaxBytes int64
TTL time.Duration
TTLInterval time.Duration
EvictionInterval time.Duration
BucketDuration time.Duration
Clock Clock
Log *slog.Logger
Backend Backend // "memory" or pebble.Open()
Registerer prometheus.Registerer
}
Backends
- In-memory (
convstore/memory): sync.RWMutex-protected maps; suitable for tests and the store: false-style use case. No TTL persistence.
- Pebble (
convstore/pebble): reuses the same Pebble DB opened by chainstore (passed in via Backend) or opens its own. Separate key prefix ensures no collision with chainstore keys.
Capacity management
- LRU eviction when
MaxConversations or MaxBytes is exceeded (same bucket-based eviction as chainstore)
- Background TTL reaper (configurable interval)
MaxMessagesPerConversation cap: AppendMessages returns ErrConversationFull when exceeded
Acceptance criteria
Blocked by: #77
Scope
Implement the
ConversationStore— the durable backing store for the/conversationsendpoint. This is an internal package (internal/convstore) consumed by bothcmd/conversationd(direct) and the proxy (via theconvstoreclient or an embedded store in single-binary mode).Design decisions
What a conversation is
A conversation is a named container for a sequence of messages. It is a higher-level concept than a response chain: a single conversation can span many response turns, each of which may have been stored in
chainstoreindependently. The conversation store holds:id,created_at,updated_at,title(optional),metadata(key-value)Messagevalues. Each message contains at minimum{role, content[]}(mirrors OpenAI Conversations API message objects).ID allocation
Conversation IDs (
conv_*) are minted bycmd/conversationdatPOST /conversationstime — not by the proxy. This mirrors how response IDs are assigned by the inference server. The proxy may pre-request an ID viaPOST /conversationsbefore inference, or post-allocate after.Storage key scheme (Pebble)
conv:meta:{id}ConversationMetaproto/JSONconv:msg:{id}:{seq:8}Message(seq is a zero-padded uint64 for lexicographic ordering)conv:lru:{bucket}:{id}chainstorebucket design)Interface
Config mirrors chainstore.Config
Backends
convstore/memory):sync.RWMutex-protected maps; suitable for tests and thestore: false-style use case. No TTL persistence.convstore/pebble): reuses the same Pebble DB opened bychainstore(passed in viaBackend) or opens its own. Separate key prefix ensures no collision with chainstore keys.Capacity management
MaxConversationsorMaxBytesis exceeded (same bucket-based eviction as chainstore)MaxMessagesPerConversationcap:AppendMessagesreturnsErrConversationFullwhen exceededAcceptance criteria
internal/convstorepackage compiles with both in-memory and Pebble backendsConversationStoreinterface covers Create / Get / Delete / AppendMessages / ListMessagesconvstore/testutil)MaxConversations, verify oldest evictedMaxMessagesPerConversationcap returns correct errormake presubmitpassesBlocked by: #77