Files
amcs/llm/personas.md
Hein 1f671dad54
Some checks failed
CI / build-and-test (push) Failing after -31m39s
feat(personas): add implementation plan for agent personas
2026-05-03 22:18:40 +02:00

289 lines
10 KiB
Markdown

# 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