Add schema for agent personas, parts, traits, and character arcs
Some checks failed
CI / build-and-test (push) Failing after -31m18s

- Created tables for agent_personas, agent_parts, agent_traits, and character_arcs.
- Established relationships between personas, parts, skills, guardrails, and traits.
- Added arc stages and their corresponding parts, along with a persona_arc table to track current stages.
- Implemented cascading delete rules for referential integrity.
This commit is contained in:
2026-05-05 09:43:14 +02:00
parent 1ceb317f4b
commit e285a03639
20 changed files with 4671 additions and 30 deletions

View File

@@ -0,0 +1,873 @@
package tools
import (
"context"
"strings"
"github.com/modelcontextprotocol/go-sdk/mcp"
"git.warky.dev/wdevs/amcs/internal/store"
ext "git.warky.dev/wdevs/amcs/internal/types"
)
type AgentPersonasTool struct {
store *store.DB
}
func NewAgentPersonasTool(db *store.DB) *AgentPersonasTool {
return &AgentPersonasTool{store: db}
}
// ──────────────────────────────────────────────
// Personas
// ──────────────────────────────────────────────
// create_agent_persona
type CreatePersonaInput struct {
Name string `json:"name" jsonschema:"unique persona name — used as the load key"`
Description string `json:"description,omitempty" jsonschema:"short description of the persona"`
Summary string `json:"summary" jsonschema:"concise behaviour summary, returned by default on load"`
Detail string `json:"detail,omitempty" jsonschema:"full behaviour detail, returned only when detail=true"`
Tags []string `json:"tags,omitempty" jsonschema:"optional tags for grouping or filtering"`
}
type CreatePersonaOutput struct {
Persona ext.Persona `json:"persona"`
}
func (t *AgentPersonasTool) CreatePersona(ctx context.Context, _ *mcp.CallToolRequest, in CreatePersonaInput) (*mcp.CallToolResult, CreatePersonaOutput, error) {
if strings.TrimSpace(in.Name) == "" {
return nil, CreatePersonaOutput{}, errRequiredField("name")
}
if strings.TrimSpace(in.Summary) == "" {
return nil, CreatePersonaOutput{}, errRequiredField("summary")
}
if in.Tags == nil {
in.Tags = []string{}
}
persona, err := t.store.CreatePersona(ctx, ext.Persona{
Name: strings.TrimSpace(in.Name),
Description: strings.TrimSpace(in.Description),
Summary: strings.TrimSpace(in.Summary),
Detail: strings.TrimSpace(in.Detail),
Tags: in.Tags,
})
if err != nil {
return nil, CreatePersonaOutput{}, err
}
return nil, CreatePersonaOutput{Persona: persona}, nil
}
// update_agent_persona
type UpdatePersonaInput struct {
Name string `json:"name" jsonschema:"name of the persona to update"`
NewName string `json:"new_name,omitempty" jsonschema:"rename the persona"`
Description *string `json:"description,omitempty" jsonschema:"update description"`
Summary *string `json:"summary,omitempty" jsonschema:"update summary"`
Detail *string `json:"detail,omitempty" jsonschema:"update detail"`
Tags []string `json:"tags,omitempty" jsonschema:"replace tags"`
}
type UpdatePersonaOutput struct {
Persona ext.Persona `json:"persona"`
}
func (t *AgentPersonasTool) UpdatePersona(ctx context.Context, _ *mcp.CallToolRequest, in UpdatePersonaInput) (*mcp.CallToolResult, UpdatePersonaOutput, error) {
if strings.TrimSpace(in.Name) == "" {
return nil, UpdatePersonaOutput{}, errRequiredField("name")
}
updates := map[string]any{}
if n := strings.TrimSpace(in.NewName); n != "" {
updates["name"] = n
}
if in.Description != nil {
updates["description"] = strings.TrimSpace(*in.Description)
}
if in.Summary != nil {
updates["summary"] = strings.TrimSpace(*in.Summary)
}
if in.Detail != nil {
updates["detail"] = strings.TrimSpace(*in.Detail)
}
if in.Tags != nil {
updates["tags"] = in.Tags
}
persona, err := t.store.UpdatePersona(ctx, strings.TrimSpace(in.Name), updates)
if err != nil {
return nil, UpdatePersonaOutput{}, err
}
return nil, UpdatePersonaOutput{Persona: persona}, nil
}
// delete_agent_persona
type DeletePersonaInput struct {
Name string `json:"name" jsonschema:"name of the persona to delete"`
}
type DeletePersonaOutput struct {
Deleted bool `json:"deleted"`
}
func (t *AgentPersonasTool) DeletePersona(ctx context.Context, _ *mcp.CallToolRequest, in DeletePersonaInput) (*mcp.CallToolResult, DeletePersonaOutput, error) {
if strings.TrimSpace(in.Name) == "" {
return nil, DeletePersonaOutput{}, errRequiredField("name")
}
if err := t.store.DeletePersona(ctx, strings.TrimSpace(in.Name)); err != nil {
return nil, DeletePersonaOutput{}, err
}
return nil, DeletePersonaOutput{Deleted: true}, nil
}
// list_agent_personas
type ListPersonasInput struct {
Tag string `json:"tag,omitempty" jsonschema:"filter by tag"`
}
type ListPersonasOutput struct {
Personas []ext.Persona `json:"personas"`
}
func (t *AgentPersonasTool) ListPersonas(ctx context.Context, _ *mcp.CallToolRequest, in ListPersonasInput) (*mcp.CallToolResult, ListPersonasOutput, error) {
personas, err := t.store.ListPersonas(ctx, in.Tag)
if err != nil {
return nil, ListPersonasOutput{}, err
}
if personas == nil {
personas = []ext.Persona{}
}
return nil, ListPersonasOutput{Personas: personas}, nil
}
// get_agent_persona
type GetPersonaInput struct {
Name string `json:"name" jsonschema:"persona name to load"`
Detail bool `json:"detail,omitempty" jsonschema:"when true, return full part content and persona detail instead of summaries"`
Overrides map[string]string `json:"overrides,omitempty" jsonschema:"runtime part substitutions: {part_type: part_name}. Replaces all parts of that type without modifying the persona."`
}
type GetPersonaOutput struct {
Persona ext.PersonaFull `json:"persona"`
}
func (t *AgentPersonasTool) GetPersona(ctx context.Context, _ *mcp.CallToolRequest, in GetPersonaInput) (*mcp.CallToolResult, GetPersonaOutput, error) {
if strings.TrimSpace(in.Name) == "" {
return nil, GetPersonaOutput{}, errRequiredField("name")
}
persona, err := t.store.GetPersona(ctx, strings.TrimSpace(in.Name), in.Detail, in.Overrides)
if err != nil {
return nil, GetPersonaOutput{}, err
}
return nil, GetPersonaOutput{Persona: persona}, nil
}
// get_persona_manifest
type GetPersonaManifestInput struct {
Name string `json:"name" jsonschema:"persona name"`
}
type GetPersonaManifestOutput struct {
Manifest ext.PersonaManifest `json:"manifest"`
}
func (t *AgentPersonasTool) GetPersonaManifest(ctx context.Context, _ *mcp.CallToolRequest, in GetPersonaManifestInput) (*mcp.CallToolResult, GetPersonaManifestOutput, error) {
if strings.TrimSpace(in.Name) == "" {
return nil, GetPersonaManifestOutput{}, errRequiredField("name")
}
manifest, err := t.store.GetPersonaManifest(ctx, strings.TrimSpace(in.Name))
if err != nil {
return nil, GetPersonaManifestOutput{}, err
}
return nil, GetPersonaManifestOutput{Manifest: manifest}, nil
}
// compile_persona
type CompilePersonaInput struct {
Name string `json:"name" jsonschema:"persona name to compile"`
}
type CompilePersonaOutput struct {
Persona ext.Persona `json:"persona"`
}
func (t *AgentPersonasTool) CompilePersona(ctx context.Context, _ *mcp.CallToolRequest, in CompilePersonaInput) (*mcp.CallToolResult, CompilePersonaOutput, error) {
if strings.TrimSpace(in.Name) == "" {
return nil, CompilePersonaOutput{}, errRequiredField("name")
}
persona, err := t.store.CompilePersona(ctx, strings.TrimSpace(in.Name))
if err != nil {
return nil, CompilePersonaOutput{}, err
}
return nil, CompilePersonaOutput{Persona: persona}, nil
}
// ──────────────────────────────────────────────
// Parts
// ──────────────────────────────────────────────
// create_agent_part
type CreatePartInput struct {
Name string `json:"name" jsonschema:"globally unique part name — used as the override key"`
PartType string `json:"part_type" jsonschema:"one of: system, agent, soul, identity, skill, specialization, tone, goal, context, protocol, backstory, motivation, voice, archetype, flaw, relationship"`
Description string `json:"description,omitempty" jsonschema:"short description of what this part does"`
Summary string `json:"summary" jsonschema:"concise version, used in summary mode"`
Content string `json:"content,omitempty" jsonschema:"full content, used in detail mode"`
Tags []string `json:"tags,omitempty" jsonschema:"optional tags"`
}
type CreatePartOutput struct {
Part ext.Part `json:"part"`
}
func (t *AgentPersonasTool) CreatePart(ctx context.Context, _ *mcp.CallToolRequest, in CreatePartInput) (*mcp.CallToolResult, CreatePartOutput, error) {
if strings.TrimSpace(in.Name) == "" {
return nil, CreatePartOutput{}, errRequiredField("name")
}
if strings.TrimSpace(in.PartType) == "" {
return nil, CreatePartOutput{}, errRequiredField("part_type")
}
if strings.TrimSpace(in.Summary) == "" {
return nil, CreatePartOutput{}, errRequiredField("summary")
}
if in.Tags == nil {
in.Tags = []string{}
}
part, err := t.store.CreatePart(ctx, ext.Part{
Name: strings.TrimSpace(in.Name),
PartType: strings.TrimSpace(in.PartType),
Description: strings.TrimSpace(in.Description),
Summary: strings.TrimSpace(in.Summary),
Content: strings.TrimSpace(in.Content),
Tags: in.Tags,
})
if err != nil {
return nil, CreatePartOutput{}, err
}
return nil, CreatePartOutput{Part: part}, nil
}
// update_agent_part
type UpdatePartInput struct {
Name string `json:"name" jsonschema:"name of the part to update"`
NewName string `json:"new_name,omitempty" jsonschema:"rename the part"`
PartType *string `json:"part_type,omitempty" jsonschema:"update part type"`
Description *string `json:"description,omitempty" jsonschema:"update description"`
Summary *string `json:"summary,omitempty" jsonschema:"update summary"`
Content *string `json:"content,omitempty" jsonschema:"update content"`
Tags []string `json:"tags,omitempty" jsonschema:"replace tags"`
}
type UpdatePartOutput struct {
Part ext.Part `json:"part"`
}
func (t *AgentPersonasTool) UpdatePart(ctx context.Context, _ *mcp.CallToolRequest, in UpdatePartInput) (*mcp.CallToolResult, UpdatePartOutput, error) {
if strings.TrimSpace(in.Name) == "" {
return nil, UpdatePartOutput{}, errRequiredField("name")
}
updates := map[string]any{}
if n := strings.TrimSpace(in.NewName); n != "" {
updates["name"] = n
}
if in.PartType != nil {
updates["part_type"] = strings.TrimSpace(*in.PartType)
}
if in.Description != nil {
updates["description"] = strings.TrimSpace(*in.Description)
}
if in.Summary != nil {
updates["summary"] = strings.TrimSpace(*in.Summary)
}
if in.Content != nil {
updates["content"] = strings.TrimSpace(*in.Content)
}
if in.Tags != nil {
updates["tags"] = in.Tags
}
part, err := t.store.UpdatePart(ctx, strings.TrimSpace(in.Name), updates)
if err != nil {
return nil, UpdatePartOutput{}, err
}
return nil, UpdatePartOutput{Part: part}, nil
}
// delete_agent_part
type DeletePartInput struct {
Name string `json:"name" jsonschema:"name of the part to delete"`
}
type DeletePartOutput struct {
Deleted bool `json:"deleted"`
}
func (t *AgentPersonasTool) DeletePart(ctx context.Context, _ *mcp.CallToolRequest, in DeletePartInput) (*mcp.CallToolResult, DeletePartOutput, error) {
if strings.TrimSpace(in.Name) == "" {
return nil, DeletePartOutput{}, errRequiredField("name")
}
if err := t.store.DeletePart(ctx, strings.TrimSpace(in.Name)); err != nil {
return nil, DeletePartOutput{}, err
}
return nil, DeletePartOutput{Deleted: true}, nil
}
// list_agent_parts
type ListPartsInput struct {
PartType string `json:"part_type,omitempty" jsonschema:"filter by part type"`
Tag string `json:"tag,omitempty" jsonschema:"filter by tag"`
}
type ListPartsOutput struct {
Parts []ext.Part `json:"parts"`
}
func (t *AgentPersonasTool) ListParts(ctx context.Context, _ *mcp.CallToolRequest, in ListPartsInput) (*mcp.CallToolResult, ListPartsOutput, error) {
parts, err := t.store.ListParts(ctx, in.PartType, in.Tag)
if err != nil {
return nil, ListPartsOutput{}, err
}
if parts == nil {
parts = []ext.Part{}
}
return nil, ListPartsOutput{Parts: parts}, nil
}
// get_agent_part
type GetPartInput struct {
Name string `json:"name" jsonschema:"part name"`
}
type GetPartOutput struct {
Part ext.Part `json:"part"`
}
func (t *AgentPersonasTool) GetPart(ctx context.Context, _ *mcp.CallToolRequest, in GetPartInput) (*mcp.CallToolResult, GetPartOutput, error) {
if strings.TrimSpace(in.Name) == "" {
return nil, GetPartOutput{}, errRequiredField("name")
}
part, err := t.store.GetPartByName(ctx, strings.TrimSpace(in.Name))
if err != nil {
return nil, GetPartOutput{}, err
}
return nil, GetPartOutput{Part: part}, nil
}
// ──────────────────────────────────────────────
// Persona-Part / Skill / Guardrail links
// ──────────────────────────────────────────────
// add_persona_part
type AddPersonaPartInput struct {
Persona string `json:"persona" jsonschema:"persona name"`
Part string `json:"part" jsonschema:"part name to link"`
Order int `json:"order,omitempty" jsonschema:"assembly order (lower first, default 0)"`
Priority int `json:"priority,omitempty" jsonschema:"context-budget priority (higher loads first when trimming)"`
}
type AddPersonaPartOutput struct {
Persona string `json:"persona"`
Part string `json:"part"`
}
func (t *AgentPersonasTool) AddPersonaPart(ctx context.Context, _ *mcp.CallToolRequest, in AddPersonaPartInput) (*mcp.CallToolResult, AddPersonaPartOutput, error) {
if strings.TrimSpace(in.Persona) == "" {
return nil, AddPersonaPartOutput{}, errRequiredField("persona")
}
if strings.TrimSpace(in.Part) == "" {
return nil, AddPersonaPartOutput{}, errRequiredField("part")
}
if err := t.store.AddPersonaPart(ctx, strings.TrimSpace(in.Persona), strings.TrimSpace(in.Part), in.Order, in.Priority); err != nil {
return nil, AddPersonaPartOutput{}, err
}
return nil, AddPersonaPartOutput{Persona: in.Persona, Part: in.Part}, nil
}
// remove_persona_part
type RemovePersonaPartInput struct {
Persona string `json:"persona" jsonschema:"persona name"`
Part string `json:"part" jsonschema:"part name to unlink"`
}
type RemovePersonaPartOutput struct {
Removed bool `json:"removed"`
}
func (t *AgentPersonasTool) RemovePersonaPart(ctx context.Context, _ *mcp.CallToolRequest, in RemovePersonaPartInput) (*mcp.CallToolResult, RemovePersonaPartOutput, error) {
if strings.TrimSpace(in.Persona) == "" {
return nil, RemovePersonaPartOutput{}, errRequiredField("persona")
}
if strings.TrimSpace(in.Part) == "" {
return nil, RemovePersonaPartOutput{}, errRequiredField("part")
}
if err := t.store.RemovePersonaPart(ctx, strings.TrimSpace(in.Persona), strings.TrimSpace(in.Part)); err != nil {
return nil, RemovePersonaPartOutput{}, err
}
return nil, RemovePersonaPartOutput{Removed: true}, nil
}
// add_persona_skill
type AddPersonaSkillInput struct {
PersonaID int64 `json:"persona_id" jsonschema:"persona id"`
SkillID int64 `json:"skill_id" jsonschema:"agent skill id to link"`
}
type AddPersonaSkillOutput struct {
PersonaID int64 `json:"persona_id"`
SkillID int64 `json:"skill_id"`
}
func (t *AgentPersonasTool) AddPersonaSkill(ctx context.Context, _ *mcp.CallToolRequest, in AddPersonaSkillInput) (*mcp.CallToolResult, AddPersonaSkillOutput, error) {
if err := t.store.AddPersonaSkill(ctx, in.PersonaID, in.SkillID); err != nil {
return nil, AddPersonaSkillOutput{}, err
}
return nil, AddPersonaSkillOutput{PersonaID: in.PersonaID, SkillID: in.SkillID}, nil
}
// remove_persona_skill
type RemovePersonaSkillInput struct {
PersonaID int64 `json:"persona_id" jsonschema:"persona id"`
SkillID int64 `json:"skill_id" jsonschema:"agent skill id to unlink"`
}
type RemovePersonaSkillOutput struct {
Removed bool `json:"removed"`
}
func (t *AgentPersonasTool) RemovePersonaSkill(ctx context.Context, _ *mcp.CallToolRequest, in RemovePersonaSkillInput) (*mcp.CallToolResult, RemovePersonaSkillOutput, error) {
if err := t.store.RemovePersonaSkill(ctx, in.PersonaID, in.SkillID); err != nil {
return nil, RemovePersonaSkillOutput{}, err
}
return nil, RemovePersonaSkillOutput{Removed: true}, nil
}
// add_persona_guardrail
type AddPersonaGuardrailInput struct {
PersonaID int64 `json:"persona_id" jsonschema:"persona id"`
GuardrailID int64 `json:"guardrail_id" jsonschema:"agent guardrail id to link"`
}
type AddPersonaGuardrailOutput struct {
PersonaID int64 `json:"persona_id"`
GuardrailID int64 `json:"guardrail_id"`
}
func (t *AgentPersonasTool) AddPersonaGuardrail(ctx context.Context, _ *mcp.CallToolRequest, in AddPersonaGuardrailInput) (*mcp.CallToolResult, AddPersonaGuardrailOutput, error) {
if err := t.store.AddPersonaGuardrail(ctx, in.PersonaID, in.GuardrailID); err != nil {
return nil, AddPersonaGuardrailOutput{}, err
}
return nil, AddPersonaGuardrailOutput{PersonaID: in.PersonaID, GuardrailID: in.GuardrailID}, nil
}
// remove_persona_guardrail
type RemovePersonaGuardrailInput struct {
PersonaID int64 `json:"persona_id" jsonschema:"persona id"`
GuardrailID int64 `json:"guardrail_id" jsonschema:"agent guardrail id to unlink"`
}
type RemovePersonaGuardrailOutput struct {
Removed bool `json:"removed"`
}
func (t *AgentPersonasTool) RemovePersonaGuardrail(ctx context.Context, _ *mcp.CallToolRequest, in RemovePersonaGuardrailInput) (*mcp.CallToolResult, RemovePersonaGuardrailOutput, error) {
if err := t.store.RemovePersonaGuardrail(ctx, in.PersonaID, in.GuardrailID); err != nil {
return nil, RemovePersonaGuardrailOutput{}, err
}
return nil, RemovePersonaGuardrailOutput{Removed: true}, nil
}
// ──────────────────────────────────────────────
// Traits
// ──────────────────────────────────────────────
// create_agent_trait
type CreateTraitInput struct {
Name string `json:"name" jsonschema:"globally unique trait name"`
TraitType string `json:"trait_type" jsonschema:"one of: personality, cognitive, emotional, social, behavioral"`
Description string `json:"description,omitempty" jsonschema:"short description of this trait"`
Instruction string `json:"instruction,omitempty" jsonschema:"how to apply this trait in practice"`
Tags []string `json:"tags,omitempty" jsonschema:"optional tags"`
}
type CreateTraitOutput struct {
Trait ext.Trait `json:"trait"`
}
func (t *AgentPersonasTool) CreateTrait(ctx context.Context, _ *mcp.CallToolRequest, in CreateTraitInput) (*mcp.CallToolResult, CreateTraitOutput, error) {
if strings.TrimSpace(in.Name) == "" {
return nil, CreateTraitOutput{}, errRequiredField("name")
}
if strings.TrimSpace(in.TraitType) == "" {
return nil, CreateTraitOutput{}, errRequiredField("trait_type")
}
if in.Tags == nil {
in.Tags = []string{}
}
trait, err := t.store.CreateTrait(ctx, ext.Trait{
Name: strings.TrimSpace(in.Name),
TraitType: strings.TrimSpace(in.TraitType),
Description: strings.TrimSpace(in.Description),
Instruction: strings.TrimSpace(in.Instruction),
Tags: in.Tags,
})
if err != nil {
return nil, CreateTraitOutput{}, err
}
return nil, CreateTraitOutput{Trait: trait}, nil
}
// update_agent_trait
type UpdateTraitInput struct {
Name string `json:"name" jsonschema:"name of the trait to update"`
NewName string `json:"new_name,omitempty" jsonschema:"rename the trait"`
TraitType *string `json:"trait_type,omitempty" jsonschema:"update trait type"`
Description *string `json:"description,omitempty" jsonschema:"update description"`
Instruction *string `json:"instruction,omitempty" jsonschema:"update instruction"`
Tags []string `json:"tags,omitempty" jsonschema:"replace tags"`
}
type UpdateTraitOutput struct {
Trait ext.Trait `json:"trait"`
}
func (t *AgentPersonasTool) UpdateTrait(ctx context.Context, _ *mcp.CallToolRequest, in UpdateTraitInput) (*mcp.CallToolResult, UpdateTraitOutput, error) {
if strings.TrimSpace(in.Name) == "" {
return nil, UpdateTraitOutput{}, errRequiredField("name")
}
updates := map[string]any{}
if n := strings.TrimSpace(in.NewName); n != "" {
updates["name"] = n
}
if in.TraitType != nil {
updates["trait_type"] = strings.TrimSpace(*in.TraitType)
}
if in.Description != nil {
updates["description"] = strings.TrimSpace(*in.Description)
}
if in.Instruction != nil {
updates["instruction"] = strings.TrimSpace(*in.Instruction)
}
if in.Tags != nil {
updates["tags"] = in.Tags
}
trait, err := t.store.UpdateTrait(ctx, strings.TrimSpace(in.Name), updates)
if err != nil {
return nil, UpdateTraitOutput{}, err
}
return nil, UpdateTraitOutput{Trait: trait}, nil
}
// delete_agent_trait
type DeleteTraitInput struct {
Name string `json:"name" jsonschema:"name of the trait to delete"`
}
type DeleteTraitOutput struct {
Deleted bool `json:"deleted"`
}
func (t *AgentPersonasTool) DeleteTrait(ctx context.Context, _ *mcp.CallToolRequest, in DeleteTraitInput) (*mcp.CallToolResult, DeleteTraitOutput, error) {
if strings.TrimSpace(in.Name) == "" {
return nil, DeleteTraitOutput{}, errRequiredField("name")
}
if err := t.store.DeleteTrait(ctx, strings.TrimSpace(in.Name)); err != nil {
return nil, DeleteTraitOutput{}, err
}
return nil, DeleteTraitOutput{Deleted: true}, nil
}
// list_agent_traits
type ListTraitsInput struct {
TraitType string `json:"trait_type,omitempty" jsonschema:"filter by trait type"`
Tag string `json:"tag,omitempty" jsonschema:"filter by tag"`
}
type ListTraitsOutput struct {
Traits []ext.Trait `json:"traits"`
}
func (t *AgentPersonasTool) ListTraits(ctx context.Context, _ *mcp.CallToolRequest, in ListTraitsInput) (*mcp.CallToolResult, ListTraitsOutput, error) {
traits, err := t.store.ListTraits(ctx, in.TraitType, in.Tag)
if err != nil {
return nil, ListTraitsOutput{}, err
}
if traits == nil {
traits = []ext.Trait{}
}
return nil, ListTraitsOutput{Traits: traits}, nil
}
// get_agent_trait
type GetTraitInput struct {
Name string `json:"name" jsonschema:"trait name"`
}
type GetTraitOutput struct {
Trait ext.Trait `json:"trait"`
}
func (t *AgentPersonasTool) GetTrait(ctx context.Context, _ *mcp.CallToolRequest, in GetTraitInput) (*mcp.CallToolResult, GetTraitOutput, error) {
if strings.TrimSpace(in.Name) == "" {
return nil, GetTraitOutput{}, errRequiredField("name")
}
trait, err := t.store.GetTraitByName(ctx, strings.TrimSpace(in.Name))
if err != nil {
return nil, GetTraitOutput{}, err
}
return nil, GetTraitOutput{Trait: trait}, nil
}
// add_persona_trait
type AddPersonaTraitInput struct {
PersonaID int64 `json:"persona_id" jsonschema:"persona id"`
TraitID int64 `json:"trait_id" jsonschema:"agent trait id to link"`
}
type AddPersonaTraitOutput struct {
PersonaID int64 `json:"persona_id"`
TraitID int64 `json:"trait_id"`
}
func (t *AgentPersonasTool) AddPersonaTrait(ctx context.Context, _ *mcp.CallToolRequest, in AddPersonaTraitInput) (*mcp.CallToolResult, AddPersonaTraitOutput, error) {
if err := t.store.AddPersonaTrait(ctx, in.PersonaID, in.TraitID); err != nil {
return nil, AddPersonaTraitOutput{}, err
}
return nil, AddPersonaTraitOutput{PersonaID: in.PersonaID, TraitID: in.TraitID}, nil
}
// remove_persona_trait
type RemovePersonaTraitInput struct {
PersonaID int64 `json:"persona_id" jsonschema:"persona id"`
TraitID int64 `json:"trait_id" jsonschema:"agent trait id to unlink"`
}
type RemovePersonaTraitOutput struct {
Removed bool `json:"removed"`
}
func (t *AgentPersonasTool) RemovePersonaTrait(ctx context.Context, _ *mcp.CallToolRequest, in RemovePersonaTraitInput) (*mcp.CallToolResult, RemovePersonaTraitOutput, error) {
if err := t.store.RemovePersonaTrait(ctx, in.PersonaID, in.TraitID); err != nil {
return nil, RemovePersonaTraitOutput{}, err
}
return nil, RemovePersonaTraitOutput{Removed: true}, nil
}
// ──────────────────────────────────────────────
// Character Arcs
// ──────────────────────────────────────────────
// create_character_arc
type CreateCharacterArcInput struct {
Name string `json:"name" jsonschema:"unique arc name"`
Description string `json:"description,omitempty" jsonschema:"description of the arc"`
Summary string `json:"summary,omitempty" jsonschema:"brief arc summary"`
}
type CreateCharacterArcOutput struct {
Arc ext.CharacterArc `json:"arc"`
}
func (t *AgentPersonasTool) CreateCharacterArc(ctx context.Context, _ *mcp.CallToolRequest, in CreateCharacterArcInput) (*mcp.CallToolResult, CreateCharacterArcOutput, error) {
if strings.TrimSpace(in.Name) == "" {
return nil, CreateCharacterArcOutput{}, errRequiredField("name")
}
arc, err := t.store.CreateCharacterArc(ctx, ext.CharacterArc{
Name: strings.TrimSpace(in.Name),
Description: strings.TrimSpace(in.Description),
Summary: strings.TrimSpace(in.Summary),
})
if err != nil {
return nil, CreateCharacterArcOutput{}, err
}
return nil, CreateCharacterArcOutput{Arc: arc}, nil
}
// list_character_arcs
type ListCharacterArcsInput struct{}
type ListCharacterArcsOutput struct {
Arcs []ext.CharacterArc `json:"arcs"`
}
func (t *AgentPersonasTool) ListCharacterArcs(ctx context.Context, _ *mcp.CallToolRequest, _ ListCharacterArcsInput) (*mcp.CallToolResult, ListCharacterArcsOutput, error) {
arcs, err := t.store.ListCharacterArcs(ctx)
if err != nil {
return nil, ListCharacterArcsOutput{}, err
}
if arcs == nil {
arcs = []ext.CharacterArc{}
}
return nil, ListCharacterArcsOutput{Arcs: arcs}, nil
}
// add_arc_stage
type AddArcStageInput struct {
Arc string `json:"arc" jsonschema:"arc name"`
Name string `json:"name" jsonschema:"stage name"`
StageOrder int `json:"stage_order,omitempty" jsonschema:"position in arc sequence (lower first)"`
Description string `json:"description,omitempty" jsonschema:"what happens at this stage"`
Condition string `json:"condition,omitempty" jsonschema:"trigger condition description (evaluated externally)"`
}
type AddArcStageOutput struct {
Stage ext.ArcStage `json:"stage"`
}
func (t *AgentPersonasTool) AddArcStage(ctx context.Context, _ *mcp.CallToolRequest, in AddArcStageInput) (*mcp.CallToolResult, AddArcStageOutput, error) {
if strings.TrimSpace(in.Arc) == "" {
return nil, AddArcStageOutput{}, errRequiredField("arc")
}
if strings.TrimSpace(in.Name) == "" {
return nil, AddArcStageOutput{}, errRequiredField("name")
}
stage, err := t.store.AddArcStage(ctx, strings.TrimSpace(in.Arc), ext.ArcStage{
Name: strings.TrimSpace(in.Name),
StageOrder: in.StageOrder,
Description: strings.TrimSpace(in.Description),
Condition: strings.TrimSpace(in.Condition),
})
if err != nil {
return nil, AddArcStageOutput{}, err
}
return nil, AddArcStageOutput{Stage: stage}, nil
}
// add_stage_part
type AddStagePartInput struct {
StageID int64 `json:"stage_id" jsonschema:"arc stage id"`
PartName string `json:"part_name" jsonschema:"part name to link"`
}
type AddStagePartOutput struct {
StageID int64 `json:"stage_id"`
PartName string `json:"part_name"`
}
func (t *AgentPersonasTool) AddStagePart(ctx context.Context, _ *mcp.CallToolRequest, in AddStagePartInput) (*mcp.CallToolResult, AddStagePartOutput, error) {
if strings.TrimSpace(in.PartName) == "" {
return nil, AddStagePartOutput{}, errRequiredField("part_name")
}
if err := t.store.AddStagePart(ctx, in.StageID, strings.TrimSpace(in.PartName)); err != nil {
return nil, AddStagePartOutput{}, err
}
return nil, AddStagePartOutput{StageID: in.StageID, PartName: in.PartName}, nil
}
// remove_stage_part
type RemoveStagePartInput struct {
StageID int64 `json:"stage_id" jsonschema:"arc stage id"`
PartName string `json:"part_name" jsonschema:"part name to unlink"`
}
type RemoveStagePartOutput struct {
Removed bool `json:"removed"`
}
func (t *AgentPersonasTool) RemoveStagePart(ctx context.Context, _ *mcp.CallToolRequest, in RemoveStagePartInput) (*mcp.CallToolResult, RemoveStagePartOutput, error) {
if strings.TrimSpace(in.PartName) == "" {
return nil, RemoveStagePartOutput{}, errRequiredField("part_name")
}
if err := t.store.RemoveStagePart(ctx, in.StageID, strings.TrimSpace(in.PartName)); err != nil {
return nil, RemoveStagePartOutput{}, err
}
return nil, RemoveStagePartOutput{Removed: true}, nil
}
// assign_persona_arc
type AssignPersonaArcInput struct {
Persona string `json:"persona" jsonschema:"persona name"`
Arc string `json:"arc" jsonschema:"arc name to assign"`
StartStage string `json:"start_stage" jsonschema:"name of the starting stage"`
}
type AssignPersonaArcOutput struct {
Persona string `json:"persona"`
Arc string `json:"arc"`
Stage string `json:"stage"`
}
func (t *AgentPersonasTool) AssignPersonaArc(ctx context.Context, _ *mcp.CallToolRequest, in AssignPersonaArcInput) (*mcp.CallToolResult, AssignPersonaArcOutput, error) {
if strings.TrimSpace(in.Persona) == "" {
return nil, AssignPersonaArcOutput{}, errRequiredField("persona")
}
if strings.TrimSpace(in.Arc) == "" {
return nil, AssignPersonaArcOutput{}, errRequiredField("arc")
}
if strings.TrimSpace(in.StartStage) == "" {
return nil, AssignPersonaArcOutput{}, errRequiredField("start_stage")
}
if err := t.store.AssignPersonaArc(ctx, strings.TrimSpace(in.Persona), strings.TrimSpace(in.Arc), strings.TrimSpace(in.StartStage)); err != nil {
return nil, AssignPersonaArcOutput{}, err
}
return nil, AssignPersonaArcOutput{Persona: in.Persona, Arc: in.Arc, Stage: in.StartStage}, nil
}
// advance_persona_stage
type AdvancePersonaStageInput struct {
Persona string `json:"persona" jsonschema:"persona name"`
}
type AdvancePersonaStageOutput struct {
Stage ext.ArcStage `json:"stage"`
}
func (t *AgentPersonasTool) AdvancePersonaStage(ctx context.Context, _ *mcp.CallToolRequest, in AdvancePersonaStageInput) (*mcp.CallToolResult, AdvancePersonaStageOutput, error) {
if strings.TrimSpace(in.Persona) == "" {
return nil, AdvancePersonaStageOutput{}, errRequiredField("persona")
}
stage, err := t.store.AdvancePersonaStage(ctx, strings.TrimSpace(in.Persona))
if err != nil {
return nil, AdvancePersonaStageOutput{}, err
}
return nil, AdvancePersonaStageOutput{Stage: stage}, nil
}
// reset_persona_stage
type ResetPersonaStageInput struct {
Persona string `json:"persona" jsonschema:"persona name"`
}
type ResetPersonaStageOutput struct {
Stage ext.ArcStage `json:"stage"`
}
func (t *AgentPersonasTool) ResetPersonaStage(ctx context.Context, _ *mcp.CallToolRequest, in ResetPersonaStageInput) (*mcp.CallToolResult, ResetPersonaStageOutput, error) {
if strings.TrimSpace(in.Persona) == "" {
return nil, ResetPersonaStageOutput{}, errRequiredField("persona")
}
stage, err := t.store.ResetPersonaStage(ctx, strings.TrimSpace(in.Persona))
if err != nil {
return nil, ResetPersonaStageOutput{}, err
}
return nil, ResetPersonaStageOutput{Stage: stage}, nil
}