10 KiB
Agent Personas — Implementation Plan
Purpose
Composable agent settings system. An agent loads a named persona which assembles behavior from reusable parts. Parts can be overridden at load time without modifying the persona.
Schema: 5 New Tables
agent_personas
Named, loadable agent configurations.
| Column | Type | Notes |
|---|---|---|
| id | bigserial | PK |
| guid | uuid | DEFAULT gen_random_uuid() |
| name | text UNIQUE NOT NULL | load-by-name key |
| description | text | |
| summary | text NOT NULL | returned by default on load |
| detail | text | returned only when detail=true |
| tags | text[] | DEFAULT '{}' |
| created_at | timestamptz | |
| updated_at | timestamptz |
agent_parts
Reusable behavior building blocks. Name is globally unique for reference by name.
| Column | Type | Notes |
|---|---|---|
| id | bigserial | PK |
| guid | uuid | DEFAULT gen_random_uuid() |
| name | text UNIQUE NOT NULL | reference key for overrides |
| part_type | text NOT NULL | system|agent|soul|identity|skill|specialization|tone|goal|context|protocol |
| description | text | |
| summary | text NOT NULL | returned in summary view |
| content | text | returned in detail view |
| tags | text[] | DEFAULT '{}' |
| created_at | timestamptz | |
| updated_at | timestamptz |
agent_persona_parts
Junction — links reusable parts to a persona. part_order controls assembly sequence.
| Column | Type |
|---|---|
| persona_id | bigint FK → agent_personas.id |
| part_id | bigint FK → agent_parts.id |
| part_order | int DEFAULT 0 |
Composite PK: (persona_id, part_id)
agent_persona_skills
Links existing agent_skills to a persona.
| Column | Type |
|---|---|
| persona_id | bigint FK → agent_personas.id |
| skill_id | bigint FK → agent_skills.id |
Composite PK: (persona_id, skill_id)
agent_persona_guardrails
Links existing agent_guardrails to a persona.
| Column | Type |
|---|---|
| persona_id | bigint FK → agent_personas.id |
| guardrail_id | bigint FK → agent_guardrails.id |
Composite PK: (persona_id, guardrail_id)
Part Types
| Type | Purpose |
|---|---|
| system | Base rules and constraints |
| agent | Core behavior definition |
| soul | Fundamental character and values |
| identity | Name, role, backstory |
| skill | Specific capabilities (distinct from agent_skills) |
| specialization | Domain expertise |
| tone | Communication style, formality, voice |
| goal | Objectives and success criteria |
| context | Operational environment and available tools |
| protocol | Step-by-step workflows or procedures |
| backstory | Formative history and past experiences (what happened to them, distinct from identity which is who they are) |
| motivation | Deep inner drives and why the character acts (distinct from goal which is task objective) |
| voice | Speech patterns, vocabulary, mannerisms, catchphrases (distinct from tone which is formality level) |
| archetype | Narrative template — hero, mentor, trickster, etc. Composable base layer |
| flaw | Weaknesses, fears, internal conflicts |
| relationship | How the character relates to other agents, users, or entities |
Override Mechanism
get_agent_persona(name, detail=false, overrides={"tone":"part-name","goal":"part-name"})
- Overrides replace all linked parts of that
part_typewith the named part at query time - No DB write — runtime substitution only
- Works for any part type, not just tone/goal
- Parts resolved by unique
name
Response Shape
Summary (default): persona summary + each linked part's summary + skill/guardrail description
Detail (detail=true): persona detail + each part's full content + skill/guardrail full content + guardrail severity
Tools (16 total)
| Tool | Purpose |
|---|---|
create_agent_persona |
Create persona |
update_agent_persona |
Update persona fields |
delete_agent_persona |
Delete by name |
list_agent_personas |
List, filter by tags |
get_agent_persona |
Load by name; detail bool + overrides map |
create_agent_part |
Create reusable part |
update_agent_part |
Update part fields |
delete_agent_part |
Delete by name |
list_agent_parts |
Filter by part_type, tags |
get_agent_part |
Get single part by name |
add_persona_part |
Link part to persona |
remove_persona_part |
Unlink part from persona |
add_persona_skill |
Link existing agent_skill |
remove_persona_skill |
Unlink skill |
add_persona_guardrail |
Link existing guardrail |
remove_persona_guardrail |
Unlink guardrail |
Character Evolution
Characters can evolve through defined arcs. Stage parts override the persona's base parts for matching types.
Additional Tables
character_arcs — named progression definition
id,nameUNIQUE NOT NULL,description,summary,created_at,updated_at
arc_stages — ordered stages within an arc
id,arc_id → character_arcs.id,name,stage_order int,description,condition text(trigger description — evaluated externally by the calling agent),created_at
arc_stage_parts — parts active at a given stage, override matching types from persona base
stage_id → arc_stages.id,part_id → agent_parts.id— composite PK
persona_arc — links a persona to an arc and tracks current stage (one arc per persona)
persona_id → agent_personas.idUNIQUE,arc_id → character_arcs.id,current_stage_id → arc_stages.id,updated_at
Part Assembly Priority (highest wins per type)
- Runtime
overridespassed in the call - Active arc stage parts
- Persona's own linked parts
Additional Tools
| Tool | Purpose |
|---|---|
create_character_arc |
Define a new arc |
list_character_arcs |
List arcs |
add_arc_stage |
Add a stage to an arc |
add_stage_part |
Link a part to a stage |
remove_stage_part |
Unlink part from stage |
assign_persona_arc |
Attach an arc to a persona, set starting stage |
advance_persona_stage |
Move persona to next stage in its arc |
reset_persona_stage |
Reset to first stage |
Limited Context Support
For agents with tight context budgets: fewer round-trips, fewer tokens.
Schema Changes
agent_personas — add columns:
compiled_summary text— pre-merged part summaries + persona summary, ready to use directly in a system promptcompiled_detail text— pre-merged full part contentcompiled_at timestamptz— when last regenerated
agent_persona_parts — add column:
priority int DEFAULT 0— higher priority parts load first when trimming
get_agent_persona Additional Parameters
| Param | Purpose |
|---|---|
types []string |
Return only parts of specified types e.g. ["soul","goal","tone"] |
limit int |
Return only top-N parts by priority |
Additional Tool
| Tool | Purpose |
|---|---|
compile_persona |
Regenerate compiled_summary and compiled_detail from current parts + active arc stage |
Usage Pattern
Agents with limited context call get_agent_persona(name) and use compiled_summary directly — one fetch, no assembly, minimal tokens. Call compile_persona after any part or arc stage change.
On-Demand Loading & Traits
Core philosophy: load nothing upfront, discover everything, fetch on demand.
agent_traits — Atomic Personality Units
Parts are too coarse for on-demand loading. Traits are individual, atomic units fetched one at a time.
agent_traits table:
idbigserial PK,guiduuid,nametext UNIQUE NOT NULL,trait_typetext NOT NULL,descriptiontext,instructiontext (how to apply this trait in practice),tagstext[] DEFAULT '{}',created_at,updated_at
agent_persona_traits junction:
persona_id → agent_personas.id,trait_id → agent_traits.id— composite PK
| Trait Type | Examples |
|---|---|
personality |
curious, warm, stubborn, irreverent |
cognitive |
analytical, creative, methodical, lateral |
emotional |
empathetic, stoic, excitable, measured |
social |
assertive, collaborative, reserved, direct |
behavioral |
cautious, thorough, impulsive, adaptive |
Manifest — Lightweight Discovery
get_persona_manifest(name) — agent calls this first. Returns structure only, no content.
Response includes:
- Persona name + description
- Available parts:
{name, type, description}— no content - Available traits:
{name, trait_type, description}— no instruction - Linked skill + guardrail names
on_demand_toolshint block — tells the agent which tools to call and when
on_demand_tools:
get_agent_part(name) — load full content of a specific part
get_agent_trait(name) — load instruction for a specific trait
get_agent_skill(name) — load skill content
get_agent_guardrail(name) — load guardrail content
compile_persona(name) — load pre-merged compiled_summary if full context needed
Additional Tools
| Tool | Purpose |
|---|---|
get_persona_manifest |
Lightweight discovery — structure only, includes tool hints |
create_agent_trait |
Create an atomic trait |
update_agent_trait |
Update trait fields |
delete_agent_trait |
Delete by name |
list_agent_traits |
Filter by trait_type, tags |
get_agent_trait |
Load single trait instruction by name |
add_persona_trait |
Link trait to persona |
remove_persona_trait |
Unlink trait |
Loading Strategy
| Situation | Call |
|---|---|
| First load, unknown persona | get_persona_manifest(name) |
| Need a specific behavior | get_agent_part(name) |
| Need a personality nuance | get_agent_trait(name) |
| Full context available | compile_persona(name) → use compiled_summary |
| Arc stage changed | advance_persona_stage → compile_persona |
Implementation Order
schema/agent_personas.dbml— DBML table definitionsmigrations/NNN_agent_personas.sql— SQL migration- Regenerate models via relspecgo →
internal/generatedmodels/ internal/store/agent_personas.go— DB access methodsinternal/types/agent_persona.go— Go types and I/O structsinternal/tools/agent_personas.go— Tool implementationinternal/mcpserver/server.go— Register tools + catalog entriesinternal/app/app.go— Instantiate and wire tool