# 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_type` with 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`, `name` UNIQUE 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.id` UNIQUE, `arc_id → character_arcs.id`, `current_stage_id → arc_stages.id`, `updated_at` ### Part Assembly Priority (highest wins per type) 1. Runtime `overrides` passed in the call 2. Active arc stage parts 3. 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 prompt - `compiled_detail text` — pre-merged full part content - `compiled_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: - `id` bigserial PK, `guid` uuid, `name` text UNIQUE NOT NULL, `trait_type` text NOT NULL, `description` text, `instruction` text (how to apply this trait in practice), `tags` text[] 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_tools` hint 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 1. `schema/agent_personas.dbml` — DBML table definitions 2. `migrations/NNN_agent_personas.sql` — SQL migration 3. Regenerate models via relspecgo → `internal/generatedmodels/` 4. `internal/store/agent_personas.go` — DB access methods 5. `internal/types/agent_persona.go` — Go types and I/O structs 6. `internal/tools/agent_personas.go` — Tool implementation 7. `internal/mcpserver/server.go` — Register tools + catalog entries 8. `internal/app/app.go` — Instantiate and wire tool