Documentation
¶
Index ¶
- Constants
- Variables
- func AllSessionTypes() []string
- func ApplyACPMetadataDefaults(meta map[string]any) map[string]any
- func ApplyExternalMetadataDefaults(meta map[string]any) map[string]any
- func DescriptorFromLegacyType(typ string) (string, string)
- func IsACPRuntime(thread Thread) bool
- func IsDirectRuntime(thread Thread) bool
- func IsDirectRuntimeType(runtimeType string) bool
- func IsKnownRuntimeType(runtimeType string) bool
- func IsKnownSessionMode(mode string) bool
- func IsKnownType(typ string) bool
- func IsUserFacingType(typ string) bool
- func LegacyTypeForDescriptor(sessionMode, runtimeType string) string
- func ResolveDescriptor(legacyType, sessionMode, runtimeType string) (string, string, string, error)
- func SupportsSkillActivation(sessionMode, legacyType, runtimeType string) bool
- func UserFacingSessionTypes() []string
- func UsesDecisionWaiter(thread Thread) bool
- type ACPSetupValidation
- type ACPSetupValidator
- type CreateInput
- type CreateSubagentInput
- type Cursor
- type ForkFromAssistantInput
- type ListFilter
- type Queries
- type Service
- func (s *Service) Create(ctx context.Context, input CreateInput) (Thread, error)
- func (s *Service) CreateSubagent(ctx context.Context, input CreateSubagentInput) (Thread, SubagentConfig, error)
- func (s *Service) ForkFromAssistantTurn(ctx context.Context, input ForkFromAssistantInput) (Thread, error)
- func (s *Service) Get(ctx context.Context, sessionID string) (Thread, error)
- func (s *Service) GetSubagentConfig(ctx context.Context, sessionID string) (SubagentConfig, error)
- func (s *Service) LatestModelPreferenceSeed(ctx context.Context, botID, userID string) (string, string, error)
- func (s *Service) ListByBot(ctx context.Context, botID string) ([]Thread, error)
- func (s *Service) ListByBotAndCreatedByUser(ctx context.Context, botID, userID string) ([]Thread, error)
- func (s *Service) ListByBotAndCreatedByUserPaged(ctx context.Context, botID, userID string, types []string, cursor Cursor, ...) ([]Thread, error)
- func (s *Service) ListByBotAndCreatedByUserPagedWithFilter(ctx context.Context, botID, userID string, types []string, cursor Cursor, ...) ([]Thread, error)
- func (s *Service) ListByBotPaged(ctx context.Context, botID string, types []string, cursor Cursor, limit int64) ([]Thread, error)
- func (s *Service) ListByBotPagedWithFilter(ctx context.Context, botID string, types []string, cursor Cursor, limit int64, ...) ([]Thread, error)
- func (s *Service) ListByRoute(ctx context.Context, routeID string) ([]Thread, error)
- func (s *Service) ListSubagentForkContext(ctx context.Context, sessionID string) ([]SubagentForkContextMessage, error)
- func (s *Service) ListSubagentsByParent(ctx context.Context, parentSessionID string) ([]Thread, error)
- func (s *Service) MergeRuntimeMetadata(ctx context.Context, sessionID, runtimeType string, delta map[string]any) (Thread, error)
- func (s *Service) MessageCount(ctx context.Context, sessionID string) (int64, error)
- func (s *Service) SetACPSetupValidator(validator ACPSetupValidator)
- func (s *Service) SetHookService(h *hooks.Service)
- func (s *Service) SoftDelete(ctx context.Context, sessionID string) error
- func (s *Service) Touch(ctx context.Context, sessionID string) error
- func (s *Service) UpdateDescriptorAndMetadataWithOwner(ctx context.Context, sessionID, typ, sessionMode, runtimeType string, ...) (Thread, error)
- func (s *Service) UpdateEmptyDescriptorAndMetadataWithOwner(ctx context.Context, sessionID, typ, sessionMode, runtimeType string, ...) (Thread, error)
- func (s *Service) UpdateMetadata(ctx context.Context, sessionID string, metadata map[string]any) (Thread, error)
- func (s *Service) UpdateTitle(ctx context.Context, sessionID, title string) (Thread, error)
- func (s *Service) UpdateTypeAndMetadata(ctx context.Context, sessionID, typ string, metadata map[string]any) (Thread, error)
- func (s *Service) UpdateTypeAndMetadataWithOwner(ctx context.Context, sessionID, typ string, metadata map[string]any, ...) (Thread, error)
- type SubagentConfig
- type SubagentForkContextMessage
- type Thread
- type Visibility
Constants ¶
const ( TypeChat = "chat" TypeSchedule = "schedule" TypeSubagent = "subagent" TypeDiscuss = "discuss" TypeACPAgent = "acp_agent" RuntimeModel = string(runtimekind.Model) RuntimeACPAgent = string(runtimekind.ACPAgent) RuntimeCodex = string(runtimekind.Codex) RuntimeClaudeCode = string(runtimekind.ClaudeCode) DefaultACPProjectMode = "project" DefaultACPProjectPath = "/data" )
The runtime vocabulary is owned by runtimekind (a leaf package shared with consumers that cannot import this one); these constants are pinned aliases so the session domain keeps its established names.
Variables ¶
var ( ErrACPAgentIDRequired = errors.New("acp_agent_id is required for acp_agent sessions") ErrACPProjectPathMissing = errors.New("project_path is required for acp_agent sessions") ErrACPUnknownAgent = errors.New("unknown ACP agent") ErrACPAgentNotEnabled = errors.New("ACP agent is not enabled for this bot") ErrACPAgentNotConfigured = errors.New("ACP agent is not configured for this bot") ErrACPRuntimeOwnerMissing = errors.New("runtime_owner_account_id is required for acp_agent sessions") ErrACPProjectModeInvalid = errors.New("unknown ACP project mode") // ErrExternalRuntimeOwnerMissing mirrors the ACP owner requirement for // direct external runtimes: turns execute with workspace authority, so a // session without an owner has nobody to authorize them against. ErrExternalRuntimeOwnerMissing = errors.New("runtime_owner_account_id is required for external runtime sessions") ErrForkSourceNotFound = errors.New("fork source session not found") ErrForkSourceNotReply = errors.New("fork source must be a visible assistant reply") ErrForkSourceNotChat = errors.New("fork source must be a chat session") ErrSessionHasMessages = errors.New("session has visible messages") )
var ErrRuntimeMetadataStale = errors.New("session runtime changed; runtime metadata merge skipped")
ErrRuntimeMetadataStale reports that a runtime-metadata merge was skipped because the session no longer runs the expected runtime.
Functions ¶
func AllSessionTypes ¶
func AllSessionTypes() []string
AllSessionTypes returns every known session type. The paged list queries always demand an explicit type filter; callers that filter by visibility instead pass this list to make the type predicate a no-op.
func ApplyACPMetadataDefaults ¶
ApplyACPMetadataDefaults fills omitted ACP session project fields.
func ApplyExternalMetadataDefaults ¶ added in v0.20.0
ApplyExternalMetadataDefaults fills omitted working-directory metadata for direct external runtime sessions. Unlike ACP there is no project mode: the runtime always works inside the bot workspace.
func DescriptorFromLegacyType ¶
DescriptorFromLegacyType maps the legacy single type column to the split session mode/runtime descriptor used by new code paths.
func IsACPRuntime ¶
IsACPRuntime reports whether a session is backed by an ACP runtime. It keeps legacy chat ACP sessions (`type=acp_agent`) working while allowing newer descriptors such as `session_mode=discuss` + `runtime_type=acp_agent`.
func IsDirectRuntime ¶ added in v0.20.0
IsDirectRuntime reports whether a session runs on a direct external agent runtime.
func IsDirectRuntimeType ¶ added in v0.20.0
IsDirectRuntimeType reports whether a runtime type is one of the direct external agent runtimes, outside the ACP compatibility pool.
func IsKnownRuntimeType ¶
func IsKnownSessionMode ¶
func IsKnownType ¶
func IsUserFacingType ¶
IsUserFacingType reports whether a session type is one that user-facing session list endpoints should return by default.
func LegacyTypeForDescriptor ¶
LegacyTypeForDescriptor returns the compatibility type value for a split session descriptor.
func ResolveDescriptor ¶
ResolveDescriptor returns the normalized compatibility type plus split session-mode/runtime descriptor without applying metadata side effects.
func SupportsSkillActivation ¶
SupportsSkillActivation reports whether a session shape can accept user-requested skill activation: chat mode (with the usual legacy-type fallback) on the built-in model runtime. This is the single definition for the rule — the web WS pre-check, the channel inbound gate, and the flow resolver's final guard all call it, so the three surfaces cannot drift.
func UserFacingSessionTypes ¶
func UserFacingSessionTypes() []string
UserFacingSessionTypes returns a fresh copy of the user-facing session type list so callers can read or mutate it without disturbing the package-level source of truth.
func UsesDecisionWaiter ¶ added in v0.20.0
UsesDecisionWaiter reports whether tool-approval and user-input decisions for this session are answered by waking an in-process runtime waiter (the ACP pool or a direct external runtime) instead of resuming the native agent loop with a tool result.
Types ¶
type ACPSetupValidation ¶
ACPSetupValidation is the channel- and runtime-independent policy result needed before a thread can persist an ACP runtime descriptor.
type ACPSetupValidator ¶
type ACPSetupValidator interface {
ValidateACPSetup(agentID string, botMetadata map[string]any) ACPSetupValidation
}
ACPSetupValidator is implemented by an Agent adapter. Thread owns this port so chat persistence never imports an Agent runtime implementation.
type CreateInput ¶
type CreateInput struct {
BotID string
BotAgentID string
RouteID string
ChannelType string
Type string
SessionMode string
RuntimeType string
Title string
Metadata map[string]any
RuntimeMetadata map[string]any
ParentThreadID string
CreatedByUserID string
// WorkdirID immutably binds the thread to a bot workdir. The caller
// (handler layer) validates the workdir; thread persistence only stores
// the reference so this package never imports the workdir domain.
WorkdirID string
// WorkdirPath is the resolved workdir directory, set if and only if
// WorkdirID is set. For ACP threads it overrides the metadata
// project_path so the runtime works in that directory; it also
// leaves a creation-time path snapshot in the metadata for history.
WorkdirPath string
// PreferredChatModelID / PreferredReasoningEffort are the first-message
// pair written at INSERT time (issue #879, spec §3.3-D) so the
// session_created broadcast never precedes the value. Empty = NULL.
// Non-UUID model references degrade to NULL; the steady-state
// write-back completes the pair on the same turn.
PreferredChatModelID string
PreferredReasoningEffort string
// Visibility overrides the default visibility derived from the session
// mode. Schedule-created sessions use this to surface in user-facing
// session lists while keeping session_mode='schedule' for prompt and
// tool gating. Empty means "derive from mode".
Visibility Visibility
}
CreateInput holds input for creating a new thread.
type CreateSubagentInput ¶
type CreateSubagentInput struct {
Thread CreateInput
ModelUUID string
ModelID string
ProviderName string
Forked bool
ForkContext []SubagentForkContextMessage
}
type Cursor ¶
Cursor identifies the position in a thread listing for keyset pagination. The zero value means "start from the head".
func (Cursor) IsZero ¶
IsZero reports whether the cursor carries neither half — the start-of-list signal that pagedCursorParams maps to "no cursor predicate". A partially-populated cursor (only the timestamp or only the id) is not zero; pagedCursorParams rejects it as a programmer error so we never send malformed bindings down to SQL.
type ForkFromAssistantInput ¶
type ForkFromAssistantInput struct {
BotID string
ThreadID string
// RuntimeMetadataOverride replaces the clone's runtime metadata. External
// runtime forks must pass it carrying the driver's freshly forked session
// keys; without it the two Memoh sessions would share one runtime session.
RuntimeMetadataOverride map[string]any
// TurnID names the round the fork inherits through. A turn is the identity
// a client holds while the round is still live, and the cut is turn-level
// anyway, so the fork point is named by turn rather than by stored message.
TurnID string
// MessageID is the pre-turn spelling of TurnID.
//
// Deprecated: accepted so a client shipped against the message-id contract
// keeps working after a server upgrade. It is resolved to the round that
// contains it. Remove once the compatibility window closes.
MessageID string
Title string
CreatedByUserID string
}
ForkFromAssistantInput creates a new chat thread from the source thread's visible history through the assistant message's turn.
type ListFilter ¶
type ListFilter struct {
ParentThreadID string
// WorkdirID filters to sessions bound to one workdir. Mutually
// exclusive with WorkdirUnassigned.
WorkdirID string
// WorkdirUnassigned filters to sessions with no workdir binding — the
// sidebar's ungrouped bucket.
WorkdirUnassigned bool
// Visibility filters to sessions with the given stored visibility.
// Empty means no visibility predicate. The default session listing
// passes VisibilityUser so schedule-created sessions surface by
// visibility rather than by widening the legacy type filter.
Visibility Visibility
}
type Queries ¶
type Queries interface {
CountMessagesBySession(context.Context, pgtype.UUID) (int64, error)
CreateSession(context.Context, sqlc.CreateSessionParams) (sqlc.BotSession, error)
CreateSubagentConfig(context.Context, sqlc.CreateSubagentConfigParams) (sqlc.SubagentConfig, error)
CreateSubagentForkContext(context.Context, sqlc.CreateSubagentForkContextParams) (sqlc.CreateSubagentForkContextRow, error)
ForkSessionFromAssistantTurn(context.Context, sqlc.ForkSessionFromAssistantTurnParams) (sqlc.ForkSessionFromAssistantTurnRow, error)
GetVisibleHistoryTurnByMessage(context.Context, sqlc.GetVisibleHistoryTurnByMessageParams) (dbstore.HistoryTurn, error)
GetBotByID(context.Context, pgtype.UUID) (sqlc.GetBotByIDRow, error)
GetSessionByID(context.Context, pgtype.UUID) (sqlc.BotSession, error)
GetLatestSessionModelPreference(context.Context, sqlc.GetLatestSessionModelPreferenceParams) (sqlc.GetLatestSessionModelPreferenceRow, error)
GetSubagentConfig(context.Context, pgtype.UUID) (sqlc.SubagentConfig, error)
ListSessionsByBot(context.Context, pgtype.UUID) ([]sqlc.ListSessionsByBotRow, error)
ListSessionsByBotAndCreatedByUser(context.Context, sqlc.ListSessionsByBotAndCreatedByUserParams) ([]sqlc.ListSessionsByBotAndCreatedByUserRow, error)
ListSessionsByBotAndCreatedByUserPaged(context.Context, sqlc.ListSessionsByBotAndCreatedByUserPagedParams) ([]sqlc.ListSessionsByBotAndCreatedByUserPagedRow, error)
ListSessionsByBotPaged(context.Context, sqlc.ListSessionsByBotPagedParams) ([]sqlc.ListSessionsByBotPagedRow, error)
ListSessionsByRoute(context.Context, pgtype.UUID) ([]sqlc.BotSession, error)
ListSubagentForkContext(context.Context, pgtype.UUID) ([]sqlc.ListSubagentForkContextRow, error)
ListSubagentSessionsByParent(context.Context, pgtype.UUID) ([]sqlc.BotSession, error)
SoftDeleteSession(context.Context, pgtype.UUID) error
TouchSession(context.Context, pgtype.UUID) error
UpdateSessionMetadata(context.Context, sqlc.UpdateSessionMetadataParams) (sqlc.BotSession, error)
UpdateSessionRuntimeMetadata(context.Context, sqlc.UpdateSessionRuntimeMetadataParams) (sqlc.BotSession, error)
UpdateSessionTitle(context.Context, sqlc.UpdateSessionTitleParams) (sqlc.BotSession, error)
UpdateSessionTypeAndMetadata(context.Context, sqlc.UpdateSessionTypeAndMetadataParams) (sqlc.BotSession, error)
}
Queries is the storage surface owned by the Thread domain. Route lookup and route activation intentionally stay outside this contract.
type Service ¶
type Service struct {
// contains filtered or unexported fields
}
Service manages bot chat threads.
func NewService ¶
NewService creates a thread service. publisher may be nil — thread creation still succeeds when there is no event hub wired in (tests, or any caller that doesn't surface activity events).
func (*Service) CreateSubagent ¶
func (s *Service) CreateSubagent(ctx context.Context, input CreateSubagentInput) (Thread, SubagentConfig, error)
CreateSubagent atomically creates the hidden child thread and its pinned model/fork configuration when the backing store supports transactions.
func (*Service) ForkFromAssistantTurn ¶
func (s *Service) ForkFromAssistantTurn(ctx context.Context, input ForkFromAssistantInput) (Thread, error)
ForkFromAssistantTurn creates a new chat thread containing the source thread's visible linear history through the selected assistant turn.
func (*Service) GetSubagentConfig ¶
func (*Service) LatestModelPreferenceSeed ¶ added in v0.20.0
func (s *Service) LatestModelPreferenceSeed(ctx context.Context, botID, userID string) (string, string, error)
Get returns a session by ID. LatestModelPreferenceSeed is the welcome composer seed (issue #879, spec §3.4): the bot's most recent native user-facing session with a persisted pair, scoped to the calling user. Empty pair + nil error = no seed; welcome falls back to the bot default.
func (*Service) ListByBotAndCreatedByUser ¶
func (s *Service) ListByBotAndCreatedByUser(ctx context.Context, botID, userID string) ([]Thread, error)
ListByBotAndCreatedByUser returns all active sessions for a bot created by a user.
func (*Service) ListByBotAndCreatedByUserPaged ¶
func (s *Service) ListByBotAndCreatedByUserPaged(ctx context.Context, botID, userID string, types []string, cursor Cursor, limit int64) ([]Thread, error)
ListByBotAndCreatedByUserPaged is the paged variant scoped to a single user.
func (*Service) ListByBotAndCreatedByUserPagedWithFilter ¶
func (*Service) ListByBotPaged ¶
func (s *Service) ListByBotPaged(ctx context.Context, botID string, types []string, cursor Cursor, limit int64) ([]Thread, error)
ListByBotPaged returns one page of sessions for a bot, filtered to the given types and starting after the given cursor. Callers that want a "has more" signal pass limit+1 and look for an extra row.
func (*Service) ListByBotPagedWithFilter ¶
func (*Service) ListByRoute ¶
ListByRoute returns all active sessions for a route.
func (*Service) ListSubagentForkContext ¶
func (s *Service) ListSubagentForkContext(ctx context.Context, sessionID string) ([]SubagentForkContextMessage, error)
ListSubagentForkContext loads only the hidden rows reserved for a forked subagent's inherited model context. Other invisible history rows are ignored.
func (*Service) ListSubagentsByParent ¶
func (s *Service) ListSubagentsByParent(ctx context.Context, parentSessionID string) ([]Thread, error)
ListSubagentsByParent returns active subagent sessions created under a parent session.
func (*Service) MergeRuntimeMetadata ¶ added in v0.20.0
func (s *Service) MergeRuntimeMetadata(ctx context.Context, sessionID, runtimeType string, delta map[string]any) (Thread, error)
MergeRuntimeMetadata merges driver-owned keys into a session's runtime metadata; a nil value deletes the key. The write is guarded on runtimeType so a concurrent runtime switch turns the merge into ErrRuntimeMetadataStale instead of polluting another runtime's metadata, and on the caller's runtime fencing token so a superseded owner's late write after a cluster ownership handoff cannot clobber the new owner's runtime thread id.
func (*Service) MessageCount ¶
func (*Service) SetACPSetupValidator ¶
func (s *Service) SetACPSetupValidator(validator ACPSetupValidator)
func (*Service) SetHookService ¶
func (*Service) SoftDelete ¶
SoftDelete marks a session as deleted.
func (*Service) UpdateDescriptorAndMetadataWithOwner ¶
func (s *Service) UpdateDescriptorAndMetadataWithOwner(ctx context.Context, sessionID, typ, sessionMode, runtimeType string, metadata, runtimeMetadata map[string]any, botAgentID *string, runtimeOwnerUserID string) (Thread, error)
UpdateDescriptorAndMetadataWithOwner updates the session mode/runtime descriptor directly. Callers that only patch metadata for a Phase 3 session must pass the existing descriptor so discuss+ACP sessions keep their runtime.
func (*Service) UpdateEmptyDescriptorAndMetadataWithOwner ¶
func (s *Service) UpdateEmptyDescriptorAndMetadataWithOwner(ctx context.Context, sessionID, typ, sessionMode, runtimeType string, metadata, runtimeMetadata map[string]any, botAgentID *string, runtimeOwnerUserID string) (Thread, error)
UpdateEmptyDescriptorAndMetadataWithOwner updates a session runtime identity only while its visible history is still empty. PostgreSQL-backed stores lock the bot and session in the same transaction as the message check, descriptor update, runtime-fence bump, and ACP snapshot invalidation. The lock ordering matches runtime activation, so an already-running turn linearizes before the empty-history check and an old runtime token cannot write after the update.
func (*Service) UpdateMetadata ¶
func (s *Service) UpdateMetadata(ctx context.Context, sessionID string, metadata map[string]any) (Thread, error)
UpdateMetadata updates a session's metadata.
func (*Service) UpdateTitle ¶
UpdateTitle updates a session's title.
func (*Service) UpdateTypeAndMetadata ¶
func (s *Service) UpdateTypeAndMetadata(ctx context.Context, sessionID, typ string, metadata map[string]any) (Thread, error)
UpdateTypeAndMetadata updates a session's runtime type and metadata in one statement so callers don't expose a half-updated agent selection.
func (*Service) UpdateTypeAndMetadataWithOwner ¶
func (s *Service) UpdateTypeAndMetadataWithOwner(ctx context.Context, sessionID, typ string, metadata map[string]any, runtimeOwnerUserID string) (Thread, error)
UpdateTypeAndMetadataWithOwner updates a session descriptor and binds any External Agent runtime ownership to a server-confirmed account id. The metadata owner field is never trusted from callers.
type SubagentConfig ¶
type SubagentConfig struct {
ThreadID string `json:"session_id"`
ModelUUID string `json:"model_uuid,omitempty"`
ModelID string `json:"model_id"`
ProviderName string `json:"provider"`
Forked bool `json:"fork"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
SubagentConfig is the persisted runtime selection for a managed subagent.
type SubagentForkContextMessage ¶
type SubagentForkContextMessage struct {
SourceMessageID string `json:"source_message_id,omitempty"`
Role string `json:"role"`
Message json.RawMessage `json:"message"`
}
SubagentForkContextMessage is one hidden history message copied or materialized into a forked subagent session.
type Thread ¶
type Thread struct {
ID string `json:"id"`
BotID string `json:"bot_id"`
BotAgentID string `json:"bot_agent_id,omitempty"`
RouteID string `json:"route_id,omitempty"`
ChannelType string `json:"channel_type,omitempty"`
Type string `json:"type"`
SessionMode string `json:"session_mode"`
RuntimeType string `json:"runtime_type"`
RuntimeMetadata map[string]any `json:"runtime_metadata,omitempty"`
Title string `json:"title"`
Metadata map[string]any `json:"metadata,omitempty"`
ParentThreadID string `json:"parent_session_id,omitempty"`
CreatedByUserID string `json:"created_by_user_id,omitempty"`
WorkdirID string `json:"workdir_id,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
// Preferred* is the session's persisted (model, effort) pair (issue #879).
// Empty means "no memory"; the composer reseeds from it on open/repoint.
PreferredExternalModelID string `json:"preferred_external_model_id,omitempty"`
ModelPreferenceRevision string `json:"model_preference_revision,omitempty"`
PreferredChatModelID string `json:"preferred_chat_model_id,omitempty"`
PreferredReasoningEffort string `json:"preferred_reasoning_effort,omitempty"`
RouteMetadata map[string]any `json:"route_metadata,omitempty"`
RouteConversationType string `json:"route_conversation_type,omitempty"`
Visibility Visibility `json:"-"`
} // @name session.Session
Thread represents a chat thread within a bot.
type Visibility ¶
type Visibility string
Visibility describes whether a thread belongs in user-facing history or is reserved for an internal agent workflow.
const ( VisibilityUser Visibility = "user" VisibilityInternal Visibility = "internal" )
func NormalizeVisibility ¶
func NormalizeVisibility(stored string, mode string) Visibility
NormalizeVisibility resolves a possibly-absent stored visibility value against the session mode's default. Exported for restore paths that replay archived session rows which may predate the visibility column.