Documentation
¶
Index ¶
- Constants
- Variables
- func CoerceFieldInputs(schema *config.FieldSchema, in []FieldInput) (map[string]FieldValue, []string)
- func IsCaseAccessible(c *Case, userID string) bool
- func IsSystemActor(actorUserID string) bool
- func IsValidImportIssueSeverity(s ImportIssueSeverity) bool
- func IsValidImportItemResultStatus(s ImportItemResultStatus) bool
- func IsValidImportSessionStatus(s ImportSessionStatus) bool
- func ParseGitHubRepo(input string) (owner, repo string, err error)
- func ParseNotionID(input string) (string, error)
- type Action
- type ActionEvent
- type ActionStatusColorPreset
- type ActionStatusDefinition
- type ActionStatusSet
- func (s *ActionStatusSet) ClosedIDs() []string
- func (s *ActionStatusSet) Emoji(id string) string
- func (s *ActionStatusSet) Get(id string) (ActionStatusDefinition, bool)
- func (s *ActionStatusSet) IDs() []string
- func (s *ActionStatusSet) Initial() ActionStatusDefinition
- func (s *ActionStatusSet) InitialID() string
- func (s *ActionStatusSet) IsClosed(id string) bool
- func (s *ActionStatusSet) IsValid(id string) bool
- func (s *ActionStatusSet) Statuses() []ActionStatusDefinition
- type ActionStep
- type AssistLog
- type AssistLogID
- type Case
- func (c *Case) AssignUsers(userIDs []string) bool
- func (c *Case) IsDraft() bool
- func (c *Case) IsThreadBound() bool
- func (c *Case) SubmitDraft() error
- func (c *Case) SyncLifecycleFromBoardStatus(set *ActionStatusSet)
- func (c *Case) UnassignUsers(userIDs []string) bool
- func (c *Case) Validate() error
- func (c *Case) ValidateNew() error
- type CaseEventConfig
- type CaseLifecycle
- type CaseMode
- type CaseProposal
- type CaseProposalID
- type CaseRef
- type CaseTrigger
- type FieldInput
- type FieldValidator
- func (v *FieldValidator) ValidateCaseFields(fieldValues map[string]FieldValue) (map[string]FieldValue, error)
- func (v *FieldValidator) ValidateCaseFieldsAll(fieldValues map[string]FieldValue) (map[string]FieldValue, error)
- func (v *FieldValidator) ValidateCaseFieldsPartial(fieldValues map[string]FieldValue) (map[string]FieldValue, error)
- func (v *FieldValidator) ValidateCaseFieldsPartialStrict(fieldValues map[string]FieldValue) (map[string]FieldValue, error)
- type FieldValue
- type GitHubConfig
- type GitHubRepository
- type HomeMessage
- type HomeMessageID
- type ImportActionResult
- type ImportCaseResult
- type ImportIssue
- type ImportIssueSeverity
- type ImportItemResultStatus
- type ImportSession
- type ImportSessionID
- type ImportSessionStatus
- type ImportSnapshot
- type ImportSnapshotAction
- type ImportSnapshotCase
- type ImportSource
- type Job
- type JobEventDomain
- type JobEvents
- type JobRun
- type JobRunEvent
- type JobRunEventKind
- type JobRunKey
- type JobRunLog
- type JobRunStage
- type JobRunStatus
- type JobStrategy
- type Knowledge
- type KnowledgeID
- type LLMContentBlock
- type LLMFunctionCall
- type LLMMessage
- type LLMRequestPayload
- type LLMResponsePayload
- type LLMToolSpec
- type MaterializationValidationIssue
- type Memo
- type MemoID
- type MyDueAction
- type MyOpenCase
- type NotificationSlot
- type NotificationSlotEntry
- type NotionDBConfig
- type NotionPageConfig
- type PendingInteraction
- type PendingInteractionItem
- type PendingQuestion
- type PendingQuestionItem
- type ProposalMessage
- type ProposalSource
- type RunErrorPayload
- type ScheduledEventConfig
- type Session
- type SessionEndReason
- type SessionKind
- type SessionTurnState
- type SlackChannel
- type SlackConfig
- type SlackUser
- type SlackUserID
- type SlackUserMetadata
- type Source
- type SourceID
- type SourceType
- type Tag
- type TagID
- type ToolCallPayload
- type UserPreference
- type Workspace
- type WorkspaceEntry
- type WorkspaceGroup
- type WorkspaceGroupRegistry
- type WorkspaceMaterialization
- type WorkspaceRegistry
- func (r *WorkspaceRegistry) FindByMonitorChannel(channelID string) (*WorkspaceEntry, bool)
- func (r *WorkspaceRegistry) FindByReactionEmoji(emoji string) (*WorkspaceEntry, bool)
- func (r *WorkspaceRegistry) FindByWorkspaceChannel(channelID string) (*WorkspaceEntry, bool)
- func (r *WorkspaceRegistry) Get(workspaceID string) (*WorkspaceEntry, error)
- func (r *WorkspaceRegistry) List() []*WorkspaceEntry
- func (r *WorkspaceRegistry) Register(entry *WorkspaceEntry)
- func (r *WorkspaceRegistry) Workspaces() []Workspace
Constants ¶
const ( // ExecutorKindSingleLoop is a single gollem ReAct loop (Job // strategy=simple, and the casebound mention host, which runs gollem // directly). ExecutorKindSingleLoop = "single_loop" // ExecutorKindPlanexec is the plan-and-execute runtime (Job // strategy=planexec, and the threadcase mention host). ExecutorKindPlanexec = "plan_execute" )
ExecutorKind values recorded on JobRunLog.ExecutorKind. They identify which runtime drove a run so a consumer can tell a single-loop turn from a plan-execute one without re-deriving it.
const ( // MaxClaimLength is the maximum rune length of the Markdown claim body. MaxClaimLength = 8000 // MaxTags is the maximum number of tag references on a single Knowledge entry. MaxTags = 50 )
Knowledge invariants. These are domain constants (not deployment-tunable configuration): they bound a single shared-knowledge entry so a runaway agent cannot write unbounded documents. The defaults live here as exported constants rather than hidden inside Validate so callers and tests reference the same source of truth.
const ( FieldIDKey = "field_id" ExpectedTypeKey = "expected_type" ActualTypeKey = "actual_type" OptionIDKey = "option_id" FieldValueKey = "field_value" )
Context keys for error values
const AgentAdditionalPromptMaxLen = 16384
AgentAdditionalPromptMaxLen caps the per-Case agent additional prompt length (UTF-8 bytes). The cap protects callers (LLM context window, Firestore doc size) and is enforced at the persistence boundary.
const CaseProposalTTL = 24 * time.Hour
CaseProposalTTL is the lifetime of a draft after creation.
const EmbeddingDimension = 768
EmbeddingDimension is the dimension of the embedding vector produced by the configured EmbedClient. The default model (Gemini text-embedding-004) emits 768-dimensional vectors. The constant is preserved for the upcoming similarity-search redesign that will reintroduce vector-backed features.
const EventTypeMention = "mention"
EventTypeMention is the JobRunLog.EventType provenance value for a mention-triggered run (a Slack mention in an existing case, handled by the casebound / threadcase hosts). Such runs are NOT TOML-configured Jobs, but they persist through the same JobRunLog schema so the case agent page lists them alongside real Job runs. EventType is the discriminator that tells a mention run apart from the "case" / "scheduled" JobEventDomain values the Job runner writes — each mention turn gets its own fresh JobID, so there is no reserved sentinel to match on.
const MaxInlineBytes = 800 * 1024
MaxInlineBytes is the soft cap on any single string/JSON payload field stored in Firestore (well under the 1 MiB doc limit). Payloads longer than this are truncated from the tail before persisting; this log is for rough flow tracing, not exact reproduction.
const MaxTagNameLength = 64
MaxTagNameLength is the maximum rune length of a Tag.Name.
const SystemActorID = "@system"
SystemActorID is the sentinel ActorUserID used when a Job's tool invokes a mutation on behalf of the system (no human user). The '@' prefix ensures the value never collides with Slack user IDs (which always start with 'U' or 'W').
Variables ¶
var ( ErrImportSessionMissingID = goerr.New("import session has no ID") ErrImportSessionMissingWorkspace = goerr.New("import session has no workspace ID") ErrImportSessionMissingCreator = goerr.New("import session has no creator user ID") ErrImportSessionInvalidStatus = goerr.New("import session has invalid status") ErrImportSessionInvalidSnapshot = goerr.New("import session snapshot is invalid") ErrImportSessionInvalidIssueSeverity = goerr.New("import session issue has invalid severity") ErrImportSessionInvalidItemStatus = goerr.New("import session item has invalid status") )
Sentinel errors. Repositories MUST call Validate before every write so that an upstream bug (e.g. forgetting to populate CreatorUserID after auth.ContextWithToken) fails loudly at the persistence boundary instead of silently producing an unattributable record.
var ( ErrInvalidFieldType = goerr.New("invalid field type") ErrInvalidOptionID = goerr.New("invalid option ID") ErrMissingRequired = goerr.New("required field is missing") // ErrCaseFieldValidation wraps the aggregated, non-fail-fast validation // result produced by ValidateCaseFieldsAll. Its message lists every // violation (one per line) so the caller can feed the complete set back // to an LLM in a single round. ErrCaseFieldValidation = goerr.New("case field validation failed") // ErrUnknownFieldID is reported by ValidateCaseFieldsAll when a supplied // field id is not present in the workspace schema. (The fail-fast // ValidateCaseFields preserves unknown fields for forward compatibility; // the create path rejects them instead of silently dropping data.) ErrUnknownFieldID = goerr.New("unknown field ID") )
Validation errors
var ErrActionEventValidation = goerr.New("action event validation failed")
ErrActionEventValidation is returned when an ActionEvent fails its persistence-boundary invariants.
var ErrActionStepValidation = goerr.New("action step validation failed")
ErrActionStepValidation is returned when an ActionStep fails its persistence-boundary invariants.
var ErrActionValidation = goerr.New("action validation failed")
ErrActionValidation is returned when an Action fails its persistence-boundary invariants.
var ErrAssistLogValidation = goerr.New("assist log validation failed")
ErrAssistLogValidation is returned when an AssistLog fails its persistence-boundary invariants.
var ErrCaseAgentPromptTooLong = goerr.New("case agent additional prompt is too long")
ErrCaseAgentPromptTooLong is returned by Case.Validate when the Case-specific agent additional prompt exceeds AgentAdditionalPromptMaxLen.
var ErrCaseMissingReporter = goerr.New("case has no reporter")
ErrCaseMissingReporter is returned by Case.ValidateNew when a channel-mode case is about to be created without a reporter. A channel-mode case originates from some authenticated user (Web cookie, Slack interactivity callback, no-auth dev-mode token); losing that identity later means the Cases / Drafts UI shows an empty Reporter column and Slack invites have nobody to add as the channel creator, so repositories enforce it at the Create boundary. Thread-mode cases (SlackThreadTS set) are exempt: a channel-root intake post relayed by an integration bot may name no human, so an empty ReporterID is a legitimate state there.
var ErrCaseNotDraft = goerr.New("case is not a draft")
ErrCaseNotDraft is returned when an operation requires a case to be in DRAFT status but the case is already submitted (OPEN/CLOSED) or otherwise not a draft.
var ErrHomeMessageValidation = goerr.New("home message validation failed")
ErrHomeMessageValidation is returned when a HomeMessage fails validation.
var ErrInvalidGitHubRepo = goerr.New("invalid GitHub repository")
ErrInvalidGitHubRepo is returned when the input cannot be parsed as a GitHub repository
var ErrInvalidNotionID = goerr.New("invalid Notion ID")
ErrInvalidNotionID is returned when the input cannot be parsed as a Notion ID
var ErrInvalidWorkspaceGroup = goerr.New("invalid workspace group")
ErrInvalidWorkspaceGroup is returned when a group violates its core invariant (an empty ID). Richer validation (ID pattern, cross-file duplicate IDs, member existence) lives in the config layer; this is the last-line guard so a code path that builds a group with no identity fails loudly before Register.
var ErrKnowledgeValidation = goerr.New("knowledge validation failed")
ErrKnowledgeValidation is returned when a Knowledge fails its structural invariants.
var ErrMemoValidation = goerr.New("memo validation failed")
ErrMemoValidation is returned when a Memo fails its structural invariants.
var ErrNotificationSlotValidation = goerr.New("notification slot validation failed")
ErrNotificationSlotValidation is returned when a NotificationSlot fails its persistence-boundary invariants.
var ErrSessionValidation = goerr.New("session validation failed")
ErrSessionValidation is returned when a Session fails its persistence-boundary invariants.
var ErrSlackUserValidation = goerr.New("slack user validation failed")
ErrSlackUserValidation is returned when a SlackUser fails its persistence-boundary invariants.
var ErrSourceValidation = goerr.New("source validation failed")
ErrSourceValidation is returned when a Source fails its persistence-boundary invariants.
var ErrTagValidation = goerr.New("tag validation failed")
ErrTagValidation is returned when a Tag fails its structural invariants.
var ErrUserPreferenceValidation = goerr.New("user preference validation failed")
ErrUserPreferenceValidation is returned when a UserPreference fails validation.
var ErrWorkspaceGroupNotFound = goerr.New("workspace group not found")
ErrWorkspaceGroupNotFound is returned when a group is not found in the registry.
var ErrWorkspaceNotFound = goerr.New("workspace not found")
ErrWorkspaceNotFound is returned when a workspace is not found in the registry
Functions ¶
func CoerceFieldInputs ¶
func CoerceFieldInputs(schema *config.FieldSchema, in []FieldInput) (map[string]FieldValue, []string)
CoerceFieldInputs maps FieldInput entries into FieldValue keyed by field id, resolving each field's storage shape from the schema (multi -> []string, number -> float64). It performs TYPE COERCION ONLY, not business validation: an unknown field id is passed through with its raw value and Type unset so the downstream validator (ValidateCaseFieldsPartialStrict / ...All) reports the schema violation with a single consistent message. An unparseable number is the one shape the coercion cannot represent, so it is returned as a violation string (rather than written as a string the storage layer would reject) giving the caller a precise "must be a number" hint.
Empty scalar values are intentionally PRESERVED (as an explicit "set to empty") — callers that must instead drop empties (e.g. the materialize path guarding against clobbering an existing value) do their own filtering and do not route through this helper.
func IsCaseAccessible ¶
IsCaseAccessible checks if a user has access to a case. Non-private cases are always accessible. Private cases are accessible only if the userID is in ChannelUserIDs.
func IsSystemActor ¶
IsSystemActor reports whether the given actor user ID is the system sentinel. Permission checks that key off a Slack user identity must skip for system actor — there is no human to authenticate as.
func IsValidImportIssueSeverity ¶
func IsValidImportIssueSeverity(s ImportIssueSeverity) bool
IsValidImportIssueSeverity reports whether s is a known severity.
func IsValidImportItemResultStatus ¶
func IsValidImportItemResultStatus(s ImportItemResultStatus) bool
IsValidImportItemResultStatus reports whether s is a known per-item result status.
func IsValidImportSessionStatus ¶
func IsValidImportSessionStatus(s ImportSessionStatus) bool
IsValidImportSessionStatus reports whether s is a known status value.
func ParseGitHubRepo ¶
ParseGitHubRepo extracts owner and repo from either "owner/repo" or a GitHub URL. Accepted formats:
- "owner/repo"
- "https://github.com/owner/repo"
- "https://github.com/owner/repo.git"
func ParseNotionID ¶
ParseNotionID extracts a Notion ID from either a raw ID or a Notion URL. The returned ID is always in UUID format (8-4-4-4-12) as required by the Notion API. Accepted formats:
- Raw ID: "abc123def456789012345678901234567"
- UUID format: "12345678-90ab-cdef-1234-567890abcdef"
- Notion URL: "https://www.notion.so/workspace/abc123def456...?v=..."
- Notion URL: "https://www.notion.so/abc123def456..."
- Notion URL: "https://app.notion.com/p/workspace/Title-abc123def456..."
Types ¶
type Action ¶
type Action struct {
ID int64
CaseID int64 // Required: Action must be associated with a Case (1:n relationship)
Title string
Description string
AssigneeID string // Slack User ID; empty string means unassigned
SlackMessageTS string // Optional: Slack message ID (timestamp)
Status types.ActionStatus
DueDate *time.Time // Optional: deadline for the action
ArchivedAt *time.Time // nil = active; non-nil = archived at the given time
CreatedAt time.Time
UpdatedAt time.Time
}
Action represents a task/action associated with a case
func (*Action) IsArchived ¶
IsArchived reports whether the action is currently archived.
type ActionEvent ¶
type ActionEvent struct {
ID string // unique within the action
ActionID int64 // parent action id
Kind types.ActionEventKind // CREATED / TITLE_CHANGED / STATUS_CHANGED / ASSIGNEE_CHANGED
ActorID string // Slack user id of the person who triggered the change ("" = system)
OldValue string // old field value rendered as a string (empty for CREATED)
NewValue string // new field value rendered as a string
CreatedAt time.Time
}
ActionEvent is one entry in an Action's structural change history. Used to render the WebUI activity feed (creation, title / status / assignee edits). Messages-from-Slack go through ActionMessageRepository instead.
func (*ActionEvent) Validate ¶
func (e *ActionEvent) Validate() error
Validate enforces the invariants required before any persistence write. The caller supplies both the entry ID (unique within the action) and the parent ActionID, so a history entry with no parent or no ID fails loudly here instead of landing in storage.
type ActionStatusColorPreset ¶
type ActionStatusColorPreset string
ActionStatusColorPreset is a semantic preset name. The actual color value is resolved on the frontend; the backend only validates membership.
const ( ActionStatusColorIdle ActionStatusColorPreset = "idle" ActionStatusColorActive ActionStatusColorPreset = "active" ActionStatusColorWaiting ActionStatusColorPreset = "waiting" ActionStatusColorPaused ActionStatusColorPreset = "paused" ActionStatusColorAttention ActionStatusColorPreset = "attention" ActionStatusColorBlocked ActionStatusColorPreset = "blocked" ActionStatusColorSuccess ActionStatusColorPreset = "success" ActionStatusColorNeutralDone ActionStatusColorPreset = "neutral_done" ActionStatusColorFailure ActionStatusColorPreset = "failure" )
type ActionStatusDefinition ¶
type ActionStatusDefinition struct {
ID string
Name string
Description string
Color string // either a preset name (lowercase) or "#RRGGBB" / "#RGB"
Emoji string
}
ActionStatusDefinition describes a single status definable per workspace.
func (ActionStatusDefinition) SlackColor ¶
func (d ActionStatusDefinition) SlackColor() string
SlackColor returns a Slack-compatible color string for this status (the value used by `attachment.color`). Hex values stored in `Color` pass through verbatim; preset names are resolved via `actionStatusPresetHex`. Empty or unrecognized values return `""`, which Slack treats as "no color bar".
type ActionStatusSet ¶
type ActionStatusSet struct {
// contains filtered or unexported fields
}
ActionStatusSet is the resolved per-workspace status configuration. It is constructed once at startup and treated as immutable thereafter.
func DefaultActionStatusSet ¶
func DefaultActionStatusSet() *ActionStatusSet
DefaultActionStatusSet returns the legacy 5-status set used when a workspace does not define its own statuses. Kept here so storage values from before configurable statuses (`"BACKLOG"`, `"TODO"`, ...) keep working unchanged.
func NewActionStatusSet ¶
func NewActionStatusSet(initialID string, closedIDs []string, statuses []ActionStatusDefinition) (*ActionStatusSet, error)
NewActionStatusSet constructs an ActionStatusSet and validates the input.
func (*ActionStatusSet) ClosedIDs ¶
func (s *ActionStatusSet) ClosedIDs() []string
ClosedIDs returns the IDs flagged as closed, in insertion order of `statuses`.
func (*ActionStatusSet) Emoji ¶
func (s *ActionStatusSet) Emoji(id string) string
Emoji returns the emoji for the given id, or a fallback if unknown / empty.
func (*ActionStatusSet) Get ¶
func (s *ActionStatusSet) Get(id string) (ActionStatusDefinition, bool)
Get returns the definition for the given id.
func (*ActionStatusSet) IDs ¶
func (s *ActionStatusSet) IDs() []string
IDs returns all status ids in declaration order.
func (*ActionStatusSet) Initial ¶
func (s *ActionStatusSet) Initial() ActionStatusDefinition
Initial returns the initial status definition.
func (*ActionStatusSet) InitialID ¶
func (s *ActionStatusSet) InitialID() string
InitialID returns the initial status ID.
func (*ActionStatusSet) IsClosed ¶
func (s *ActionStatusSet) IsClosed(id string) bool
IsClosed reports whether the given status id is configured as closed.
func (*ActionStatusSet) IsValid ¶
func (s *ActionStatusSet) IsValid(id string) bool
IsValid reports whether the given id matches a defined status.
func (*ActionStatusSet) Statuses ¶
func (s *ActionStatusSet) Statuses() []ActionStatusDefinition
Statuses returns a copy of all status definitions in declaration order.
type ActionStep ¶
type ActionStep struct {
ID string
ActionID int64
Title string
DoneAt *time.Time
DoneBy string
CreatedBy string
CreatedAt time.Time
UpdatedAt time.Time
}
ActionStep is a small, binary-state work item that lives under an Action. Used to track granular progress toward completing an Action. The completion state is encoded by DoneAt: nil means ongoing, non-nil means done at the given time. There is no separate Done boolean to avoid the two-source-of- truth hazard that the Action.ArchivedAt / IsArchived pattern already established for archive state.
func (*ActionStep) IsDone ¶
func (s *ActionStep) IsDone() bool
IsDone reports whether the step is currently marked done.
func (*ActionStep) Validate ¶
func (s *ActionStep) Validate() error
Validate enforces the invariants required before any persistence write. The caller supplies both the step ID and the parent ActionID (the latter is the repository storage key), so a step with no parent or no ID fails loudly here instead of landing in storage.
type AssistLog ¶
type AssistLog struct {
ID AssistLogID
CaseID int64
Summary string // One-line summary of this session
Actions string // What was done in this session (may be empty if nothing was done)
Reasoning string // Rationale behind decisions made
NextSteps string // Items to address in future sessions
CreatedAt time.Time
}
AssistLog represents an execution log entry from the assist agent. After each assist session, the agent's actions are summarized and stored as a log for context in subsequent runs.
func (*AssistLog) Validate ¶
Validate enforces the invariants required before any persistence write. The repository assigns the storage ID (NewAssistLogID when empty) and sets CaseID from its caseID argument, so callers MUST invoke this after that assignment; a log with no owning Case (CaseID == 0) fails loudly here instead of landing in storage.
type AssistLogID ¶
type AssistLogID string
AssistLogID is a UUID-based identifier for AssistLog
func NewAssistLogID ¶
func NewAssistLogID() AssistLogID
NewAssistLogID generates a new UUID v4 AssistLogID
type Case ¶
type Case struct {
ID int64
Title string
Description string
Status types.CaseStatus
ReporterID string // Slack User ID of the case reporter (immutable after creation)
AssigneeIDs []string // Slack User IDs
SlackChannelID string
// SlackThreadTS binds a thread-mode Case to its Slack thread. Empty for
// channel-mode Cases (SlackChannelID is then a dedicated channel). For
// thread-mode Cases SlackChannelID is the monitored channel and
// SlackThreadTS is the thread parent timestamp. See CaseMode.
SlackThreadTS string
// BoardStatus is the configurable workflow status id (the Kanban column)
// for thread-mode Cases, validated against the workspace's CaseStatusSet.
// Empty for channel-mode Cases, where the configurable status attaches to
// Actions instead. The lifecycle Status is kept in sync with BoardStatus
// via SyncLifecycleFromBoardStatus (closed status id => CLOSED).
BoardStatus string
IsPrivate bool // Private mode flag
IsTest bool // Marks a case filed for testing/verification, distinguished from production cases
ChannelUserIDs []string // Slack User IDs of channel members (synced for all cases)
AccessDenied bool // Runtime-only: set by UseCase when access is restricted (not persisted)
FieldValues map[string]FieldValue // key = FieldID
RequestKey string // UUID for preventing duplicate case creation from Slack modals
// AgentAdditionalPrompt is a Case-specific Markdown snippet that the
// Job runner appends to the TOML-defined Job prompt at agent execution
// time. Empty by default; capped at AgentAdditionalPromptMaxLen bytes
// and enforced by Validate().
AgentAdditionalPrompt string
// AgentSourceIDs restricts which Sources the agent uses when running
// against this Case. Empty (nil or len==0) means "use every Source
// the agent would normally consider" — i.e. preserves the existing
// default of all enabled Workspace Sources. Non-empty narrows the
// set to exactly the listed IDs (unknown / disabled IDs are dropped
// at use time, not at write time, so a Source toggled off later does
// not invalidate the stored selection).
AgentSourceIDs []SourceID
CreatedAt time.Time
UpdatedAt time.Time
}
Case represents a generic case/project entity.
Lifecycle (see types.CaseStatus): DRAFT → OPEN → CLOSED. A case in DRAFT is an "in-progress" entry saved from the Slack creation modal's Save as Draft button; it is visible only to its reporter and triggers none of the channel-binding / notification side effects of a real Case until SubmitDraft promotes it to OPEN.
func RestrictCase ¶
RestrictCase returns a copy of the case with sensitive fields removed. Only ID, Status, IsPrivate, CreatedAt, UpdatedAt are preserved. AccessDenied is set to true.
func (*Case) AssignUsers ¶
AssignUsers adds the given Slack user IDs to AssigneeIDs as a set union: IDs already present are ignored, blank IDs are skipped, and genuinely new IDs are appended in input order so existing order is preserved. It reports whether the assignee set actually changed, letting callers skip a no-op write. This is the in-memory half of the atomic assign operation; the repository supplies the concurrency guarantee (transaction / lock).
func (*Case) IsDraft ¶
IsDraft reports whether this Case is currently in the unsubmitted draft state.
func (*Case) IsThreadBound ¶
IsThreadBound reports whether this Case is bound to a Slack thread (thread-mode). Channel-mode Cases have an empty SlackThreadTS.
func (*Case) SubmitDraft ¶
SubmitDraft transitions the case from DRAFT to OPEN in place. Returns an error if the case is not in DRAFT (callers must not silently no-op when promoting an already-submitted case). Persistence and any post-promotion side effects are the caller's responsibility.
func (*Case) SyncLifecycleFromBoardStatus ¶
func (c *Case) SyncLifecycleFromBoardStatus(set *ActionStatusSet)
SyncLifecycleFromBoardStatus keeps the lifecycle Status consistent with the configurable BoardStatus for thread-mode Cases: a closed board status maps to CaseStatusClosed, any other to CaseStatusOpen. DRAFT cases are left untouched (thread-mode Cases are never drafts, but guard defensively). It is a no-op when set is nil or BoardStatus is empty.
func (*Case) UnassignUsers ¶
UnassignUsers removes the given Slack user IDs from AssigneeIDs, preserving the order of the IDs that remain. Removing an ID that is not present is a no-op. It reports whether the assignee set actually changed.
func (*Case) Validate ¶
Validate checks the basic invariants every persisted Case must satisfy. Repositories MUST call this before every write. For new cases (Create), call ValidateNew instead, which additionally enforces ReporterID.
func (*Case) ValidateNew ¶
ValidateNew checks the invariants that must hold for a newly created Case. For channel-mode cases it additionally enforces ReporterID at creation time: that field is the channel creator and its silent loss (the canonical failure mode where a Slack handler forgot to inject auth.ContextWithToken) is caught at the persistence boundary. Thread-mode cases (SlackThreadTS set) are exempt — a channel-root intake post relayed by an integration bot may name no human, so an empty ReporterID is allowed (it is best-effort resolved from the post body; see usecase.HandleThreadCaseCreation). Repositories MUST call this instead of Validate for Create operations.
type CaseEventConfig ¶
type CaseEventConfig struct {
// On lists the lifecycle events the Job is subscribed to. Always a
// normalised, non-empty slice produced by the config loader from the
// TOML "on" field. The domain layer never accepts a single-string form
// — see config/job.go for the parsing rules.
On []CaseLifecycle
}
CaseEventConfig is the listen filter for the `case` event domain. A Job fires when the published Case event's CaseLifecycle is contained in On.
func (*CaseEventConfig) Matches ¶
func (c *CaseEventConfig) Matches(lc CaseLifecycle) bool
Matches reports whether the given lifecycle event matches this config's On filter.
func (*CaseEventConfig) Validate ¶
func (c *CaseEventConfig) Validate() error
Validate enforces invariants for the case event filter: - On must be non-empty - every value must be a known CaseLifecycle
type CaseLifecycle ¶
type CaseLifecycle string
CaseLifecycle enumerates the case lifecycle events that a Job can listen to via `events.case.on` in TOML. Each value names a distinct, observable transition.
const ( // CaseLifecycleCreated fires when a Case is newly created (regardless of // its initial status — DRAFT or OPEN both qualify). CaseLifecycleCreated CaseLifecycle = "created" // CaseLifecycleClosed fires when a Case's status transitions to CLOSED. CaseLifecycleClosed CaseLifecycle = "closed" )
func AllCaseLifecycles ¶
func AllCaseLifecycles() []CaseLifecycle
AllCaseLifecycles returns every valid CaseLifecycle for validation and documentation purposes.
func (CaseLifecycle) IsValid ¶
func (l CaseLifecycle) IsValid() bool
IsValid reports whether the lifecycle value is one of the recognised enum members.
func (CaseLifecycle) String ¶
func (l CaseLifecycle) String() string
String returns the string form for prompt rendering / logging.
type CaseMode ¶
type CaseMode string
CaseMode selects how Cases bind to Slack within a workspace.
- CaseModeChannel (default): one Case maps to a dedicated Slack channel created at case activation. This is the original behaviour.
- CaseModeThread: the workspace monitors a single channel; each top-level human message in that channel becomes a Case bound to its thread. No dedicated channel is created, Actions and Drafts are not used, and the configurable status set ([case.status]) attaches to the Case itself.
type CaseProposal ¶
type CaseProposal struct {
ID CaseProposalID
CreatedBy string // Slack user ID who triggered the mention
CreatedAt time.Time
ExpiresAt time.Time
// (a) Source — workspace-agnostic, immutable after creation
RawMessages []ProposalMessage
MentionText string
Source ProposalSource
// (b) Current AI materialization (overwritten on workspace switch)
SelectedWorkspaceID string
Materialization *WorkspaceMaterialization
// InferenceInProgress is the server-side guard against concurrent
// Submit/Edit/WS-switch interactions while a Materializer call is running.
InferenceInProgress bool
// EphemeralChannelID and EphemeralMessageTS identify the originally posted
// ephemeral message. Used by interaction handlers that lack a response_url
// (e.g., view_submission flows) to update the ephemeral via chat APIs.
EphemeralChannelID string
EphemeralMessageTS string
}
CaseProposal holds the workspace-agnostic source material plus the current (single) AI-materialized Case payload for the currently-selected workspace. Switching workspace re-runs the Materializer and overwrites Materialization; no per-workspace cache is kept (intentional simplification).
func NewCaseProposal ¶
func NewCaseProposal(now time.Time, createdBy string) *CaseProposal
NewCaseProposal constructs a fresh draft with a new ID, current timestamps, and a default 24h expiry. Caller must populate Source / RawMessages / MentionText / SelectedWorkspaceID before saving.
type CaseProposalID ¶
type CaseProposalID string
CaseProposalID is a UUID-based identifier for CaseProposal
func NewCaseProposalID ¶
func NewCaseProposalID() CaseProposalID
NewCaseProposalID generates a new UUID v4 CaseProposalID
func (CaseProposalID) String ¶
func (id CaseProposalID) String() string
String returns the string representation
type CaseRef ¶
type CaseRef struct {
ID int64
Title string
Status types.CaseStatus
WorkspaceID string
}
CaseRef is the summary projection of a Case used by case_ref fields: the picker, the agent search tool, and value-label resolution. It carries only the fields safe to surface for a referenceable (non-private) Case and deliberately omits Description / Reporter / Assignees / FieldValues so a summary can never leak more than a title.
func NewCaseRef ¶
NewCaseRef builds a CaseRef summary for a Case in the given workspace.
type CaseTrigger ¶
type CaseTrigger string
CaseTrigger selects what starts a Case in a thread-mode workspace.
- CaseTriggerInstant (default): every channel-root post in the monitored channel starts a Case. This is the original thread-mode behaviour.
- CaseTriggerMention: a Case is started only when the bot is @mentioned — either in a channel-root message or inside a thread that has no Case yet. A plain post (no mention) never starts a Case.
It is orthogonal to CaseMode and is only meaningful when CaseMode is thread; channel-mode workspaces ignore it.
const ( // CaseTriggerInstant is the default: any channel-root post starts a Case. CaseTriggerInstant CaseTrigger = "instant" // CaseTriggerMention starts a Case only on an @mention of the bot. CaseTriggerMention CaseTrigger = "mention" )
func (CaseTrigger) IsMention ¶
func (t CaseTrigger) IsMention() bool
IsMention reports whether Cases are started only on an @mention.
func (CaseTrigger) IsValid ¶
func (t CaseTrigger) IsValid() bool
IsValid reports whether the trigger is a recognised value. The empty string is not valid here; callers normalise empty to CaseTriggerInstant before use.
func (CaseTrigger) Normalize ¶
func (t CaseTrigger) Normalize() CaseTrigger
Normalize maps the empty string to the default CaseTriggerInstant.
type FieldInput ¶
type FieldInput struct {
// FieldID references FieldDefinition.ID from the workspace schema.
FieldID string
// Value carries the scalar form (text / number / url / date / single
// select option id / single user id).
Value string
// Values carries the multi form (multi-select option ids / multi-user ids).
Values []string
}
FieldInput is the LLM-facing shape of one custom-field assignment: a field id plus either a scalar value or a list of values. It is the agent-boundary form that planners / tools emit before the value is coerced into a typed FieldValue.
type FieldValidator ¶
type FieldValidator struct {
// contains filtered or unexported fields
}
FieldValidator validates field values against field schema
func NewFieldValidator ¶
func NewFieldValidator(schema *config.FieldSchema) *FieldValidator
NewFieldValidator creates a new FieldValidator with the given schema
func (*FieldValidator) ValidateCaseFields ¶
func (v *FieldValidator) ValidateCaseFields(fieldValues map[string]FieldValue) (map[string]FieldValue, error)
ValidateCaseFields validates field values for a Case and returns enriched values with Type injected from config. The input map is not modified; a new map is returned with Type set on each FieldValue.
func (*FieldValidator) ValidateCaseFieldsAll ¶
func (v *FieldValidator) ValidateCaseFieldsAll(fieldValues map[string]FieldValue) (map[string]FieldValue, error)
ValidateCaseFieldsAll is the full-validation variant that does NOT fail fast. It walks every supplied value and every required field, accumulating all violations, and returns them as a single ErrCaseFieldValidation whose message lists every violation (one per line). Unlike the fail-fast ValidateCaseFields, an unknown field id is reported as a violation (not silently preserved) so the thread-mode create agent learns it referenced a field that does not exist. On success it returns the enriched values (Type injected) and a nil error.
func (*FieldValidator) ValidateCaseFieldsPartial ¶
func (v *FieldValidator) ValidateCaseFieldsPartial(fieldValues map[string]FieldValue) (map[string]FieldValue, error)
ValidateCaseFieldsPartial is the partial-update variant: only the supplied field values are type-checked, and missing required fields do NOT fail. Use this for UpdateCase where the caller may submit a subset of fields and the remaining values are preserved untouched on the existing Case.
func (*FieldValidator) ValidateCaseFieldsPartialStrict ¶
func (v *FieldValidator) ValidateCaseFieldsPartialStrict(fieldValues map[string]FieldValue) (map[string]FieldValue, error)
ValidateCaseFieldsPartialStrict is the agent-and-API boundary variant: like ValidateCaseFieldsPartial it does NOT require missing required fields (a partial update need not resubmit every field), but — unlike the lenient partial variant — an unknown field id is reported as a violation rather than silently preserved, and every violation is accumulated into a single ErrCaseFieldValidation so the caller can fix them in one shot. Use this for UpdateCase / MaterializeThreadCase where the submitted values come from an untrusted source (LLM / API client) that must not write a field id the workspace does not define.
type FieldValue ¶
type FieldValue struct {
FieldID types.FieldID // References FieldDefinition.ID from configuration
Type types.FieldType // Field type (self-descriptive)
Value any // Actual value (Go type depends on field type)
}
FieldValue represents a single custom field value embedded in a Case document. Each value carries its own Type for self-describing data.
func (FieldValue) IsValueInSet ¶
IsValueInSet checks whether the field value is contained in the given valid set. For select fields, the value must be a string present in validSet. For multi-select fields, all elements must be strings present in validSet. For other field types, it always returns true.
type GitHubConfig ¶
type GitHubConfig struct {
Repositories []GitHubRepository
}
GitHubConfig holds GitHub specific configuration
type GitHubRepository ¶
GitHubRepository represents a GitHub repository reference
type HomeMessage ¶
type HomeMessage struct {
ID HomeMessageID
UserID string // Slack User ID (auth token Sub). Required.
Message string // Generated one-liner. Required.
Lang string // Language at generation ("en"/"ja"); used for reuse matching.
CreatedAt time.Time
}
HomeMessage is one LLM-generated home greeting for a user. Messages are append-only (never updated or TTL-deleted); freshness is judged in the usecase by comparing CreatedAt against a window. Keeping the history lets the generator avoid repeating recent phrasings.
func (*HomeMessage) Validate ¶
func (m *HomeMessage) Validate() error
Validate enforces the invariants the repository relies on before every write.
type HomeMessageID ¶
type HomeMessageID string
HomeMessageID identifies a stored home message. It is a UUID v7 so that document IDs and CreatedAt order agree (lexicographically time-ordered).
func NewHomeMessageID ¶
func NewHomeMessageID() HomeMessageID
NewHomeMessageID mints a time-ordered home message ID.
func (HomeMessageID) String ¶
func (id HomeMessageID) String() string
type ImportActionResult ¶
type ImportActionResult struct {
Status ImportItemResultStatus
CreatedActionID *int64
Error *ImportIssue
}
ImportActionResult is the per-Action execution outcome, parallel to ImportCaseResult.
type ImportCaseResult ¶
type ImportCaseResult struct {
Status ImportItemResultStatus
CreatedCaseID *int64
Error *ImportIssue
}
ImportCaseResult is the per-Case execution outcome. CreatedCaseID is set when Status == created; Error is set when Status == failed.
type ImportIssue ¶
type ImportIssue struct {
Path string
Message string
Severity ImportIssueSeverity
}
ImportIssue is a single validation or execution-time finding. Path is a JSONPath-like locator (e.g. "cases[0].fields.severity") used by the frontend to highlight the offending value. Message is the human- readable text, already translated to the user's language (the i18n boundary lives in the usecase that produces the issue).
type ImportIssueSeverity ¶
type ImportIssueSeverity string
ImportIssueSeverity classifies a single ImportIssue. error blocks execute; warning is informational only.
const ( ImportIssueError ImportIssueSeverity = "error" ImportIssueWarning ImportIssueSeverity = "warning" )
type ImportItemResultStatus ¶
type ImportItemResultStatus string
ImportItemResultStatus represents the per-Case / per-Action execution result. pending = not yet executed; created = persisted successfully; failed = persistence attempted and failed; skipped = not attempted because an earlier item in the same execute call failed.
const ( ImportItemPending ImportItemResultStatus = "pending" ImportItemCreated ImportItemResultStatus = "created" ImportItemFailed ImportItemResultStatus = "failed" ImportItemSkipped ImportItemResultStatus = "skipped" )
type ImportSession ¶
type ImportSession struct {
ID ImportSessionID
WorkspaceID string
CreatorUserID string // Slack User ID of the creator (immutable)
Status ImportSessionStatus
Source ImportSource // Original file metadata
Snapshot ImportSnapshot // Normalized payload (per-Case result is stored here)
Issues []ImportIssue // Session-level issues (parse failure, version, schema stale, etc.)
// FieldSchemaHash is a digest of the workspace field schema captured at
// createCaseImport time. executeCaseImport compares against the current
// hash and refuses to run if it differs (callers must create a new import).
FieldSchemaHash string
CreatedAt time.Time
UpdatedAt time.Time
// ExecutedAt is set when status transitions to applied or failed.
ExecutedAt *time.Time
// Aggregate counts over Snapshot.Cases[*].Result.Status. Recomputed
// by the usecase on every Update so they stay consistent.
CreatedCount int
FailedCount int
SkippedCount int
}
ImportSession is the persistent record of one "import YAML → Case/Action" workflow. Created by createCaseImport (status=pending), advanced once by executeCaseImport (→ applied or failed). Stored at tenants/{tenantID}/imports/{importID} in Firestore. The owning user (CreatorUserID) is the only principal allowed to read or execute it.
func (*ImportSession) HasErrorIssues ¶
func (s *ImportSession) HasErrorIssues() bool
HasErrorIssues reports whether the session has any error-severity issue, including those nested under per-Case / per-Action issues. Used to compute the GraphQL `valid` field and to gate executeCaseImport.
func (*ImportSession) RecomputeCounts ¶
func (s *ImportSession) RecomputeCounts()
RecomputeCounts updates CreatedCount / FailedCount / SkippedCount from the current Snapshot. The usecase calls this whenever Snapshot.Cases is mutated so the cached counts stay consistent with reality.
Counting rules (matches the spec / Design Canvas i05):
- "created" counts Cases with Result.Status == created.
- "failed" counts Cases with Result.Status == failed AND Actions with Result.Status == failed (a Case can be created while one of its Actions fails — that Action contributes to FailedCount).
- "skipped" counts Cases with Result.Status == skipped only. Action-level skipped (under a created Case whose earlier Action failed) is NOT counted, matching the summary line shown to the user (Design Canvas treats the summary as Case-level except for failed which sweeps in Action-level failures too).
func (*ImportSession) Valid ¶
func (s *ImportSession) Valid() bool
Valid is the logical opposite of HasErrorIssues. Provided as a method so converters can mirror the GraphQL `valid: Boolean!` field directly.
func (*ImportSession) Validate ¶
func (s *ImportSession) Validate() error
Validate checks the invariants every persisted ImportSession must satisfy. Repositories MUST call this before every write.
type ImportSessionID ¶
type ImportSessionID string
ImportSessionID is a UUID-based identifier for ImportSession.
func NewImportSessionID ¶
func NewImportSessionID() ImportSessionID
NewImportSessionID generates a new UUID v4 ImportSessionID.
func (ImportSessionID) String ¶
func (id ImportSessionID) String() string
String returns the string representation.
type ImportSessionStatus ¶
type ImportSessionStatus string
ImportSessionStatus is the lifecycle status of an ImportSession.
Allowed transitions: pending → applied | failed. Once an ImportSession has reached applied or failed, it is terminal; retrying requires creating a new ImportSession via createCaseImport.
const ( ImportSessionPending ImportSessionStatus = "pending" ImportSessionApplied ImportSessionStatus = "applied" ImportSessionFailed ImportSessionStatus = "failed" )
func (ImportSessionStatus) IsTerminal ¶
func (s ImportSessionStatus) IsTerminal() bool
IsTerminal reports whether the session is in a state that cannot be further mutated (applied or failed).
type ImportSnapshot ¶
type ImportSnapshot struct {
Version int
Cases []ImportSnapshotCase
}
ImportSnapshot is the workspace-normalized interpretation of the uploaded YAML. Per-Case results live inside Cases[*].Result; the snapshot itself is mutated in place as execute progresses.
type ImportSnapshotAction ¶
type ImportSnapshotAction struct {
Index int
Title string
Description string
AssigneeID string
DueDate *time.Time
Issues []ImportIssue
Result ImportActionResult
}
ImportSnapshotAction is one Action to be created under its parent Snapshot Case. AssigneeID is a single Slack User ID (matches the existing Action.AssigneeID semantics).
type ImportSnapshotCase ¶
type ImportSnapshotCase struct {
Index int
Title string
Description string
IsPrivate bool
AssigneeIDs []string
FieldValues map[string]FieldValue
Actions []ImportSnapshotAction
Issues []ImportIssue
Result ImportCaseResult
}
ImportSnapshotCase is one Case to be created. Issues holds per-Case validation findings detected at createCaseImport time (and at execute time when field schema has drifted). Result is the per-Case execution outcome.
type ImportSource ¶
type ImportSource struct {
OriginalFileName string
ContentDigest string // sha256 of the raw YAML, hex-encoded
SizeBytes int
}
ImportSource captures metadata about the uploaded YAML payload.
type Job ¶
type Job struct {
// ID is the workspace-unique identifier (kebab-case).
ID string
// Name and Description are human-facing labels used in logs and
// system prompts.
Name string
Description string
// Prompt is the user prompt template (Go text/template). Job-specific
// behaviour is fully expressed here; runtime context (Case / Workspace
// / Event) is injected as template data.
Prompt string
// Disabled defaults to false (= active). Setting it true silently
// excludes the Job from event matching without removing the TOML
// entry.
Disabled bool
// Quiet defaults to false. When true, the Job runs without emitting
// operational Slack notifications (the "starting..." marker, the
// per-run session-log thread, and the completion/failure markers).
// It does NOT silence the agent's own slack__post_message tool — that
// is a deliberate agent action, not an operational log.
Quiet bool
// Strategy selects which execution runtime drives this Job. Empty
// (the zero value) is equivalent to JobStrategySimple; the config
// loader normalises before Validate runs.
Strategy JobStrategy
// Interactive enables mid-run user interaction. When true, the Job may
// suspend a run to ask the user a question (planexec Question), persist
// the pending interaction on its run state, post a Slack form, and
// resume when the user answers. Defaults to false (= the run is fully
// unattended). Only meaningful for JobStrategyPlanexec: the simple
// single-loop runtime has no Question mechanism, so Validate rejects
// interactive simple Jobs. Independent of Quiet — the question form is a
// deliberate agent interaction, not an operational log, so Quiet does
// not suppress it.
Interactive bool
// Reflection, when true, runs a post-execution reflection pass after a
// successful run: a knowledge-only agent reviews the run's conversation
// history and curates the workspace's shared Knowledge / Tags. Defaults
// to false. Skipped for private cases and for failed runs.
Reflection bool
// Events maps the event domains this Job subscribes to. Validate
// guarantees at least one non-nil entry.
Events JobEvents
}
Job is a workspace-scoped, declaratively configured agent that runs in response to events. Jobs are loaded from workspace TOML and held in memory on the WorkspaceEntry — they are not persisted to a backend.
func (*Job) ListensCase ¶
func (j *Job) ListensCase(lc CaseLifecycle) bool
ListensCase reports whether the Job subscribes to the given case lifecycle.
func (*Job) ListensScheduled ¶
ListensScheduled reports whether the Job subscribes to the scheduled domain.
type JobEventDomain ¶
type JobEventDomain string
JobEventDomain enumerates the event domains the dispatcher recognises. Each domain has a distinct filter schema (see CaseEventConfig / ScheduledEventConfig).
const ( JobEventDomainCase JobEventDomain = "case" JobEventDomainScheduled JobEventDomain = "scheduled" )
type JobEvents ¶
type JobEvents struct {
Case *CaseEventConfig
Scheduled *ScheduledEventConfig
}
JobEvents collects every event filter a Job listens to. A nil pointer means the corresponding domain is not subscribed; a non-nil pointer indicates the Job listens to that domain with the given filter.
type JobRun ¶
type JobRun struct {
WorkspaceID string
CaseID int64
JobID string
LastRunAt time.Time
LastStatus JobRunStatus
LastError string
LastRunID string
LastTraceID string
// LeaseUntil is the wall-clock time at which the current run lock
// expires. Zero value means no live lease (= idle). A lease may be
// reclaimed by another acquirer once LeaseUntil < now (clock
// agreement is assumed; lease durations are large enough to absorb
// minor skew).
LeaseUntil time.Time
// SuspendedRunID, when non-empty, names a Run that is currently
// suspended awaiting user input (Stage=AWAITING_INPUT in its
// JobRunLog). While set, the scheduler/dispatcher MUST NOT start a new
// Run for this (workspace, case, job) — the in-flight interactive Run
// owns the slot until the user answers (resume clears it) or the
// unanswered-run sweep expires it. The lease is released while
// suspended (a human wait can outlast any lease), so SuspendedRunID is
// the durable "do not double-start" signal, not the lease.
SuspendedRunID string
// SuspendedAt is when the current suspension began; the unanswered-run
// sweep uses it to expire stale AWAITING_INPUT runs. Zero when not
// suspended.
SuspendedAt time.Time
}
JobRun records the most recent state of a Job × Case pair: when it last ran, what happened, and (when in flight) the lease that prevents concurrent execution.
The same document doubles as the lock record so a lease can be atomically taken or released in the same transaction that updates run history.
Identifiers are flat top-level fields (no nested JobRunKey) so a Firestore doc viewed in isolation surfaces "which (workspace, case, job) is this?" without having to parse the document path, and a BigQuery export yields rows that JOIN directly on WorkspaceID / CaseID / JobID with JobRunLog / JobRunEvent.
func (*JobRun) IsLeased ¶
IsLeased reports whether the JobRun currently holds a non-expired lease as of `now`. Used by acquirers to decide whether to take the lock or step aside.
func (*JobRun) IsSuspended ¶
IsSuspended reports whether a Run is currently suspended awaiting user input for this (workspace, case, job). New triggers must step aside while this is true.
type JobRunEvent ¶
type JobRunEvent struct {
// Identifiers (flat, all populated on every write).
WorkspaceID string
CaseID int64
JobID string
RunID string
TraceID string
// EventID is the doc ID of this event. It is a UUIDv7 string,
// chosen for Firestore-console readability (the doc ID surfaces a
// millisecond timestamp prefix) and global uniqueness. doc IDs are
// NOT relied on for strict ordering — `Sequence` is the
// authoritative monotonic order.
EventID string
// Position.
// Sequence is the authoritative monotonic order within a Run. It is
// int64 (not uint64) because the Firestore SDK rejects uint64. The
// value space is still effectively unbounded for this workload
// (max int64 = 9.2e18 events per Run). List queries MUST OrderBy
// "Sequence" — doc ID order may diverge under clock skew.
Sequence int64
OccurredAt time.Time
Kind JobRunEventKind
// ParentSequence links a child event to its parent. Currently used
// by ToolCall events to point back at the LLMResponse whose tool_use
// they implement. Zero means top-level (LLMRequest / LLMResponse /
// RunError are always top-level).
ParentSequence int64
// Forward-compatible runtime tagging. v1 SingleLoopJobExecutor emits
// Phase="execute" and AgentLabel="" for every event. A future
// plan-execute runtime may emit Phase="plan" / "execute" / "review"
// and AgentLabel="planner" / "executor" / etc.
Phase string
AgentLabel string
// Payload (exactly one is non-nil and matches Kind).
LLMRequest *LLMRequestPayload
LLMResponse *LLMResponsePayload
ToolCall *ToolCallPayload
RunError *RunErrorPayload
}
JobRunEvent is one entry in the per-Run timeline. Stored at jobRuns/{JobID}/logs/{RunID}/events/{Sequence}. Identifiers are flat for BigQuery-friendliness; TraceID is duplicated here so trace-level analytics can group events without joining back to the log.
func (*JobRunEvent) Validate ¶
func (e *JobRunEvent) Validate() error
Validate enforces invariants on a JobRunEvent. The sink calls this before every Append so that a payload-kind mismatch surfaces as an error at the boundary instead of producing unreadable docs.
type JobRunEventKind ¶
type JobRunEventKind string
JobRunEventKind enumerates the per-call event types appended during a Run. Each maps to a specific gollem trace.Handler hook boundary:
- LLM_REQUEST : input snapshot captured at EndLLMCall
- LLM_RESPONSE : output snapshot captured at EndLLMCall
- TOOL_CALL : single tool execution captured at EndToolExec
- RUN_ERROR : terminal failure emitted by JobRunner via EmitRunError
const ( JobRunEventKindLLMRequest JobRunEventKind = "LLM_REQUEST" JobRunEventKindLLMResponse JobRunEventKind = "LLM_RESPONSE" JobRunEventKindToolCall JobRunEventKind = "TOOL_CALL" JobRunEventKindRunError JobRunEventKind = "RUN_ERROR" )
func (JobRunEventKind) IsValid ¶
func (k JobRunEventKind) IsValid() bool
IsValid reports whether the kind is a known enum member.
func (JobRunEventKind) String ¶
func (k JobRunEventKind) String() string
String returns the string form for logging.
type JobRunKey ¶
JobRunKey identifies a single (workspace, case, job) lock and run-record tuple. The tuple is the lock granularity for both lease acquisition and last-run bookkeeping.
type JobRunLog ¶
type JobRunLog struct {
// Identifiers (all flat, all required, all populated on every write).
WorkspaceID string
CaseID int64
JobID string
RunID string
TraceID string
// Lifecycle.
Stage JobRunStage
StartedAt time.Time
EndedAt time.Time // zero while RUNNING
Error string // empty unless Stage == FAILED
// Runtime identification (forward-compatible with plan-execute).
// v1 always writes ExecutorKind="single_loop".
ExecutorKind string
ExecutorVersion string
// Provenance — copied from the triggering Event so a single Get tells
// you why this Run happened.
EventType string
EventTriggerAt time.Time
// SystemPrompt is held once per Run, here, rather than inside every
// LLMRequest event (it doesn't change turn-to-turn within a Run).
// Truncated from the tail to MaxInlineBytes if longer.
SystemPrompt string
// PendingInteraction is set ONLY while Stage == AWAITING_INPUT: it holds
// the question put to the user and the Slack message coordinates needed
// to update the form in place on resume. It MUST be nil in every other
// stage (the resume path clears it). It is the entire persisted state a
// suspend needs — no planexec plan snapshot is stored, because the
// gollem conversation history (keyed by RunID) already carries the
// sub-agent observations and the resume re-enters planexec at the
// replan branch with a fresh budget.
PendingInteraction *PendingInteraction
}
JobRunLog records ONE invocation of an agent against a Case. Stored at workspaces/{WorkspaceID}/cases/{CaseID}/jobRuns/{JobID}/logs/{RunID}.
Despite the "Job" name it holds every case-scoped agent run, not only TOML-configured Job runs: mention-triggered runs (casebound / threadcase) persist here too, each under its own fresh per-turn JobID and with EventType=EventTypeMention, so the case agent page lists them alongside Job runs through the same read path.
Every identifier is held as a TOP-LEVEL scalar field (no nested Key struct) so that a Firestore -> BigQuery export yields a flat row that joins directly on WorkspaceID / CaseID / JobID / RunID / TraceID.
type JobRunStage ¶
type JobRunStage string
JobRunStage is the lifecycle stage of an individual Run (one invocation of a Job against a Case). Unlike JobRunStatus on the JobRun lock doc, JobRunStage includes RUNNING so a Run record exists while the agent is still executing — that way a crash mid-Run leaves a Stage=RUNNING log with the events captured up to that point.
const ( JobRunStageRunning JobRunStage = "RUNNING" JobRunStageSuccess JobRunStage = "SUCCESS" JobRunStageFailed JobRunStage = "FAILED" // JobRunStageAwaitingInput marks an interactive Run that suspended to // ask the user a question. It is a non-terminal stage: EndedAt stays // zero and PendingInteraction carries the question so the resume path // can parse the answer and continue. Only interactive (planexec) Jobs // ever reach it. JobRunStageAwaitingInput JobRunStage = "AWAITING_INPUT" )
func (JobRunStage) IsValid ¶
func (s JobRunStage) IsValid() bool
IsValid reports whether the stage is a known enum member.
func (JobRunStage) String ¶
func (s JobRunStage) String() string
String returns the string form for logging.
type JobRunStatus ¶
type JobRunStatus string
JobRunStatus enumerates the terminal outcome of the most recent Job run for a (workspace, case, job) tuple. It is set by JobRunRepository.RecordRun after the agent finishes (or fails). RUNNING is reserved for a future extension if we ever expose mid-flight observability — v1 does not write RUNNING; the in-flight signal is the lease_until column instead.
const ( JobRunStatusSuccess JobRunStatus = "SUCCESS" JobRunStatusFailed JobRunStatus = "FAILED" )
func (JobRunStatus) IsValid ¶
func (s JobRunStatus) IsValid() bool
IsValid reports whether the status is a known enum member.
func (JobRunStatus) String ¶
func (s JobRunStatus) String() string
String returns the string form for logging.
type JobStrategy ¶
type JobStrategy string
JobStrategy selects which execution runtime drives a Job. The default (zero value) is JobStrategySimple, which preserves the v1 single-loop behaviour. JobStrategyPlanexec opts the Job into the planexec (plan-and-execute) runtime shared with proposal — useful when the Job needs to investigate, replan, and produce a structured summary rather than emit a single ReAct turn.
const ( // JobStrategySimple drives the Job through the v1 // SingleLoopJobExecutor (one gollem.Agent.Execute call with the // configured tool set). JobStrategySimple JobStrategy = "simple" // JobStrategyPlanexec drives the Job through the planexec runtime // (plan → parallel sub-agents → replan → final response). The host // disables Question because Jobs run unattended. JobStrategyPlanexec JobStrategy = "planexec" )
func NormaliseJobStrategy ¶
func NormaliseJobStrategy(s JobStrategy) JobStrategy
NormaliseJobStrategy collapses the empty value to JobStrategySimple so callers downstream of TOML loading can treat every Job uniformly. Unknown non-empty values pass through unchanged and are then rejected by Validate, surfacing the typo at config-load time.
func (JobStrategy) IsValid ¶
func (s JobStrategy) IsValid() bool
IsValid reports whether s is one of the recognised strategy values. The zero value (empty string) is NOT considered valid here — callers MUST normalise via NormaliseJobStrategy first, which substitutes JobStrategySimple for the empty input. This split keeps Validate strict (any non-empty unknown value is rejected) while letting TOML omit the field entirely.
func (JobStrategy) String ¶
func (s JobStrategy) String() string
String returns the canonical string form for prompt rendering / logs.
type Knowledge ¶
type Knowledge struct {
ID KnowledgeID
WorkspaceID string
Title string
// Claim is a single Markdown text body (not an array). It is stored verbatim
// and rendered as Markdown by the consumer (Web UI). The agent write tools
// produce it directly.
Claim string
// TagIDs reference first-class Tag entities for classification and filtering.
// At least one tag is required. Tags must be created (create_tag / GraphQL
// createTag) before they can be referenced here; the usecase verifies every
// ID exists in the workspace before persistence. The slice is de-duplicated
// (order preserved) by the usecase.
TagIDs []TagID
// Embedding is the vector embedding of Title + Claim used for in-memory
// cosine semantic search. It is empty when no embedding client is configured
// (fail-open). It is never exposed through the GraphQL surface.
Embedding []float64
// CreatorID is the Slack user id of the human author. It is empty when the
// entry was authored by an agent (system actor).
CreatorID string
CreatedAt time.Time
UpdatedAt time.Time
}
Knowledge is a workspace-wide shared knowledge entry: organization-specific information that does not exist in the LLM's general knowledge (operating rules, internal proper nouns, past judgements, threat intel, ...) accumulated so it can be reused on future case processing. Unlike Memo it is NOT scoped to a case and carries no custom fields — only a Markdown claim body and tags.
All identifiers are flat top-level fields even though the Firestore path already encodes WorkspaceID, mirroring the Memo convention so a document inspected in isolation answers "which workspace is this?".
func (*Knowledge) Validate ¶
Validate enforces the structural invariants required before any persistence write. Repositories MUST call it before every write so a usecase / handler bug that forgot to inject an identity field fails loudly at the first write.
TagIDs must already be normalized by the caller; Validate does not mutate. Validate only enforces structural invariants — the existence of each TagID is verified by the usecase (which has repository access).
type KnowledgeID ¶
type KnowledgeID string
KnowledgeID is a UUID-based identifier for a Knowledge entry.
As with MemoID, a string UUID (v7) is used rather than an int64 counter: knowledge entries are created by agents during case processing as well as by humans, so a counter document would add transaction contention. UUID v7 is generated in-process (multi-instance safe) and is time-ordered, keeping CreatedAt-order listing naturally sortable.
func NewKnowledgeID ¶
func NewKnowledgeID() KnowledgeID
NewKnowledgeID generates a new time-ordered UUID v7 KnowledgeID.
func (KnowledgeID) String ¶
func (id KnowledgeID) String() string
String returns the raw string form of the KnowledgeID.
type LLMContentBlock ¶
type LLMContentBlock struct {
Type string // "text" / "tool_call" / "tool_response" / "reasoning" / "image" / etc.
Text string // text / reasoning
// tool_call
ID string
Name string
ArgumentsJSON string
// tool_response
ToolCallID string
ResultJSON string
// image / pdf / document / file
MediaType string
URL string
Title string
}
LLMContentBlock is one chunk inside a message. Mirrors gollem trace.MessageContent — fields are populated only when relevant to Type.
type LLMFunctionCall ¶
type LLMFunctionCall struct {
ID string // provider-issued id
Name string
ArgumentsJSON string // raw JSON of trace.FunctionCall.Arguments
}
LLMFunctionCall mirrors gollem trace.FunctionCall.
type LLMMessage ¶
type LLMMessage struct {
Role string
Contents []LLMContentBlock
}
LLMMessage models one turn in the conversation history sent to the LLM. Mirrors gollem trace.Message.
type LLMRequestPayload ¶
type LLMRequestPayload struct {
Model string // LLMCallData.Model
Messages []LLMMessage // LLMRequest.Messages
Tools []LLMToolSpec // LLMRequest.Tools (name + description only)
}
LLMRequestPayload captures the input sent to the LLM API in one call, except the system prompt (which lives on JobRunLog because it doesn't change turn-to-turn). Maps to gollem trace.LLMRequest + LLMCallData.Model.
type LLMResponsePayload ¶
type LLMResponsePayload struct {
Model string // echoed back (LLMCallData.Model)
Texts []string // LLMResponse.Texts
FunctionCalls []LLMFunctionCall // LLMResponse.FunctionCalls
InputTokens int64 // LLMCallData.InputTokens
OutputTokens int64 // LLMCallData.OutputTokens
DurationMs int64 // wall-clock time between Start/End hook
}
LLMResponsePayload captures one assistant response. gollem returns texts and function calls as separate arrays (no interleaving order info), so this payload mirrors that shape rather than fabricating an ordering we cannot observe.
type LLMToolSpec ¶
LLMToolSpec mirrors gollem trace.ToolSpec.
type MaterializationValidationIssue ¶
type MaterializationValidationIssue struct {
FieldID types.FieldID // empty for issues on Title/Description
Code string // machine-readable category, e.g. "missing_required"
Message string
Fatal bool
}
MaterializationValidationIssue describes a single problem found by WorkspaceMaterialization.Validate. Issues are partitioned into "fatal" (the materialization is unusable as-is — must be re-generated or rejected) and "fixable" (the user can correct it through the Edit modal).
func (MaterializationValidationIssue) Error ¶
func (i MaterializationValidationIssue) Error() string
Error implements the error interface for individual issues so callers may surface them via goerr.Wrap.
type Memo ¶
type Memo struct {
ID MemoID
WorkspaceID string
CaseID int64
Title string
// FieldValues holds the custom field values keyed by field id, using the
// same representation as Case (model.FieldValue, validated by the shared
// FieldValidator against the workspace's memo field schema).
FieldValues map[string]FieldValue
// CreatorID is the Slack user id of the human author. It is empty when the
// memo was authored by an agent (system actor).
CreatorID string
// ArchivedAt is nil for an active memo and non-nil once archived (soft
// delete). Archive state is expressed by this single field; there is no
// derived boolean.
ArchivedAt *time.Time
CreatedAt time.Time
UpdatedAt time.Time
}
Memo is a Case-scoped "memory": a note attached to a single Case that records facts / observations / hypotheses / decisions accumulated while working the Case. Like a Case it carries workspace-defined custom field values; only ID and Title are fixed, everything else is a custom field.
All identifiers (ID / WorkspaceID / CaseID) are flat top-level fields even though the Firestore path already encodes WorkspaceID and CaseID. This mirrors the JobRunLog convention so a document inspected in isolation answers "which workspace / case is this?" and a Firestore -> BigQuery export yields rows that JOIN directly on WorkspaceID / CaseID.
func (*Memo) IsArchived ¶
IsArchived reports whether the memo is currently archived.
type MemoID ¶
type MemoID string
MemoID is a UUID-based identifier for a Memo.
A string UUID (v7) is used rather than the int64 counter scheme of Case / Action: memos are created frequently by agents and live in a per-Case subcollection, so a counter document would add transaction contention and per-Case counter bookkeeping. UUID v7 is generated in-process (no counter read, multi-instance safe) and is time-ordered, which keeps CreatedAt-order listing and BigQuery export naturally sortable. This matches the existing string-UUID identifiers used by Source / ActionStep / ActionEvent / Session.
type MyDueAction ¶
type MyDueAction struct {
WorkspaceID string
WorkspaceName string
Action *Action
CaseID int64
CaseTitle string
}
MyDueAction is one row of the cross-workspace "my incomplete actions" home aggregation. CaseID / CaseTitle are pre-expanded from the parent Case so the frontend need not resolve the parent (avoids an N+1 per row).
type MyOpenCase ¶
type MyOpenCase struct {
WorkspaceID string
WorkspaceName string
Case *Case
// Stalled is true when the Case is open but has not been updated within the
// configured stale window. Always false when the window is disabled.
Stalled bool
}
MyOpenCase is one row of the cross-workspace "my open cases" home aggregation. It carries the workspace identity alongside the Case because the GraphQL Case type has no workspaceId field (a Case is always workspace-scoped elsewhere).
type NotificationSlot ¶
type NotificationSlot struct {
ChannelID string // Slack channel id (primary key)
MessageTS string // Timestamp of the aggregated channel message
Entries []NotificationSlotEntry // Recorded change events (oldest first)
SlotStart time.Time // UTC time when the slot's channel message was first posted
ExpiresAt time.Time // SlotStart + slotDuration
UpdatedAt time.Time
}
NotificationSlot is a per-Slack-channel rolling aggregation window for action/step change notifications. While a slot is active, additional channel-side notifications are folded into the same channel message via chat.update (one entry per event). The renderer groups entries by ActionMessageTS so the channel reader sees one section per Action with all of that Action's recent changes underneath, rather than every line repeating "for action X". When the slot expires, the next event posts a fresh channel message and replaces the slot.
The struct is also the Firestore wire format: do NOT add `firestore:"..."` tags (see .claude/rules/firestore.md) and prefer Go-native field names.
func (*NotificationSlot) Validate ¶
func (s *NotificationSlot) Validate() error
Validate enforces the invariants required before any persistence write. ChannelID is the primary key, so a slot with no channel fails loudly here instead of landing in storage under an empty key.
type NotificationSlotEntry ¶
type NotificationSlotEntry struct {
ActionMessageTS string // Parent Action card timestamp — used as the grouping key
ActionTitle string // Action title as of the event (most recent wins per group)
ActionPermalink string // Slack permalink to the Action card; empty when lookup failed
Body string // Pre-rendered change line ("@user changed status: A → B")
EventTime time.Time // UTC time the event was recorded
}
NotificationSlotEntry captures one change event folded into a slot. ActionMessageTS is the grouping key (timestamp of the parent Action card in Slack); the renderer collects every entry sharing this key into a single section. ActionPermalink is resolved once at enqueue time and cached so subsequent updates don't re-hit Slack's chat.getPermalink.
type NotionDBConfig ¶
NotionDBConfig holds Notion DB specific configuration
type NotionPageConfig ¶
type NotionPageConfig struct {
PageID string
PageTitle string
PageURL string
Recursive bool
MaxDepth int
}
NotionPageConfig holds Notion Page specific configuration
type PendingInteraction ¶
type PendingInteraction struct {
// PostedChannelID / PostedMessageTS locate the Slack question form so
// the resume path can update it in place to an "answered" view.
PostedChannelID string
PostedMessageTS string
// Reason is the shared rationale rendered once above the items.
Reason string
// Items is the ordered list of questions (1..5).
Items []PendingInteractionItem
}
PendingInteraction is the persisted snapshot of a question put to the user while an interactive Run is suspended. It lives on the run's JobRunLog (Stage=AWAITING_INPUT). The shape mirrors the host-neutral interaction.Request, but is defined here (domain layer) so persistence does not depend on pkg/agent/interaction; the Job host converts at the boundary.
func (*PendingInteraction) Validate ¶
func (p *PendingInteraction) Validate() error
Validate enforces the same invariants the host-neutral interaction.Request enforces, so a snapshot that round-trips through Firestore stays well formed. Slack coordinates are required because the resume path must locate the form to update it.
type PendingInteractionItem ¶
type PendingInteractionItem struct {
ID string
Text string
Type string // select | multi_select | free_text
Options []string
}
PendingInteractionItem is one question within a PendingInteraction.
type PendingQuestion ¶
type PendingQuestion struct {
// PostedChannelID / PostedMessageTS locate the Slack message hosting the
// question form so the submit handler can update it in place into the
// read-only "answered" view.
PostedChannelID string
PostedMessageTS string
// Reason is the planner's single-rationale text shared across all items.
Reason string
// Items mirrors proposal.QuestionPayload.Items at the time the question was
// posted. Stored here so the submit handler can label each answer back
// against the original question text and option list, even after the
// planner advances and the Slack message blocks have been rebuilt.
Items []PendingQuestionItem
}
PendingQuestion is the persisted snapshot of a question turn while we wait for the user's submission. It is set when the planner emits a `question` terminal action and consumed when the Submit button fires.
type PendingQuestionItem ¶
type PendingQuestionItem struct {
ID string
Text string
Type string // "select" | "multi_select"
Options []string
}
PendingQuestionItem is a single question's persisted snapshot.
type ProposalMessage ¶
ProposalMessage is a single Slack message captured for context.
type ProposalSource ¶
type ProposalSource struct {
TeamID string
ChannelID string
ThreadTS string // empty when the mention was not in a thread
MentionTS string
}
ProposalSource describes where the mention happened.
type RunErrorPayload ¶
RunErrorPayload is the terminal log entry on a FAILED Run, emitted by JobRunner via the handler's EmitRunError API.
type ScheduledEventConfig ¶
type ScheduledEventConfig struct {
// Every is the duration since the last run after which the Job becomes
// due. Mutually exclusive with Cron.
Every time.Duration
// Cron is the parsed schedule for cron-style triggers. Mutually
// exclusive with Every. Parsed at config load time so a malformed
// expression fails loud before any Job dispatch.
Cron cron.Schedule
// CronExpr is the original cron expression preserved for display and
// system-prompt rendering. Empty when Every is in use.
CronExpr string
}
ScheduledEventConfig is the listen filter for the `scheduled` event domain. Exactly one of Every / Cron must be populated.
func (*ScheduledEventConfig) Validate ¶
func (s *ScheduledEventConfig) Validate() error
Validate enforces the mutual exclusion: exactly one of Every / Cron.
type Session ¶
type Session struct {
ID string
ChannelID string
ThreadTS string
LastMentionTS string
LastAction SessionEndReason
// Kind discriminates the conversation that owns this thread. It is set when
// the Session is created and never changed afterwards: the Slack dispatcher
// reads it to decide whether an @mention in a case-less thread starts a Case
// (SessionKindCase) or continues the workspace agent
// (SessionKindWorkspaceAgent).
Kind SessionKind
// Case binding — zero values when the thread is not in a case-bound channel.
WorkspaceID string
CaseID int64
ActionID int64
// Open-mode metadata — zero values when case-bound.
CreatorUserID string
ProposalID CaseProposalID
// Reaction-origin metadata — set only for a cross-channel reaction-triggered
// case creation, where the case root lives in the monitored channel but the
// flagged message lives elsewhere. Persisted so a later resume turn (after a
// question) can still link the exact source message. Zero for every other
// creation path.
ReactionSourceChannelID string
ReactionSourceMessageTS string
// Turn lock fields. Maintained by SessionRepository.AcquireTurnLock /
// Heartbeat / ReleaseTurnLock. Heartbeat staleness (TurnHeartbeatAt vs now)
// is the activity signal; TurnStartedAt is recorded for traces / UX only.
TurnState SessionTurnState
TurnOwnerID string
TurnStartedAt time.Time
TurnHeartbeatAt time.Time
TurnTriggerTS string
// Reserved for future interrupt support. Never read or written by Phase A
// code paths; populated by RequestInterrupt in a later spec.
InterruptRequestedAt time.Time
InterruptByTriggerTS string
// PendingQuestion mirrors the planner's most recent question payload when
// LastAction == SessionEndedWithQuestion. It is the single source of
// truth for the Slack-side question form: rendering it, parsing the
// submission state back into typed answers, and rebuilding the read-only
// "answered" view after the user clicks Submit. Cleared on the next
// terminal action.
PendingQuestion *PendingQuestion
CreatedAt time.Time
UpdatedAt time.Time
}
Session represents an ongoing agent conversation bound to a Slack thread. It unifies what was previously split between AgentSession (case-bound) and per-mention draft state (open mode). One Session per (channelID, threadTS).
Lookup keys are (ChannelID, ThreadTS). Case binding is detected via CaseID != 0; open mode (draft creation) is the zero-CaseID case.
func (*Session) IsCaseBound ¶
IsCaseBound reports whether this Session belongs to a case-bound thread.
func (*Session) ResumeOnReply ¶
ResumeOnReply reports whether a thread reply (without @mention) should kick off a new turn. Currently only true when the previous turn ended on a post_question (open mode); case-bound resume is @mention-only.
type SessionEndReason ¶
type SessionEndReason string
SessionEndReason captures the terminal plan action that ended the most recent turn for a Session. The dispatcher uses it to decide whether a thread reply (without an @mention) should resume the agent.
const ( SessionEndedNone SessionEndReason = "" SessionEndedWithMessage SessionEndReason = "post_message" SessionEndedWithQuestion SessionEndReason = "post_question" SessionEndedWithMaterialize SessionEndReason = "materialize" SessionEndedWithCaseBoundReply SessionEndReason = "case_bound" )
type SessionKind ¶ added in v0.2.0
type SessionKind string
SessionKind discriminates what kind of agent conversation owns a thread. The zero value is SessionKindCase so every Session persisted before this field existed keeps its original meaning without a migration.
const ( // SessionKindCase is a thread owned by a Case: either a Case-bound thread // (CaseID != 0) or a thread whose Case is still being formed by the // creation agent (CaseID == 0). SessionKindCase SessionKind = "" // SessionKindWorkspaceAgent is a thread owned by the workspace agent. An // @mention in such a thread never starts a Case. SessionKindWorkspaceAgent SessionKind = "workspace_agent" )
type SessionTurnState ¶
type SessionTurnState string
SessionTurnState captures whether a turn is currently running on a Session. It is the CAS key used by the Firestore-backed turn lock and is updated by AcquireTurnLock / Heartbeat / ReleaseTurnLock atomically.
const ( SessionTurnIdle SessionTurnState = "" SessionTurnRunning SessionTurnState = "running" SessionTurnInterrupted SessionTurnState = "interrupted" )
type SlackChannel ¶
type SlackChannel struct {
ID string // Slack Channel ID (e.g., C01234567)
Name string // Fallback name for display when API is unavailable
}
SlackChannel represents a Slack channel configuration
type SlackConfig ¶
type SlackConfig struct {
Channels []SlackChannel
}
SlackConfig holds Slack specific configuration
type SlackUser ¶
type SlackUser struct {
ID SlackUserID
Name string // Slack username / handle (e.g., "john.doe")
RealName string // User-facing display name (Profile.DisplayName, with Profile.RealName / RealName as fallback). Field is named "RealName" for legacy reasons.
Email string // Email address (for future features)
ImageURL string // Avatar URL (empty string = no image)
UpdatedAt time.Time // Last synchronized from Slack
}
SlackUser represents a Slack workspace user stored in the database
type SlackUserID ¶
type SlackUserID string
SlackUserID represents a unique identifier for a Slack user
type SlackUserMetadata ¶
type SlackUserMetadata struct {
LastRefreshSuccess time.Time // Last successful refresh time
LastRefreshAttempt time.Time // Last refresh attempt time (success or failure)
UserCount int // Number of users at last successful refresh
}
SlackUserMetadata tracks the health and status of Slack user synchronization
type Source ¶
type Source struct {
ID SourceID
Name string
SourceType SourceType
Description string
Enabled bool
NotionDBConfig *NotionDBConfig
NotionPageConfig *NotionPageConfig
SlackConfig *SlackConfig
GitHubConfig *GitHubConfig
CreatedAt time.Time
UpdatedAt time.Time
}
Source represents an external data source for risk monitoring
func (*Source) Validate ¶
Validate enforces the invariants required before any persistence write. The repository assigns the storage ID (NewSourceID when empty), so ID is not checked here; Name and SourceType are the source's mandatory identity/kind fields (every create path supplies a non-empty default for both).
type SourceType ¶
type SourceType string
SourceType represents the type of source
const ( SourceTypeNotionDB SourceType = "notion_db" SourceTypeNotionPage SourceType = "notion_page" SourceTypeSlack SourceType = "slack" SourceTypeGitHub SourceType = "github" )
type Tag ¶
type Tag struct {
ID TagID
WorkspaceID string
// Name is an optional, human-facing label. It is mutable (update_tag) and
// carries no uniqueness constraint — the ID is the stable key.
Name string
CreatedAt time.Time
UpdatedAt time.Time
}
Tag is a workspace-scoped, first-class classification label referenced by Knowledge entries via its immutable ID. Unlike the previous free-form string tags, a Tag must be created explicitly (the create_tag tool / GraphQL createTag) before any Knowledge can reference it.
All identifiers are flat top-level fields even though the Firestore path already encodes WorkspaceID, mirroring the Knowledge / Memo convention so a document inspected in isolation answers "which workspace is this?".
type TagID ¶
type TagID string
TagID is the immutable identifier of a Tag. A random UUIDv4 is used rather than a sequential counter: tags are created by agents (the create_tag tool / the reflection agent) as well as by humans, so a per-workspace counter document would add transaction contention for no benefit. Tag ordering is driven by the explicit CreatedAt field, so the time-ordering of UUIDv7 buys nothing here.
func NormalizeTagIDs ¶
NormalizeTagIDs drops empty entries and de-duplicates while preserving first-seen order. The usecase applies this before existence verification / Validate / persistence.
type ToolCallPayload ¶
type ToolCallPayload struct {
ToolName string
ArgumentsJSON string // raw JSON of trace.ToolExecData.Args
ResultJSON string // raw JSON of trace.ToolExecData.Result; empty on pure error
IsError bool
ErrorMessage string // trace.ToolExecData.Error when IsError
StartedAt time.Time
EndedAt time.Time
}
ToolCallPayload is the full record of one tool execution. Maps to gollem trace.ToolExecData plus wall-clock timestamps captured by the handler at Start/End boundaries.
type UserPreference ¶
type UserPreference struct {
// UserID is the Slack User ID (auth token Sub). Document key. Required.
UserID string
// FavoriteWorkspaceIDs are the workspace IDs the user starred, in the
// order the caller supplies (deduplicated). May be empty.
FavoriteWorkspaceIDs []string
CreatedAt time.Time
UpdatedAt time.Time
}
UserPreference holds per-user settings that span workspaces. Keyed by the user's Slack ID (the auth token's Sub). Currently it only carries the favorite workspace list surfaced on the home dashboard.
func (*UserPreference) Validate ¶
func (p *UserPreference) Validate() error
Validate enforces the identity invariant the repository relies on before every write.
type Workspace ¶
type Workspace struct {
ID string
Name string
Description string // Human-readable description (e.g. for AI workspace estimation, UI tooltips)
// Emoji is an optional display glyph for the workspace badge. Mutually
// exclusive with Color (enforced at config load). Empty when unset.
Emoji string
// Color is an optional #RRGGBB hex used as the workspace badge background.
// Mutually exclusive with Emoji (enforced at config load). Empty when unset.
Color string
}
Workspace represents a workspace's identity
type WorkspaceEntry ¶
type WorkspaceEntry struct {
Workspace Workspace
FieldSchema *config.FieldSchema
MemoConfig *config.MemoConfig // Memo "strong definition" + custom field schema (nil/empty when memos disabled)
ActionStatusSet *ActionStatusSet
SlackChannelPrefix string
SlackTeamID string // Slack Team ID for org-level app support (empty for WS-level apps)
SlackInviteUsers []string
SlackInviteGroups []string
SlackWelcomeMessages []string // Go text/template strings posted to the case channel after creation
CompilePrompt string
AssistPrompt string
AssistLanguage string
// CaseCreatePrompt is the workspace-specific additional prompt for the
// thread-mode case initialization (create) agent, configured via TOML
// [case.prompts].create. Empty when unset; appended to the ModeCreate
// planner system prompt.
CaseCreatePrompt string
Jobs []*Job // Event-driven agent jobs loaded from workspace TOML
// CaseMode selects channel-per-case (default) or thread-per-case binding.
CaseMode CaseMode
// CaseTrigger selects what starts a Case in thread mode: instant (default,
// every channel-root post) or mention (only an @mention of the bot). Only
// meaningful when CaseMode is thread.
CaseTrigger CaseTrigger
// SlackMonitorChannelID is the channel watched for thread-mode case
// creation. Required (and only meaningful) when CaseMode is thread.
SlackMonitorChannelID string
// AcceptBot opts the thread-mode monitored channel into
// treating bot-authored channel-root posts (e.g. an intake-form app's
// relayed request) as case-creation triggers. Default false: only human
// channel-root posts start a case, so the channel is not flooded with a case
// per bot notification. When true, every bot root post (bot_message /
// bot_id) starts a case.
AcceptBot bool
// CaseStatusSet is the configurable workflow status set that attaches to
// Cases in thread mode (the Kanban columns). Non-nil only for thread-mode
// workspaces; reuses the generic ActionStatusSet value type.
CaseStatusSet *ActionStatusSet
// ReactionEmoji is the Slack reaction (emoji name, without surrounding
// colons) that triggers case creation for this workspace. Empty when the
// reaction trigger is disabled. Only meaningful in thread mode, and unique
// across workspaces (enforced at config load).
ReactionEmoji string
// SlackWorkspaceChannelID is the workspace-level shared channel where the
// cross-case workspace agent runs (and future notifications flow). Channel
// mode only; empty when unset. Unique across workspaces / monitor channels
// (enforced at config load).
SlackWorkspaceChannelID string
// WorkspaceAgentPrompt is the operator-supplied custom instruction for the
// workspace agent (from [slack.workspace_agent] prompt/prompt_file). It is
// appended to the host-owned base system prompt and cannot relax it. Empty
// when unset.
WorkspaceAgentPrompt string
}
WorkspaceEntry holds workspace identity and its field schema
func (*WorkspaceEntry) IsThreadMode ¶
func (e *WorkspaceEntry) IsThreadMode() bool
IsThreadMode reports whether this workspace uses thread-per-case binding.
type WorkspaceGroup ¶
type WorkspaceGroup struct {
ID string
Name string
Description string
// MemberIDs are workspace IDs in declared order. May be empty. Existence of
// each ID in the workspace set is verified at config load, not here.
MemberIDs []string
}
WorkspaceGroup is a deployment-wide, organizational grouping of workspaces. It is an orthogonal concept: membership never changes a workspace's own behavior, and a deployment may define zero groups. It is loaded from the global config (see pkg/cli/config) and held read-only in memory, exactly like WorkspaceEntry.
func (*WorkspaceGroup) Validate ¶
func (g *WorkspaceGroup) Validate() error
Validate enforces the group's core identity invariant: a non-empty ID.
type WorkspaceGroupRegistry ¶
type WorkspaceGroupRegistry struct {
// contains filtered or unexported fields
}
WorkspaceGroupRegistry holds workspace group configurations. Like WorkspaceRegistry it carries settings only (no Repository / UseCase).
func NewWorkspaceGroupRegistry ¶
func NewWorkspaceGroupRegistry() *WorkspaceGroupRegistry
NewWorkspaceGroupRegistry creates a new empty WorkspaceGroupRegistry.
func (*WorkspaceGroupRegistry) Get ¶
func (r *WorkspaceGroupRegistry) Get(id string) (*WorkspaceGroup, error)
Get retrieves a group by ID.
func (*WorkspaceGroupRegistry) List ¶
func (r *WorkspaceGroupRegistry) List() []*WorkspaceGroup
List returns all registered groups in registration order. It never returns nil, so callers (and the GraphQL non-null list contract) can range freely.
func (*WorkspaceGroupRegistry) Register ¶
func (r *WorkspaceGroupRegistry) Register(g *WorkspaceGroup)
Register adds a group to the registry. A repeated ID overwrites the existing entry while preserving its position in the registration order.
type WorkspaceMaterialization ¶
type WorkspaceMaterialization struct {
GeneratedAt time.Time
Title string
Description string
CustomFieldValues map[string]FieldValue // key = FieldID defined in workspace's FieldSchema
// IsTest carries the agent's test-case suggestion through the preview so
// the human can confirm or override it before the case is created.
IsTest bool
}
WorkspaceMaterialization is the AI-generated Case payload for a specific workspace's FieldSchema. Generated lazily and overwritten on workspace switch.
func (*WorkspaceMaterialization) FilterToValid ¶
func (m *WorkspaceMaterialization) FilterToValid(schema *config.FieldSchema) (*WorkspaceMaterialization, error)
FilterToValid returns a copy of the materialization with all fields that failed validation stripped out (so the human can fill them via Edit). The returned bool indicates whether the *Title* survived; callers should regenerate the materialization when title is missing.
func (*WorkspaceMaterialization) Validate ¶
func (m *WorkspaceMaterialization) Validate(schema *config.FieldSchema) (issues []MaterializationValidationIssue, fatal bool)
Validate checks the materialization against the workspace's FieldSchema. It does NOT mutate the receiver — it only inspects.
Returned issues describe per-field / per-attribute problems; the boolean `fatal` is true iff at least one issue is marked fatal (currently: missing required Title/Description, or a fundamentally unusable structure).
Callers typically:
- reject (regenerate) on fatal == true
- drop or surface non-fatal field issues so the human fills them via Edit
type WorkspaceRegistry ¶
type WorkspaceRegistry struct {
// contains filtered or unexported fields
}
WorkspaceRegistry holds workspace configurations. It does not hold Repository or UseCase instances (settings only).
func NewWorkspaceRegistry ¶
func NewWorkspaceRegistry() *WorkspaceRegistry
NewWorkspaceRegistry creates a new empty WorkspaceRegistry
func (*WorkspaceRegistry) FindByMonitorChannel ¶
func (r *WorkspaceRegistry) FindByMonitorChannel(channelID string) (*WorkspaceEntry, bool)
FindByMonitorChannel returns the thread-mode workspace entry whose monitored channel matches channelID. It only considers thread-mode workspaces; the boolean is false when no thread-mode workspace watches the channel.
func (*WorkspaceRegistry) FindByReactionEmoji ¶
func (r *WorkspaceRegistry) FindByReactionEmoji(emoji string) (*WorkspaceEntry, bool)
FindByReactionEmoji returns the thread-mode workspace entry whose reaction emoji matches. It only considers thread-mode workspaces; the boolean is false when no thread-mode workspace uses the emoji. emoji must already be normalized (no surrounding colons); an empty emoji never matches.
func (*WorkspaceRegistry) FindByWorkspaceChannel ¶
func (r *WorkspaceRegistry) FindByWorkspaceChannel(channelID string) (*WorkspaceEntry, bool)
FindByWorkspaceChannel returns the channel-mode workspace entry whose workspace channel matches channelID. Thread-mode workspaces are never matched (the workspace channel is a channel-mode feature). The boolean is false when no channel-mode workspace uses the channel.
func (*WorkspaceRegistry) Get ¶
func (r *WorkspaceRegistry) Get(workspaceID string) (*WorkspaceEntry, error)
Get retrieves a workspace entry by ID
func (*WorkspaceRegistry) List ¶
func (r *WorkspaceRegistry) List() []*WorkspaceEntry
List returns all registered workspace entries in registration order
func (*WorkspaceRegistry) Register ¶
func (r *WorkspaceRegistry) Register(entry *WorkspaceEntry)
Register adds a workspace entry to the registry
func (*WorkspaceRegistry) Workspaces ¶
func (r *WorkspaceRegistry) Workspaces() []Workspace
Workspaces returns all registered workspaces in registration order
Source Files
¶
- action.go
- action_event.go
- action_status.go
- action_step.go
- actor.go
- assist_log.go
- case.go
- case_mode.go
- case_proposal.go
- case_ref.go
- case_trigger.go
- dashboard.go
- embedding.go
- field_input.go
- field_value.go
- home_message.go
- import_session.go
- job.go
- job_run.go
- knowledge.go
- memo.go
- notification_slot.go
- session.go
- slack_user.go
- source.go
- tag.go
- user_preference.go
- validator.go
- validator_errors.go
- workspace.go
- workspace_group.go
Directories
¶
| Path | Synopsis |
|---|---|
|
Package authz holds the Rego input/output document shapes used to authenticate and authorize MCP requests, plus the context plumbing that carries per-request HTTP metadata from the controller middleware down to the point where a tool call is evaluated against the policy.
|
Package authz holds the Rego input/output document shapes used to authenticate and authorize MCP requests, plus the context plumbing that carries per-request HTTP metadata from the controller middleware down to the point where a tool call is evaluated against the policy. |