persona

package
v1.0.0-beta.10 Latest Latest
Warning

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

Go to latest
Published: Apr 22, 2026 License: MIT Imports: 13 Imported by: 0

Documentation

Overview

Package persona provides storage and CRUD for agent-role prompt fragments.

A Persona is the stored form of a role's prompt fragment — the static bits a human or LLM authors and a product might persist per-flow. Runtime-only aspects of prompt composition (dynamic ContentFunc, Condition predicates) stay in processor/agentic-loop/prompt.Fragment; Persona round-trips cleanly through JSON and KV.

Per ADR-029, Persona sits in Pattern B (KV-backed CRUD Manager). See docs/adr/029-instance-type-patterns.md for the framework/product split: semstreams ships the primitive, products (semteams, semspec, soul.md-style BMAD adopters) compose their own persona registries.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func LoadFromDirectory

func LoadFromDirectory(ctx context.Context, root string, mgr *Manager, logger *slog.Logger) error

LoadFromDirectory walks <root>/<role>/*.md and upserts each file into the persona manager as a fragment. Fragment ID is derived from the filename stem (e.g. "00-identity.md" -> "00-identity"); role is derived from the immediate parent directory name.

Source-of-truth semantics: files in this directory are the durable source of truth. Every call overwrites any KV entry whose fragment ID matches a file here — including entries written at runtime by tools such as update_persona. Runtime tool edits are ephemeral and reset to file state on the next restart. Call LoadFromDirectory at startup, after building the manager, before starting the KV watch.

Missing directories and read errors are logged as warnings but do not fail boot. Startup-only; no watch — edits require restart.

Types

type Manager

type Manager struct {
	// contains filtered or unexported fields
}

Manager provides CRUD against the PERSONAS KV bucket. Pattern-B per ADR-029. Not safe for concurrent use from a single caller only in the narrow sense that the underlying KVStore handles concurrency — multiple goroutines sharing a Manager instance is fine.

func NewManager

func NewManager(natsClient *natsclient.Client) (*Manager, error)

NewManager opens (or creates) the PERSONAS bucket and returns a Manager for it. Returning an error on bucket-open failure lets main decide whether to skip registration or fail fast — mirrors how flowstore.NewManager behaves.

func (*Manager) Create

func (m *Manager) Create(ctx context.Context, p *Persona) error

Create stores a new persona. Fails if ID already exists so callers must use Update for edits — matches the flowstore.Manager pattern and gives Pattern-B CRUD its optimistic-create semantics.

func (*Manager) Delete

func (m *Manager) Delete(ctx context.Context, id string) error

Delete removes a persona by ID. Missing ID returns the underlying KV error so callers can distinguish "not found" from "transport failure" if they care.

func (*Manager) Fragments

func (m *Manager) Fragments(ctx context.Context) ([]prompt.Fragment, error)

Fragments materialises every stored persona as prompt.Fragments so a caller can UpsertAll them onto a prompt.Registry seeded with DefaultFragments. Safe to call on a nil Manager — returns (nil, nil) so products can wire persona support conditionally without guarding every call site.

Callers choose the cadence: once at startup for static personas, or again on KV-watch events if hot-reload of persona edits is required.

func (*Manager) Get

func (m *Manager) Get(ctx context.Context, id string) (*Persona, error)

Get retrieves a persona by ID.

func (*Manager) List

func (m *Manager) List(ctx context.Context) (map[string]*Persona, error)

List returns every persona in the bucket. Small registries expected (dozens, not thousands); no pagination added. If a product's registry grows large, pagination lands alongside the use case.

func (*Manager) Update

func (m *Manager) Update(ctx context.Context, p *Persona) error

Update overwrites an existing persona. Unlike flowstore.Manager.Update there's no version-based optimistic concurrency here — personas are edited less often and rarely concurrently; last-writer-wins is fine. If concurrent edit becomes a real concern we add a Version field to Persona and a CAS check.

func (*Manager) Upsert

func (m *Manager) Upsert(ctx context.Context, p *Persona) error

Upsert creates or updates a persona. It attempts Create first; if and only if Create fails because the key already exists (ErrKVKeyExists), it falls through to Update. Any other Create error — transient NATS failure, invalid input, etc. — is returned directly so callers see the real cause rather than a misleading Update error.

Update failure after a conflict-detected Create is unlikely (would require a concurrent Delete between the two calls) but propagated as a transient error if it occurs.

type Persona

type Persona struct {
	// ID is the unique key within the PERSONAS bucket. Kebab-case
	// convention aligns with rule IDs (e.g. "role-researcher",
	// "domain-finance").
	ID string `json:"id"`

	// Category maps to prompt.Category values (system=0, role=100,
	// tools=200, domain=300, constraints=400, context=500). Stored as
	// int so persona records don't leak the prompt package's enum type
	// into the Pattern-B storage surface.
	Category int `json:"category"`

	// Priority orders fragments inside a Category (lower = earlier).
	Priority int `json:"priority,omitempty"`

	// Content is the static prompt text.
	Content string `json:"content"`

	// Roles restricts this persona to specific agent roles. An empty
	// list means all roles — same semantics as prompt.Fragment.Roles.
	Roles []string `json:"roles,omitempty"`

	// Description is optional free-text context for operators who manage
	// personas via the CRUD tools — not included in the assembled prompt.
	Description string `json:"description,omitempty"`
}

Persona is a serialisable prompt-fragment definition.

Only static content is stored; any dynamic generation (ContentFunc) or conditional inclusion (Condition) remains a code-level concern on prompt.Fragment. When the assembler consumes a Persona it converts to a prompt.Fragment with those runtime hooks nil — documented in the assembler integration (ADR-029 step 3b).

func (*Persona) ToFragment

func (p *Persona) ToFragment() prompt.Fragment

ToFragment converts a stored Persona into a prompt.Fragment.

The dynamic hooks on prompt.Fragment — ContentFunc and Condition — stay nil: those are Go closures that can't round-trip through JSON. A stored Persona can only contribute static content and role-based gating; richer runtime behaviour remains a code-level concern on DefaultFragments. See ADR-029 step 3b for the framework/product split.

func (*Persona) Validate

func (p *Persona) Validate() error

Validate checks the minimum invariants every stored persona must satisfy. Called by Manager.Create / Manager.Update before writing to KV so the bucket never holds obviously-broken records.

Jump to

Keyboard shortcuts

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