store

package
v0.2.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 21, 2026 License: MIT Imports: 17 Imported by: 0

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

View Source
const (
	DefaultPageLimit = 50
	MaxPageLimit     = 500
)

Page limits, the same shape podium-server uses.

View Source
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.

View Source
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.

View Source
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.

View Source
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.

View Source
const ChatPreviewChars = 80

ChatPreviewChars is how much of the last message the chat list shows.

View Source
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.

View Source
const DefaultChatTitle = "New chat"

DefaultChatTitle is what a chat created with no title is called.

View Source
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.

View Source
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.

View Source
const MaxSlackChannelDescriptionRunes = 500

MaxSlackChannelDescriptionRunes caps an operator note so it cannot eat the turn brief.

View Source
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.

View Source
const ReviewSurfaceSlack = "slack"

ReviewSurfaceSlack is the kind a Slack thread is stored under on review_surfaces.

Variables

View Source
var ErrConflict = errors.New("agent store: already exists")

ErrConflict is what a write returns when the name it wanted is already taken.

View Source
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.

View Source
var ErrInvalidSlackChannelDescription = errors.New("invalid slack channel description")

ErrInvalidSlackChannelDescription is a note the UI would not store: too long.

View Source
var ErrNotFound = errors.New("agent store: not found")

ErrNotFound is what the readers return for a row that is not there.

View Source
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

func ChatSourceKey(chatID string) string

ChatSourceKey is the session identity of one chat.

func SlackChannelID added in v0.2.0

func SlackChannelID(sourceKey string) (string, bool)

SlackChannelID is the C…/G…/D… inside a slack:<channel>:<thread> source key.

Types

type Backend

type Backend struct {
	Agent    string
	Model    string
	Effort   string
	Provider string
}

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

type ChatChoice struct {
	Agent  string
	Model  string
	Effort string
}

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

type ReviewSurface struct {
	SourceKey string
	Kind      string
	Ref       string
	CreatedAt time.Time
}

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

type SlackChannel struct {
	ID          string
	Name        string
	Description string
	UpdatedAt   time.Time
}

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

func New(ctx context.Context, databaseURL string) (*Store, error)

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

func (s *Store) BindReviewSurface(ctx context.Context, sourceKey, kind, ref string) error

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

func (s *Store) ChatBySourceKey(ctx context.Context, sourceKey string) (Chat, error)

ChatBySourceKey reads the chat mirroring one conversation, or ErrNotFound.

func (*Store) ChatParticipants

func (s *Store) ChatParticipants(ctx context.Context, chatID string) ([]string, error)

ChatParticipants is everyone who has spoken in a chat, first appearance first.

func (*Store) ChatTurnRunning

func (s *Store) ChatTurnRunning(ctx context.Context, chatID string) (bool, error)

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

func (s *Store) ClearMcpServerTokenMeta(ctx context.Context, name, login string) error

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

func (s *Store) CountRelayed(ctx context.Context, taskID string) (int, error)

CountRelayed is how many of a task's events have been relayed. Tests use it to prove "exactly once".

func (*Store) CreateChat

func (s *Store) CreateChat(ctx context.Context, login, title string) (Chat, error)

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

func (s *Store) DelegationsForTurn(ctx context.Context, turnID string) ([]Delegation, error)

DelegationsForTurn is everything one turn delegated, oldest first.

func (*Store) DeleteChat

func (s *Store) DeleteChat(ctx context.Context, id, login string) error

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

func (s *Store) DeleteMcpServer(ctx context.Context, name string) error

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

func (s *Store) DeleteSetting(ctx context.Context, key string) error

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

func (s *Store) DetachChatPullRequest(ctx context.Context, chatID, url string) error

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

func (s *Store) GetChat(ctx context.Context, id string) (Chat, error)

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

func (s *Store) GetDelegation(ctx context.Context, id string) (Delegation, error)

GetDelegation reads one by id. ErrNotFound when there is no such delegation.

func (*Store) GetLinearCursor

func (s *Store) GetLinearCursor(ctx context.Context, key string) (time.Time, error)

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

func (s *Store) GetReviewSurface(ctx context.Context, kind, ref string) (ReviewSurface, error)

GetReviewSurface looks up one surface. ErrNotFound means it is not bound.

func (*Store) GetSession

func (s *Store) GetSession(ctx context.Context, id string) (Session, error)

GetSession reads one session by id.

func (*Store) GetSessionByKey

func (s *Store) GetSessionByKey(ctx context.Context, sourceKey string) (Session, error)

GetSessionByKey reads one session by its source key.

func (*Store) GetSetting

func (s *Store) GetSetting(ctx context.Context, key string, out any) error

GetSetting decodes one stored value into out. ErrNotFound means the key was never set.

func (*Store) GetTurn

func (s *Store) GetTurn(ctx context.Context, id string) (Turn, error)

GetTurn reads one turn by id.

func (*Store) InsertMcpServer

func (s *Store) InsertMcpServer(ctx context.Context, srv mcp.Server, login string) error

InsertMcpServer registers a new server. ErrConflict means the name is already taken.

func (*Store) LinkChatPullRequest

func (s *Store) LinkChatPullRequest(ctx context.Context, pr ChatPullRequest) (bool, error)

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

func (s *Store) ListChatPullRequests(ctx context.Context, chatID string) ([]ChatPullRequest, error)

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

func (s *Store) ListMcpServers(ctx context.Context) ([]mcp.Server, error)

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

func (s *Store) ListRunningTurns(ctx context.Context) ([]Turn, error)

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) ListTurns

func (s *Store) ListTurns(ctx context.Context, sessionID string, limit int) ([]Turn, error)

ListTurns returns a session's turns, newest first.

func (*Store) MarkRelayed

func (s *Store) MarkRelayed(ctx context.Context, taskID string, seq uint64) (bool, error)

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

func (s *Store) MaxRelayedSeq(ctx context.Context, taskID string) (uint64, error)

MaxRelayedSeq is where a resumed follow starts: the highest seq this conductor has already said out loud for a task.

func (*Store) McpServer

func (s *Store) McpServer(ctx context.Context, name string) (mcp.Server, error)

McpServer reads one. ErrNotFound means it is not registered.

func (*Store) Migrate

func (s *Store) Migrate(ctx context.Context) error

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) Ping

func (s *Store) Ping(ctx context.Context) error

Ping reports whether Postgres is reachable. /readyz calls this.

func (*Store) PutLinearCursor

func (s *Store) PutLinearCursor(ctx context.Context, key string, at time.Time) error

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

func (s *Store) PutSetting(ctx context.Context, key string, value any) error

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

func (s *Store) RenameChat(ctx context.Context, id, login, title string) (Chat, error)

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

func (s *Store) RunningChatTask(ctx context.Context, chatID string) (string, error)

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

func (s *Store) RunningDelegationsForRef(ctx context.Context, ref string) ([]Delegation, error)

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

func (s *Store) SetChatChannel(ctx context.Context, id, channel string) error

SetChatChannel records the Slack channel name a mirrored chat should show. Empty is valid and means "not resolved yet".

func (*Store) SetChatChoice

func (s *Store) SetChatChoice(ctx context.Context, id string, c ChatChoice) (Chat, error)

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

func (s *Store) SetChatTitle(ctx context.Context, id, title string) (Chat, error)

SetChatTitle rewrites an auto-named chat. A title supplied at create is left alone and the current row is returned.

func (*Store) SetDelegationTask

func (s *Store) SetDelegationTask(ctx context.Context, id, taskID string) error

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

func (s *Store) SetSessionPlaybook(ctx context.Context, id, playbook string) error

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

func (s *Store) SetTurnTask(ctx context.Context, turnID, taskID string) error

SetTurnTask binds a turn to the Podium task that is running it.

func (*Store) SlackChannel added in v0.2.0

func (s *Store) SlackChannel(ctx context.Context, id string) (SlackChannel, error)

SlackChannel reads one. ErrNotFound means this conductor has never seen it.

func (*Store) UpdateMcpServer

func (s *Store) UpdateMcpServer(ctx context.Context, srv mcp.Server, login string) error

UpdateMcpServer replaces a registration, leaving the token metadata alone. ErrNotFound means there was none to replace.

func (*Store) UpsertSession

func (s *Store) UpsertSession(ctx context.Context, want Session) (Session, error)

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

func (s *Store) UpsertSlackChannelName(ctx context.Context, id, name string) error

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.

func (*Store) Usage

func (s *Store) Usage(ctx context.Context, q UsageQuery) (Usage, error)

Usage reports spend over q's range. An empty or backwards range is not an error: it reports nothing, because "no turns ran then" is the honest answer to it.

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

type UsageBackend struct {
	Backend
	CostUSD    float64
	Turns      int
	ModelTurns int
	Unpriced   int
}

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

type UsageDay struct {
	Date       string
	CostUSD    float64
	Turns      int
	ModelTurns int
	Unpriced   int
}

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.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL