Documentation
¶
Overview ¶
Package prompt provides prompt management for the MCP data platform. It defines the Store interface for prompt persistence and the Prompt type that represents a user-managed MCP prompt.
Index ¶
- Constants
- Variables
- func IndexText(p *Prompt) string
- func RequiresReview(before, after *Prompt) bool
- func SnapshotChanged(before, after *Prompt) bool
- func ValidateName(name string) error
- func ValidateScope(scope string) error
- func ValidateStatus(status string) error
- func ValidateStatusTransition(from, to string) error
- func ValidateTags(tags []string) error
- type Argument
- type EditOutcome
- type ListFilter
- type Prompt
- func (p *Prompt) ApplyPromotionRequest(requestedScope string, requestedPersonas []string) error
- func (p *Prompt) ApplyStatusTransition(newStatus, supersededBy, actorEmail string, isAdmin bool, now time.Time) error
- func (p *Prompt) ApprovePromotion(actorEmail string, now time.Time) error
- func (p *Prompt) RejectPromotion()
- type ScoredPrompt
- type SearchQuery
- type Searcher
- type Store
- type Usage
- type UsageReader
- type Version
- type VersionStore
Constants ¶
const ( ScopeGlobal = "global" ScopePersona = "persona" ScopePersonal = "personal" )
Scope constants define prompt visibility levels.
const ( SourceOperator = "operator" SourceAgent = "agent" SourceSystem = "system" )
Source constants define prompt origins.
const ( StatusDraft = "draft" StatusApproved = "approved" StatusDeprecated = "deprecated" StatusSuperseded = "superseded" )
Status constants define the prompt promotion lifecycle. A prompt starts as draft, becomes approved (on admin promotion to persona/global scope), may later be deprecated, and is finally superseded by a replacement.
const ( VersionStatusDraft = "draft" VersionStatusApplied = "applied" VersionStatusSuperseded = "superseded" VersionStatusRejected = "rejected" )
Version status constants. A version row is the immutable snapshot of a prompt's reviewable substance at one mutation:
- draft: a proposed edit to an approved shared prompt, awaiting admin approval; the live prompt row continues to serve the previously applied snapshot.
- applied: the snapshot was applied to the live row (either directly, for edits that need no review, or via ApproveVersion). The version the live row currently serves is Prompt.Version; older applied rows are history.
- superseded: a draft that was still pending when a different draft was approved; kept for history, never applicable.
- rejected: a draft an admin explicitly rejected.
const (
DefaultSearchLimit = 20
)
Search result limits. DefaultSearchLimit is the top-K returned when the caller does not specify one; maxSearchLimit bounds an explicit request so a single ranked query cannot ask for an unbounded result set.
Variables ¶
var ErrReviewRequiredMixedEdit = errors.New(
"content changes to an approved shared prompt require review and cannot be combined with " +
"scope, status, or other non-versioned changes; submit them as separate updates")
ErrReviewRequiredMixedEdit rejects an edit that combines a review-gated substance change (content or arguments of an approved shared prompt) with changes a draft version cannot carry (scope, personas, status, name, category, owner, enabled, promotion request). The two must be submitted separately so the deferred draft is exactly the reviewable snapshot.
var ErrVersionConflict = errors.New("prompt version conflict")
ErrVersionConflict marks a version write rejected because the prompt or version state changed underneath the caller (a draft already resolved, a prompt retired, an edit racing an approval). REST handlers map it to 409; any other store error is an internal failure.
Functions ¶
func IndexText ¶ added in v1.81.0
IndexText composes the text a prompt is embedded and lexically indexed on for semantic discovery: its title (display name, falling back to the name), description, body, and tags. The indexjobs prompts consumer and the request-path search MUST agree on this composition so a stored embedding lives in the same space as the query; it is defined once here for both. Empty fields are skipped so a sparse prompt does not pad the text with blank lines.
func RequiresReview ¶ added in v1.108.0
RequiresReview reports whether an edit must go through admin review before being served: a change to the prompt's substance (content or arguments) when the pre-edit prompt is an approved shared (global or persona) prompt. Personal prompts version silently (the owner is the only consumer), a prompt that was never approved has no approved snapshot to protect, and system rows are read-only config mirrors that are never versioned or reviewed (their content is owned by server configuration and re-ingested at startup).
func SnapshotChanged ¶ added in v1.108.0
SnapshotChanged reports whether any versioned snapshot field (content, display name, description, arguments, tags) differs between the two states.
func ValidateName ¶
ValidateName checks that a prompt name is well-formed.
func ValidateScope ¶
ValidateScope checks that a scope value is allowed for database-stored prompts.
func ValidateStatus ¶ added in v1.81.0
ValidateStatus checks that a status value is recognized.
func ValidateStatusTransition ¶ added in v1.81.0
ValidateStatusTransition checks whether a status transition is allowed.
func ValidateTags ¶ added in v1.81.0
ValidateTags checks that a prompt's tag list is within bounds.
Types ¶
type Argument ¶
type Argument struct {
Name string `json:"name" example:"date"`
Description string `json:"description" example:"The date to analyze (YYYY-MM-DD)"`
Required bool `json:"required" example:"true"`
}
Argument describes a prompt argument.
type EditOutcome ¶ added in v1.108.0
type EditOutcome struct {
// Applied is true when the live row was updated (the edit is being served).
Applied bool `json:"applied"`
// PendingVersion is the draft version number when the edit was deferred
// for review; zero when Applied.
PendingVersion int `json:"pending_version,omitempty"`
}
EditOutcome reports how ApplyEdit landed an edit: applied to the live prompt row, or deferred as a pending draft version awaiting admin approval.
func ApplyEdit ¶ added in v1.108.0
func ApplyEdit(ctx context.Context, store Store, before, after *Prompt, author string) (EditOutcome, error)
ApplyEdit lands a prompt edit through the one shared review gate used by every mutation surface (manage_prompt tool, admin REST, portal REST). before must be the persisted pre-edit state and after the fully mutated copy; author is the actor recorded on any version produced.
A review-gated edit (RequiresReview) becomes a pending draft version and leaves the live row untouched, so other users keep being served the approved snapshot until an admin approves the draft. Every other edit is applied via UpdateWithVersion, which snapshots a new applied version when a versioned field changed. The versioning capability is asserted from the store itself; a store without it (no-DB deployments, plain test stores) degrades to a plain unversioned update.
type ListFilter ¶
type ListFilter struct {
Scope string // "global", "persona", "personal", or "" for all
Personas []string // filter by persona membership (OR match)
OwnerEmail string // filter by owner
Enabled *bool // filter by enabled state
Search string // free-text search on name, display_name, description
ReviewRequested *bool // filter by pending promotion request (admin queue)
Source string // include only this origin (operator, agent, system); "" for all
ExcludeSource string // exclude this origin (e.g. "system" to hide ingested static prompts)
}
ListFilter controls which prompts are returned by List.
type Prompt ¶
type Prompt struct {
ID string `json:"id" example:"prompt_a1b2c3d4"`
Name string `json:"name" example:"daily-sales-report"`
DisplayName string `json:"display_name" example:"Daily Sales Report"`
Description string `json:"description" example:"Generate a daily sales summary by region"`
Content string `json:"content" example:"Analyze sales data for {date} grouped by region."`
Arguments []Argument `json:"arguments"`
Category string `json:"category" example:"analysis"`
Scope string `json:"scope" example:"persona"`
Personas []string `json:"personas" example:"analyst,data-engineer"`
OwnerEmail string `json:"owner_email" example:"admin@example.com"`
Source string `json:"source" example:"database"`
Enabled bool `json:"enabled" example:"true"`
Tags []string `json:"tags" example:"sales,reporting"`
// Promotion lifecycle.
Status string `json:"status" example:"approved"`
ApprovedBy string `json:"approved_by,omitempty" example:"admin@example.com"`
ApprovedAt *time.Time `json:"approved_at,omitempty"`
DeprecatedAt *time.Time `json:"deprecated_at,omitempty"`
SupersededBy string `json:"superseded_by,omitempty" example:"daily-sales-report-v2"`
// Version is the number of the snapshot the live row currently serves
// (see VersionStore). Pending draft versions above this number exist in
// the version history but are not served until approved.
Version int `json:"version" example:"4"`
// Serve-derived usage (see UsageReader). Populated only on read surfaces
// wired to a usage reader (manage_prompt get, the prompt usage REST
// endpoints); omitted elsewhere. Zero/absent means never served within the
// audit retention window.
RunCount int64 `json:"run_count,omitempty" example:"37"`
LastRunAt *time.Time `json:"last_run_at,omitempty"`
// Promotion request: an owner asks to move a personal prompt into a shared
// scope. ReviewRequested marks the prompt as pending in the admin queue;
// RequestedScope/RequestedPersonas record the target. Cleared on approve or
// reject (see ApplyPromotionRequest / ApprovePromotion).
ReviewRequested bool `json:"review_requested" example:"false"`
RequestedScope string `json:"requested_scope,omitempty" example:"persona"`
RequestedPersonas []string `json:"requested_personas,omitempty" example:"analyst"`
CreatedAt time.Time `json:"created_at" example:"2026-01-15T14:30:00Z"`
UpdatedAt time.Time `json:"updated_at" example:"2026-01-15T14:30:00Z"`
}
Prompt represents a user-managed MCP prompt.
func (*Prompt) ApplyPromotionRequest ¶ added in v1.81.0
ApplyPromotionRequest records an owner's request to promote a personal prompt into a shared scope. The prompt must be personal; the target must be persona (with at least one persona) or global. It only sets the request signal; the scope does not change until an admin approves (see ApprovePromotion).
func (*Prompt) ApplyStatusTransition ¶ added in v1.81.0
func (p *Prompt) ApplyStatusTransition(newStatus, supersededBy, actorEmail string, isAdmin bool, now time.Time) error
ApplyStatusTransition validates and applies a status change to the prompt, stamping the lifecycle metadata. A no-op when newStatus is empty or unchanged. Approval (-> approved) requires isAdmin; supersededBy is recorded when moving to superseded. now is passed in for testability. Returns an error on an invalid or unauthorized transition. Shared by the manage_prompt tool and the admin API so both enforce the lifecycle identically.
func (*Prompt) ApprovePromotion ¶ added in v1.81.0
ApprovePromotion applies a pending promotion request: it moves the prompt to the requested scope/personas, marks it approved (stamping the admin), and clears the request signal. Returns an error if there is no pending request or the prompt is no longer personal (its scope changed out from under the request). The caller is responsible for checking the target shared name is free.
func (*Prompt) RejectPromotion ¶ added in v1.81.0
func (p *Prompt) RejectPromotion()
RejectPromotion clears a pending promotion request, leaving the prompt personal and otherwise unchanged.
type ScoredPrompt ¶ added in v1.81.0
ScoredPrompt pairs a prompt with its relevance score in [0,1].
type SearchQuery ¶ added in v1.81.0
type SearchQuery struct {
Embedding []float32 // query vector; nil selects lexical-only ranking
QueryText string // raw query text for the lexical arm
OwnerEmail string // caller identity, for personal-scope visibility
Persona string // caller persona, for persona-scope visibility
IsAdmin bool // admin callers rank across all approved prompts
Scope string // optional explicit scope filter ("" = all visible)
Limit int // max results; see EffectiveLimit
}
SearchQuery describes a relevance ranking request over the prompt library. Visibility is applied before ranking (you cannot rank a prompt you cannot read): a non-admin caller sees global prompts, persona prompts matching Persona, and their own personal prompts; an admin sees every approved prompt. Only approved, enabled prompts are ever ranked. A nil Embedding selects lexical-only ranking (the graceful-degradation path when no embedding provider is configured); a non-nil Embedding selects hybrid ranking.
func (SearchQuery) EffectiveLimit ¶ added in v1.81.0
func (q SearchQuery) EffectiveLimit() int
EffectiveLimit clamps the requested limit into [1, maxSearchLimit], defaulting an unset or out-of-range value to DefaultSearchLimit.
type Searcher ¶ added in v1.81.0
type Searcher interface {
Search(ctx context.Context, q SearchQuery) ([]ScoredPrompt, error)
}
Searcher ranks approved prompts by relevance to a query within the caller's visibility. It is a capability separate from Store: only a backing store that can rank (the PostgreSQL store with pgvector) implements it, so the feature degrades to absent rather than forcing every Store implementation to carry a ranking query.
type Store ¶
type Store interface {
// Create persists a new prompt.
Create(ctx context.Context, p *Prompt) error
// Get retrieves a non-personal (global or persona) prompt by name, which is
// globally unique. Returns nil, nil if not found. Personal prompts are
// per-owner and must be fetched with GetPersonal.
Get(ctx context.Context, name string) (*Prompt, error)
// GetPersonal retrieves a personal prompt by its owner and name. Personal
// names are unique only within an owner, so the owner is required to
// disambiguate. Returns nil, nil if not found.
GetPersonal(ctx context.Context, ownerEmail, name string) (*Prompt, error)
// GetByID retrieves a prompt by ID. Returns nil, nil if not found.
GetByID(ctx context.Context, id string) (*Prompt, error)
// Update modifies an existing prompt.
Update(ctx context.Context, p *Prompt) error
// Delete removes a prompt by name.
Delete(ctx context.Context, name string) error
// DeleteByID removes a prompt by ID.
DeleteByID(ctx context.Context, id string) error
// List returns prompts matching the filter.
List(ctx context.Context, filter ListFilter) ([]Prompt, error)
// Count returns the number of prompts matching the filter.
Count(ctx context.Context, filter ListFilter) (int, error)
}
Store defines the interface for prompt persistence.
type Usage ¶ added in v1.108.0
type Usage struct {
RunCount int64 `json:"run_count" example:"37"`
LastRunAt *time.Time `json:"last_run_at,omitempty"`
}
Usage is the audit-derived usage rollup for one prompt: how many times it was served (prompts/get or manage_prompt use) within the audit retention window, and when it was last served. A prompt that was never served has no entry in a usage map.
type UsageReader ¶ added in v1.108.0
type UsageReader interface {
PromptUsage(ctx context.Context, promptIDs []string) (map[string]Usage, error)
}
UsageReader reports serve-derived usage for a set of prompt IDs. The audit store implements it by aggregating prompt_serve audit events; prompts absent from the returned map have never been served (within retention).
type Version ¶ added in v1.108.0
type Version struct {
ID string `json:"id" example:"ver_a1b2c3d4"`
PromptID string `json:"prompt_id" example:"prompt_a1b2c3d4"`
Version int `json:"version" example:"4"`
DisplayName string `json:"display_name" example:"Daily Sales Report"`
Description string `json:"description" example:"Generate a daily sales summary by region"`
Content string `json:"content" example:"Analyze sales data for {date} grouped by region."`
Arguments []Argument `json:"arguments"`
Tags []string `json:"tags" example:"sales,reporting"`
Author string `json:"author" example:"jane@example.com"`
Status string `json:"status" example:"applied"`
ApprovedBy string `json:"approved_by,omitempty" example:"admin@example.com"`
ApprovedAt *time.Time `json:"approved_at,omitempty"`
CreatedAt time.Time `json:"created_at" example:"2026-06-12T14:30:00Z"`
}
Version is one immutable snapshot of a prompt's versioned fields (content, display name, description, arguments, tags), with the author who produced it and the approval stamp bound to this specific version. Approval stamps on a version never change once set: approving v5 does not alter the recorded approval of v4.
type VersionStore ¶ added in v1.108.0
type VersionStore interface {
// UpdateWithVersion persists p like Store.Update and, when any versioned
// snapshot field (content, display name, description, arguments, tags)
// differs from the stored row, records a new applied version authored by
// author and advances p.Version to it.
UpdateWithVersion(ctx context.Context, p *Prompt, author string) error
// CreateDraftVersion snapshots proposed's versioned fields as a new draft
// version of the prompt without touching the live row, returning the new
// version number. The live row continues to be served unchanged until the
// draft is approved.
CreateDraftVersion(ctx context.Context, promptID string, proposed *Prompt, author string) (int, error)
// ListVersions returns every version of the prompt, newest first.
ListVersions(ctx context.Context, promptID string) ([]Version, error)
// GetVersion returns one version with its full content, or nil, nil when
// the prompt has no such version.
GetVersion(ctx context.Context, promptID string, version int) (*Version, error)
// ApproveVersion applies draft version's snapshot to the live prompt row,
// marks the prompt approved with approver's stamp, binds the same stamp to
// the version row (status applied), and supersedes any other pending
// drafts. Returns the updated prompt.
ApproveVersion(ctx context.Context, promptID string, version int, approver string) (*Prompt, error)
// RejectVersion marks a pending draft version rejected, leaving the live
// prompt row untouched.
RejectVersion(ctx context.Context, promptID string, version int) error
}
VersionStore is the optional versioning capability of a prompt store. The PostgreSQL store implements it; a store without it (some tests) degrades to unversioned updates via ApplyEdit's fallback. All write methods are transactional with the prompts row they touch.
Directories
¶
| Path | Synopsis |
|---|---|
|
Package postgres provides PostgreSQL storage for prompts.
|
Package postgres provides PostgreSQL storage for prompts. |
|
Package promptindex is the prompt-library consumer of the shared indexjobs framework (#557, epic #525 phase 4).
|
Package promptindex is the prompt-library consumer of the shared indexjobs framework (#557, epic #525 phase 4). |
|
Package versionhttp exposes prompt version history, draft review actions, and audit-derived usage stats over REST (#1009).
|
Package versionhttp exposes prompt version history, draft review actions, and audit-derived usage stats over REST (#1009). |