Documentation
¶
Overview ¶
Package store is the conductor's own Postgres database (podium_agent): sessions, turns, the relay ledger and its settings. It is a separate database from the control plane's and nothing here imports internal/server/store — the conductor is an API client of podium-server, not a second owner of its schema.
Index ¶
- Constants
- Variables
- func ChatSourceKey(chatID string) string
- func SlackChannelID(sourceKey string) (string, bool)
- type Backend
- type Chat
- type ChatAttachment
- type ChatChoice
- type ChatMessage
- type ChatPullRequest
- type Delegation
- type NewDelegation
- type ReviewSurface
- type Session
- type SlackChannel
- type Store
- func (s *Store) AppendChatMessage(ctx context.Context, msg ChatMessage) (ChatMessage, error)
- func (s *Store) AttachChatPullRequest(ctx context.Context, pr ChatPullRequest) (ChatPullRequest, error)
- func (s *Store) AttachToLastAssistantMessage(ctx context.Context, chatID string, file ChatAttachment) (ChatMessage, error)
- func (s *Store) BindReviewSurface(ctx context.Context, sourceKey, kind, ref string) error
- func (s *Store) ChatBySourceKey(ctx context.Context, sourceKey string) (Chat, error)
- func (s *Store) ChatParticipants(ctx context.Context, chatID string) ([]string, error)
- func (s *Store) ChatTurnRunning(ctx context.Context, chatID string) (bool, error)
- func (s *Store) ClearMcpServerTokenMeta(ctx context.Context, name, login string) error
- func (s *Store) Close()
- func (s *Store) CountRelayed(ctx context.Context, taskID string) (int, error)
- func (s *Store) CreateChat(ctx context.Context, login, title string) (Chat, error)
- func (s *Store) CreateDelegation(ctx context.Context, want NewDelegation) (Delegation, error)
- func (s *Store) CreateMirrorChat(ctx context.Context, sourceKey, origin, startedBy, title string) (Chat, error)
- func (s *Store) CreateTurn(ctx context.Context, sessionID, triggerRef string, b Backend) (Turn, error)
- func (s *Store) DelegationsForTurn(ctx context.Context, turnID string) ([]Delegation, error)
- func (s *Store) DeleteChat(ctx context.Context, id, login string) error
- func (s *Store) DeleteMcpServer(ctx context.Context, name string) error
- func (s *Store) DeleteSetting(ctx context.Context, key string) error
- func (s *Store) DetachChatPullRequest(ctx context.Context, chatID, url string) error
- func (s *Store) FinishDelegation(ctx context.Context, id, status, finalText string, numTurns *int, ...) error
- func (s *Store) FinishTurn(ctx context.Context, turnID, status string, numTurns *int, costUSD *float64, ...) error
- func (s *Store) GetChat(ctx context.Context, id string) (Chat, error)
- func (s *Store) GetDelegation(ctx context.Context, id string) (Delegation, error)
- func (s *Store) GetLinearCursor(ctx context.Context, key string) (time.Time, error)
- func (s *Store) GetReviewSurface(ctx context.Context, kind, ref string) (ReviewSurface, error)
- func (s *Store) GetSession(ctx context.Context, id string) (Session, error)
- func (s *Store) GetSessionByKey(ctx context.Context, sourceKey string) (Session, error)
- func (s *Store) GetSetting(ctx context.Context, key string, out any) error
- func (s *Store) GetTurn(ctx context.Context, id string) (Turn, error)
- func (s *Store) InsertMcpServer(ctx context.Context, srv mcp.Server, login string) error
- func (s *Store) LinkChatPullRequest(ctx context.Context, pr ChatPullRequest) (bool, error)
- func (s *Store) ListChatMessages(ctx context.Context, chatID string, fromSeq uint64) ([]ChatMessage, error)
- func (s *Store) ListChatPullRequests(ctx context.Context, chatID string) ([]ChatPullRequest, error)
- func (s *Store) ListChats(ctx context.Context, login string, limit int, cursor string) ([]Chat, string, error)
- func (s *Store) ListMcpServers(ctx context.Context) ([]mcp.Server, error)
- func (s *Store) ListReviewSurfaces(ctx context.Context, sourceKey, kind string) ([]ReviewSurface, error)
- func (s *Store) ListRunningTurns(ctx context.Context) ([]Turn, error)
- func (s *Store) ListSessions(ctx context.Context, limit int, cursor string) ([]Session, string, error)
- func (s *Store) ListSlackChannels(ctx context.Context) ([]SlackChannel, error)
- func (s *Store) ListTurns(ctx context.Context, sessionID string, limit int) ([]Turn, error)
- func (s *Store) MarkRelayed(ctx context.Context, taskID string, seq uint64) (bool, error)
- func (s *Store) MaxRelayedSeq(ctx context.Context, taskID string) (uint64, error)
- func (s *Store) McpServer(ctx context.Context, name string) (mcp.Server, error)
- func (s *Store) Migrate(ctx context.Context) error
- func (s *Store) Ping(ctx context.Context) error
- func (s *Store) PutLinearCursor(ctx context.Context, key string, at time.Time) error
- func (s *Store) PutSetting(ctx context.Context, key string, value any) error
- func (s *Store) RefreshMcpServerOAuth(ctx context.Context, name string, version int32, o mcp.OAuth) error
- func (s *Store) RenameChat(ctx context.Context, id, login, title string) (Chat, error)
- func (s *Store) RunningChatTask(ctx context.Context, chatID string) (string, error)
- func (s *Store) RunningDelegations(ctx context.Context) ([]Delegation, error)
- func (s *Store) RunningDelegationsForRef(ctx context.Context, ref string) ([]Delegation, error)
- func (s *Store) SetChatChannel(ctx context.Context, id, channel string) error
- func (s *Store) SetChatChoice(ctx context.Context, id string, c ChatChoice) (Chat, error)
- func (s *Store) SetChatTitle(ctx context.Context, id, title string) (Chat, error)
- func (s *Store) SetDelegationTask(ctx context.Context, id, taskID string) error
- func (s *Store) SetMcpServerOAuth(ctx context.Context, name, login string, version int32, o mcp.OAuth) error
- func (s *Store) SetMcpServerTokenMeta(ctx context.Context, name, hint, login string, version int32) error
- func (s *Store) SetSessionPlaybook(ctx context.Context, id, playbook string) error
- func (s *Store) SetSlackChannelDescription(ctx context.Context, id, description string) (SlackChannel, error)
- func (s *Store) SetTurnTask(ctx context.Context, turnID, taskID string) error
- func (s *Store) SlackChannel(ctx context.Context, id string) (SlackChannel, error)
- func (s *Store) UpdateMcpServer(ctx context.Context, srv mcp.Server, login string) error
- func (s *Store) UpsertSession(ctx context.Context, want Session) (Session, error)
- func (s *Store) UpsertSlackChannelName(ctx context.Context, id, name string) error
- func (s *Store) Usage(ctx context.Context, q UsageQuery) (Usage, error)
- type Turn
- type TurnCost
- type Usage
- type UsageBackend
- type UsageDay
- type UsageQuery
Constants ¶
const ( DefaultPageLimit = 50 MaxPageLimit = 500 )
Page limits, the same shape podium-server uses.
const ( TurnRunning = "running" TurnSucceeded = "succeeded" TurnFailed = "failed" TurnLost = "lost" TurnCancelled = "cancelled" TurnTimeout = "timeout" )
Turn statuses. These are exactly the values the turns.status check constraint allows.
const ( RoleUser = "user" RoleAssistant = "assistant" // RoleProgress is a line a task said on its way to an answer. It is the task talking, // so it is stored and rendered like anything else it said — but it is not an answer, // which is why it is not RoleAssistant: the brief leaves it out, and an attachment // lands on the answer rather than on the last thought before it. RoleProgress = "progress" )
Chat message roles. They are the same two words conductor.RoleUser/RoleAssistant use — deliberately duplicated rather than imported, because the conductor imports this package and one of them has to be the copy. These are the column's values; those are the brief schema's.
const ( OriginWeb = "web" OriginSlack = "slack" )
The origins a chat can have. OriginWeb is a conversation Podium owns and may be written to; OriginSlack is one it mirrors for reading, which lives in Slack and is answered there.
const ( // PullRequestFromTurn is a link a turn's own answer named. PullRequestFromTurn = "turn" // PullRequestFromHuman is a link a person attached by hand. PullRequestFromHuman = "human" )
Where a chat's pull-request link came from. They are exactly the values the chat_pull_requests.source check constraint allows.
const ChatPreviewChars = 80
ChatPreviewChars is how much of the last message the chat list shows.
const ChatSourceKeyPrefix = "chat:"
ChatSourceKeyPrefix is what a chat's session key starts with. The chat source builds it and the conductor treats it as opaque; the store needs it only to answer "is a turn of this chat running" in the same query that lists the chats.
const DefaultChatTitle = "New chat"
DefaultChatTitle is what a chat created with no title is called.
const LinearCursorKey = "issues_updated_at"
LinearCursorKey is the only key in the linear_cursor table: the high-water mark of Issue.updatedAt the Linear source has already turned into events.
const MaxChatTitleRunes = 80
MaxChatTitleRunes is the longest title RenameChat will store. The rail truncates visually well before this; the cap exists so a paste cannot write a multi-kilobyte title into every ListChats payload.
const MaxSlackChannelDescriptionRunes = 500
MaxSlackChannelDescriptionRunes caps an operator note so it cannot eat the turn brief.
const MaxUsageRange = 366 * 24 * time.Hour
MaxUsageRange is the widest window Usage will read. A year of turns is already more than any calendar draws, and an unbounded range is a table scan an operator can ask for by typing a date.
const ReviewSurfaceSlack = "slack"
ReviewSurfaceSlack is the kind a Slack thread is stored under on review_surfaces.
Variables ¶
var ErrConflict = errors.New("agent store: already exists")
ErrConflict is what a write returns when the name it wanted is already taken.
var ErrInvalidChatTitle = errors.New("invalid chat title")
ErrInvalidChatTitle is a title a rename would not store: empty, too long, or holding a control character. The handler maps it to InvalidArgument.
var ErrInvalidSlackChannelDescription = errors.New("invalid slack channel description")
ErrInvalidSlackChannelDescription is a note the UI would not store: too long.
var ErrNotFound = errors.New("agent store: not found")
ErrNotFound is what the readers return for a row that is not there.
var ErrSurfaceBound = errors.New("this surface is already bound to another pull request")
ErrSurfaceBound is a Slack thread (or other surface) that is already reviewing a different pull request. One thread is one review; a second PR URL in the same thread is refused rather than silently switching.
Functions ¶
func ChatSourceKey ¶
ChatSourceKey is the session identity of one chat.
func SlackChannelID ¶ added in v0.2.0
SlackChannelID is the C…/G…/D… inside a slack:<channel>:<thread> source key.
Types ¶
type Backend ¶
Backend is the resolved agent, model and effort a turn ran on, and the provider that was billed for it. Provider is stored rather than derived from Agent so that remapping a backend to another provider later cannot rewrite what past turns cost whom.
type Chat ¶
type Chat struct {
ID string
Title string
// Login owns the chat, and is empty for a MIRRORED conversation: a Slack thread
// belongs to the workspace rather than to a Podium identity. Empty is what makes it
// readable by every login and renameable and deletable by none, because both of those
// queries filter on `login = @login` and no null matches that.
Login string
// Origin is where the conversation actually lives: OriginWeb for a chat Podium owns,
// OriginSlack for a thread it is only mirroring. The UI reads it to decide whether the
// composer is offered at all.
Origin string
// StartedBy is the person who asked first, by display name. It is a mirrored thread's
// attribution, in place of the login it has not got, and empty for a web chat.
StartedBy string
// Participants is everyone who has spoken, first appearance first. Loaded for one chat
// and not for a list: it is a second query per conversation and the list only needs
// StartedBy.
Participants []string
CreatedAt time.Time
// ChatChoice is what this chat is answered on, remembered so a model is picked once per
// conversation rather than on every message.
ChatChoice
// AutoTitle is true when Podium may rewrite Title from the first query. False when
// the caller supplied a title at create.
AutoTitle bool
// LastMessageAt is nil for a chat nobody has spoken in yet.
LastMessageAt *time.Time
// Preview is the head of the last message, or "" when there is none.
Preview string
// TurnRunning is true while a turn of this chat is in flight.
TurnRunning bool
// TaskRunning is true while a task this conversation delegated is still running, which
// outlasts the turn that delegated it.
TaskRunning bool
// Channel is the Slack channel's human name for a mirrored thread, without a leading #.
// Empty for a web chat and for a thread whose name has not been resolved yet.
Channel string
}
Chat is one web-chat conversation, owned by the login that created it.
type ChatAttachment ¶
type ChatAttachment struct {
ArtifactID string `json:"artifact_id"`
Name string `json:"name"`
ContentType string `json:"content_type"`
SizeBytes int64 `json:"size_bytes"`
}
ChatAttachment is a file a turn produced, resolved to the artifact it is. The id is stored, never the name alone: two turns both producing report.csv are two artifacts.
type ChatChoice ¶
ChatChoice is what a chat is answered on: the OVERRIDE a person picked, all empty for the assistant's own model.
The override and not the resolution, deliberately. A conversation that never asked for anything specific stays on whatever profile.yaml says and follows it when that changes; storing the resolved triple — which `turns` already records per turn — would pin every chat to the model its first turn happened to run, turning a default into a choice nobody made.
type ChatMessage ¶
type ChatMessage struct {
ChatID string
Seq uint64
Role string
Text string
Attachments []ChatAttachment
TS time.Time
// Author is who said it, by display name. Empty for a web chat, whose login already
// says who is typing, and set for every message of a mirrored Slack thread — including
// the bot's own, which is what lets the UI show the thread as the people in it saw it.
Author string
// TaskID is the task whose words these are, and empty for the assistant's own. It is
// what lets a reader tell the three kinds of progress apart: the assistant thinking on
// this host, the conductor announcing a delegation, and a delegated task talking.
TaskID string
}
ChatMessage is one stored turn of a conversation. Text is content: a human wrote the user messages and a task wrote the assistant ones, and nothing here interprets either.
type ChatPullRequest ¶
type ChatPullRequest struct {
ChatID string
// URL is canonical: https://github.com/<owner>/<repo>/pull/<number>. It is the
// identity of the link, which is what makes /pull/12/files and /pull/12 one row.
URL string
Owner string
Repo string
Number int
// Source is PullRequestFromTurn or PullRequestFromHuman.
Source string
CreatedAt time.Time
}
ChatPullRequest is one pull request a chat's work produced. Owner, Repo and Number are what the URL itself said — nothing here was learnt from GitHub, and the conductor holds no credential that could ask it.
type Delegation ¶
type Delegation struct {
ID string
SessionID string
TurnID string
TriggerRef string
Playbook string
Instruction string
TaskID string
Status string
FinalText string
CreatedAt time.Time
FinishedAt *time.Time
// Backend is what ran it, recorded as the row is written. Zero for a delegation from
// before the columns existed.
Backend Backend
// NumTurns and CostUSD are what the task reported spending, nil when its accounting
// never arrived. A conversation's spend is almost entirely here rather than on its
// turns: the assistant answers on the host and the container does the work.
NumTurns *int
CostUSD *float64
}
Delegation is one delegated task. Status shares the turns table's vocabulary, so the same classify() maps a terminal task onto either.
type NewDelegation ¶
type NewDelegation struct {
SessionID string
TurnID string
TriggerRef string
Playbook string
Instruction string
// Backend is what this task will run on, resolved by the caller from the playbook. It is
// recorded now rather than derived later, for the same reason a turn's is: a playbook's
// model is a default, and editing it would otherwise relabel every delegation that ever
// ran under it.
Backend Backend
}
NewDelegation is what a caller has to know to record one.
type ReviewSurface ¶ added in v0.2.0
ReviewSurface is one door into a GitHub pull-request review session.
type Session ¶
type Session struct {
ID string
SourceKind string
SourceKey string
Profile string
Playbook string
CreatedAt time.Time
LastTurnAt *time.Time
}
Session is one conversation. Its identity is SourceKey, which the source computes; the conductor never parses it.
type SlackChannel ¶ added in v0.2.0
SlackChannel is one Slack channel this conductor has seen, and the note a turn of it is briefed with.
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store is a handle on the conductor's database. It is safe for concurrent use.
func New ¶
New opens a pgx pool against databaseURL and verifies it is reachable. Call Migrate before using it. The caller owns the returned Store and must Close it.
func (*Store) AppendChatMessage ¶
func (s *Store) AppendChatMessage(ctx context.Context, msg ChatMessage) (ChatMessage, error)
AppendChatMessage stores one message and gives it the chat's next seq. The seq comes from the insert itself, so two concurrent appends get two seqs rather than one collision.
func (*Store) AttachChatPullRequest ¶
func (s *Store) AttachChatPullRequest(ctx context.Context, pr ChatPullRequest) (ChatPullRequest, error)
AttachChatPullRequest is a person linking one by hand. It revives a link they detached earlier and takes it over from the turn that found it: re-attaching what you removed is meant, and the row is yours afterwards.
func (*Store) AttachToLastAssistantMessage ¶
func (s *Store) AttachToLastAssistantMessage( ctx context.Context, chatID string, file ChatAttachment, ) (ChatMessage, error)
AttachToLastAssistantMessage adds one file to the newest assistant message of a chat and returns the message as it now stands.
It is an update rather than part of the insert because the turn loop's ordering is fixed: a final is posted before its attachments are resolved (step 17), so the row exists before the artifact ids do. ErrNotFound means the turn produced a file but said nothing — the caller decides what to do about that.
func (*Store) BindReviewSurface ¶ added in v0.2.0
BindReviewSurface records that ref (for kind) is a door into sourceKey. A first bind takes; binding the same pair again is a no-op; binding a ref that already points at a different sourceKey is ErrSurfaceBound.
func (*Store) ChatBySourceKey ¶
ChatBySourceKey reads the chat mirroring one conversation, or ErrNotFound.
func (*Store) ChatParticipants ¶
ChatParticipants is everyone who has spoken in a chat, first appearance first.
func (*Store) ChatTurnRunning ¶
ChatTurnRunning reports whether a turn of this chat is in flight. It is the server-side half of "one turn at a time per conversation": the UI disables the composer, and this is what makes a second send impossible rather than unlikely.
func (*Store) ClearMcpServerTokenMeta ¶
ClearMcpServerTokenMeta forgets a token that has been removed from the control plane.
func (*Store) Close ¶
func (s *Store) Close()
Close releases every pooled connection. It is idempotent.
func (*Store) CountRelayed ¶
CountRelayed is how many of a task's events have been relayed. Tests use it to prove "exactly once".
func (*Store) CreateChat ¶
CreateChat opens a chat owned by login. An empty title becomes DefaultChatTitle, and AutoTitle stays true so the first query may rename it. A supplied title is kept.
func (*Store) CreateDelegation ¶
func (s *Store) CreateDelegation(ctx context.Context, want NewDelegation) (Delegation, error)
CreateDelegation records a delegation before its task exists, so a task the control plane accepts is never one this database has never heard of. SetDelegationTask fills in the id.
func (*Store) CreateMirrorChat ¶
func (s *Store) CreateMirrorChat(ctx context.Context, sourceKey, origin, startedBy, title string) (Chat, error)
CreateMirrorChat opens the chat that MIRRORS a conversation living somewhere else. It has no login, because nobody in Slack has one; sourceKey is the conductor session's own key, which is both the link the list joins on and the uniqueness that stops one thread becoming two chats.
The title comes from the first thing asked, the same rule a web chat follows, and AutoTitle stays true so a later turn may still improve it.
func (*Store) CreateTurn ¶
func (s *Store) CreateTurn(ctx context.Context, sessionID, triggerRef string, b Backend) (Turn, error)
CreateTurn records a turn as running. The task does not exist yet: SetTurnTask fills it in once CreateTask has answered.
func (*Store) DelegationsForTurn ¶
DelegationsForTurn is everything one turn delegated, oldest first.
func (*Store) DeleteChat ¶
DeleteChat removes one login's chat, or a mirrored thread's copy, and every message in it (ON DELETE CASCADE). ErrNotFound means it was not there or not theirs: the two are the same answer so the existence of another login's chat is not leaked. A mirrored thread has no owner and every login sees it, so any may delete the copy; the thread itself lives in Slack and is mirrored afresh by its next message. Sessions and turns are left alone — they are the audit of the work, not the transcript.
func (*Store) DeleteMcpServer ¶
DeleteMcpServer removes a registration. ErrNotFound means it was not registered; the caller is what deletes the secret, because a row that is gone must not leave a credential behind it.
func (*Store) DeleteSetting ¶
DeleteSetting removes one stored value. A key that was never set is not an error: the caller wants it gone, and it is.
func (*Store) DetachChatPullRequest ¶
DetachChatPullRequest takes one link off a chat. ErrNotFound means it was not linked, or was detached already; the two are the same answer.
func (*Store) FinishDelegation ¶
func (s *Store) FinishDelegation( ctx context.Context, id, status, finalText string, numTurns *int, costUSD *float64, ) error
FinishDelegation records how a delegated task ended. finalText is what the task actually said, which may be empty; numTurns and costUSD are nil when its accounting never arrived.
func (*Store) FinishTurn ¶
func (s *Store) FinishTurn( ctx context.Context, turnID, status string, numTurns *int, costUSD *float64, finalText string, ) error
FinishTurn records how a turn ended. numTurns and costUSD are nil when the runtime's turn.json never arrived; finalText is what was actually relayed, which may be empty.
func (*Store) GetChat ¶
GetChat reads one chat by id, whoever owns it. The caller checks the login: a handler that must not leak another login's chat needs the row to compare against.
func (*Store) GetDelegation ¶
GetDelegation reads one by id. ErrNotFound when there is no such delegation.
func (*Store) GetLinearCursor ¶
GetLinearCursor reads the poll watermark. ErrNotFound means the source has never run against this database, which is what makes the first tick look 24 hours back.
func (*Store) GetReviewSurface ¶ added in v0.2.0
GetReviewSurface looks up one surface. ErrNotFound means it is not bound.
func (*Store) GetSession ¶
GetSession reads one session by id.
func (*Store) GetSessionByKey ¶
GetSessionByKey reads one session by its source key.
func (*Store) GetSetting ¶
GetSetting decodes one stored value into out. ErrNotFound means the key was never set.
func (*Store) InsertMcpServer ¶
InsertMcpServer registers a new server. ErrConflict means the name is already taken.
func (*Store) LinkChatPullRequest ¶
LinkChatPullRequest records a pull request a turn's answer named, and reports whether this call is what linked it. False means the chat already had it — the same turn saying it twice, or a human having detached it, and neither is an error.
func (*Store) ListChatMessages ¶
func (s *Store) ListChatMessages(ctx context.Context, chatID string, fromSeq uint64) ([]ChatMessage, error)
ListChatMessages returns a chat's messages with seq > fromSeq, in seq order. fromSeq is exclusive, which is what makes replay-then-follow exactly once.
func (*Store) ListChatPullRequests ¶
ListChatPullRequests returns a chat's links, oldest first. Detached ones are not there.
func (*Store) ListChats ¶
func (s *Store) ListChats(ctx context.Context, login string, limit int, cursor string) ([]Chat, string, error)
ListChats returns one login's own chats, newest first. Another login's are not returned and cannot be paged into: the filter is in the query, not in the caller.
func (*Store) ListMcpServers ¶
ListMcpServers returns every registered server, sorted by name.
func (*Store) ListReviewSurfaces ¶ added in v0.2.0
func (s *Store) ListReviewSurfaces(ctx context.Context, sourceKey, kind string) ([]ReviewSurface, error)
ListReviewSurfaces returns every surface of kind bound to sourceKey, oldest first.
func (*Store) ListRunningTurns ¶
ListRunningTurns is the recovery pass's working set: every turn that was in flight when the process died.
func (*Store) ListSessions ¶
func (s *Store) ListSessions(ctx context.Context, limit int, cursor string) ([]Session, string, error)
ListSessions returns sessions newest first. The cursor is the last id of the previous page, which sorts by time because every id is a ULID.
func (*Store) ListSlackChannels ¶ added in v0.2.0
func (s *Store) ListSlackChannels(ctx context.Context) ([]SlackChannel, error)
ListSlackChannels returns every known channel, named ones first (empty names sort last only by id when names match).
func (*Store) MarkRelayed ¶
MarkRelayed claims (taskID, seq) for relaying. The bool is "this is the first time", and it is the whole of the exactly-once guarantee: a replayed stream claims nothing and the caller therefore says nothing twice.
func (*Store) MaxRelayedSeq ¶
MaxRelayedSeq is where a resumed follow starts: the highest seq this conductor has already said out loud for a task.
func (*Store) Migrate ¶
Migrate applies every embedded migration that is not already recorded in schema_migrations. It is idempotent and safe to run concurrently from several processes.
func (*Store) PutLinearCursor ¶
PutLinearCursor advances the poll watermark. The caller writes it AFTER the page's events have been handed to the conductor, so a crash in between replays the page rather than losing it.
func (*Store) PutSetting ¶
PutSetting stores one JSON value under key. Step 18 keeps the provider-key metadata here; nothing in this step writes one.
func (*Store) RefreshMcpServerOAuth ¶
func (s *Store) RefreshMcpServerOAuth( ctx context.Context, name string, version int32, o mcp.OAuth, ) error
RefreshMcpServerOAuth moves a sign-in onto a new access token. It deliberately does not touch the provenance: the human who signed in is still the human who signed in, and a background pass writing its own name over theirs would lose the only record of who did.
func (*Store) RenameChat ¶
RenameChat sets the title of one of login's chats, or of a mirrored thread, which every login can see and so any may rename. Another login's chat is not found, the same as every other chat read: knowing the id is not access. The row it returns has AutoTitle cleared: the name is the owner's now.
func (*Store) RunningChatTask ¶
RunningChatTask is the Podium task currently answering this chat. Empty when nothing is in flight, when the chat has never had a turn, and when the turn row exists but CreateTask has not answered yet — there is then nothing to cancel.
func (*Store) RunningDelegations ¶
func (s *Store) RunningDelegations(ctx context.Context) ([]Delegation, error)
RunningDelegations is every delegated task still in flight, for the recovery pass on start. A delegation's task runs on a NODE, so it survives this process dying — which is exactly why it has to be picked up again rather than abandoned.
func (*Store) RunningDelegationsForRef ¶
RunningDelegationsForRef is the in-flight delegations of one conversation, which is what deleting a chat has to stop.
func (*Store) SetChatChannel ¶ added in v0.2.0
SetChatChannel records the Slack channel name a mirrored chat should show. Empty is valid and means "not resolved yet".
func (*Store) SetChatChoice ¶
SetChatChoice records what a chat is answered on. An all-empty choice is a real value — it means the assistant's own model — so this writes whatever it is given rather than treating empty as "leave it alone": switching back to the default is a choice too.
func (*Store) SetChatTitle ¶
SetChatTitle rewrites an auto-named chat. A title supplied at create is left alone and the current row is returned.
func (*Store) SetDelegationTask ¶
SetDelegationTask binds a delegation to the task now running it.
func (*Store) SetMcpServerOAuth ¶
func (s *Store) SetMcpServerOAuth( ctx context.Context, name, login string, version int32, o mcp.OAuth, ) error
SetMcpServerOAuth records a completed sign-in: who did it, which secret version holds the access token, and the bag a refresh needs. It is the OAuth twin of SetMcpServerTokenMeta and is exclusive with it — either column set clears the other, so a server has one credential and one story about where it came from.
func (*Store) SetMcpServerTokenMeta ¶
func (s *Store) SetMcpServerTokenMeta( ctx context.Context, name, hint, login string, version int32, ) error
SetMcpServerTokenMeta records that a token was stored, and which version of the secret the hint describes. The token itself is already in the control plane by the time this is called — the order is deliberate, and api/mcp.go carries the reasoning.
func (*Store) SetSessionPlaybook ¶
SetSessionPlaybook changes which playbook a session runs. It is for a CONVERSATION only: a chat window whose person picks a playbook per message. A thread keeps what it started with, and UpsertSession is what enforces that.
func (*Store) SetSlackChannelDescription ¶ added in v0.2.0
func (s *Store) SetSlackChannelDescription(ctx context.Context, id, description string) (SlackChannel, error)
SetSlackChannelDescription stores the operator note a turn of this channel is briefed with. Empty is valid. ErrNotFound means the channel is not in the catalogue yet.
func (*Store) SetTurnTask ¶
SetTurnTask binds a turn to the Podium task that is running it.
func (*Store) SlackChannel ¶ added in v0.2.0
SlackChannel reads one. ErrNotFound means this conductor has never seen it.
func (*Store) UpdateMcpServer ¶
UpdateMcpServer replaces a registration, leaving the token metadata alone. ErrNotFound means there was none to replace.
func (*Store) UpsertSession ¶
UpsertSession returns the session for want.SourceKey, creating it if it is new. The playbook of an existing session is never changed: one session, one playbook, fixed at creation. The returned row is authoritative, so a caller that wanted a different playbook can see it did not get one.
func (*Store) UpsertSlackChannelName ¶ added in v0.2.0
UpsertSlackChannelName records the name Slack last gave this channel. An empty name on an existing row is ignored so a failed lookup cannot blank a name we already had.
type Turn ¶
type Turn struct {
ID string
SessionID string
TaskID string
TriggerRef string
Status string
StartedAt time.Time
FinishedAt *time.Time
NumTurns *int
CostUSD *float64
FinalText string
// Backend is what this turn actually ran on, recorded at creation. Zero for a turn from
// before the columns existed — never guessed from the playbook, whose model is only a
// default and may have been edited since.
Backend Backend
}
Turn is one inbound message turned into one Podium task.
type TurnCost ¶
type TurnCost struct {
TurnID string
TaskID string
SessionID string
SourceKind string
SourceKey string
Playbook string
Profile string
Status string
StartedAt time.Time
FinishedAt *time.Time
NumTurns *int
CostUSD *float64
// Backend is what ran it. Zero for a turn recorded before it was written down.
Backend Backend
}
TurnCost is one turn's spend with the session fields that say what spent it. TaskID is empty for a turn whose task was never created — the money was still spent, so it counts towards a total, but it joins to no task.
type Usage ¶
type Usage struct {
Days []UsageDay
Costs []TurnCost
Backends []UsageBackend
// The totals are for the whole range. Costs is capped by limit and may be shorter.
TotalCostUSD float64
TotalTurns int
TotalModelTurns int
Unpriced int
}
Usage is what the conductor spent over a range: a total per day, and the individual turns behind it.
type UsageBackend ¶
UsageBackend is one (provider, agent, model, effort) and what it cost over the range. Every field may be empty together, which is the bucket for turns that predate the columns; the API reports that as unrecorded rather than as a model with no name.
type UsageDay ¶
UsageDay is one day's spend, bucketed in the caller's own time zone. Date is YYYY-MM-DD as that zone saw it, which is why it is a string and not a time: it names a day, not an instant, and turning it back into one would put it in the server's zone.
type UsageQuery ¶
type UsageQuery struct {
From time.Time
To time.Time
// CompareFrom widens the day rows, and nothing else, back to an earlier instant. Zero or
// later than From means the days start at From like the rest of the answer.
CompareFrom time.Time
// TZOffsetMinutes is the caller's offset from UTC, east-positive, and decides where a
// day boundary falls.
TZOffsetMinutes int
// Limit caps Costs. Days always covers the whole range.
Limit int
}
UsageQuery bounds a usage read. From is inclusive and To is exclusive, both against the turn's start.