Documentation
¶
Overview ¶
Package api is the conductor's own HTTP surface: the AgentService handlers, the bearer that guards them, and the health and metrics endpoints. There is no other way in — the listener is loopback by default and podium-server is what a browser reaches it through.
Index ¶
- Constants
- func Infra(ctx context.Context) bool
- func Login(ctx context.Context) string
- func NewTurnREST(turns *TurnService, gitcred *GitCredentialService, prMCP http.Handler) http.Handler
- func ReloadProfile(ctx context.Context, st *store.Store, live *profiles.Live) (*profiles.Profile, error)
- func ReloadProfileDir(ctx context.Context, st *store.Store, live *profiles.Live, dir string) (*profiles.Profile, error)
- func RequireBearer(token string, next http.Handler) http.Handler
- func SeesAll(ctx context.Context) bool
- type AgentService
- func (s *AgentService) AttachChatPullRequest(ctx context.Context, ...) (*connect.Response[agentv1.AttachChatPullRequestResponse], error)
- func (s *AgentService) ClearGitHubConnection(ctx context.Context, _ *connect.Request[agentv1.ClearGitHubConnectionRequest]) (*connect.Response[agentv1.ClearGitHubConnectionResponse], error)
- func (s *AgentService) ClearMcpServerToken(ctx context.Context, req *connect.Request[agentv1.ClearMcpServerTokenRequest]) (*connect.Response[agentv1.ClearMcpServerTokenResponse], error)
- func (s *AgentService) ClearProviderKey(ctx context.Context, req *connect.Request[agentv1.ClearProviderKeyRequest]) (*connect.Response[agentv1.ClearProviderKeyResponse], error)
- func (s *AgentService) ClearSlackConnection(ctx context.Context, _ *connect.Request[agentv1.ClearSlackConnectionRequest]) (*connect.Response[agentv1.ClearSlackConnectionResponse], error)
- func (s *AgentService) CompleteGitHubOAuth(ctx context.Context, req *connect.Request[agentv1.CompleteGitHubOAuthRequest]) (*connect.Response[agentv1.CompleteGitHubOAuthResponse], error)
- func (s *AgentService) CompleteMcpOAuth(ctx context.Context, req *connect.Request[agentv1.CompleteMcpOAuthRequest]) (*connect.Response[agentv1.CompleteMcpOAuthResponse], error)
- func (s *AgentService) CreateChat(ctx context.Context, req *connect.Request[agentv1.CreateChatRequest]) (*connect.Response[agentv1.CreateChatResponse], error)
- func (s *AgentService) CreateMcpServer(ctx context.Context, req *connect.Request[agentv1.CreateMcpServerRequest]) (*connect.Response[agentv1.CreateMcpServerResponse], error)
- func (s *AgentService) CreatePersonality(ctx context.Context, req *connect.Request[agentv1.CreatePersonalityRequest]) (*connect.Response[agentv1.CreatePersonalityResponse], error)
- func (s *AgentService) CreatePlaybook(ctx context.Context, req *connect.Request[agentv1.CreatePlaybookRequest]) (*connect.Response[agentv1.CreatePlaybookResponse], error)
- func (s *AgentService) DeleteChat(ctx context.Context, req *connect.Request[agentv1.DeleteChatRequest]) (*connect.Response[agentv1.DeleteChatResponse], error)
- func (s *AgentService) DeleteMcpServer(ctx context.Context, req *connect.Request[agentv1.DeleteMcpServerRequest]) (*connect.Response[agentv1.DeleteMcpServerResponse], error)
- func (s *AgentService) DeleteMemory(ctx context.Context, req *connect.Request[agentv1.DeleteMemoryRequest]) (*connect.Response[agentv1.DeleteMemoryResponse], error)
- func (s *AgentService) DeletePersonality(ctx context.Context, req *connect.Request[agentv1.DeletePersonalityRequest]) (*connect.Response[agentv1.DeletePersonalityResponse], error)
- func (s *AgentService) DeletePlaybook(ctx context.Context, req *connect.Request[agentv1.DeletePlaybookRequest]) (*connect.Response[agentv1.DeletePlaybookResponse], error)
- func (s *AgentService) DeleteSkill(ctx context.Context, req *connect.Request[agentv1.DeleteSkillRequest]) (*connect.Response[agentv1.DeleteSkillResponse], error)
- func (s *AgentService) DetachChatPullRequest(ctx context.Context, ...) (*connect.Response[agentv1.DetachChatPullRequestResponse], error)
- func (s *AgentService) DisconnectGitHubAccount(ctx context.Context, ...) (*connect.Response[agentv1.DisconnectGitHubAccountResponse], error)
- func (s *AgentService) GetConnections(ctx context.Context, _ *connect.Request[agentv1.GetConnectionsRequest]) (*connect.Response[agentv1.GetConnectionsResponse], error)
- func (s *AgentService) GetGitHubAccount(ctx context.Context, _ *connect.Request[agentv1.GetGitHubAccountRequest]) (*connect.Response[agentv1.GetGitHubAccountResponse], error)
- func (s *AgentService) GetProfile(ctx context.Context, _ *connect.Request[agentv1.GetProfileRequest]) (*connect.Response[agentv1.GetProfileResponse], error)
- func (s *AgentService) GetProfileFile(_ context.Context, _ *connect.Request[agentv1.GetProfileFileRequest]) (*connect.Response[agentv1.GetProfileFileResponse], error)
- func (s *AgentService) GetRunsOn(ctx context.Context, _ *connect.Request[agentv1.GetRunsOnRequest]) (*connect.Response[agentv1.GetRunsOnResponse], error)
- func (s *AgentService) GetSession(ctx context.Context, req *connect.Request[agentv1.GetSessionRequest]) (*connect.Response[agentv1.GetSessionResponse], error)
- func (s *AgentService) GetSettings(ctx context.Context, _ *connect.Request[agentv1.GetSettingsRequest]) (*connect.Response[agentv1.GetSettingsResponse], error)
- func (s *AgentService) GetUsage(ctx context.Context, req *connect.Request[agentv1.GetUsageRequest]) (*connect.Response[agentv1.GetUsageResponse], error)
- func (s *AgentService) HostCredential(ctx context.Context, provider string) (string, error)
- func (s *AgentService) ListAgents(ctx context.Context, _ *connect.Request[agentv1.ListAgentsRequest]) (*connect.Response[agentv1.ListAgentsResponse], error)
- func (s *AgentService) ListChats(ctx context.Context, req *connect.Request[agentv1.ListChatsRequest]) (*connect.Response[agentv1.ListChatsResponse], error)
- func (s *AgentService) ListMcpServers(ctx context.Context, _ *connect.Request[agentv1.ListMcpServersRequest]) (*connect.Response[agentv1.ListMcpServersResponse], error)
- func (s *AgentService) ListMemories(ctx context.Context, req *connect.Request[agentv1.ListMemoriesRequest]) (*connect.Response[agentv1.ListMemoriesResponse], error)
- func (s *AgentService) ListPersonalities(ctx context.Context, _ *connect.Request[agentv1.ListPersonalitiesRequest]) (*connect.Response[agentv1.ListPersonalitiesResponse], error)
- func (s *AgentService) ListPlaybooks(ctx context.Context, _ *connect.Request[agentv1.ListPlaybooksRequest]) (*connect.Response[agentv1.ListPlaybooksResponse], error)
- func (s *AgentService) ListSessions(ctx context.Context, req *connect.Request[agentv1.ListSessionsRequest]) (*connect.Response[agentv1.ListSessionsResponse], error)
- func (s *AgentService) ListSkills(_ context.Context, _ *connect.Request[agentv1.ListSkillsRequest]) (*connect.Response[agentv1.ListSkillsResponse], error)
- func (s *AgentService) ListSlackChannels(ctx context.Context, _ *connect.Request[agentv1.ListSlackChannelsRequest]) (*connect.Response[agentv1.ListSlackChannelsResponse], error)
- func (s *AgentService) ListTurns(ctx context.Context, req *connect.Request[agentv1.ListTurnsRequest]) (*connect.Response[agentv1.ListTurnsResponse], error)
- func (s *AgentService) MoveCredentialsIntoStore(ctx context.Context)
- func (s *AgentService) PersonalHostCredential(ctx context.Context, login, provider string) (string, error)
- func (s *AgentService) PollProviderOAuth(ctx context.Context, req *connect.Request[agentv1.PollProviderOAuthRequest]) (*connect.Response[agentv1.PollProviderOAuthResponse], error)
- func (s *AgentService) Reconcile(ctx context.Context) error
- func (s *AgentService) RefreshTokens(ctx context.Context)
- func (s *AgentService) ReloadProfileDir(ctx context.Context, _ *connect.Request[agentv1.ReloadProfileDirRequest]) (*connect.Response[agentv1.ReloadProfileDirResponse], error)
- func (s *AgentService) RenameChat(ctx context.Context, req *connect.Request[agentv1.RenameChatRequest]) (*connect.Response[agentv1.RenameChatResponse], error)
- func (s *AgentService) SearchMemories(ctx context.Context, req *connect.Request[agentv1.SearchMemoriesRequest]) (*connect.Response[agentv1.SearchMemoriesResponse], error)
- func (s *AgentService) SendChatMessage(ctx context.Context, req *connect.Request[agentv1.SendChatMessageRequest]) (*connect.Response[agentv1.SendChatMessageResponse], error)
- func (s *AgentService) SetGitHubConnection(ctx context.Context, req *connect.Request[agentv1.SetGitHubConnectionRequest]) (*connect.Response[agentv1.SetGitHubConnectionResponse], error)
- func (s *AgentService) SetGitHubWebhookFilters(ctx context.Context, ...) (*connect.Response[agentv1.SetGitHubWebhookFiltersResponse], error)
- func (s *AgentService) SetMcpServerToken(ctx context.Context, req *connect.Request[agentv1.SetMcpServerTokenRequest]) (*connect.Response[agentv1.SetMcpServerTokenResponse], error)
- func (s *AgentService) SetProviderKey(ctx context.Context, req *connect.Request[agentv1.SetProviderKeyRequest]) (*connect.Response[agentv1.SetProviderKeyResponse], error)
- func (s *AgentService) SetRunning(cfg config.Config, slack SlackDirectory)
- func (s *AgentService) SetRunsOn(ctx context.Context, req *connect.Request[agentv1.SetRunsOnRequest]) (*connect.Response[agentv1.SetRunsOnResponse], error)
- func (s *AgentService) SetSkillEnabled(context.Context, *connect.Request[agentv1.SetSkillEnabledRequest]) (*connect.Response[agentv1.SetSkillEnabledResponse], error)
- func (s *AgentService) SetSlackChannelDescription(ctx context.Context, ...) (*connect.Response[agentv1.SetSlackChannelDescriptionResponse], error)
- func (s *AgentService) SetSlackConnection(ctx context.Context, req *connect.Request[agentv1.SetSlackConnectionRequest]) (*connect.Response[agentv1.SetSlackConnectionResponse], error)
- func (s *AgentService) StartGitHubOAuth(ctx context.Context, req *connect.Request[agentv1.StartGitHubOAuthRequest]) (*connect.Response[agentv1.StartGitHubOAuthResponse], error)
- func (s *AgentService) StartMcpOAuth(ctx context.Context, req *connect.Request[agentv1.StartMcpOAuthRequest]) (*connect.Response[agentv1.StartMcpOAuthResponse], error)
- func (s *AgentService) StartProviderOAuth(ctx context.Context, req *connect.Request[agentv1.StartProviderOAuthRequest]) (*connect.Response[agentv1.StartProviderOAuthResponse], error)
- func (s *AgentService) StreamChat(ctx context.Context, req *connect.Request[agentv1.StreamChatRequest], ...) error
- func (s *AgentService) UpdateMcpServer(ctx context.Context, req *connect.Request[agentv1.UpdateMcpServerRequest]) (*connect.Response[agentv1.UpdateMcpServerResponse], error)
- func (s *AgentService) UpdatePersonality(ctx context.Context, req *connect.Request[agentv1.UpdatePersonalityRequest]) (*connect.Response[agentv1.UpdatePersonalityResponse], error)
- func (s *AgentService) UpdatePlaybook(ctx context.Context, req *connect.Request[agentv1.UpdatePlaybookRequest]) (*connect.Response[agentv1.UpdatePlaybookResponse], error)
- func (s *AgentService) UpdateProfile(ctx context.Context, req *connect.Request[agentv1.UpdateProfileRequest]) (*connect.Response[agentv1.UpdateProfileResponse], error)
- func (s *AgentService) UpdateProfileFile(ctx context.Context, req *connect.Request[agentv1.UpdateProfileFileRequest]) (*connect.Response[agentv1.UpdateProfileFileResponse], error)
- func (s *AgentService) UploadSkill(ctx context.Context, req *connect.Request[agentv1.UploadSkillRequest]) (*connect.Response[agentv1.UploadSkillResponse], error)
- type AgentServiceOptions
- type ChatSource
- type ConnectionApplier
- type Delegator
- type DevSource
- type GitCredentialService
- type GitMinter
- type SecretStore
- type SlackDirectory
- type TaskCanceller
- type TurnService
- func (s *TurnService) CancelDelegation(ctx context.Context, req *connect.Request[agentv1.CancelDelegationRequest]) (*connect.Response[agentv1.CancelDelegationResponse], error)
- func (s *TurnService) Delegate(ctx context.Context, req *connect.Request[agentv1.DelegateRequest]) (*connect.Response[agentv1.DelegateResponse], error)
- func (s *TurnService) GetDelegation(ctx context.Context, req *connect.Request[agentv1.GetDelegationRequest]) (*connect.Response[agentv1.GetDelegationResponse], error)
- func (s *TurnService) InjectDelegation(ctx context.Context, req *connect.Request[agentv1.InjectDelegationRequest]) (*connect.Response[agentv1.InjectDelegationResponse], error)
- func (s *TurnService) ListDelegations(ctx context.Context, req *connect.Request[agentv1.ListDelegationsRequest]) (*connect.Response[agentv1.ListDelegationsResponse], error)
- func (s *TurnService) ReleasePreview(ctx context.Context, req *connect.Request[agentv1.ReleasePreviewRequest]) (*connect.Response[agentv1.ReleasePreviewResponse], error)
- type TurnStopper
Constants ¶
const ( ScopeHeader = "X-Podium-Scope" ScopeAll = "all" // InfraHeader is set by podium-server when the caller may edit the Slack bot's MCP // list: the dev token, or a signed-in admin or owner. A member does not get it. InfraHeader = "X-Podium-Infra" )
ScopeHeader is what podium-server's proxy sets so the conductor can tell a shared dev token from a signed-in person. The proxy strips any client-supplied copy. ScopeAll is the only value that widens a list; anything else, including a missing header, is one login's own chats.
const ( DevInboundPath = "/dev/inbound" DevOutboundPath = "/dev/outbound" )
Dev source routes. They exist only when PODIUM_AGENT_DEV_SOURCE is true and they sit behind the same bearer as the AgentService.
const ( OAuthPending = "pending" OAuthSlowDown = "slow_down" OAuthDone = "done" OAuthDenied = "denied" OAuthExpired = "expired" )
The states PollProviderOAuth reports. They are the whole client contract: everything the provider can answer maps onto one of these five.
const ( ProviderAnthropic = profiles.ProviderAnthropic ProviderXAI = profiles.ProviderXAI ProviderOpenAI = profiles.ProviderOpenAI ProviderGoogle = profiles.ProviderGoogle )
The providers this control plane knows. A provider is a set of credentials and an endpoint; profiles.Backends is what says which agent backend spends which of them.
const ( AuthAPIKey = "api_key" AuthOAuth = "oauth" )
How a stored credential was obtained. The distinction is not cosmetic: an API key does not expire and an access token does, so only one of the two is refreshed and only one of the two has a hint worth showing.
const ( CredentialPersonal = "personal" CredentialGlobal = "global" )
Whose credential a turn spends, as ListAgents reports it.
const ClaudeAgentCredential = "claude-agent"
ClaudeAgentCredential is what HostCredential is asked for to get the global Claude subscription: the name of opencode's provider that spends it.
const DefaultUsageDays = 30
DefaultUsageDays is the range GetUsage reads when the request names neither end.
const LoginHeader = "X-Podium-Login"
LoginHeader is what podium-server's proxy sets to the calling operator's login. Any client-supplied copy is stripped there, so what arrives here is the server's word.
const PullRequestMCPRESTPath = "/v1/mcp/pull-request"
PullRequestMCPRESTPath is the pull-request reader among the resources.
const TurnRESTPrefix = "/v1/"
TurnRESTPrefix is where these resources live, on the conductor and under podium-server's /turn-surface.
const TurnTokenHeader = "X-Podium-Turn"
TurnTokenHeader carries a turn's token. A header and not a request field: it is a credential, and a credential in a message body is a credential in every log that ever prints a request.
Variables ¶
This section is empty.
Functions ¶
func Infra ¶ added in v0.4.0
Infra reports whether this caller may edit the Slack bot's MCP list. The proxy sets the header for the dev token and for an admin or owner. A member's request does not.
func Login ¶
Login is the operator the server says is calling, or "unknown" when a handler was reached without the middleware.
func NewTurnREST ¶ added in v0.8.0
func NewTurnREST(turns *TurnService, gitcred *GitCredentialService, prMCP http.Handler) http.Handler
NewTurnREST returns the resources above. A nil service leaves its routes out; prMCP is the pull-request reader, mounted as it is.
func ReloadProfile ¶
func ReloadProfile(ctx context.Context, st *store.Store, live *profiles.Live) (*profiles.Profile, error)
ReloadProfile rebuilds the profile a turn runs from and swaps it into live.
A saved assistant definition replaces the assistant fields of the install file. Until one has been saved, the legacy override row is applied instead. Playbooks come from the profile directory either way.
This is the whole of "a change reaches a running conductor without a restart": every reader takes live.Current() per use, so the swap is the only thing that has to happen. It is a plain function because the process calls it at boot, before the AgentService exists, as well as on every override write and on a timer.
func ReloadProfileDir ¶
func ReloadProfileDir( ctx context.Context, st *store.Store, live *profiles.Live, dir string, ) (*profiles.Profile, error)
ReloadProfileDir is the same rebuild after re-reading the profile directory off disk, which is the half a restart used to be the only way to change: profile.yaml, playbooks/*.yaml and every prompt a `file:` points at.
**Nothing is swapped until all of it succeeds.** A directory in the middle of being edited is a load error, and a load error has to leave the conductor running the profile it already has rather than half of a new one. That is also why this is only ever called for an operator who asked: a timer re-reading a file somebody is saving would be a way to break a working bot by touching a keyboard.
func RequireBearer ¶
RequireBearer refuses everything that does not present the conductor's token, and puts the proxied login in the request context. The comparison is constant time: the token is the only thing standing between a local process and every session the bot has had.
Types ¶
type AgentService ¶
type AgentService struct {
// contains filtered or unexported fields
}
AgentService implements podium.agent.v1.AgentService.
func NewAgentService ¶
func NewAgentService(opts AgentServiceOptions) *AgentService
NewAgentService returns the handlers.
func (*AgentService) AttachChatPullRequest ¶
func (s *AgentService) AttachChatPullRequest( ctx context.Context, req *connect.Request[agentv1.AttachChatPullRequestRequest], ) (*connect.Response[agentv1.AttachChatPullRequestResponse], error)
AttachChatPullRequest links a pull request to one of the caller's chats by hand.
func (*AgentService) ClearGitHubConnection ¶ added in v0.5.0
func (s *AgentService) ClearGitHubConnection( ctx context.Context, _ *connect.Request[agentv1.ClearGitHubConnectionRequest], ) (*connect.Response[agentv1.ClearGitHubConnectionResponse], error)
ClearGitHubConnection drops the saved App and turns it off in the running conductor.
func (*AgentService) ClearMcpServerToken ¶
func (s *AgentService) ClearMcpServerToken( ctx context.Context, req *connect.Request[agentv1.ClearMcpServerTokenRequest], ) (*connect.Response[agentv1.ClearMcpServerTokenResponse], error)
ClearMcpServerToken removes the credential and leaves the registration. The goal state is "no token", so a NotFound from the control plane is success: calling it twice is not an error.
func (*AgentService) ClearProviderKey ¶
func (s *AgentService) ClearProviderKey( ctx context.Context, req *connect.Request[agentv1.ClearProviderKeyRequest], ) (*connect.Response[agentv1.ClearProviderKeyResponse], error)
ClearProviderKey removes the credential and the metadata. The goal state is "no credential", so a NotFound from the control plane is success: calling it twice is not an error.
func (*AgentService) ClearSlackConnection ¶ added in v0.5.0
func (s *AgentService) ClearSlackConnection( ctx context.Context, _ *connect.Request[agentv1.ClearSlackConnectionRequest], ) (*connect.Response[agentv1.ClearSlackConnectionResponse], error)
ClearSlackConnection drops the saved pair and falls back to the environment's, or to no Slack at all.
func (*AgentService) CompleteGitHubOAuth ¶ added in v0.6.0
func (s *AgentService) CompleteGitHubOAuth( ctx context.Context, req *connect.Request[agentv1.CompleteGitHubOAuthRequest], ) (*connect.Response[agentv1.CompleteGitHubOAuthResponse], error)
CompleteGitHubOAuth trades the code for a user token and stores it under the person who started the flow, which must also be the person completing it.
func (*AgentService) CompleteMcpOAuth ¶
func (s *AgentService) CompleteMcpOAuth( ctx context.Context, req *connect.Request[agentv1.CompleteMcpOAuthRequest], ) (*connect.Response[agentv1.CompleteMcpOAuthResponse], error)
CompleteMcpOAuth trades the code for a token and stores it as the server's credential.
From here on there is no difference between a sign-in and a pasted token: the access token goes into the same Podium secret, so the brief, the task spec and the runtime cannot tell them apart. What the sign-in leaves extra is the bag that lets it be refreshed.
func (*AgentService) CreateChat ¶
func (s *AgentService) CreateChat( ctx context.Context, req *connect.Request[agentv1.CreateChatRequest], ) (*connect.Response[agentv1.CreateChatResponse], error)
CreateChat opens a conversation owned by the caller.
func (*AgentService) CreateMcpServer ¶
func (s *AgentService) CreateMcpServer( ctx context.Context, req *connect.Request[agentv1.CreateMcpServerRequest], ) (*connect.Response[agentv1.CreateMcpServerResponse], error)
CreateMcpServer registers a new server, and stores its token when one came with it.
The order is the registration first and the token second, which is the opposite of SetProviderKey's. A provider key is validated with the provider before anything is written; an MCP server cannot be asked whether it is there without also being handed the credential, so there is nothing to validate against and no reason to write the secret before the row that owns it.
func (*AgentService) CreatePersonality ¶ added in v0.6.0
func (s *AgentService) CreatePersonality( ctx context.Context, req *connect.Request[agentv1.CreatePersonalityRequest], ) (*connect.Response[agentv1.CreatePersonalityResponse], error)
CreatePersonality stores a voice for the caller. The request name is ignored. The store generates one from the display name.
func (*AgentService) CreatePlaybook ¶
func (s *AgentService) CreatePlaybook( ctx context.Context, req *connect.Request[agentv1.CreatePlaybookRequest], ) (*connect.Response[agentv1.CreatePlaybookResponse], error)
CreatePlaybook writes playbooks/<name>.yaml and re-reads the profile directory.
func (*AgentService) DeleteChat ¶
func (s *AgentService) DeleteChat( ctx context.Context, req *connect.Request[agentv1.DeleteChatRequest], ) (*connect.Response[agentv1.DeleteChatResponse], error)
DeleteChat removes one of the caller's chats and every message in it.
A task still answering the chat is cancelled first. The transcript is about to disappear, and a container nobody is listening to would otherwise keep the node slot and can still finish work the person who asked for it will never see. Cancel does not wait: the chat is gone immediately, the node has up to 30s. Sessions and turns stay.
func (*AgentService) DeleteMcpServer ¶
func (s *AgentService) DeleteMcpServer( ctx context.Context, req *connect.Request[agentv1.DeleteMcpServerRequest], ) (*connect.Response[agentv1.DeleteMcpServerResponse], error)
DeleteMcpServer removes a registration and the token with it.
A server a playbook still names is deletable, exactly as a skill is: profiles deliberately does not check that a named server exists — a playbook file has to load on a machine with no database — so refusing here would be the only place in Podium where the two disagreed. The playbook's turns then fail naming the server, and ListMcpServers reports which playbooks name each one so a human can see that before pressing the button.
The secret goes first. A row that is gone with a credential left behind it is a credential nothing in the UI can see, name or remove.
func (*AgentService) DeleteMemory ¶
func (s *AgentService) DeleteMemory( ctx context.Context, req *connect.Request[agentv1.DeleteMemoryRequest], ) (*connect.Response[agentv1.DeleteMemoryResponse], error)
DeleteMemory takes one memory out of every future recall.
The memory engine has no single-memory delete, so this is its curation tombstone: the memory is excluded from recall, from consolidation and from the graph, the observations derived from it are pruned, and the row is kept in an archive. What the operator asked for holds — no future turn sees it, and neither does this UI — but the record of it having existed is not destroyed.
func (*AgentService) DeletePersonality ¶ added in v0.6.0
func (s *AgentService) DeletePersonality( ctx context.Context, req *connect.Request[agentv1.DeletePersonalityRequest], ) (*connect.Response[agentv1.DeletePersonalityResponse], error)
DeletePersonality removes one of the caller's voices. Chats that used it stay.
func (*AgentService) DeletePlaybook ¶
func (s *AgentService) DeletePlaybook( ctx context.Context, req *connect.Request[agentv1.DeletePlaybookRequest], ) (*connect.Response[agentv1.DeletePlaybookResponse], error)
DeletePlaybook removes playbooks/<name>.yaml.
func (*AgentService) DeleteSkill ¶
func (s *AgentService) DeleteSkill( ctx context.Context, req *connect.Request[agentv1.DeleteSkillRequest], ) (*connect.Response[agentv1.DeleteSkillResponse], error)
DeleteSkill removes the skill directory.
func (*AgentService) DetachChatPullRequest ¶
func (s *AgentService) DetachChatPullRequest( ctx context.Context, req *connect.Request[agentv1.DetachChatPullRequestRequest], ) (*connect.Response[agentv1.DetachChatPullRequestResponse], error)
DetachChatPullRequest takes one link off one of the caller's chats.
func (*AgentService) DisconnectGitHubAccount ¶ added in v0.6.0
func (s *AgentService) DisconnectGitHubAccount( ctx context.Context, _ *connect.Request[agentv1.DisconnectGitHubAccountRequest], ) (*connect.Response[agentv1.DisconnectGitHubAccountResponse], error)
DisconnectGitHubAccount deletes the caller's token and record. Doing it twice is not an error.
func (*AgentService) GetConnections ¶ added in v0.5.0
func (s *AgentService) GetConnections( ctx context.Context, _ *connect.Request[agentv1.GetConnectionsRequest], ) (*connect.Response[agentv1.GetConnectionsResponse], error)
GetConnections reports Slack, GitHub and Linear without any secret value.
func (*AgentService) GetGitHubAccount ¶ added in v0.6.0
func (s *AgentService) GetGitHubAccount( ctx context.Context, _ *connect.Request[agentv1.GetGitHubAccountRequest], ) (*connect.Response[agentv1.GetGitHubAccountResponse], error)
GetGitHubAccount answers for the caller only.
func (*AgentService) GetProfile ¶
func (s *AgentService) GetProfile( ctx context.Context, _ *connect.Request[agentv1.GetProfileRequest], ) (*connect.Response[agentv1.GetProfileResponse], error)
GetProfile reports the profile in force and every playbook in full.
A playbook's secrets are reported by NAME, which is all a playbook file holds either. No value reaches this response: Podium has no endpoint that reads a secret's value back, and a playbook naming one is not a privilege — a task spec names secrets exactly the same way.
func (*AgentService) GetProfileFile ¶ added in v0.3.2
func (s *AgentService) GetProfileFile( _ context.Context, _ *connect.Request[agentv1.GetProfileFileRequest], ) (*connect.Response[agentv1.GetProfileFileResponse], error)
GetProfileFile returns profile.yaml as it is on disk.
func (*AgentService) GetRunsOn ¶ added in v0.7.0
func (s *AgentService) GetRunsOn( ctx context.Context, _ *connect.Request[agentv1.GetRunsOnRequest], ) (*connect.Response[agentv1.GetRunsOnResponse], error)
GetRunsOn reports where a turn runs when its playbook does not say. Never saved is the fleet's own machines.
func (*AgentService) GetSession ¶
func (s *AgentService) GetSession( ctx context.Context, req *connect.Request[agentv1.GetSessionRequest], ) (*connect.Response[agentv1.GetSessionResponse], error)
GetSession reads one session by id.
func (*AgentService) GetSettings ¶
func (s *AgentService) GetSettings( ctx context.Context, _ *connect.Request[agentv1.GetSettingsRequest], ) (*connect.Response[agentv1.GetSettingsResponse], error)
GetSettings reports what the conductor is configured with.
key_set means "an agent turn will find a credential", so it comes from the **control plane**, not from the metadata row: the secret is the control plane's and `podium secret rm` can take it away without the conductor hearing. The row only supplies the hint, the login and the time, and only when it still describes the version that is actually stored. Nothing here reads a value — there is no endpoint that could.
func (*AgentService) GetUsage ¶
func (s *AgentService) GetUsage( ctx context.Context, req *connect.Request[agentv1.GetUsageRequest], ) (*connect.Response[agentv1.GetUsageResponse], error)
GetUsage reports what the conductor spent over a range: one row per day for a calendar, and the per-task cost of each turn inside it.
func (*AgentService) HostCredential ¶
HostCredential is the bearer a host turn spends for one provider, read back from the provider's global Podium secret: the same one a container turn is given. A person's own is PersonalHostCredential.
ClaudeAgentCredential asks for the global Claude subscription instead, which only the chat host's claude-agent provider spends.
func (*AgentService) ListAgents ¶
func (s *AgentService) ListAgents( ctx context.Context, _ *connect.Request[agentv1.ListAgentsRequest], ) (*connect.Response[agentv1.ListAgentsResponse], error)
ListAgents is the picker's catalogue: the backends this conductor can run a turn on, the models each offers, and whether a credential for it is actually stored.
func (*AgentService) ListChats ¶
func (s *AgentService) ListChats( ctx context.Context, req *connect.Request[agentv1.ListChatsRequest], ) (*connect.Response[agentv1.ListChatsResponse], error)
ListChats returns chats newest first. A signed-in user gets their own web chats. The dev token, which the proxy marks with X-Podium-Scope: all, gets every chat.
func (*AgentService) ListMcpServers ¶
func (s *AgentService) ListMcpServers( ctx context.Context, _ *connect.Request[agentv1.ListMcpServersRequest], ) (*connect.Response[agentv1.ListMcpServersResponse], error)
ListMcpServers reports every registered server.
func (*AgentService) ListMemories ¶
func (s *AgentService) ListMemories( ctx context.Context, req *connect.Request[agentv1.ListMemoriesRequest], ) (*connect.Response[agentv1.ListMemoriesResponse], error)
ListMemories pages through the shared memory, newest first.
func (*AgentService) ListPersonalities ¶ added in v0.6.0
func (s *AgentService) ListPersonalities( ctx context.Context, _ *connect.Request[agentv1.ListPersonalitiesRequest], ) (*connect.Response[agentv1.ListPersonalitiesResponse], error)
ListPersonalities returns the caller's voices. Podium is not one of them.
func (*AgentService) ListPlaybooks ¶
func (s *AgentService) ListPlaybooks( ctx context.Context, _ *connect.Request[agentv1.ListPlaybooksRequest], ) (*connect.Response[agentv1.ListPlaybooksResponse], error)
ListPlaybooks reports who answers a conversation and what that turn may delegate to.
It is the chat's surface and stays minimal: per playbook a name, an image and the first line of a prompt the operator wrote. The whole definition — tools, secrets, resources — is what GetProfile is for.
func (*AgentService) ListSessions ¶
func (s *AgentService) ListSessions( ctx context.Context, req *connect.Request[agentv1.ListSessionsRequest], ) (*connect.Response[agentv1.ListSessionsResponse], error)
ListSessions returns conversations newest first.
func (*AgentService) ListSkills ¶
func (s *AgentService) ListSkills( _ context.Context, _ *connect.Request[agentv1.ListSkillsRequest], ) (*connect.Response[agentv1.ListSkillsResponse], error)
ListSkills reports every skill this conductor can hand a turn: the directories under PODIUM_AGENT_SKILLS_DIR. A directory that is there and will not load is reported with the reason attached rather than hidden.
func (*AgentService) ListSlackChannels ¶ added in v0.2.0
func (s *AgentService) ListSlackChannels( ctx context.Context, _ *connect.Request[agentv1.ListSlackChannelsRequest], ) (*connect.Response[agentv1.ListSlackChannelsResponse], error)
ListSlackChannels reports every known Slack channel. When Slack is connected it also refreshes names from membership, which is how a channel the bot was invited to appears before anyone has mentioned it.
func (*AgentService) ListTurns ¶
func (s *AgentService) ListTurns( ctx context.Context, req *connect.Request[agentv1.ListTurnsRequest], ) (*connect.Response[agentv1.ListTurnsResponse], error)
ListTurns returns one session's turns, newest first.
func (*AgentService) MoveCredentialsIntoStore ¶ added in v0.6.0
func (s *AgentService) MoveCredentialsIntoStore(ctx context.Context)
MoveCredentialsIntoStore moves the credentials a database written before the secret store answered the conductor still holds in clear: providers' refresh tokens and bearer copies, and MCP sign-ins' refresh tokens and client secrets. A failure is logged and the move is tried again on the next start, or for a provider on its next read.
func (*AgentService) PersonalHostCredential ¶ added in v0.8.0
func (s *AgentService) PersonalHostCredential(ctx context.Context, login, provider string) (string, error)
PersonalHostCredential is login's own key for provider — the personal secret spec.PersonalModelKeySecretName — or "" when they set none, which is not an error: the company's key is spent instead.
func (*AgentService) PollProviderOAuth ¶
func (s *AgentService) PollProviderOAuth( ctx context.Context, req *connect.Request[agentv1.PollProviderOAuthRequest], ) (*connect.Response[agentv1.PollProviderOAuthResponse], error)
PollProviderOAuth asks the provider whether the human has approved yet, and stores the credential on the one call that comes back authorised.
The token is validated against the provider's own API before it is stored, exactly as a pasted key is. That is not ceremony: xAI's OAuth surface has its own allow-list, so a sign-in can succeed and still produce a token that cannot call the API — and finding that out now, with the provider's own sentence, beats finding it out on the first turn.
func (*AgentService) Reconcile ¶
func (s *AgentService) Reconcile(ctx context.Context) error
Reconcile is the timer's rebuild: the saved assistant, re-read on an interval so a row changed by another conductor or by psql lands here too. A conductor that has not saved one still re-reads the legacy override row.
It takes the write lock, and that is the whole reason it exists as a method of its own. A rebuild is a read of the file half, a read of the database and then a swap; a timer doing that unlocked could read the files, have ReloadProfileDir replace them underneath it, and then swap in a profile built from a directory nobody is running any more — undoing an operator's re-read seconds after they asked for it.
func (*AgentService) RefreshTokens ¶
func (s *AgentService) RefreshTokens(ctx context.Context)
RefreshTokens keeps stored OAuth credentials alive until the process stops — the subscription sign-in's, and every signed-in MCP server's.
It runs as its own goroutine because nothing else is watching: a token issued for an hour would otherwise stop working an hour after a sign-in, and the first anybody would hear of it is a turn failing. A refresh that fails is logged and retried on the next tick — the stored token is left alone, because a token that still has thirty minutes on it is more use than no token at all.
func (*AgentService) ReloadProfileDir ¶
func (s *AgentService) ReloadProfileDir( ctx context.Context, _ *connect.Request[agentv1.ReloadProfileDirRequest], ) (*connect.Response[agentv1.ReloadProfileDirResponse], error)
ReloadProfileDir re-reads the profile directory and swaps what it finds in.
The failure is an InvalidArgument and not an Internal on purpose: what fails here is almost always the YAML the operator has just written, and the message profiles.Load returns names the file and the line. Nothing is swapped when it does, so the stale reason is not touched either — the conductor is still running the profile it was, and the error in front of the operator is the whole report.
func (*AgentService) RenameChat ¶
func (s *AgentService) RenameChat( ctx context.Context, req *connect.Request[agentv1.RenameChatRequest], ) (*connect.Response[agentv1.RenameChatResponse], error)
RenameChat changes the title of one of the caller's chats.
func (*AgentService) SearchMemories ¶
func (s *AgentService) SearchMemories( ctx context.Context, req *connect.Request[agentv1.SearchMemoriesRequest], ) (*connect.Response[agentv1.SearchMemoriesResponse], error)
SearchMemories is the semantic search behind the Memory tab's search box.
func (*AgentService) SendChatMessage ¶
func (s *AgentService) SendChatMessage( ctx context.Context, req *connect.Request[agentv1.SendChatMessageRequest], ) (*connect.Response[agentv1.SendChatMessageResponse], error)
SendChatMessage stores one human message and starts a turn on it.
func (*AgentService) SetGitHubConnection ¶ added in v0.5.0
func (s *AgentService) SetGitHubConnection( ctx context.Context, req *connect.Request[agentv1.SetGitHubConnectionRequest], ) (*connect.Response[agentv1.SetGitHubConnectionResponse], error)
SetGitHubConnection validates the App the way start-up does, tries it against GitHub, stores it and switches the running conductor to it.
func (*AgentService) SetGitHubWebhookFilters ¶ added in v0.7.1
func (s *AgentService) SetGitHubWebhookFilters( ctx context.Context, req *connect.Request[agentv1.SetGitHubWebhookFiltersRequest], ) (*connect.Response[agentv1.SetGitHubWebhookFiltersResponse], error)
SetGitHubWebhookFilters stores the webhook filters. The source reads them on every delivery, so they apply without a restart.
func (*AgentService) SetMcpServerToken ¶
func (s *AgentService) SetMcpServerToken( ctx context.Context, req *connect.Request[agentv1.SetMcpServerTokenRequest], ) (*connect.Response[agentv1.SetMcpServerTokenResponse], error)
SetMcpServerToken stores the credential a server authenticates with.
It is not validated, because there is no way to validate it. An MCP server has no unauthenticated "are you there" call, so the only test of a token is a turn using it — which is why the failure an operator sees for a bad token is a turn's, and why the hint exists at all.
func (*AgentService) SetProviderKey ¶
func (s *AgentService) SetProviderKey( ctx context.Context, req *connect.Request[agentv1.SetProviderKeyRequest], ) (*connect.Response[agentv1.SetProviderKeyResponse], error)
SetProviderKey validates a key with the provider and, only then, stores it.
The order is the contract: validate, write the secret, write the metadata. An operator who reads "saved" has to be able to trust that agents will run, so an unvalidated key is never written, and a metadata row never claims a key the secret store does not hold.
func (*AgentService) SetRunning ¶ added in v0.7.1
func (s *AgentService) SetRunning(cfg config.Config, slack SlackDirectory)
SetRunning records the connections the conductor now holds open: the configuration it is running and its Slack source, nil when Slack is off.
func (*AgentService) SetRunsOn ¶ added in v0.7.0
func (s *AgentService) SetRunsOn( ctx context.Context, req *connect.Request[agentv1.SetRunsOnRequest], ) (*connect.Response[agentv1.SetRunsOnResponse], error)
SetRunsOn saves where a turn runs when its playbook does not say. A turn already running stays where it is.
func (*AgentService) SetSkillEnabled ¶
func (s *AgentService) SetSkillEnabled( context.Context, *connect.Request[agentv1.SetSkillEnabledRequest], ) (*connect.Response[agentv1.SetSkillEnabledResponse], error)
SetSkillEnabled has no file to flip: a directory skill is on if it is on disk.
func (*AgentService) SetSlackChannelDescription ¶ added in v0.2.0
func (s *AgentService) SetSlackChannelDescription( ctx context.Context, req *connect.Request[agentv1.SetSlackChannelDescriptionRequest], ) (*connect.Response[agentv1.SetSlackChannelDescriptionResponse], error)
SetSlackChannelDescription stores the operator note a turn of this channel is briefed with.
func (*AgentService) SetSlackConnection ¶ added in v0.5.0
func (s *AgentService) SetSlackConnection( ctx context.Context, req *connect.Request[agentv1.SetSlackConnectionRequest], ) (*connect.Response[agentv1.SetSlackConnectionResponse], error)
SetSlackConnection validates the pair, tries it against Slack, stores it and switches the running conductor to it. restart_required is left true only when that switch failed.
func (*AgentService) StartGitHubOAuth ¶ added in v0.6.0
func (s *AgentService) StartGitHubOAuth( ctx context.Context, req *connect.Request[agentv1.StartGitHubOAuthRequest], ) (*connect.Response[agentv1.StartGitHubOAuthResponse], error)
StartGitHubOAuth answers with the URL to send the caller's browser to.
func (*AgentService) StartMcpOAuth ¶
func (s *AgentService) StartMcpOAuth( ctx context.Context, req *connect.Request[agentv1.StartMcpOAuthRequest], ) (*connect.Response[agentv1.StartMcpOAuthResponse], error)
StartMcpOAuth discovers a server's OAuth, registers this conductor as a client of it, and answers with the URL to send the operator's browser to.
Everything expensive happens here rather than on the callback, so that a server which advertises no OAuth, or refuses a registration, is a refusal the operator gets while looking at the button they pressed — not a dead end after a round trip through a consent screen.
func (*AgentService) StartProviderOAuth ¶
func (s *AgentService) StartProviderOAuth( ctx context.Context, req *connect.Request[agentv1.StartProviderOAuthRequest], ) (*connect.Response[agentv1.StartProviderOAuthResponse], error)
StartProviderOAuth asks the provider for a code a human can approve, and remembers the device code that goes with it. It stores no credential: only a poll that comes back authorised does that.
func (*AgentService) StreamChat ¶
func (s *AgentService) StreamChat( ctx context.Context, req *connect.Request[agentv1.StreamChatRequest], stream *connect.ServerStream[agentv1.ChatFrame], ) error
StreamChat replays a chat and then follows it live.
The order is: subscribe, send the turn state, replay, then forward. Subscribing first is what closes the gap — a message stored between the replay and the subscription would otherwise be seen by nobody — and the cost is that a frame may arrive twice, which is harmless because a client keys on seq.
The status frame goes FIRST and unconditionally, for two reasons. It is what tells a browser joining mid-turn that the composer is disabled without waiting for the next progress line. And it is what puts the response headers on the wire immediately: a Connect server stream writes no headers until its first message, and podium-server's proxy gives the conductor 30 seconds to produce them — so a subscriber on a quiet chat would otherwise be disconnected every 30 seconds and reconnect for ever.
It never returns on its own. The client cancels, and the request context is what unwinds the subscription.
func (*AgentService) UpdateMcpServer ¶
func (s *AgentService) UpdateMcpServer( ctx context.Context, req *connect.Request[agentv1.UpdateMcpServerRequest], ) (*connect.Response[agentv1.UpdateMcpServerResponse], error)
UpdateMcpServer replaces a registration. The token is untouched: it belongs to the name, and the name is what cannot be edited here.
func (*AgentService) UpdatePersonality ¶ added in v0.6.0
func (s *AgentService) UpdatePersonality( ctx context.Context, req *connect.Request[agentv1.UpdatePersonalityRequest], ) (*connect.Response[agentv1.UpdatePersonalityResponse], error)
UpdatePersonality replaces the display name, instructions, and model of one of the caller's voices. The request name is ignored, and the stored name stays. Another login's row is not found.
func (*AgentService) UpdatePlaybook ¶
func (s *AgentService) UpdatePlaybook( ctx context.Context, req *connect.Request[agentv1.UpdatePlaybookRequest], ) (*connect.Response[agentv1.UpdatePlaybookResponse], error)
UpdatePlaybook replaces playbooks/<name>.yaml.
func (*AgentService) UpdateProfile ¶
func (s *AgentService) UpdateProfile( ctx context.Context, req *connect.Request[agentv1.UpdateProfileRequest], ) (*connect.Response[agentv1.UpdateProfileResponse], error)
UpdateProfile stores the active assistant definition and swaps the rebuilt profile in. The request is the whole definition. Playbooks stay files. A legacy override row is removed on the first save so the two cannot both apply.
func (*AgentService) UpdateProfileFile ¶ added in v0.3.2
func (s *AgentService) UpdateProfileFile( ctx context.Context, req *connect.Request[agentv1.UpdateProfileFileRequest], ) (*connect.Response[agentv1.UpdateProfileFileResponse], error)
UpdateProfileFile replaces profile.yaml and re-reads the profile directory. The old file is put back when the new one does not load, so a bad save leaves the conductor as it was.
func (*AgentService) UploadSkill ¶
func (s *AgentService) UploadSkill( ctx context.Context, req *connect.Request[agentv1.UploadSkillRequest], ) (*connect.Response[agentv1.UploadSkillResponse], error)
UploadSkill unpacks a zip or a bare SKILL.md into PODIUM_AGENT_SKILLS_DIR.
type AgentServiceOptions ¶
type AgentServiceOptions struct {
Store *store.Store
Secrets SecretStore
// Model is the model reported by GetSettings when no profile is loaded. The profile's
// own model wins when there is one, because it is what a turn will actually run.
Model string
// AnthropicBaseURL is where SetProviderKey validates an Anthropic key.
AnthropicBaseURL string
// XAIBaseURL is where it validates an xAI credential, key or token.
XAIBaseURL string
// XAIOAuthIssuer, XAIOAuthClientID and XAIOAuthScopes configure the subscription
// sign-in. An empty client id means this control plane offers API keys only, which is
// a supported configuration and not an error.
XAIOAuthIssuer string
XAIOAuthClientID string
XAIOAuthScopes string
// OpenAIBaseURL is where SetProviderKey validates an OpenAI API key.
OpenAIBaseURL string
// OpenAICodexBaseURL is where a ChatGPT / Codex subscription token is validated.
OpenAICodexBaseURL string
// OpenAIOAuthIssuer and OpenAIOAuthClientID configure the ChatGPT subscription
// sign-in. An empty client id means API keys only.
OpenAIOAuthIssuer string
OpenAIOAuthClientID string
// AnthropicOAuth* configure the Claude subscription sign-in. An empty client id is API
// keys only, and so is ClaudeAgent false: a Claude sign-in is only offered where the
// chat host can run Claude Code, the one place it is spent.
AnthropicOAuthClientID string
AnthropicOAuthAuthorizeURL string
AnthropicOAuthTokenURL string
AnthropicOAuthRedirectURI string
AnthropicOAuthScopes string
ClaudeAgent bool
// GoogleBaseURL is where SetProviderKey validates a Gemini API key.
GoogleBaseURL string
// GitHubURL and GitHubAPIURL are where a person's GitHub account is connected. Empty
// means github.com.
GitHubURL string
GitHubAPIURL string
// HTTPClient validates the key. Nil means a client with a timeout of its own.
HTTPClient *http.Client
// Memory is the shared-memory client. Nil is a supported configuration: the three
// memory RPCs then answer FailedPrecondition and the UI says memory is not configured.
Memory memory.Client
// SkillsDir is PODIUM_AGENT_SKILLS_DIR: the Agent Skills on this host. Empty means this
// conductor delivers no skills, and a playbook that names one fails that turn.
SkillsDir string
// Profiles is the profile in force, swapped when the directory is re-read or an override
// changes. Nil makes the playbook and profile RPCs answer FailedPrecondition.
Profiles *profiles.Live
// ProfileDir is PODIUM_AGENT_PROFILE_DIR, the directory Profiles' file half was read
// from. It is what ReloadProfileDir re-reads. Empty means this conductor has no
// directory to go back to and that RPC answers FailedPrecondition.
ProfileDir string
// Chat is the web chat's write path and live fan-out. Nil makes the chat RPCs answer
// FailedPrecondition.
Chat ChatSource
// Tasks stops a running Podium task. DeleteChat uses it so a conversation that still
// has a turn in flight does not leave a container running after it is gone. Nil is a
// supported configuration: tests that never start a turn omit it.
Tasks TaskCanceller
// Turns stops the work a conversation owns that no task id can reach: the host turn
// answering it, which runs in this process, and the tasks that turn delegated. Nil is
// a conductor with no host turns, and DeleteChat then has nothing extra to stop.
Turns TurnStopper
// Slack lists the channels the bot is in, so ListSlackChannels can seed the catalogue
// before the first mention. Nil means this conductor has no Slack source.
Slack SlackDirectory
Logger *slog.Logger
// Env is the connection configuration the process was started with, before a saved
// connection replaced it. Running is what this process actually opened. GetConnections
// uses the two to say whether a restart is still owed. The zero value is an install
// with nothing configured, which is what tests that never touch connections want.
Env config.Config
Running config.Config
// Connections switches the running conductor to a saved Slack pair or GitHub App, so a
// save takes effect without a restart. Nil leaves the change for the next start, and
// restart_required says so.
Connections ConnectionApplier
}
AgentServiceOptions is what the handlers need. Everything but Store and Secrets is optional; without Secrets the settings RPCs answer FailedPrecondition rather than panic.
type ChatSource ¶
type ChatSource interface {
Send(ctx context.Context, req chat.SendRequest) (store.ChatMessage, error)
Subscribe(ctx context.Context, chatID string) *chat.Subscriber
Running(ctx context.Context, chatID string) (bool, error)
Awaiting(chatID string) bool
AttachPullRequest(ctx context.Context, chatID, login, url string) ([]store.ChatPullRequest, error)
DetachPullRequest(ctx context.Context, chatID, login, url string) ([]store.ChatPullRequest, error)
}
ChatSource is the part of the chat source the handlers use: the write paths, and the live fan-out. Reads go to the store directly.
The pull-request writes are here rather than on the store because the point of them is the frame: every browser watching the conversation has to see the list change, not only the one that pressed the button.
type ConnectionApplier ¶ added in v0.7.1
type ConnectionApplier interface {
// Check tries the Slack pair and GitHub App in cfg against Slack and GitHub, where they
// differ from what is running. It is how a mistyped token is refused at save rather
// than found by the conductor failing to connect with it.
Check(ctx context.Context, cfg config.Config) error
// Apply rereads the saved connections and swaps in whatever changed. Turns in flight
// keep running. On success it calls SetRunning with what it switched to.
Apply(ctx context.Context) error
}
ConnectionApplier is the part of the conductor that holds Slack and GitHub open.
type Delegator ¶
type Delegator interface {
Delegate(ctx context.Context, token, playbook, instruction string, opts conductor.DelegateOptions) (store.Delegation, error)
GetDelegation(ctx context.Context, token, id string) (store.Delegation, string, error)
Delegations(ctx context.Context, token string) ([]store.Delegation, error)
CancelDelegation(ctx context.Context, token, id, reason string) (store.Delegation, error)
InjectDelegation(ctx context.Context, token, id, text string) (store.Delegation, error)
ReleaseDelegationPreview(ctx context.Context, token, id string) (store.Delegation, error)
}
Delegator is the conductor, as this service needs it. An interface rather than the concrete type so the handler can be tested without a turn loop, a database or a control plane behind it.
type DevSource ¶
type DevSource struct {
*fakesource.Source
}
DevSource is the in-memory source with two HTTP routes bolted on. It is TEST ONLY: it lets a test inject an inbound message as if a human had sent it and read back every single thing the conductor said, in order, without a Slack workspace.
func NewDevSource ¶
func NewDevSource() *DevSource
NewDevSource returns a started-and-ready dev source.
type GitCredentialService ¶
type GitCredentialService struct {
// contains filtered or unexported fields
}
GitCredentialService implements podium.agent.v1.GitCredentialService.
func NewGitCredentialService ¶
func NewGitCredentialService(minter GitMinter, logger *slog.Logger) *GitCredentialService
NewGitCredentialService returns the handler. A nil minter is a conductor with no GitHub App: every call then answers FailedPrecondition, which is the truth rather than a crash.
func (*GitCredentialService) MintToken ¶
func (s *GitCredentialService) MintToken( ctx context.Context, req *connect.Request[agentv1.MintTokenRequest], ) (*connect.Response[agentv1.MintTokenResponse], error)
MintToken issues a GitHub installation token for the calling turn.
type GitMinter ¶
type GitMinter interface {
MintGitToken(ctx context.Context, capability string) (conductor.GitCredential, error)
}
GitMinter is the conductor, as this service needs it. An interface so the handler can be tested without a GitHub App, a database or a turn loop behind it.
type SecretStore ¶
type SecretStore interface {
SetSecret(ctx context.Context, name string, value []byte) (version int32, err error)
DeleteSecret(ctx context.Context, name string) error
SecretVersion(ctx context.Context, name string) (int32, error)
// SetPersonalSecret stores a secret owned by one login. The conductor uses it for
// the token a person saved on their own MCP server, so the secret shows on their
// list and not the company list.
SetPersonalSecret(ctx context.Context, owner, name string, value []byte) (version int32, err error)
DeletePersonalSecret(ctx context.Context, owner, name string) error
// PersonalSecretVersion is the version of one person's secret, or 0 when it is absent.
PersonalSecretVersion(ctx context.Context, owner, name string) (int32, error)
// ReadSecret reads back one of the names the conductor keeps for itself, such as a
// saved connection's credentials. See spec.ConductorMayRead.
ReadSecret(ctx context.Context, owner, name string) ([]byte, error)
}
SecretStore is the part of the Podium API the settings handlers need. It is an interface rather than the client itself so a test needs no control plane, and so this package keeps depending on nothing but a name, some bytes and a version.
SecretVersion is what keeps the conductor honest. The secret belongs to the control plane and an operator can set or remove it with `podium secret set`/`rm` without the conductor hearing about it, so the metadata row here is never taken as proof that a key exists — the control plane is asked. ListSecrets carries metadata only; no value is ever read back.
type SlackDirectory ¶ added in v0.2.0
type SlackDirectory interface {
ListChannels(ctx context.Context) ([]slack.ChannelInfo, error)
}
SlackDirectory is the Slack source as the Channels screen is allowed to see it: the conversations the bot is in, so the catalogue can be seeded before the first mention.
type TaskCanceller ¶
TaskCanceller stops a Podium task. It is the same CancelTask `podium task cancel` calls: the node gets SIGTERM and up to 30s, and nothing here waits.
type TurnService ¶
type TurnService struct {
// contains filtered or unexported fields
}
TurnService implements podium.agent.v1.TurnService.
func NewTurnService ¶
func NewTurnService(delegator Delegator, logger *slog.Logger) *TurnService
NewTurnService returns the handler. A nil delegator is a conductor that runs no host turns: every call then answers FailedPrecondition, which is the truth rather than a crash.
func (*TurnService) CancelDelegation ¶
func (s *TurnService) CancelDelegation( ctx context.Context, req *connect.Request[agentv1.CancelDelegationRequest], ) (*connect.Response[agentv1.CancelDelegationResponse], error)
CancelDelegation stops one.
func (*TurnService) Delegate ¶
func (s *TurnService) Delegate( ctx context.Context, req *connect.Request[agentv1.DelegateRequest], ) (*connect.Response[agentv1.DelegateResponse], error)
Delegate starts a task for the calling turn's conversation.
func (*TurnService) GetDelegation ¶
func (s *TurnService) GetDelegation( ctx context.Context, req *connect.Request[agentv1.GetDelegationRequest], ) (*connect.Response[agentv1.GetDelegationResponse], error)
GetDelegation is where a delegated task has got to. It is what a turn polls, so it is deliberately cheap: one row and the last thing the task said.
func (*TurnService) InjectDelegation ¶
func (s *TurnService) InjectDelegation( ctx context.Context, req *connect.Request[agentv1.InjectDelegationRequest], ) (*connect.Response[agentv1.InjectDelegationResponse], error)
InjectDelegation delivers one human message into a running delegated task.
func (*TurnService) ListDelegations ¶
func (s *TurnService) ListDelegations( ctx context.Context, req *connect.Request[agentv1.ListDelegationsRequest], ) (*connect.Response[agentv1.ListDelegationsResponse], error)
ListDelegations is everything the calling turn has delegated.
func (*TurnService) ReleasePreview ¶ added in v0.6.0
func (s *TurnService) ReleasePreview( ctx context.Context, req *connect.Request[agentv1.ReleasePreviewRequest], ) (*connect.Response[agentv1.ReleasePreviewResponse], error)
ReleasePreview takes down the preview a finished delegated task left up.
type TurnStopper ¶
type TurnStopper interface {
// CancelHostTurn stops the host turn answering ref, reporting whether there was one.
CancelHostTurn(ref string) bool
// CancelDelegationsForRef stops every delegated task of one conversation.
CancelDelegationsForRef(ctx context.Context, ref, reason string) (int, error)
}
TurnStopper is the conductor, as deleting a chat needs it. Neither of these can be reached with a task id: a host turn is a child process of this conductor and has no task, and the tasks it delegated are owned by the CONVERSATION rather than by the turn that asked for them — which is precisely why deleting the conversation has to end them.