agents

package
v0.18.0 Latest Latest
Warning

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

Go to latest
Published: Aug 17, 2026 License: MIT Imports: 15 Imported by: 0

Documentation

Overview

Package agents is the virtual-agents service layer (INIT-OMNIAGENT-005): a persisted agent = a persona/model bound to an enabled subset of the deployment's skills, its per-agent owner/maintainer roles, an authorization matrix (Can), and a private/listed + featured registry. An agent is the anchor that chats (INIT-003) and agent-scoped secrets (INIT-004) attach to; conversing with an agent never grants the right to configure it — that is a per-agent role, independent of chat membership.

The service is the primary authorization gate; PostgreSQL row-level security (team/store) is the defense-in-depth backstop. On the single-user SQLite path there are no policies, so the service-layer checks stand alone.

Index

Constants

View Source
const (
	MaxNameBytes        = 200
	MaxDescriptionBytes = 2000
	MaxPersonaBytes     = 32 << 10
)

MaxNameBytes / MaxDescriptionBytes / MaxPersonaBytes cap the free-text fields so a single agent row stays bounded.

Variables

View Source
var (
	// ErrUserNotFound is returned when a maintainer username resolves to no user.
	ErrUserNotFound = errors.New("user not found")
	// ErrLastOwner is returned when the sole owner tries to leave an agent that
	// still has other role holders (which would orphan its configuration).
	ErrLastOwner = errors.New("cannot leave: you are the agent's only owner")
	// ErrCannotRemoveOwner is returned when RemoveMaintainer targets an owner;
	// owners leave via LeaveAgent (or are reassigned by a superadmin).
	ErrCannotRemoveOwner = errors.New("cannot remove an owner")
)

Role-management sentinel errors (RMI-304).

View Source
var (
	// ErrForbidden is returned when the actor lacks the required per-agent
	// capability (e.g. a non-editor trying to configure an agent).
	ErrForbidden = errors.New("forbidden")
	// ErrNotFound is returned when the agent does not exist (or is invisible
	// to the actor — indistinguishable by design under RLS).
	ErrNotFound = errors.New("not found")
	// ErrInvalidSlug is returned for slugs outside the allowed form.
	ErrInvalidSlug = errors.New("invalid slug: use 3-32 characters of a-z, 0-9, '-' or '_', starting with a letter or digit")
	// ErrEmptyName is returned when an agent is created without a name.
	ErrEmptyName = errors.New("agent name is empty")
	// ErrSlugTaken is returned when the requested slug already exists.
	ErrSlugTaken = errors.New("agent slug is already taken")
)

Sentinel errors returned by the service layer.

View Source
var (
	// ErrUnknownSkill is returned when an enabled skill is not in the
	// deployment's available-skills catalog.
	ErrUnknownSkill = errors.New("unknown skill: not in the deployment's available-skills catalog")
	// ErrBlockedSkill is returned when an enabled skill is on the operator
	// deny-list (agents.blocked_skills) and can never be enabled.
	ErrBlockedSkill = errors.New("blocked skill: withheld by the operator deny-list")
)

Skill-related sentinel errors (RMI-302).

View Source
var ErrInvalidVisibility = errors.New(`invalid visibility: use "private" or "listed"`)

ErrInvalidVisibility is returned for a visibility value other than "private" or "listed".

Functions

This section is empty.

Types

type Actor

type Actor struct {
	UserID     uuid.UUID
	Superadmin bool
}

Actor identifies the authenticated caller. Superadmin flows into the store so the owner/superadmin administration rules resolve correctly under RLS, and into the service's own capability checks.

type Capability

type Capability int

Capability enumerates the per-agent authorization checks (INIT-005 TRD §3). Can(actor, agent, capability) is the single service-layer gate the HTTP and chat layers consult; RLS is the defense-in-depth backstop.

const (
	// CapChat is the right to converse with the agent (in a chat the actor
	// belongs to). Governed here at the agent level; per-chat membership is
	// enforced separately by the chats service.
	CapChat Capability = iota
	// CapCreateChat is the right to start a new chat with the agent.
	CapCreateChat
	// CapConfigure is the right to change the agent's skills, persona, model,
	// and secrets.
	CapConfigure
	// CapManageMaintainers is the right to add/remove maintainers (owner only).
	CapManageMaintainers
	// CapManageRegistry is the right to change the agent's visibility
	// (private/listed). Featured is superadmin-only curation, checked
	// separately in SetFeatured — not covered by this capability.
	CapManageRegistry
	// CapAdminister is superadmin deployment administration.
	CapAdminister
)

func (Capability) String

func (c Capability) String() string

String renders a Capability for logging.

type Catalog

type Catalog struct {
	Featured []CatalogEntry `json:"featured"`
	Listed   []CatalogEntry `json:"listed"`
}

Catalog is the discovery view: superadmin-curated Featured agents and owner-opted Listed agents, each scoped to what the caller may see.

type CatalogEntry

type CatalogEntry struct {
	ID          uuid.UUID `json:"id"`
	Slug        string    `json:"slug"`
	Name        string    `json:"name"`
	Description string    `json:"description"`
	Visibility  string    `json:"visibility"`
	Featured    bool      `json:"featured"`
	// CanStart reports whether this caller may start a chat with the agent
	// (Can(CapCreateChat)).
	CanStart bool `json:"canStart"`
}

CatalogEntry is one discoverable agent as seen by a particular caller.

type Config

type Config struct {
	// Logger defaults to slog.Default().
	Logger *slog.Logger

	// AvailableSkills is the deployment's registered skill catalog (the
	// compiled/MCP/markdown skill names). SetAgentSkills accepts only skills
	// in this catalog. Empty means the deployment registered none, so no
	// skill may be enabled (RMI-302, TRD §5).
	AvailableSkills []string

	// BlockedSkills is the operator deny-list (config agents.blocked_skills):
	// skills removed from the catalog that can never be enabled, bounding the
	// blast radius of user-created agents (e.g. a shell/exec skill).
	BlockedSkills []string
}

Config configures the agents service.

type CreateSpec

type CreateSpec struct {
	Slug        string
	Name        string
	Description string
	Persona     string
	Model       string
	Provider    string
}

CreateSpec is the input to CreateAgent. Slug and Name are required; the rest are optional descriptive/runtime fields. Visibility starts private and featured starts false — both are changed later via the registry methods.

type RoleView

type RoleView struct {
	UserID    uuid.UUID `json:"userId"`
	Username  string    `json:"username"`
	Role      string    `json:"role"`
	CreatedAt time.Time `json:"createdAt"`
}

RoleView pairs a per-agent role with the holder's username, for display. Usernames are resolved via system context because RLS hides other users' rows from a non-superadmin; among an agent's editors, revealing co-editors' usernames is expected.

type RuntimeConfig

type RuntimeConfig struct {
	ID       uuid.UUID
	Slug     string
	Name     string
	Persona  string
	Model    string
	Provider string
	Skills   []string
}

RuntimeConfig is an agent's resolved runtime configuration — the persona, model/provider, and enabled-skill subset a per-agent runtime instance (RMI-OMNIAGENT-309) is built from. It carries no secrets; secret binding is RMI-OMNIAGENT-310.

type Service

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

Service exposes agent operations over the team store.

func NewService

func NewService(st *store.Store, cfg Config) (*Service, error)

NewService creates the agents service. The available-skills catalog is resolved once here (available minus blocked); an empty catalog is valid and simply means no skill may be enabled.

func (*Service) AddMaintainer

func (s *Service) AddMaintainer(ctx context.Context, actor Actor, agentID uuid.UUID, username string) (*ent.AgentRole, error)

AddMaintainer grants the maintainer role to a user (by username) on an agent. Owner or superadmin only (CapManageMaintainers). Idempotent: if the user already holds a role on the agent, that role is returned unchanged (an existing owner is never demoted to maintainer).

func (*Service) AgentSkills

func (s *Service) AgentSkills(ctx context.Context, actor Actor, agentID uuid.UUID) ([]string, error)

AgentSkills returns an agent's enabled skills, sorted. Readable by anyone who can see the agent (editor, listed, or superadmin — enforced by RLS); returns ErrNotFound when the agent is absent or invisible to the actor.

func (*Service) AgentSlugByID

func (s *Service) AgentSlugByID(ctx context.Context, agentID uuid.UUID) (string, error)

AgentSlugByID returns just an agent's slug, in system context — the cheap read a group turn makes for @-mention matching, without loading the full runtime configuration. Returns ErrNotFound when the agent does not exist.

func (*Service) AuthorizeStartChat

func (s *Service) AuthorizeStartChat(ctx context.Context, userID uuid.UUID, superadmin bool, agentID uuid.UUID) (bool, error)

AuthorizeStartChat is the primitive-signature adapter that satisfies the chats package's AgentGate interface, so the chats service can gate agent-bound chat creation on Can(CapCreateChat) without importing the agents package (the composition root wires *agents.Service in as the gate).

func (*Service) AvailableSkills

func (s *Service) AvailableSkills() []string

AvailableSkills returns the deployment's available-skills catalog (registered skills minus the operator deny-list), sorted by canonical name. This is the set an agent's enabled skills may be drawn from.

func (*Service) Can

func (s *Service) Can(ctx context.Context, actor Actor, agentID uuid.UUID, capability Capability) (bool, error)

Can reports whether the actor holds a capability on an agent, per the TRD §3 matrix:

  • owner → all of Configure, ManageMaintainers, ManageRegistry, CreateChat, Chat.
  • maintainer → Configure, ManageRegistry, CreateChat, Chat — not ManageMaintainers.
  • superadmin → Administer, plus everything except an agent's secret values (secret reads are an INIT-004 concern, not modeled here).
  • any user → CreateChat/Chat iff the agent's visibility is "listed" (private agents are startable only by editors/superadmin; an invitee converses via chat membership, enforced by the chats service).

Can returns false (not an error) for a non-existent or invisible agent, so callers may treat "cannot" and "not found" uniformly where appropriate. The underlying facts are read via system context so the decision itself is not filtered by RLS.

func (*Service) CanStartChat

func (s *Service) CanStartChat(ctx context.Context, actor Actor, agentID uuid.UUID) (bool, error)

CanStartChat reports whether the actor may start a chat with the agent — the start-chat authorization the chats service consults (RMI-308). It is Can(CapCreateChat): listed → any allowlisted user; private → owner, maintainer, or superadmin.

func (*Service) Catalog

func (s *Service) Catalog(ctx context.Context, actor Actor) (*Catalog, error)

Catalog returns the agents discoverable by the actor, in two sections (TRD §4): Featured (superadmin curation) and Listed (owner-opted visibility=listed, excluding those already in Featured). Both are computed in the actor's scope, so RLS filters out agents the caller cannot see — a featured-but-private agent surfaces only to its editors/superadmin, never leaking a private agent to the whole deployment.

func (*Service) CreateAgent

func (s *Service) CreateAgent(ctx context.Context, actor Actor, spec CreateSpec) (*ent.Agent, error)

CreateAgent creates an agent and inserts the creator's owner role in the same transaction (the two-step bootstrap the RLS insert policy authorizes via team_is_agent_creator). Permissive: any allowlisted user may create an agent and becomes its owner. Slugs are globally unique.

func (*Service) DeleteAgent

func (s *Service) DeleteAgent(ctx context.Context, actor Actor, agentID uuid.UUID) error

DeleteAgent removes an agent (and, by cascade, its skills and roles). Requires the actor to be an owner or superadmin.

func (*Service) GetAgent

func (s *Service) GetAgent(ctx context.Context, actor Actor, agentID uuid.UUID) (*ent.Agent, error)

GetAgent returns an agent the actor may see (editor, listed, or superadmin — enforced by RLS on postgres). Returns ErrNotFound when it does not exist or is invisible to the actor.

func (*Service) LeaveAgent

func (s *Service) LeaveAgent(ctx context.Context, actor Actor, agentID uuid.UUID) error

LeaveAgent removes the actor's own role from an agent (self-leave). The sole owner cannot leave while other role holders remain (it would orphan the agent's configuration); a superadmin reassigns ownership in that case (TRD §9). An agent with no remaining roles is permitted (orphaned, superadmin- reassignable) — matching the chat sole-owner semantics.

func (*Service) ListMyAgents

func (s *Service) ListMyAgents(ctx context.Context, actor Actor) ([]*ent.Agent, error)

ListMyAgents returns the agents the actor owns or maintains, newest first.

func (*Service) LoadRuntimeConfig

func (s *Service) LoadRuntimeConfig(ctx context.Context, agentID uuid.UUID) (RuntimeConfig, error)

LoadRuntimeConfig loads an agent's runtime configuration by ID in system context. The per-agent runtime is a system principal, not a user, so this deliberately bypasses RLS visibility (a private agent still runs its own chats' turns). Returns ErrNotFound when the agent does not exist.

func (*Service) RemoveMaintainer

func (s *Service) RemoveMaintainer(ctx context.Context, actor Actor, agentID, userID uuid.UUID) error

RemoveMaintainer removes another user's role from an agent. Owner or superadmin only. Owners cannot be removed this way (ErrCannotRemoveOwner); use LeaveAgent to remove yourself.

func (*Service) Roles

func (s *Service) Roles(ctx context.Context, actor Actor, agentID uuid.UUID) ([]RoleView, error)

Roles lists an agent's role holders (owners and maintainers) with usernames, oldest first. The actor must be an editor or superadmin.

func (*Service) SetAgentSkills

func (s *Service) SetAgentSkills(ctx context.Context, actor Actor, agentID uuid.UUID, skills []string) error

SetAgentSkills replaces an agent's enabled-skill set. Every skill must be in the available-skills catalog (AvailableSkills); an unknown skill yields ErrUnknownSkill and a deny-listed one ErrBlockedSkill, so nothing is persisted unless the whole set validates. Requires the actor to be an editor (CapConfigure). Skills are matched case-insensitively (citext) and stored in their canonical catalog casing; duplicates are collapsed.

func (*Service) SetFeatured

func (s *Service) SetFeatured(ctx context.Context, actor Actor, agentID uuid.UUID, featured bool) (*ent.Agent, error)

SetFeatured toggles the featured flag. Superadmin-only curation (TRD §9 Q1): owners/maintainers control visibility, never featured. A non-superadmin gets ErrForbidden; a missing agent gets ErrNotFound.

func (*Service) SetVisibility

func (s *Service) SetVisibility(ctx context.Context, actor Actor, agentID uuid.UUID, visibility string) (*ent.Agent, error)

SetVisibility changes an agent's discoverability (private/listed). This is CapManageRegistry — an owner or maintainer (or superadmin) may set it. It is distinct from featured, which is superadmin-only curation.

func (*Service) UpdateAgent

func (s *Service) UpdateAgent(ctx context.Context, actor Actor, agentID uuid.UUID, spec UpdateSpec) (*ent.Agent, error)

UpdateAgent applies an UpdateSpec. Requires the actor to be an editor (owner/maintainer) or superadmin (CapConfigure).

type UpdateSpec

type UpdateSpec struct {
	Name        *string
	Description *string
	Persona     *string
	Model       *string
	Provider    *string
}

UpdateSpec patches an agent's descriptive/runtime fields. A nil pointer leaves the field unchanged. Visibility and featured are not here — they are registry operations (SetVisibility / SetFeatured) with their own authz.

Jump to

Keyboard shortcuts

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