Repository navigation
agent threads: thread messages and grant, agent-db service to gRPC - #1841
Conversation
AgentThread messages for the durable conversation an agent has with a person, AgentThreadGrant under the agentThread claim, and the AT_ and AST_ id prefixes. The agent-db and thread services are internal gRPC in cloud-protocol, so the AgentDBService Twirp service is removed. A database lives in its project's data region: AgentDB.CreateRequest.region is reserved.
🦋 Changeset detectedLatest commit: 18e535d The changes in this PR will be included in the next version bump. This PR includes changesets to release 2 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Keys stay readable in logs, which is what debugging a lookup needs.
One agent can expose several endpoints (sms, chat, voice). Endpoints names the ones the holder may call; empty allows all. Threads stay per agent, so every endpoint continues the same thread.
| // endpoints the holder may call; empty allows all of them. | ||
| type AgentThreadGrant struct { | ||
| AgentName string `json:"agentName"` | ||
| Sub string `json:"sub,omitempty"` |
There was a problem hiding this comment.
is this Subject? or Subscription?
There was a problem hiding this comment.
It’s Subject, the idea is that you can “group” some threads based on whatever key you want.
This is just there for very simple use cases like: list all the threads from my user (and in this case user is in sub)
There was a problem hiding this comment.
Will rename to Subject
There was a problem hiding this comment.
should this be a user identity or some sort? since we use identity in RTC land
There was a problem hiding this comment.
Yeah i can use Identity, but the thing is that it isn’t necessarily a “Human” ?
There was a problem hiding this comment.
It’s also used in the scope. You can give a JWT token to the client side. With this Subject. And it can read/list the threads.
But I expect most ppl to write their own backend when their usecase becomes more advanced
| // or by id, with per-action flags. Endpoints limits which of the agent's | ||
| // endpoints the holder may call; empty allows all of them. | ||
| type AgentThreadGrant struct { | ||
| AgentName string `json:"agentName"` |
There was a problem hiding this comment.
should this be here? is a thread a piece of data? or is it tied to a specific agent?
it's worth keeping in mind that I may have two separate agents that is capable of picking up the same convo.
There was a problem hiding this comment.
Good question, so you can have multiple agents picking up the same conversation. But they must share the same agent_name.
remember that with text mode, an agent is agent_name/{endpoint}
| AgentName string `json:"agentName"` | ||
| Sub string `json:"sub,omitempty"` | ||
| ThreadIDs []string `json:"threadIds,omitempty"` | ||
| Endpoints []string `json:"endpoints,omitempty"` |
There was a problem hiding this comment.
how does this mesh with agentEndpoint grant? what is the relation here?
| // When set, create is get-or-create on (agent_name, key). | ||
| string key = 2; | ||
| // The subject the thread belongs to, as scoped by an AgentThreadGrant. | ||
| string sub = 3 [(logger.sensitivity) = SENSITIVITY_PII]; |
There was a problem hiding this comment.
does the end user provide subject? or is it something that we generate? how is this determined?
There was a problem hiding this comment.
The end user provide it
| message DeleteResponse {} | ||
|
|
||
| // Creates the thread's database if it has none. Idempotent. | ||
| message EnsureStateDatabaseRequest { |
There was a problem hiding this comment.
not sure about naming.. maybe
| message EnsureStateDatabaseRequest { | |
| message CreateThreadDatabaseRequest { |
| } | ||
|
|
||
| // Marks a thread as merged into redirect_to. | ||
| message RedirectRequest { |
There was a problem hiding this comment.
| message RedirectRequest { | |
| message MergeThreadRequest { |
|
|
||
| message Thread { | ||
| string thread_id = 1 [(logger.name) = "threadID"]; | ||
| string agent_name = 2; |
There was a problem hiding this comment.
same as earlier comment.. might not want to sticky to agent
| map<string, string> attributes = 5 [(logger.sensitivity) = SENSITIVITY_PII]; | ||
| int64 created_at = 6; | ||
| int64 last_active = 7; | ||
| int64 expires_at = 8; |
There was a problem hiding this comment.
how does expires_at play with idle_ttl_seconds?
what is the unit for time?
There was a problem hiding this comment.
Will use google.protobuf.Duration/Timestamp
expires_at = last_active + idle_ttl. It's a sliding expiry: every touch or send pushes it out by the idle TTL, and the thread is swept once it passes.
| message AgentDB { | ||
| message CreateRequest { | ||
| string region = 1; | ||
| // A database lives in its project's data region. |
…e and MergeThread From review: sub is spelled subject (grant and messages), times are google.protobuf.Timestamp/Duration (threads and the AgentDB lifecycle), EnsureStateDatabase is CreateThreadDatabase, Redirect is MergeThread with merged_into on the thread.
The thread grant covers thread data only, by scope or id across the project's agents: no agent name and no endpoints, so it never opens a non-public endpoint (agentEndpoint does that). Send is write. The thread's subject is its scope.
|
|
||
| // Allows reports whether a thread is in the grant, by scope or by id. An empty | ||
| // Scope or thread id never matches; the caller checks the flags. | ||
| func (s *AgentThreadGrant) Allows(scope, threadID string) bool { |
There was a problem hiding this comment.
grant.CanRead(threadID)
grant.CanList(scope)
grant.CanDelete(threadID)
grant.CanWrite(threadID)
One check per action. Per-thread checks take the thread, since a grant matches by scope or by id; create needs only the grant, which creates in its own scope.
A conversation is not owned by one agent: any agent of the project can pick it up. Keys resolve per project, listing is by scope, and GetRequest looks up by thread id or key.
… touch Keys are internal (LiveKit phone numbers): one per thread, set at create. Removes AddKey, RemoveKey, Touch and CreateThreadDatabase; an update counts as activity and the database comes with the thread.
…rotocol into theo/agent-threads-api
…ork state apart; items drop type
…rotocol into theo/agent-threads-api
A Batch answers one result per statement, in order: Columns and ColumnBatch frames for a statement that returns rows, one ExecResult for one that does not, then a single Done for the whole batch. The new statement field on Columns, ColumnBatch and ExecResult is the 0-based index within the batch; a plain Exec or Query leaves it 0. (cherry picked from commit 39517ac)
agent/livekit_agent_thread.proto, one file:AgentThread: a thread (AT_) with scope, attributes, idle TTL and the database created with it; create, get, list, update, delete; items asChatContext.ChatItem, oldest firstAgentTask: background work an agent runs in a thread (name,status: running, completed, failed, canceled); all of a thread's, optionally by statuslivekit/agent/thread_schema_v1.sql(embedded asagent.ThreadSchemaV1): the conversation tables the agents framework writes and the platform readsauth.AgentThreadGrant(agentThreadclaim):scope,threadIds,all, list/create/write/read/delete;CanCreate(scope)andCanList(scope)by scope,CanRead,CanWrite,CanDelete(threadID)by id (e.g. a frontend rendering one conversation);allextends it to every thread of the project with the action flags still applying, andCanUpdate()needs itAT_(thread) andAST_(agent stream)AgentDBServiceTwirp removed (served as gRPC from cloud-protocol);AgentDB.CreateRequest.regionremoved;DumpRequest/DumpResponserenamedExportRequest/ExportResponse; times areTimestamp/DurationAgentDBbatches answer per statement:statementindex onColumns,ColumnBatchandExecResult