agentdb: protocol for per-agent session storage - #1686
Merged
Conversation
livekit_agent_session_store.proto is the management API (Twirp): create, get, list and delete a session, plus Dump for a consistent download URL. livekit_agent_session_db.proto is the data plane: a request/response frame pair over one WebSocket per session, correlated by client-chosen request_id, with exec, query, atomic batch, interactive transactions, and credit-paced row batches so any result size streams with bounded memory at every hop. SessionValue mirrors SQLite's five storage classes. The other twirp files change only because protoc-gen-twirp numbers file descriptors by position in the invocation, so inserting one service shifts the ones after it.
|
Regenerated, so the same lines leave the Go doc comments.
paulwe
approved these changes
Jul 27, 2026
Gates the agent session store: creating, reading, querying, exporting and deleting a project's session databases, on both the management API and the data-plane connection. Without it that service could only verify that a token was signed by a key belonging to the project, not what it was allowed to do.
Query results were row-oriented: a SessionValue message per cell plus its oneof wrapper, which cost two allocations per cell on the server, the same again as the client unmarshalled, and left the garbage collector walking pointer-dense batches. A SessionColumnBatch carries a type tag per value (SQLite types values individually, not by column) plus one packed array per storage class, with text and blob bytes concatenated and addressed by exclusive end offsets rather than repeated string, which is what lets a decoder hand out subslices of one buffer. Measured end to end in the service, an 8000-row scan went from 47,819 allocations to 63, and a scalar-heavy batch is 43% smaller on the wire so more rows fit per credit. ListSessions now returns SessionSummary rather than GetSessionResponse, which drops tip from listings. tip advances on every commit, so a listing index cannot carry it without putting a write on the commit path, which means listing has to read every session's metadata row to fill it in. It is also stale on arrival, since anything acting on it must re-read. Everything else listing returns is immutable, so a summary can be served straight from an index. GetSession still returns tip for a single session. A separate message rather than leaving the field unset: 0 is a valid tip, for a session with no commits, so an unset field would be genuinely ambiguous.
An agent session is already a running conversation in this package's vocabulary (agent/livekit_agent_session.proto carries chat messages, function calls and handoffs), so agent.sessionAdmin read as authority over those rather than over the session store's databases, which is what it gates. The store's service is AgentSessionStore, so the grant now matches it.
They carried rationale from the implementation that motivated them, including Go allocation counts, which does not belong in a protocol every SDK reads. What is left states the contract: that values are typed per value rather than per column, how the text and blob offsets address a value, and that a summary has no tip.
Review feedback: SessionStore, SessionValue, SessionPing and CreateSessionRequest are far too generic for the flat livekit package, where they would squat on names any other service might want. Prefixing them (CSValue and friends) is noise at every call site, so these move to their own namespace instead, the way protobufs/agent already does: package livekit.agentdb, go_package livekit/agentdb. Inside it the names need no prefix at all, so SessionValue becomes Value, SessionPing becomes Ping, and SessionStoreRequest/Response become ClientMessage/ServerMessage, which says what they are in a bidirectional protocol. The same review observed the protocol reads like a cloud database rather than anything to do with sessions, which is the conclusion the lifetime question reached from the other side: a database whose TTL is unset never expires and is meant to hold an agent's memory for years, so calling it a session was already wrong. The unit is now a database: CreateDatabase, GetDatabase, ListDatabases, DeleteDatabase, database_id, and the service is AgentDB. AgentGrant.SessionStoreAdmin becomes DatabaseAdmin (json databaseAdmin). The namespace needs its own protoc invocation, since protoc-gen-twirp refuses to mix go_package values in one run.
|
Théo Monnom seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account. You have signed the CLA already but the status is still pending? Let us recheck it. |
Reverts the livekit.agentdb namespace. The review asked for encapsulation or prefixes, and a package of our own was a heavier answer than the feedback called for. Nesting gives the same encapsulation without either cost: every message lives inside a container message AgentDB, so Value, Ping, Statement and ClientMessage need no prefix and still cannot collide with anything else in the livekit package. DataStream in livekit_models.proto already does this, so it is house style rather than a new idea. Generated Go names are AgentDB_Value, AgentDB_ClientMessage and so on. Two consequences worth knowing. A service and a message cannot share a name in one proto package, so the service is AgentDBService, matching RoomService and AgentDispatchService. And a message cannot span files, so the management and wire protos merge into livekit_agentdb.proto. The eight twirp files touched are two lines each: adding a proto to the shared twirp batch renumbers twirpFileDescriptorN across the batch, so they have to be regenerated together.
davidzhao
approved these changes
Aug 5, 2026
Comments say only what the shape does not. ttl_seconds keeps its semantics (unset never expires, and expires_at_unix is 0 then) stated once rather than repeated on four messages, tip is defined once on the container, and the column encoding keeps its explanation because nothing about the field names implies it. The restatements are gone. QueryLang loses QUERY_LANG_CYPHER: the graph language is not public, so the protocol should not name it. The number is reserved so nothing reuses it.
…otos # Conflicts: # livekit/livekit_agent_simulation.twirp.go
The container held two audiences at once: the Twirp control surface and the WebSocket frames, separated only by a comment. Nesting the frames under Wire makes the split structural, so a type name says which plane it belongs to, and it stays one file with one top-level message.
No other proto here suffixes a timestamp with _unix; created_at, started_at, ended_at and token_expires_at are the shape, so created_at_unix and expires_at_unix lose the suffix. The ping fields carry milliseconds and the repo marks non-second units (timestamp_ms, started_at_ns), so they say _ms rather than leaving a reader to assume seconds. Also drops a claim about listing that is not true: ListDatabases point-reads each head today.
Nothing read it. The server checked it was well-formed JSON, stored it, and echoed it back; no path parsed, indexed or filtered on it, so it bought a column and a validation step and nothing else. A caller that needs labels can get them back as a map, with the PII annotation the rest of the repo uses, once something actually consumes them. Field numbers are renumbered rather than reserved: no client has ever spoken this protocol.
Four RPCs were <Verb>Database and one was a bare Dump; it is DumpDatabase now. Nested enums drop their redundant prefixes, since the container already namespaces them: DataStream.OperationType is CREATE, not OPERATION_TYPE_CREATE, so ValueType is NULL through BLOB and QueryLang is SQL. Generated names are AgentDB_Wire_NULL and AgentDB_Wire_SQL.
GetResponse and Summary described the same thing with four duplicated fields, and Summary's missing tip was justified by a listing that reads each head anyway. They collapse into Database, which get and list both report, the way Room is one message that create and list both return. GetDatabase returns it directly rather than through a wrapper.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.