diff --git a/internal/app/app.go b/internal/app/app.go index 6eee341..7b108e4 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -216,6 +216,7 @@ func routes(logger *slog.Logger, cfg *config.Config, info buildinfo.Info, db *st RetryMetadata: tools.NewRetryEnrichmentTool(enrichmentRetryer), //Maintenance: tools.NewMaintenanceTool(db), Skills: tools.NewSkillsTool(db, activeProjects), + Personas: tools.NewAgentPersonasTool(db), ChatHistory: tools.NewChatHistoryTool(db, activeProjects), Describe: tools.NewDescribeTool(db, mcpserver.BuildToolCatalog()), } diff --git a/internal/generatedmodels/sql_public_agent_guardrails.go b/internal/generatedmodels/sql_public_agent_guardrails.go index 2f9d1a3..fb4fb6e 100644 --- a/internal/generatedmodels/sql_public_agent_guardrails.go +++ b/internal/generatedmodels/sql_public_agent_guardrails.go @@ -8,18 +8,19 @@ import ( ) type ModelPublicAgentGuardrails struct { - bun.BaseModel `bun:"table:public.agent_guardrails,alias:agent_guardrails"` - ID resolvespec_common.SqlInt64 `bun:"id,type:bigserial,pk,autoincrement," json:"id"` - Content resolvespec_common.SqlString `bun:"content,type:text,notnull," json:"content"` - CreatedAt resolvespec_common.SqlTimeStamp `bun:"created_at,type:timestamptz,default:now(),notnull," json:"created_at"` - Description resolvespec_common.SqlString `bun:"description,type:text,default:'',notnull," json:"description"` - GUID resolvespec_common.SqlUUID `bun:"guid,type:uuid,default:gen_random_uuid(),notnull," json:"guid"` - Name resolvespec_common.SqlString `bun:"name,type:text,notnull," json:"name"` - Severity resolvespec_common.SqlString `bun:"severity,type:text,default:'medium',notnull," json:"severity"` - Tags resolvespec_common.SqlStringArray `bun:"tags,type:text[],default:'{}',notnull," json:"tags"` - UpdatedAt resolvespec_common.SqlTimeStamp `bun:"updated_at,type:timestamptz,default:now(),notnull," json:"updated_at"` - RelGuardrailIDPublicPlanGuardrails []*ModelPublicPlanGuardrails `bun:"rel:has-many,join:id=guardrail_id" json:"relguardrailidpublicplanguardrails,omitempty"` // Has many ModelPublicPlanGuardrails - RelGuardrailIDPublicProjectGuardrails []*ModelPublicProjectGuardrails `bun:"rel:has-many,join:id=guardrail_id" json:"relguardrailidpublicprojectguardrails,omitempty"` // Has many ModelPublicProjectGuardrails + bun.BaseModel `bun:"table:public.agent_guardrails,alias:agent_guardrails"` + ID resolvespec_common.SqlInt64 `bun:"id,type:bigserial,pk,autoincrement," json:"id"` + Content resolvespec_common.SqlString `bun:"content,type:text,notnull," json:"content"` + CreatedAt resolvespec_common.SqlTimeStamp `bun:"created_at,type:timestamptz,default:now(),notnull," json:"created_at"` + Description resolvespec_common.SqlString `bun:"description,type:text,default:'',notnull," json:"description"` + GUID resolvespec_common.SqlUUID `bun:"guid,type:uuid,default:gen_random_uuid(),notnull," json:"guid"` + Name resolvespec_common.SqlString `bun:"name,type:text,notnull," json:"name"` + Severity resolvespec_common.SqlString `bun:"severity,type:text,default:'medium',notnull," json:"severity"` + Tags resolvespec_common.SqlStringArray `bun:"tags,type:text[],default:'{}',notnull," json:"tags"` + UpdatedAt resolvespec_common.SqlTimeStamp `bun:"updated_at,type:timestamptz,default:now(),notnull," json:"updated_at"` + RelGuardrailIDPublicAgentPersonaGuardrails []*ModelPublicAgentPersonaGuardrails `bun:"rel:has-many,join:id=guardrail_id" json:"relguardrailidpublicagentpersonaguardrails,omitempty"` // Has many ModelPublicAgentPersonaGuardrails + RelGuardrailIDPublicPlanGuardrails []*ModelPublicPlanGuardrails `bun:"rel:has-many,join:id=guardrail_id" json:"relguardrailidpublicplanguardrails,omitempty"` // Has many ModelPublicPlanGuardrails + RelGuardrailIDPublicProjectGuardrails []*ModelPublicProjectGuardrails `bun:"rel:has-many,join:id=guardrail_id" json:"relguardrailidpublicprojectguardrails,omitempty"` // Has many ModelPublicProjectGuardrails } // TableName returns the table name for ModelPublicAgentGuardrails diff --git a/internal/generatedmodels/sql_public_agent_parts.go b/internal/generatedmodels/sql_public_agent_parts.go new file mode 100644 index 0000000..c80f299 --- /dev/null +++ b/internal/generatedmodels/sql_public_agent_parts.go @@ -0,0 +1,69 @@ +// Code generated by relspecgo. DO NOT EDIT. +package generatedmodels + +import ( + "fmt" + resolvespec_common "github.com/bitechdev/ResolveSpec/pkg/spectypes" + "github.com/uptrace/bun" +) + +type ModelPublicAgentParts struct { + bun.BaseModel `bun:"table:public.agent_parts,alias:agent_parts"` + ID resolvespec_common.SqlInt64 `bun:"id,type:bigserial,pk,autoincrement," json:"id"` + Content resolvespec_common.SqlString `bun:"content,type:text,default:'',notnull," json:"content"` + CreatedAt resolvespec_common.SqlTimeStamp `bun:"created_at,type:timestamptz,default:now(),notnull," json:"created_at"` + Description resolvespec_common.SqlString `bun:"description,type:text,default:'',notnull," json:"description"` + GUID resolvespec_common.SqlUUID `bun:"guid,type:uuid,default:gen_random_uuid(),notnull," json:"guid"` + Name resolvespec_common.SqlString `bun:"name,type:text,notnull," json:"name"` + PartType resolvespec_common.SqlString `bun:"part_type,type:text,notnull," json:"part_type"` + Summary resolvespec_common.SqlString `bun:"summary,type:text,notnull," json:"summary"` + Tags resolvespec_common.SqlStringArray `bun:"tags,type:text[],default:'{}',notnull," json:"tags"` + UpdatedAt resolvespec_common.SqlTimeStamp `bun:"updated_at,type:timestamptz,default:now(),notnull," json:"updated_at"` + RelPartIDPublicAgentPersonaParts []*ModelPublicAgentPersonaParts `bun:"rel:has-many,join:id=part_id" json:"relpartidpublicagentpersonaparts,omitempty"` // Has many ModelPublicAgentPersonaParts + RelPartIDPublicArcStageParts []*ModelPublicArcStageParts `bun:"rel:has-many,join:id=part_id" json:"relpartidpublicarcstageparts,omitempty"` // Has many ModelPublicArcStageParts +} + +// TableName returns the table name for ModelPublicAgentParts +func (m ModelPublicAgentParts) TableName() string { + return "public.agent_parts" +} + +// TableNameOnly returns the table name without schema for ModelPublicAgentParts +func (m ModelPublicAgentParts) TableNameOnly() string { + return "agent_parts" +} + +// SchemaName returns the schema name for ModelPublicAgentParts +func (m ModelPublicAgentParts) SchemaName() string { + return "public" +} + +// GetID returns the primary key value +func (m ModelPublicAgentParts) GetID() int64 { + return m.ID.Int64() +} + +// GetIDStr returns the primary key as a string +func (m ModelPublicAgentParts) GetIDStr() string { + return fmt.Sprintf("%v", m.ID) +} + +// SetID sets the primary key value +func (m ModelPublicAgentParts) SetID(newid int64) { + m.UpdateID(newid) +} + +// UpdateID updates the primary key value +func (m *ModelPublicAgentParts) UpdateID(newid int64) { + m.ID.FromString(fmt.Sprintf("%d", newid)) +} + +// GetIDName returns the name of the primary key column +func (m ModelPublicAgentParts) GetIDName() string { + return "id" +} + +// GetPrefix returns the table prefix +func (m ModelPublicAgentParts) GetPrefix() string { + return "APG" +} diff --git a/internal/generatedmodels/sql_public_agent_persona_guardrails.go b/internal/generatedmodels/sql_public_agent_persona_guardrails.go new file mode 100644 index 0000000..3b1dd3f --- /dev/null +++ b/internal/generatedmodels/sql_public_agent_persona_guardrails.go @@ -0,0 +1,34 @@ +// Code generated by relspecgo. DO NOT EDIT. +package generatedmodels + +import ( + "github.com/uptrace/bun" +) + +type ModelPublicAgentPersonaGuardrails struct { + bun.BaseModel `bun:"table:public.agent_persona_guardrails,alias:agent_persona_guardrails"` + GuardrailID int64 `bun:"guardrail_id,type:bigint,notnull," json:"guardrail_id"` + PersonaID int64 `bun:"persona_id,type:bigint,notnull," json:"persona_id"` + RelGuardrailID *ModelPublicAgentGuardrails `bun:"rel:has-one,join:guardrail_id=id" json:"relguardrailid,omitempty"` // Has one ModelPublicAgentGuardrails + RelPersonaID *ModelPublicAgentPersonas `bun:"rel:has-one,join:persona_id=id" json:"relpersonaid,omitempty"` // Has one ModelPublicAgentPersonas +} + +// TableName returns the table name for ModelPublicAgentPersonaGuardrails +func (m ModelPublicAgentPersonaGuardrails) TableName() string { + return "public.agent_persona_guardrails" +} + +// TableNameOnly returns the table name without schema for ModelPublicAgentPersonaGuardrails +func (m ModelPublicAgentPersonaGuardrails) TableNameOnly() string { + return "agent_persona_guardrails" +} + +// SchemaName returns the schema name for ModelPublicAgentPersonaGuardrails +func (m ModelPublicAgentPersonaGuardrails) SchemaName() string { + return "public" +} + +// GetPrefix returns the table prefix +func (m ModelPublicAgentPersonaGuardrails) GetPrefix() string { + return "APG" +} diff --git a/internal/generatedmodels/sql_public_agent_persona_parts.go b/internal/generatedmodels/sql_public_agent_persona_parts.go new file mode 100644 index 0000000..4b09d5a --- /dev/null +++ b/internal/generatedmodels/sql_public_agent_persona_parts.go @@ -0,0 +1,36 @@ +// Code generated by relspecgo. DO NOT EDIT. +package generatedmodels + +import ( + "github.com/uptrace/bun" +) + +type ModelPublicAgentPersonaParts struct { + bun.BaseModel `bun:"table:public.agent_persona_parts,alias:agent_persona_parts"` + PartID int64 `bun:"part_id,type:bigint,notnull," json:"part_id"` + PartOrder int32 `bun:"part_order,type:int,default:0,notnull," json:"part_order"` + PersonaID int64 `bun:"persona_id,type:bigint,notnull," json:"persona_id"` + Priority int32 `bun:"priority,type:int,default:0,notnull," json:"priority"` + RelPartID *ModelPublicAgentParts `bun:"rel:has-one,join:part_id=id" json:"relpartid,omitempty"` // Has one ModelPublicAgentParts + RelPersonaID *ModelPublicAgentPersonas `bun:"rel:has-one,join:persona_id=id" json:"relpersonaid,omitempty"` // Has one ModelPublicAgentPersonas +} + +// TableName returns the table name for ModelPublicAgentPersonaParts +func (m ModelPublicAgentPersonaParts) TableName() string { + return "public.agent_persona_parts" +} + +// TableNameOnly returns the table name without schema for ModelPublicAgentPersonaParts +func (m ModelPublicAgentPersonaParts) TableNameOnly() string { + return "agent_persona_parts" +} + +// SchemaName returns the schema name for ModelPublicAgentPersonaParts +func (m ModelPublicAgentPersonaParts) SchemaName() string { + return "public" +} + +// GetPrefix returns the table prefix +func (m ModelPublicAgentPersonaParts) GetPrefix() string { + return "APP" +} diff --git a/internal/generatedmodels/sql_public_agent_persona_skills.go b/internal/generatedmodels/sql_public_agent_persona_skills.go new file mode 100644 index 0000000..f36e8b0 --- /dev/null +++ b/internal/generatedmodels/sql_public_agent_persona_skills.go @@ -0,0 +1,34 @@ +// Code generated by relspecgo. DO NOT EDIT. +package generatedmodels + +import ( + "github.com/uptrace/bun" +) + +type ModelPublicAgentPersonaSkills struct { + bun.BaseModel `bun:"table:public.agent_persona_skills,alias:agent_persona_skills"` + PersonaID int64 `bun:"persona_id,type:bigint,notnull," json:"persona_id"` + SkillID int64 `bun:"skill_id,type:bigint,notnull," json:"skill_id"` + RelPersonaID *ModelPublicAgentPersonas `bun:"rel:has-one,join:persona_id=id" json:"relpersonaid,omitempty"` // Has one ModelPublicAgentPersonas + RelSkillID *ModelPublicAgentSkills `bun:"rel:has-one,join:skill_id=id" json:"relskillid,omitempty"` // Has one ModelPublicAgentSkills +} + +// TableName returns the table name for ModelPublicAgentPersonaSkills +func (m ModelPublicAgentPersonaSkills) TableName() string { + return "public.agent_persona_skills" +} + +// TableNameOnly returns the table name without schema for ModelPublicAgentPersonaSkills +func (m ModelPublicAgentPersonaSkills) TableNameOnly() string { + return "agent_persona_skills" +} + +// SchemaName returns the schema name for ModelPublicAgentPersonaSkills +func (m ModelPublicAgentPersonaSkills) SchemaName() string { + return "public" +} + +// GetPrefix returns the table prefix +func (m ModelPublicAgentPersonaSkills) GetPrefix() string { + return "APS" +} diff --git a/internal/generatedmodels/sql_public_agent_persona_traits.go b/internal/generatedmodels/sql_public_agent_persona_traits.go new file mode 100644 index 0000000..855b459 --- /dev/null +++ b/internal/generatedmodels/sql_public_agent_persona_traits.go @@ -0,0 +1,34 @@ +// Code generated by relspecgo. DO NOT EDIT. +package generatedmodels + +import ( + "github.com/uptrace/bun" +) + +type ModelPublicAgentPersonaTraits struct { + bun.BaseModel `bun:"table:public.agent_persona_traits,alias:agent_persona_traits"` + PersonaID int64 `bun:"persona_id,type:bigint,notnull," json:"persona_id"` + TraitID int64 `bun:"trait_id,type:bigint,notnull," json:"trait_id"` + RelPersonaID *ModelPublicAgentPersonas `bun:"rel:has-one,join:persona_id=id" json:"relpersonaid,omitempty"` // Has one ModelPublicAgentPersonas + RelTraitID *ModelPublicAgentTraits `bun:"rel:has-one,join:trait_id=id" json:"reltraitid,omitempty"` // Has one ModelPublicAgentTraits +} + +// TableName returns the table name for ModelPublicAgentPersonaTraits +func (m ModelPublicAgentPersonaTraits) TableName() string { + return "public.agent_persona_traits" +} + +// TableNameOnly returns the table name without schema for ModelPublicAgentPersonaTraits +func (m ModelPublicAgentPersonaTraits) TableNameOnly() string { + return "agent_persona_traits" +} + +// SchemaName returns the schema name for ModelPublicAgentPersonaTraits +func (m ModelPublicAgentPersonaTraits) SchemaName() string { + return "public" +} + +// GetPrefix returns the table prefix +func (m ModelPublicAgentPersonaTraits) GetPrefix() string { + return "APT" +} diff --git a/internal/generatedmodels/sql_public_agent_personas.go b/internal/generatedmodels/sql_public_agent_personas.go new file mode 100644 index 0000000..d792145 --- /dev/null +++ b/internal/generatedmodels/sql_public_agent_personas.go @@ -0,0 +1,74 @@ +// Code generated by relspecgo. DO NOT EDIT. +package generatedmodels + +import ( + "fmt" + resolvespec_common "github.com/bitechdev/ResolveSpec/pkg/spectypes" + "github.com/uptrace/bun" +) + +type ModelPublicAgentPersonas struct { + bun.BaseModel `bun:"table:public.agent_personas,alias:agent_personas"` + ID resolvespec_common.SqlInt64 `bun:"id,type:bigserial,pk,autoincrement," json:"id"` + CompiledAt resolvespec_common.SqlTimeStamp `bun:"compiled_at,type:timestamptz,nullzero," json:"compiled_at"` + CompiledDetail resolvespec_common.SqlString `bun:"compiled_detail,type:text,default:'',notnull," json:"compiled_detail"` + CompiledSummary resolvespec_common.SqlString `bun:"compiled_summary,type:text,default:'',notnull," json:"compiled_summary"` + CreatedAt resolvespec_common.SqlTimeStamp `bun:"created_at,type:timestamptz,default:now(),notnull," json:"created_at"` + Description resolvespec_common.SqlString `bun:"description,type:text,default:'',notnull," json:"description"` + Detail resolvespec_common.SqlString `bun:"detail,type:text,default:'',notnull," json:"detail"` + GUID resolvespec_common.SqlUUID `bun:"guid,type:uuid,default:gen_random_uuid(),notnull," json:"guid"` + Name resolvespec_common.SqlString `bun:"name,type:text,notnull," json:"name"` + Summary resolvespec_common.SqlString `bun:"summary,type:text,notnull," json:"summary"` + Tags resolvespec_common.SqlStringArray `bun:"tags,type:text[],default:'{}',notnull," json:"tags"` + UpdatedAt resolvespec_common.SqlTimeStamp `bun:"updated_at,type:timestamptz,default:now(),notnull," json:"updated_at"` + RelPersonaIDPublicAgentPersonaParts []*ModelPublicAgentPersonaParts `bun:"rel:has-many,join:id=persona_id" json:"relpersonaidpublicagentpersonaparts,omitempty"` // Has many ModelPublicAgentPersonaParts + RelPersonaIDPublicAgentPersonaSkills []*ModelPublicAgentPersonaSkills `bun:"rel:has-many,join:id=persona_id" json:"relpersonaidpublicagentpersonaskills,omitempty"` // Has many ModelPublicAgentPersonaSkills + RelPersonaIDPublicAgentPersonaGuardrails []*ModelPublicAgentPersonaGuardrails `bun:"rel:has-many,join:id=persona_id" json:"relpersonaidpublicagentpersonaguardrails,omitempty"` // Has many ModelPublicAgentPersonaGuardrails + RelPersonaIDPublicAgentPersonaTraits []*ModelPublicAgentPersonaTraits `bun:"rel:has-many,join:id=persona_id" json:"relpersonaidpublicagentpersonatraits,omitempty"` // Has many ModelPublicAgentPersonaTraits + RelPersonaIDPublicPersonaArcs []*ModelPublicPersonaArc `bun:"rel:has-many,join:id=persona_id" json:"relpersonaidpublicpersonaarcs,omitempty"` // Has many ModelPublicPersonaArc +} + +// TableName returns the table name for ModelPublicAgentPersonas +func (m ModelPublicAgentPersonas) TableName() string { + return "public.agent_personas" +} + +// TableNameOnly returns the table name without schema for ModelPublicAgentPersonas +func (m ModelPublicAgentPersonas) TableNameOnly() string { + return "agent_personas" +} + +// SchemaName returns the schema name for ModelPublicAgentPersonas +func (m ModelPublicAgentPersonas) SchemaName() string { + return "public" +} + +// GetID returns the primary key value +func (m ModelPublicAgentPersonas) GetID() int64 { + return m.ID.Int64() +} + +// GetIDStr returns the primary key as a string +func (m ModelPublicAgentPersonas) GetIDStr() string { + return fmt.Sprintf("%v", m.ID) +} + +// SetID sets the primary key value +func (m ModelPublicAgentPersonas) SetID(newid int64) { + m.UpdateID(newid) +} + +// UpdateID updates the primary key value +func (m *ModelPublicAgentPersonas) UpdateID(newid int64) { + m.ID.FromString(fmt.Sprintf("%d", newid)) +} + +// GetIDName returns the name of the primary key column +func (m ModelPublicAgentPersonas) GetIDName() string { + return "id" +} + +// GetPrefix returns the table prefix +func (m ModelPublicAgentPersonas) GetPrefix() string { + return "APG" +} diff --git a/internal/generatedmodels/sql_public_agent_skills.go b/internal/generatedmodels/sql_public_agent_skills.go index b71f01f..b48b505 100644 --- a/internal/generatedmodels/sql_public_agent_skills.go +++ b/internal/generatedmodels/sql_public_agent_skills.go @@ -8,22 +8,23 @@ import ( ) type ModelPublicAgentSkills struct { - bun.BaseModel `bun:"table:public.agent_skills,alias:agent_skills"` - ID resolvespec_common.SqlInt64 `bun:"id,type:bigserial,pk,autoincrement," json:"id"` - Content resolvespec_common.SqlString `bun:"content,type:text,notnull," json:"content"` - CreatedAt resolvespec_common.SqlTimeStamp `bun:"created_at,type:timestamptz,default:now(),notnull," json:"created_at"` - Description resolvespec_common.SqlString `bun:"description,type:text,default:'',notnull," json:"description"` - DomainTags resolvespec_common.SqlStringArray `bun:"domain_tags,type:text[],default:'{}',notnull," json:"domain_tags"` - FrameworkTags resolvespec_common.SqlStringArray `bun:"framework_tags,type:text[],default:'{}',notnull," json:"framework_tags"` - GUID resolvespec_common.SqlUUID `bun:"guid,type:uuid,default:gen_random_uuid(),notnull," json:"guid"` - LanguageTags resolvespec_common.SqlStringArray `bun:"language_tags,type:text[],default:'{}',notnull," json:"language_tags"` - LibraryTags resolvespec_common.SqlStringArray `bun:"library_tags,type:text[],default:'{}',notnull," json:"library_tags"` - Name resolvespec_common.SqlString `bun:"name,type:text,notnull," json:"name"` - Tags resolvespec_common.SqlStringArray `bun:"tags,type:text[],default:'{}',notnull," json:"tags"` - UpdatedAt resolvespec_common.SqlTimeStamp `bun:"updated_at,type:timestamptz,default:now(),notnull," json:"updated_at"` - RelRelatedSkillIDPublicLearnings []*ModelPublicLearnings `bun:"rel:has-many,join:id=related_skill_id" json:"relrelatedskillidpubliclearnings,omitempty"` // Has many ModelPublicLearnings - RelSkillIDPublicPlanSkills []*ModelPublicPlanSkills `bun:"rel:has-many,join:id=skill_id" json:"relskillidpublicplanskills,omitempty"` // Has many ModelPublicPlanSkills - RelSkillIDPublicProjectSkills []*ModelPublicProjectSkills `bun:"rel:has-many,join:id=skill_id" json:"relskillidpublicprojectskills,omitempty"` // Has many ModelPublicProjectSkills + bun.BaseModel `bun:"table:public.agent_skills,alias:agent_skills"` + ID resolvespec_common.SqlInt64 `bun:"id,type:bigserial,pk,autoincrement," json:"id"` + Content resolvespec_common.SqlString `bun:"content,type:text,notnull," json:"content"` + CreatedAt resolvespec_common.SqlTimeStamp `bun:"created_at,type:timestamptz,default:now(),notnull," json:"created_at"` + Description resolvespec_common.SqlString `bun:"description,type:text,default:'',notnull," json:"description"` + DomainTags resolvespec_common.SqlStringArray `bun:"domain_tags,type:text[],default:'{}',notnull," json:"domain_tags"` + FrameworkTags resolvespec_common.SqlStringArray `bun:"framework_tags,type:text[],default:'{}',notnull," json:"framework_tags"` + GUID resolvespec_common.SqlUUID `bun:"guid,type:uuid,default:gen_random_uuid(),notnull," json:"guid"` + LanguageTags resolvespec_common.SqlStringArray `bun:"language_tags,type:text[],default:'{}',notnull," json:"language_tags"` + LibraryTags resolvespec_common.SqlStringArray `bun:"library_tags,type:text[],default:'{}',notnull," json:"library_tags"` + Name resolvespec_common.SqlString `bun:"name,type:text,notnull," json:"name"` + Tags resolvespec_common.SqlStringArray `bun:"tags,type:text[],default:'{}',notnull," json:"tags"` + UpdatedAt resolvespec_common.SqlTimeStamp `bun:"updated_at,type:timestamptz,default:now(),notnull," json:"updated_at"` + RelSkillIDPublicAgentPersonaSkills []*ModelPublicAgentPersonaSkills `bun:"rel:has-many,join:id=skill_id" json:"relskillidpublicagentpersonaskills,omitempty"` // Has many ModelPublicAgentPersonaSkills + RelRelatedSkillIDPublicLearnings []*ModelPublicLearnings `bun:"rel:has-many,join:id=related_skill_id" json:"relrelatedskillidpubliclearnings,omitempty"` // Has many ModelPublicLearnings + RelSkillIDPublicPlanSkills []*ModelPublicPlanSkills `bun:"rel:has-many,join:id=skill_id" json:"relskillidpublicplanskills,omitempty"` // Has many ModelPublicPlanSkills + RelSkillIDPublicProjectSkills []*ModelPublicProjectSkills `bun:"rel:has-many,join:id=skill_id" json:"relskillidpublicprojectskills,omitempty"` // Has many ModelPublicProjectSkills } // TableName returns the table name for ModelPublicAgentSkills diff --git a/internal/generatedmodels/sql_public_agent_traits.go b/internal/generatedmodels/sql_public_agent_traits.go new file mode 100644 index 0000000..b1e75b6 --- /dev/null +++ b/internal/generatedmodels/sql_public_agent_traits.go @@ -0,0 +1,67 @@ +// Code generated by relspecgo. DO NOT EDIT. +package generatedmodels + +import ( + "fmt" + resolvespec_common "github.com/bitechdev/ResolveSpec/pkg/spectypes" + "github.com/uptrace/bun" +) + +type ModelPublicAgentTraits struct { + bun.BaseModel `bun:"table:public.agent_traits,alias:agent_traits"` + ID resolvespec_common.SqlInt64 `bun:"id,type:bigserial,pk,autoincrement," json:"id"` + CreatedAt resolvespec_common.SqlTimeStamp `bun:"created_at,type:timestamptz,default:now(),notnull," json:"created_at"` + Description resolvespec_common.SqlString `bun:"description,type:text,default:'',notnull," json:"description"` + GUID resolvespec_common.SqlUUID `bun:"guid,type:uuid,default:gen_random_uuid(),notnull," json:"guid"` + Instruction resolvespec_common.SqlString `bun:"instruction,type:text,default:'',notnull," json:"instruction"` + Name resolvespec_common.SqlString `bun:"name,type:text,notnull," json:"name"` + Tags resolvespec_common.SqlStringArray `bun:"tags,type:text[],default:'{}',notnull," json:"tags"` + TraitType resolvespec_common.SqlString `bun:"trait_type,type:text,notnull," json:"trait_type"` + UpdatedAt resolvespec_common.SqlTimeStamp `bun:"updated_at,type:timestamptz,default:now(),notnull," json:"updated_at"` + RelTraitIDPublicAgentPersonaTraits []*ModelPublicAgentPersonaTraits `bun:"rel:has-many,join:id=trait_id" json:"reltraitidpublicagentpersonatraits,omitempty"` // Has many ModelPublicAgentPersonaTraits +} + +// TableName returns the table name for ModelPublicAgentTraits +func (m ModelPublicAgentTraits) TableName() string { + return "public.agent_traits" +} + +// TableNameOnly returns the table name without schema for ModelPublicAgentTraits +func (m ModelPublicAgentTraits) TableNameOnly() string { + return "agent_traits" +} + +// SchemaName returns the schema name for ModelPublicAgentTraits +func (m ModelPublicAgentTraits) SchemaName() string { + return "public" +} + +// GetID returns the primary key value +func (m ModelPublicAgentTraits) GetID() int64 { + return m.ID.Int64() +} + +// GetIDStr returns the primary key as a string +func (m ModelPublicAgentTraits) GetIDStr() string { + return fmt.Sprintf("%v", m.ID) +} + +// SetID sets the primary key value +func (m ModelPublicAgentTraits) SetID(newid int64) { + m.UpdateID(newid) +} + +// UpdateID updates the primary key value +func (m *ModelPublicAgentTraits) UpdateID(newid int64) { + m.ID.FromString(fmt.Sprintf("%d", newid)) +} + +// GetIDName returns the name of the primary key column +func (m ModelPublicAgentTraits) GetIDName() string { + return "id" +} + +// GetPrefix returns the table prefix +func (m ModelPublicAgentTraits) GetPrefix() string { + return "ATG" +} diff --git a/internal/generatedmodels/sql_public_arc_stage_parts.go b/internal/generatedmodels/sql_public_arc_stage_parts.go new file mode 100644 index 0000000..176d07d --- /dev/null +++ b/internal/generatedmodels/sql_public_arc_stage_parts.go @@ -0,0 +1,34 @@ +// Code generated by relspecgo. DO NOT EDIT. +package generatedmodels + +import ( + "github.com/uptrace/bun" +) + +type ModelPublicArcStageParts struct { + bun.BaseModel `bun:"table:public.arc_stage_parts,alias:arc_stage_parts"` + PartID int64 `bun:"part_id,type:bigint,notnull," json:"part_id"` + StageID int64 `bun:"stage_id,type:bigint,notnull," json:"stage_id"` + RelPartID *ModelPublicAgentParts `bun:"rel:has-one,join:part_id=id" json:"relpartid,omitempty"` // Has one ModelPublicAgentParts + RelStageID *ModelPublicArcStages `bun:"rel:has-one,join:stage_id=id" json:"relstageid,omitempty"` // Has one ModelPublicArcStages +} + +// TableName returns the table name for ModelPublicArcStageParts +func (m ModelPublicArcStageParts) TableName() string { + return "public.arc_stage_parts" +} + +// TableNameOnly returns the table name without schema for ModelPublicArcStageParts +func (m ModelPublicArcStageParts) TableNameOnly() string { + return "arc_stage_parts" +} + +// SchemaName returns the schema name for ModelPublicArcStageParts +func (m ModelPublicArcStageParts) SchemaName() string { + return "public" +} + +// GetPrefix returns the table prefix +func (m ModelPublicArcStageParts) GetPrefix() string { + return "ASP" +} diff --git a/internal/generatedmodels/sql_public_arc_stages.go b/internal/generatedmodels/sql_public_arc_stages.go new file mode 100644 index 0000000..30c58fc --- /dev/null +++ b/internal/generatedmodels/sql_public_arc_stages.go @@ -0,0 +1,67 @@ +// Code generated by relspecgo. DO NOT EDIT. +package generatedmodels + +import ( + "fmt" + resolvespec_common "github.com/bitechdev/ResolveSpec/pkg/spectypes" + "github.com/uptrace/bun" +) + +type ModelPublicArcStages struct { + bun.BaseModel `bun:"table:public.arc_stages,alias:arc_stages"` + ID resolvespec_common.SqlInt64 `bun:"id,type:bigserial,pk,autoincrement," json:"id"` + ArcID int64 `bun:"arc_id,type:bigint,notnull," json:"arc_id"` + Condition resolvespec_common.SqlString `bun:"condition,type:text,default:'',notnull," json:"condition"` + CreatedAt resolvespec_common.SqlTimeStamp `bun:"created_at,type:timestamptz,default:now(),notnull," json:"created_at"` + Description resolvespec_common.SqlString `bun:"description,type:text,default:'',notnull," json:"description"` + Name resolvespec_common.SqlString `bun:"name,type:text,notnull," json:"name"` + StageOrder int32 `bun:"stage_order,type:int,default:0,notnull," json:"stage_order"` + RelArcID *ModelPublicCharacterArcs `bun:"rel:has-one,join:arc_id=id" json:"relarcid,omitempty"` // Has one ModelPublicCharacterArcs + RelStageIDPublicArcStageParts []*ModelPublicArcStageParts `bun:"rel:has-many,join:id=stage_id" json:"relstageidpublicarcstageparts,omitempty"` // Has many ModelPublicArcStageParts + RelCurrentStageIDPublicPersonaArcs []*ModelPublicPersonaArc `bun:"rel:has-many,join:id=current_stage_id" json:"relcurrentstageidpublicpersonaarcs,omitempty"` // Has many ModelPublicPersonaArc +} + +// TableName returns the table name for ModelPublicArcStages +func (m ModelPublicArcStages) TableName() string { + return "public.arc_stages" +} + +// TableNameOnly returns the table name without schema for ModelPublicArcStages +func (m ModelPublicArcStages) TableNameOnly() string { + return "arc_stages" +} + +// SchemaName returns the schema name for ModelPublicArcStages +func (m ModelPublicArcStages) SchemaName() string { + return "public" +} + +// GetID returns the primary key value +func (m ModelPublicArcStages) GetID() int64 { + return m.ID.Int64() +} + +// GetIDStr returns the primary key as a string +func (m ModelPublicArcStages) GetIDStr() string { + return fmt.Sprintf("%v", m.ID) +} + +// SetID sets the primary key value +func (m ModelPublicArcStages) SetID(newid int64) { + m.UpdateID(newid) +} + +// UpdateID updates the primary key value +func (m *ModelPublicArcStages) UpdateID(newid int64) { + m.ID.FromString(fmt.Sprintf("%d", newid)) +} + +// GetIDName returns the name of the primary key column +func (m ModelPublicArcStages) GetIDName() string { + return "id" +} + +// GetPrefix returns the table prefix +func (m ModelPublicArcStages) GetPrefix() string { + return "ASR" +} diff --git a/internal/generatedmodels/sql_public_character_arcs.go b/internal/generatedmodels/sql_public_character_arcs.go new file mode 100644 index 0000000..c7d194d --- /dev/null +++ b/internal/generatedmodels/sql_public_character_arcs.go @@ -0,0 +1,65 @@ +// Code generated by relspecgo. DO NOT EDIT. +package generatedmodels + +import ( + "fmt" + resolvespec_common "github.com/bitechdev/ResolveSpec/pkg/spectypes" + "github.com/uptrace/bun" +) + +type ModelPublicCharacterArcs struct { + bun.BaseModel `bun:"table:public.character_arcs,alias:character_arcs"` + ID resolvespec_common.SqlInt64 `bun:"id,type:bigserial,pk,autoincrement," json:"id"` + CreatedAt resolvespec_common.SqlTimeStamp `bun:"created_at,type:timestamptz,default:now(),notnull," json:"created_at"` + Description resolvespec_common.SqlString `bun:"description,type:text,default:'',notnull," json:"description"` + Name resolvespec_common.SqlString `bun:"name,type:text,notnull," json:"name"` + Summary resolvespec_common.SqlString `bun:"summary,type:text,default:'',notnull," json:"summary"` + UpdatedAt resolvespec_common.SqlTimeStamp `bun:"updated_at,type:timestamptz,default:now(),notnull," json:"updated_at"` + RelArcIDPublicArcStages []*ModelPublicArcStages `bun:"rel:has-many,join:id=arc_id" json:"relarcidpublicarcstages,omitempty"` // Has many ModelPublicArcStages + RelArcIDPublicPersonaArcs []*ModelPublicPersonaArc `bun:"rel:has-many,join:id=arc_id" json:"relarcidpublicpersonaarcs,omitempty"` // Has many ModelPublicPersonaArc +} + +// TableName returns the table name for ModelPublicCharacterArcs +func (m ModelPublicCharacterArcs) TableName() string { + return "public.character_arcs" +} + +// TableNameOnly returns the table name without schema for ModelPublicCharacterArcs +func (m ModelPublicCharacterArcs) TableNameOnly() string { + return "character_arcs" +} + +// SchemaName returns the schema name for ModelPublicCharacterArcs +func (m ModelPublicCharacterArcs) SchemaName() string { + return "public" +} + +// GetID returns the primary key value +func (m ModelPublicCharacterArcs) GetID() int64 { + return m.ID.Int64() +} + +// GetIDStr returns the primary key as a string +func (m ModelPublicCharacterArcs) GetIDStr() string { + return fmt.Sprintf("%v", m.ID) +} + +// SetID sets the primary key value +func (m ModelPublicCharacterArcs) SetID(newid int64) { + m.UpdateID(newid) +} + +// UpdateID updates the primary key value +func (m *ModelPublicCharacterArcs) UpdateID(newid int64) { + m.ID.FromString(fmt.Sprintf("%d", newid)) +} + +// GetIDName returns the name of the primary key column +func (m ModelPublicCharacterArcs) GetIDName() string { + return "id" +} + +// GetPrefix returns the table prefix +func (m ModelPublicCharacterArcs) GetPrefix() string { + return "CAH" +} diff --git a/internal/generatedmodels/sql_public_persona_arc.go b/internal/generatedmodels/sql_public_persona_arc.go new file mode 100644 index 0000000..01f2eaf --- /dev/null +++ b/internal/generatedmodels/sql_public_persona_arc.go @@ -0,0 +1,64 @@ +// Code generated by relspecgo. DO NOT EDIT. +package generatedmodels + +import ( + "fmt" + resolvespec_common "github.com/bitechdev/ResolveSpec/pkg/spectypes" + "github.com/uptrace/bun" +) + +type ModelPublicPersonaArc struct { + bun.BaseModel `bun:"table:public.persona_arc,alias:persona_arc"` + PersonaID int64 `bun:"persona_id,type:bigint,pk," json:"persona_id"` + ArcID int64 `bun:"arc_id,type:bigint,notnull," json:"arc_id"` + CurrentStageID int64 `bun:"current_stage_id,type:bigint,notnull," json:"current_stage_id"` + UpdatedAt resolvespec_common.SqlTimeStamp `bun:"updated_at,type:timestamptz,default:now(),notnull," json:"updated_at"` + RelArcID *ModelPublicCharacterArcs `bun:"rel:has-one,join:arc_id=id" json:"relarcid,omitempty"` // Has one ModelPublicCharacterArcs + RelCurrentStageID *ModelPublicArcStages `bun:"rel:has-one,join:current_stage_id=id" json:"relcurrentstageid,omitempty"` // Has one ModelPublicArcStages + RelPersonaID *ModelPublicAgentPersonas `bun:"rel:has-one,join:persona_id=id" json:"relpersonaid,omitempty"` // Has one ModelPublicAgentPersonas +} + +// TableName returns the table name for ModelPublicPersonaArc +func (m ModelPublicPersonaArc) TableName() string { + return "public.persona_arc" +} + +// TableNameOnly returns the table name without schema for ModelPublicPersonaArc +func (m ModelPublicPersonaArc) TableNameOnly() string { + return "persona_arc" +} + +// SchemaName returns the schema name for ModelPublicPersonaArc +func (m ModelPublicPersonaArc) SchemaName() string { + return "public" +} + +// GetID returns the primary key value +func (m ModelPublicPersonaArc) GetID() int64 { + return int64(m.PersonaID) +} + +// GetIDStr returns the primary key as a string +func (m ModelPublicPersonaArc) GetIDStr() string { + return fmt.Sprintf("%d", m.PersonaID) +} + +// SetID sets the primary key value +func (m ModelPublicPersonaArc) SetID(newid int64) { + m.UpdateID(newid) +} + +// UpdateID updates the primary key value +func (m *ModelPublicPersonaArc) UpdateID(newid int64) { + m.PersonaID = newid +} + +// GetIDName returns the name of the primary key column +func (m ModelPublicPersonaArc) GetIDName() string { + return "persona_id" +} + +// GetPrefix returns the table prefix +func (m ModelPublicPersonaArc) GetPrefix() string { + return "PAE" +} diff --git a/internal/mcpserver/server.go b/internal/mcpserver/server.go index 792de0f..a8de9fe 100644 --- a/internal/mcpserver/server.go +++ b/internal/mcpserver/server.go @@ -38,6 +38,7 @@ type ToolSet struct { RetryMetadata *tools.RetryEnrichmentTool //Maintenance *tools.MaintenanceTool Skills *tools.SkillsTool + Personas *tools.AgentPersonasTool ChatHistory *tools.ChatHistoryTool Describe *tools.DescribeTool Learnings *tools.LearningsTool @@ -90,6 +91,7 @@ func NewHandlers(cfg config.MCPConfig, logger *slog.Logger, toolSet ToolSet, onS registerFileTools, registerMaintenanceTools, registerSkillTools, + registerPersonaTools, registerChatHistoryTools, registerDescribeTools, } { @@ -565,10 +567,114 @@ func registerChatHistoryTools(server *mcp.Server, logger *slog.Logger, toolSet T return nil } +func registerPersonaTools(server *mcp.Server, logger *slog.Logger, toolSet ToolSet) error { + p := toolSet.Personas + if err := addTool(server, logger, &mcp.Tool{Name: "create_agent_persona", Description: "Create a named, loadable agent persona."}, p.CreatePersona); err != nil { + return err + } + if err := addTool(server, logger, &mcp.Tool{Name: "update_agent_persona", Description: "Update persona fields (name, description, summary, detail, tags)."}, p.UpdatePersona); err != nil { + return err + } + if err := addTool(server, logger, &mcp.Tool{Name: "delete_agent_persona", Description: "Delete a persona by name."}, p.DeletePersona); err != nil { + return err + } + if err := addTool(server, logger, &mcp.Tool{Name: "list_agent_personas", Description: "List all personas, optionally filtered by tag."}, p.ListPersonas); err != nil { + return err + } + if err := addTool(server, logger, &mcp.Tool{Name: "get_agent_persona", Description: "Load a persona with assembled parts. detail=true returns full content. overrides replaces parts per type at runtime."}, p.GetPersona); err != nil { + return err + } + if err := addTool(server, logger, &mcp.Tool{Name: "get_persona_manifest", Description: "Lightweight structure-only view: parts, traits, skills, guardrails — no content. Includes on_demand_tools hints."}, p.GetPersonaManifest); err != nil { + return err + } + if err := addTool(server, logger, &mcp.Tool{Name: "compile_persona", Description: "Regenerate compiled_summary and compiled_detail from current parts and arc stage. Use compiled_summary for agents with tight context budgets."}, p.CompilePersona); err != nil { + return err + } + if err := addTool(server, logger, &mcp.Tool{Name: "create_agent_part", Description: "Create a reusable behaviour building block. Parts compose personas and can be overridden at load time by name."}, p.CreatePart); err != nil { + return err + } + if err := addTool(server, logger, &mcp.Tool{Name: "update_agent_part", Description: "Update part fields (name, part_type, description, summary, content, tags)."}, p.UpdatePart); err != nil { + return err + } + if err := addTool(server, logger, &mcp.Tool{Name: "delete_agent_part", Description: "Delete a part by name."}, p.DeletePart); err != nil { + return err + } + if err := addTool(server, logger, &mcp.Tool{Name: "list_agent_parts", Description: "List parts, optionally filtered by part_type or tag."}, p.ListParts); err != nil { + return err + } + if err := addTool(server, logger, &mcp.Tool{Name: "get_agent_part", Description: "Fetch a single part with full content by name."}, p.GetPart); err != nil { + return err + } + if err := addTool(server, logger, &mcp.Tool{Name: "add_persona_part", Description: "Link a part to a persona with optional order and priority."}, p.AddPersonaPart); err != nil { + return err + } + if err := addTool(server, logger, &mcp.Tool{Name: "remove_persona_part", Description: "Unlink a part from a persona."}, p.RemovePersonaPart); err != nil { + return err + } + if err := addTool(server, logger, &mcp.Tool{Name: "add_persona_skill", Description: "Link an agent_skill to a persona."}, p.AddPersonaSkill); err != nil { + return err + } + if err := addTool(server, logger, &mcp.Tool{Name: "remove_persona_skill", Description: "Unlink an agent_skill from a persona."}, p.RemovePersonaSkill); err != nil { + return err + } + if err := addTool(server, logger, &mcp.Tool{Name: "add_persona_guardrail", Description: "Link an agent_guardrail to a persona."}, p.AddPersonaGuardrail); err != nil { + return err + } + if err := addTool(server, logger, &mcp.Tool{Name: "remove_persona_guardrail", Description: "Unlink an agent_guardrail from a persona."}, p.RemovePersonaGuardrail); err != nil { + return err + } + if err := addTool(server, logger, &mcp.Tool{Name: "create_agent_trait", Description: "Create an atomic personality trait (personality, cognitive, emotional, social, behavioral)."}, p.CreateTrait); err != nil { + return err + } + if err := addTool(server, logger, &mcp.Tool{Name: "update_agent_trait", Description: "Update trait fields."}, p.UpdateTrait); err != nil { + return err + } + if err := addTool(server, logger, &mcp.Tool{Name: "delete_agent_trait", Description: "Delete a trait by name."}, p.DeleteTrait); err != nil { + return err + } + if err := addTool(server, logger, &mcp.Tool{Name: "list_agent_traits", Description: "List traits, optionally filtered by trait_type or tag."}, p.ListTraits); err != nil { + return err + } + if err := addTool(server, logger, &mcp.Tool{Name: "get_agent_trait", Description: "Fetch a single trait with instruction by name."}, p.GetTrait); err != nil { + return err + } + if err := addTool(server, logger, &mcp.Tool{Name: "add_persona_trait", Description: "Link a trait to a persona."}, p.AddPersonaTrait); err != nil { + return err + } + if err := addTool(server, logger, &mcp.Tool{Name: "remove_persona_trait", Description: "Unlink a trait from a persona."}, p.RemovePersonaTrait); err != nil { + return err + } + if err := addTool(server, logger, &mcp.Tool{Name: "create_character_arc", Description: "Define a named character progression arc."}, p.CreateCharacterArc); err != nil { + return err + } + if err := addTool(server, logger, &mcp.Tool{Name: "list_character_arcs", Description: "List all character arcs."}, p.ListCharacterArcs); err != nil { + return err + } + if err := addTool(server, logger, &mcp.Tool{Name: "add_arc_stage", Description: "Add an ordered stage to a character arc."}, p.AddArcStage); err != nil { + return err + } + if err := addTool(server, logger, &mcp.Tool{Name: "add_stage_part", Description: "Link a part to an arc stage — active when the persona is at that stage."}, p.AddStagePart); err != nil { + return err + } + if err := addTool(server, logger, &mcp.Tool{Name: "remove_stage_part", Description: "Unlink a part from an arc stage."}, p.RemoveStagePart); err != nil { + return err + } + if err := addTool(server, logger, &mcp.Tool{Name: "assign_persona_arc", Description: "Attach an arc to a persona and set the starting stage."}, p.AssignPersonaArc); err != nil { + return err + } + if err := addTool(server, logger, &mcp.Tool{Name: "advance_persona_stage", Description: "Move persona to the next stage in its arc."}, p.AdvancePersonaStage); err != nil { + return err + } + if err := addTool(server, logger, &mcp.Tool{Name: "reset_persona_stage", Description: "Reset persona to the first stage of its arc."}, p.ResetPersonaStage); err != nil { + return err + } + return nil +} + func registerDescribeTools(server *mcp.Server, logger *slog.Logger, toolSet ToolSet) error { if err := addTool(server, logger, &mcp.Tool{ Name: "describe_tools", - Description: "Call first each session. All tools with categories and usage notes. Categories: system, thoughts, projects, files, admin, maintenance, skills, plans, chat, meta.", + Description: "Call first each session. All tools with categories and usage notes. Categories: system, thoughts, projects, files, admin, maintenance, skills, personas, plans, chat, meta.", }, toolSet.Describe.Describe); err != nil { return err } @@ -664,6 +770,41 @@ func BuildToolCatalog() []tools.ToolEntry { {Name: "remove_project_guardrail", Description: "Unlink an agent guardrail from a project. Pass project explicitly when your client does not preserve MCP sessions.", Category: "skills"}, {Name: "list_project_guardrails", Description: "List all guardrails linked to a project. Call this at the start of every project session to load agent constraints before generating new ones. Only create new guardrails if none are returned. Pass project explicitly when your client does not preserve MCP sessions.", Category: "skills"}, + // personas + {Name: "create_agent_persona", Description: "Create a named, loadable agent persona that composes parts, traits, skills, and guardrails.", Category: "personas"}, + {Name: "update_agent_persona", Description: "Update persona fields.", Category: "personas"}, + {Name: "delete_agent_persona", Description: "Delete a persona by name.", Category: "personas"}, + {Name: "list_agent_personas", Description: "List all personas, optionally filtered by tag.", Category: "personas"}, + {Name: "get_agent_persona", Description: "Load a persona with all assembled parts. detail=true returns full content. overrides replaces parts per type at runtime without modifying the persona.", Category: "personas"}, + {Name: "get_persona_manifest", Description: "Lightweight discovery: returns persona structure (parts, traits, skills, guardrails) with no content, plus on_demand_tools hints. Call this first for unknown personas.", Category: "personas"}, + {Name: "compile_persona", Description: "Regenerate compiled_summary and compiled_detail from current parts and arc stage. Agents with tight context budgets can use compiled_summary directly.", Category: "personas"}, + {Name: "create_agent_part", Description: "Create a reusable behaviour building block. Part types: system, agent, soul, identity, skill, specialization, tone, goal, context, protocol, backstory, motivation, voice, archetype, flaw, relationship.", Category: "personas"}, + {Name: "update_agent_part", Description: "Update part fields.", Category: "personas"}, + {Name: "delete_agent_part", Description: "Delete a part by name.", Category: "personas"}, + {Name: "list_agent_parts", Description: "List parts, optionally filtered by part_type or tag.", Category: "personas"}, + {Name: "get_agent_part", Description: "Fetch a single part with full content by name.", Category: "personas"}, + {Name: "add_persona_part", Description: "Link a part to a persona with optional order and priority.", Category: "personas"}, + {Name: "remove_persona_part", Description: "Unlink a part from a persona.", Category: "personas"}, + {Name: "add_persona_skill", Description: "Link an agent_skill to a persona.", Category: "personas"}, + {Name: "remove_persona_skill", Description: "Unlink an agent_skill from a persona.", Category: "personas"}, + {Name: "add_persona_guardrail", Description: "Link an agent_guardrail to a persona.", Category: "personas"}, + {Name: "remove_persona_guardrail", Description: "Unlink an agent_guardrail from a persona.", Category: "personas"}, + {Name: "create_agent_trait", Description: "Create an atomic personality trait. Trait types: personality, cognitive, emotional, social, behavioral.", Category: "personas"}, + {Name: "update_agent_trait", Description: "Update trait fields.", Category: "personas"}, + {Name: "delete_agent_trait", Description: "Delete a trait by name.", Category: "personas"}, + {Name: "list_agent_traits", Description: "List traits, optionally filtered by trait_type or tag.", Category: "personas"}, + {Name: "get_agent_trait", Description: "Fetch a single trait with instruction by name. Use this for on-demand trait loading.", Category: "personas"}, + {Name: "add_persona_trait", Description: "Link a trait to a persona.", Category: "personas"}, + {Name: "remove_persona_trait", Description: "Unlink a trait from a persona.", Category: "personas"}, + {Name: "create_character_arc", Description: "Define a named character progression arc with ordered stages.", Category: "personas"}, + {Name: "list_character_arcs", Description: "List all character arcs.", Category: "personas"}, + {Name: "add_arc_stage", Description: "Add an ordered stage to a character arc.", Category: "personas"}, + {Name: "add_stage_part", Description: "Link a part to an arc stage — overrides matching persona parts when that stage is active.", Category: "personas"}, + {Name: "remove_stage_part", Description: "Unlink a part from an arc stage.", Category: "personas"}, + {Name: "assign_persona_arc", Description: "Attach a character arc to a persona and set the starting stage.", Category: "personas"}, + {Name: "advance_persona_stage", Description: "Move persona to the next stage in its arc.", Category: "personas"}, + {Name: "reset_persona_stage", Description: "Reset persona to the first stage of its arc.", Category: "personas"}, + // chat {Name: "save_chat_history", Description: "Save a chat session's message history for later retrieval. Stores messages with optional title, summary, channel, agent, and project metadata.", Category: "chat"}, {Name: "get_chat_history", Description: "Retrieve a saved chat history by its UUID or session_id. Returns the full message list.", Category: "chat"}, @@ -671,7 +812,7 @@ func BuildToolCatalog() []tools.ToolEntry { {Name: "delete_chat_history", Description: "Permanently delete a saved chat history by id.", Category: "chat"}, // meta - {Name: "describe_tools", Description: "Call this first in every session. Returns all available MCP tools with names, descriptions, categories, and your accumulated usage notes. Filter by category to narrow results. Available categories: system, thoughts, projects, files, admin, household, maintenance, calendar, meals, crm, skills, plans, chat, meta.", Category: "meta"}, + {Name: "describe_tools", Description: "Call this first in every session. Returns all available MCP tools with names, descriptions, categories, and your accumulated usage notes. Filter by category to narrow results. Available categories: system, thoughts, projects, files, admin, household, maintenance, calendar, meals, crm, skills, personas, plans, chat, meta.", Category: "meta"}, {Name: "annotate_tool", Description: "Persist usage notes, gotchas, or workflow patterns for a specific tool. Notes survive across sessions and are returned by describe_tools. Call this whenever you discover something non-obvious about a tool's behaviour. Pass an empty string to clear notes.", Category: "meta"}, } } diff --git a/internal/store/agent_personas.go b/internal/store/agent_personas.go new file mode 100644 index 0000000..6b31d1e --- /dev/null +++ b/internal/store/agent_personas.go @@ -0,0 +1,1198 @@ +package store + +import ( + "context" + "fmt" + "strings" + "time" + + "github.com/google/uuid" + + ext "git.warky.dev/wdevs/amcs/internal/types" +) + +// ────────────────────────────────────────────── +// Helpers +// ────────────────────────────────────────────── + +func nilToEmptyStrings(s []string) []string { + if s == nil { + return []string{} + } + return s +} + +// ────────────────────────────────────────────── +// Personas +// ────────────────────────────────────────────── + +func (db *DB) CreatePersona(ctx context.Context, p ext.Persona) (ext.Persona, error) { + if p.Tags == nil { + p.Tags = []string{} + } + row := db.pool.QueryRow(ctx, ` + insert into agent_personas (name, description, summary, detail, tags) + values ($1, $2, $3, $4, $5) + returning id, guid, compiled_summary, compiled_detail, compiled_at, created_at, updated_at + `, p.Name, p.Description, p.Summary, p.Detail, p.Tags) + + created := p + var id int64 + var guid uuid.UUID + var cs, cd string + var compiledAt *time.Time + var createdAt, updatedAt time.Time + if err := row.Scan(&id, &guid, &cs, &cd, &compiledAt, &createdAt, &updatedAt); err != nil { + return ext.Persona{}, fmt.Errorf("create persona: %w", err) + } + created.ID = id + created.GUID = guid + created.CompiledSummary = cs + created.CompiledDetail = cd + created.CompiledAt = compiledAt + created.CreatedAt = createdAt + created.UpdatedAt = updatedAt + return created, nil +} + +func (db *DB) UpdatePersona(ctx context.Context, name string, updates map[string]any) (ext.Persona, error) { + if len(updates) == 0 { + return db.GetPersonaByName(ctx, name) + } + allowed := map[string]bool{"name": true, "description": true, "summary": true, "detail": true, "tags": true} + setClauses := []string{} + args := []any{} + for col, val := range updates { + if !allowed[col] { + return ext.Persona{}, fmt.Errorf("unknown field: %s", col) + } + args = append(args, val) + setClauses = append(setClauses, fmt.Sprintf("%s = $%d", col, len(args))) + } + args = append(args, name) + q := fmt.Sprintf(` + update agent_personas set %s, updated_at = now() + where name = $%d + returning id, guid, name, description, summary, detail, compiled_summary, compiled_detail, compiled_at, tags::text[], created_at, updated_at + `, strings.Join(setClauses, ", "), len(args)) + + row := db.pool.QueryRow(ctx, q, args...) + return scanPersona(row) +} + +func (db *DB) DeletePersona(ctx context.Context, name string) error { + tag, err := db.pool.Exec(ctx, `delete from agent_personas where name = $1`, name) + if err != nil { + return fmt.Errorf("delete persona: %w", err) + } + if tag.RowsAffected() == 0 { + return fmt.Errorf("persona not found: %s", name) + } + return nil +} + +func (db *DB) ListPersonas(ctx context.Context, tag string) ([]ext.Persona, error) { + q := `select id, guid, name, description, summary, detail, compiled_summary, compiled_detail, compiled_at, tags::text[], created_at, updated_at from agent_personas` + args := []any{} + if t := strings.TrimSpace(tag); t != "" { + args = append(args, t) + q += fmt.Sprintf(" where $%d = any(tags)", len(args)) + } + q += " order by name" + rows, err := db.pool.Query(ctx, q, args...) + if err != nil { + return nil, fmt.Errorf("list personas: %w", err) + } + defer rows.Close() + + var personas []ext.Persona + for rows.Next() { + p, err := scanPersona(rows) + if err != nil { + return nil, fmt.Errorf("scan persona: %w", err) + } + personas = append(personas, p) + } + return personas, rows.Err() +} + +func (db *DB) GetPersonaByName(ctx context.Context, name string) (ext.Persona, error) { + row := db.pool.QueryRow(ctx, ` + select id, guid, name, description, summary, detail, compiled_summary, compiled_detail, compiled_at, tags::text[], created_at, updated_at + from agent_personas where name = $1 + `, name) + p, err := scanPersona(row) + if err != nil { + return ext.Persona{}, fmt.Errorf("get persona: %w", err) + } + return p, nil +} + +// GetPersona assembles the full persona with parts, skills, guardrails, traits, +// and arc state. Assembly priority per part_type: overrides > arc stage > persona base. +func (db *DB) GetPersona(ctx context.Context, name string, detail bool, overrides map[string]string) (ext.PersonaFull, error) { + // 1. Persona metadata + persona, err := db.GetPersonaByName(ctx, name) + if err != nil { + return ext.PersonaFull{}, err + } + + // 2. Base persona parts (ordered) + baseParts, err := db.listPersonaParts(ctx, persona.ID) + if err != nil { + return ext.PersonaFull{}, err + } + + // 3. Arc state + stage parts + arcState, stageParts, err := db.getArcStateAndParts(ctx, persona.ID) + if err != nil { + return ext.PersonaFull{}, err + } + + // 4. Assemble: start with base, override with arc stage parts per type + assembled := assembleParts(baseParts, stageParts, "arc_stage") + + // 5. Apply runtime overrides (fetch by name, replace per type) + if len(overrides) > 0 { + overrideParts, err := db.fetchPartsByNames(ctx, overrideValues(overrides)) + if err != nil { + return ext.PersonaFull{}, err + } + // Build name→part lookup + byName := make(map[string]rawPart, len(overrideParts)) + for _, p := range overrideParts { + byName[p.Name] = p + } + for partType, partName := range overrides { + op, ok := byName[partName] + if !ok { + return ext.PersonaFull{}, fmt.Errorf("override part not found: %s", partName) + } + if op.PartType != partType { + return ext.PersonaFull{}, fmt.Errorf("override part %q has type %q, expected %q", partName, op.PartType, partType) + } + // Remove all parts of this type, then append the override + without := assembled[:0] + for _, ap := range assembled { + if ap.PartType != partType { + without = append(without, ap) + } + } + op.Source = "override" + assembled = append(without, op) + } + } + + // 6. Skills, guardrails, traits + skills, err := db.listPersonaSkills(ctx, persona.ID) + if err != nil { + return ext.PersonaFull{}, err + } + guardrails, err := db.listPersonaGuardrails(ctx, persona.ID) + if err != nil { + return ext.PersonaFull{}, err + } + traits, err := db.listPersonaTraits(ctx, persona.ID) + if err != nil { + return ext.PersonaFull{}, err + } + + // 7. Build output + body := persona.Summary + if detail && persona.Detail != "" { + body = persona.Detail + } + + out := ext.PersonaFull{ + Name: persona.Name, + Description: persona.Description, + Body: body, + CompiledSummary: persona.CompiledSummary, + Tags: persona.Tags, + Arc: arcState, + Detail: detail, + } + + for _, rp := range assembled { + partBody := rp.Summary + if detail && rp.Content != "" { + partBody = rp.Content + } + out.Parts = append(out.Parts, ext.AssembledPart{ + Name: rp.Name, + PartType: rp.PartType, + Description: rp.Description, + Body: partBody, + Tags: rp.Tags, + Source: rp.Source, + }) + } + + for _, s := range skills { + entry := ext.PersonaSkillEntry{ + ID: s.ID, + Name: s.Name, + Description: s.Description, + Tags: s.Tags, + } + if detail { + entry.Content = s.Content + } + out.Skills = append(out.Skills, entry) + } + + for _, g := range guardrails { + entry := ext.PersonaGuardrailEntry{ + ID: g.ID, + Name: g.Name, + Description: g.Description, + Tags: g.Tags, + } + if detail { + entry.Content = g.Content + entry.Severity = g.Severity + } + out.Guardrails = append(out.Guardrails, entry) + } + + for _, t := range traits { + entry := ext.PersonaTraitEntry{ + ID: t.ID, + Name: t.Name, + TraitType: t.TraitType, + Description: t.Description, + Tags: t.Tags, + } + if detail { + entry.Instruction = t.Instruction + } + out.Traits = append(out.Traits, entry) + } + + if out.Parts == nil { + out.Parts = []ext.AssembledPart{} + } + if out.Skills == nil { + out.Skills = []ext.PersonaSkillEntry{} + } + if out.Guardrails == nil { + out.Guardrails = []ext.PersonaGuardrailEntry{} + } + if out.Traits == nil { + out.Traits = []ext.PersonaTraitEntry{} + } + + return out, nil +} + +// GetPersonaManifest returns a lightweight structure-only view of the persona. +func (db *DB) GetPersonaManifest(ctx context.Context, name string) (ext.PersonaManifest, error) { + persona, err := db.GetPersonaByName(ctx, name) + if err != nil { + return ext.PersonaManifest{}, err + } + + baseParts, err := db.listPersonaParts(ctx, persona.ID) + if err != nil { + return ext.PersonaManifest{}, err + } + arcState, stageParts, err := db.getArcStateAndParts(ctx, persona.ID) + if err != nil { + return ext.PersonaManifest{}, err + } + assembled := assembleParts(baseParts, stageParts, "arc_stage") + + skills, err := db.listPersonaSkills(ctx, persona.ID) + if err != nil { + return ext.PersonaManifest{}, err + } + guardrails, err := db.listPersonaGuardrails(ctx, persona.ID) + if err != nil { + return ext.PersonaManifest{}, err + } + traits, err := db.listPersonaTraits(ctx, persona.ID) + if err != nil { + return ext.PersonaManifest{}, err + } + + manifest := ext.PersonaManifest{ + Name: persona.Name, + Description: persona.Description, + Arc: arcState, + OnDemandTools: []string{ + "get_agent_part(name) — load full content of a specific part", + "get_agent_trait(name) — load instruction for a specific trait", + "get_skill(name) — load skill content", + "get_guardrail(name) — load guardrail content", + "compile_persona(name) — load pre-merged compiled_summary if full context needed", + "get_agent_persona(name, detail=true) — load full persona with all content", + }, + } + + for _, rp := range assembled { + manifest.Parts = append(manifest.Parts, ext.ManifestPart{ + Name: rp.Name, + PartType: rp.PartType, + Description: rp.Description, + }) + } + for _, s := range skills { + manifest.Skills = append(manifest.Skills, ext.ManifestSkill{ + ID: s.ID, + Name: s.Name, + Description: s.Description, + }) + } + for _, g := range guardrails { + manifest.Guardrails = append(manifest.Guardrails, ext.ManifestGuardrail{ + ID: g.ID, + Name: g.Name, + Description: g.Description, + Severity: g.Severity, + }) + } + for _, t := range traits { + manifest.Traits = append(manifest.Traits, ext.ManifestTrait{ + Name: t.Name, + TraitType: t.TraitType, + Description: t.Description, + }) + } + + if manifest.Parts == nil { + manifest.Parts = []ext.ManifestPart{} + } + if manifest.Skills == nil { + manifest.Skills = []ext.ManifestSkill{} + } + if manifest.Guardrails == nil { + manifest.Guardrails = []ext.ManifestGuardrail{} + } + if manifest.Traits == nil { + manifest.Traits = []ext.ManifestTrait{} + } + + return manifest, nil +} + +// CompilePersona regenerates compiled_summary and compiled_detail from current parts and arc stage. +func (db *DB) CompilePersona(ctx context.Context, name string) (ext.Persona, error) { + persona, err := db.GetPersonaByName(ctx, name) + if err != nil { + return ext.Persona{}, err + } + + baseParts, err := db.listPersonaParts(ctx, persona.ID) + if err != nil { + return ext.Persona{}, err + } + _, stageParts, err := db.getArcStateAndParts(ctx, persona.ID) + if err != nil { + return ext.Persona{}, err + } + assembled := assembleParts(baseParts, stageParts, "arc_stage") + + summaryParts := []string{persona.Summary} + detailParts := []string{persona.Detail} + for _, rp := range assembled { + if rp.Summary != "" { + summaryParts = append(summaryParts, rp.Summary) + } + if rp.Content != "" { + detailParts = append(detailParts, rp.Content) + } + } + + compiledSummary := strings.Join(summaryParts, "\n\n") + compiledDetail := strings.Join(detailParts, "\n\n") + + row := db.pool.QueryRow(ctx, ` + update agent_personas + set compiled_summary = $1, compiled_detail = $2, compiled_at = now(), updated_at = now() + where id = $3 + returning id, guid, name, description, summary, detail, compiled_summary, compiled_detail, compiled_at, tags::text[], created_at, updated_at + `, compiledSummary, compiledDetail, persona.ID) + return scanPersona(row) +} + +// ────────────────────────────────────────────── +// Parts +// ────────────────────────────────────────────── + +func (db *DB) CreatePart(ctx context.Context, p ext.Part) (ext.Part, error) { + if p.Tags == nil { + p.Tags = []string{} + } + row := db.pool.QueryRow(ctx, ` + insert into agent_parts (name, part_type, description, summary, content, tags) + values ($1, $2, $3, $4, $5, $6) + returning id, guid, created_at, updated_at + `, p.Name, p.PartType, p.Description, p.Summary, p.Content, p.Tags) + + created := p + var id int64 + var guid uuid.UUID + var createdAt, updatedAt time.Time + if err := row.Scan(&id, &guid, &createdAt, &updatedAt); err != nil { + return ext.Part{}, fmt.Errorf("create part: %w", err) + } + created.ID = id + created.GUID = guid + created.CreatedAt = createdAt + created.UpdatedAt = updatedAt + return created, nil +} + +func (db *DB) UpdatePart(ctx context.Context, name string, updates map[string]any) (ext.Part, error) { + if len(updates) == 0 { + return db.GetPartByName(ctx, name) + } + allowed := map[string]bool{"name": true, "part_type": true, "description": true, "summary": true, "content": true, "tags": true} + setClauses := []string{} + args := []any{} + for col, val := range updates { + if !allowed[col] { + return ext.Part{}, fmt.Errorf("unknown field: %s", col) + } + args = append(args, val) + setClauses = append(setClauses, fmt.Sprintf("%s = $%d", col, len(args))) + } + args = append(args, name) + q := fmt.Sprintf(` + update agent_parts set %s, updated_at = now() + where name = $%d + returning id, guid, name, part_type, description, summary, content, tags::text[], created_at, updated_at + `, strings.Join(setClauses, ", "), len(args)) + + row := db.pool.QueryRow(ctx, q, args...) + return scanPart(row) +} + +func (db *DB) DeletePart(ctx context.Context, name string) error { + tag, err := db.pool.Exec(ctx, `delete from agent_parts where name = $1`, name) + if err != nil { + return fmt.Errorf("delete part: %w", err) + } + if tag.RowsAffected() == 0 { + return fmt.Errorf("part not found: %s", name) + } + return nil +} + +func (db *DB) ListParts(ctx context.Context, partType, tag string) ([]ext.Part, error) { + args := []any{} + conditions := []string{} + if t := strings.TrimSpace(partType); t != "" { + args = append(args, t) + conditions = append(conditions, fmt.Sprintf("part_type = $%d", len(args))) + } + if t := strings.TrimSpace(tag); t != "" { + args = append(args, t) + conditions = append(conditions, fmt.Sprintf("$%d = any(tags)", len(args))) + } + q := `select id, guid, name, part_type, description, summary, content, tags::text[], created_at, updated_at from agent_parts` + if len(conditions) > 0 { + q += " where " + strings.Join(conditions, " and ") + } + q += " order by part_type, name" + rows, err := db.pool.Query(ctx, q, args...) + if err != nil { + return nil, fmt.Errorf("list parts: %w", err) + } + defer rows.Close() + + var parts []ext.Part + for rows.Next() { + p, err := scanPart(rows) + if err != nil { + return nil, fmt.Errorf("scan part: %w", err) + } + parts = append(parts, p) + } + return parts, rows.Err() +} + +func (db *DB) GetPartByName(ctx context.Context, name string) (ext.Part, error) { + row := db.pool.QueryRow(ctx, ` + select id, guid, name, part_type, description, summary, content, tags::text[], created_at, updated_at + from agent_parts where name = $1 + `, name) + p, err := scanPart(row) + if err != nil { + return ext.Part{}, fmt.Errorf("get part: %w", err) + } + return p, nil +} + +// ────────────────────────────────────────────── +// Persona-Part links +// ────────────────────────────────────────────── + +func (db *DB) AddPersonaPart(ctx context.Context, personaName, partName string, order, priority int) error { + persona, err := db.GetPersonaByName(ctx, personaName) + if err != nil { + return err + } + part, err := db.GetPartByName(ctx, partName) + if err != nil { + return err + } + _, err = db.pool.Exec(ctx, ` + insert into agent_persona_parts (persona_id, part_id, part_order, priority) + values ($1, $2, $3, $4) + on conflict (persona_id, part_id) do update set part_order = excluded.part_order, priority = excluded.priority + `, persona.ID, part.ID, order, priority) + if err != nil { + return fmt.Errorf("add persona part: %w", err) + } + return nil +} + +func (db *DB) RemovePersonaPart(ctx context.Context, personaName, partName string) error { + tag, err := db.pool.Exec(ctx, ` + delete from agent_persona_parts + where persona_id = (select id from agent_personas where name = $1) + and part_id = (select id from agent_parts where name = $2) + `, personaName, partName) + if err != nil { + return fmt.Errorf("remove persona part: %w", err) + } + if tag.RowsAffected() == 0 { + return fmt.Errorf("persona-part link not found") + } + return nil +} + +// ────────────────────────────────────────────── +// Persona-Skill links +// ────────────────────────────────────────────── + +func (db *DB) AddPersonaSkill(ctx context.Context, personaID, skillID int64) error { + _, err := db.pool.Exec(ctx, ` + insert into agent_persona_skills (persona_id, skill_id) + values ($1, $2) on conflict do nothing + `, personaID, skillID) + if err != nil { + return fmt.Errorf("add persona skill: %w", err) + } + return nil +} + +func (db *DB) RemovePersonaSkill(ctx context.Context, personaID, skillID int64) error { + tag, err := db.pool.Exec(ctx, ` + delete from agent_persona_skills where persona_id = $1 and skill_id = $2 + `, personaID, skillID) + if err != nil { + return fmt.Errorf("remove persona skill: %w", err) + } + if tag.RowsAffected() == 0 { + return fmt.Errorf("persona-skill link not found") + } + return nil +} + +// ────────────────────────────────────────────── +// Persona-Guardrail links +// ────────────────────────────────────────────── + +func (db *DB) AddPersonaGuardrail(ctx context.Context, personaID, guardrailID int64) error { + _, err := db.pool.Exec(ctx, ` + insert into agent_persona_guardrails (persona_id, guardrail_id) + values ($1, $2) on conflict do nothing + `, personaID, guardrailID) + if err != nil { + return fmt.Errorf("add persona guardrail: %w", err) + } + return nil +} + +func (db *DB) RemovePersonaGuardrail(ctx context.Context, personaID, guardrailID int64) error { + tag, err := db.pool.Exec(ctx, ` + delete from agent_persona_guardrails where persona_id = $1 and guardrail_id = $2 + `, personaID, guardrailID) + if err != nil { + return fmt.Errorf("remove persona guardrail: %w", err) + } + if tag.RowsAffected() == 0 { + return fmt.Errorf("persona-guardrail link not found") + } + return nil +} + +// ────────────────────────────────────────────── +// Traits +// ────────────────────────────────────────────── + +func (db *DB) CreateTrait(ctx context.Context, t ext.Trait) (ext.Trait, error) { + if t.Tags == nil { + t.Tags = []string{} + } + row := db.pool.QueryRow(ctx, ` + insert into agent_traits (name, trait_type, description, instruction, tags) + values ($1, $2, $3, $4, $5) + returning id, guid, created_at, updated_at + `, t.Name, t.TraitType, t.Description, t.Instruction, t.Tags) + + created := t + var id int64 + var guid uuid.UUID + var createdAt, updatedAt time.Time + if err := row.Scan(&id, &guid, &createdAt, &updatedAt); err != nil { + return ext.Trait{}, fmt.Errorf("create trait: %w", err) + } + created.ID = id + created.GUID = guid + created.CreatedAt = createdAt + created.UpdatedAt = updatedAt + return created, nil +} + +func (db *DB) UpdateTrait(ctx context.Context, name string, updates map[string]any) (ext.Trait, error) { + if len(updates) == 0 { + return db.GetTraitByName(ctx, name) + } + allowed := map[string]bool{"name": true, "trait_type": true, "description": true, "instruction": true, "tags": true} + setClauses := []string{} + args := []any{} + for col, val := range updates { + if !allowed[col] { + return ext.Trait{}, fmt.Errorf("unknown field: %s", col) + } + args = append(args, val) + setClauses = append(setClauses, fmt.Sprintf("%s = $%d", col, len(args))) + } + args = append(args, name) + q := fmt.Sprintf(` + update agent_traits set %s, updated_at = now() + where name = $%d + returning id, guid, name, trait_type, description, instruction, tags::text[], created_at, updated_at + `, strings.Join(setClauses, ", "), len(args)) + + row := db.pool.QueryRow(ctx, q, args...) + return scanTrait(row) +} + +func (db *DB) DeleteTrait(ctx context.Context, name string) error { + tag, err := db.pool.Exec(ctx, `delete from agent_traits where name = $1`, name) + if err != nil { + return fmt.Errorf("delete trait: %w", err) + } + if tag.RowsAffected() == 0 { + return fmt.Errorf("trait not found: %s", name) + } + return nil +} + +func (db *DB) ListTraits(ctx context.Context, traitType, tag string) ([]ext.Trait, error) { + args := []any{} + conditions := []string{} + if t := strings.TrimSpace(traitType); t != "" { + args = append(args, t) + conditions = append(conditions, fmt.Sprintf("trait_type = $%d", len(args))) + } + if t := strings.TrimSpace(tag); t != "" { + args = append(args, t) + conditions = append(conditions, fmt.Sprintf("$%d = any(tags)", len(args))) + } + q := `select id, guid, name, trait_type, description, instruction, tags::text[], created_at, updated_at from agent_traits` + if len(conditions) > 0 { + q += " where " + strings.Join(conditions, " and ") + } + q += " order by trait_type, name" + rows, err := db.pool.Query(ctx, q, args...) + if err != nil { + return nil, fmt.Errorf("list traits: %w", err) + } + defer rows.Close() + + var traits []ext.Trait + for rows.Next() { + t, err := scanTrait(rows) + if err != nil { + return nil, fmt.Errorf("scan trait: %w", err) + } + traits = append(traits, t) + } + return traits, rows.Err() +} + +func (db *DB) GetTraitByName(ctx context.Context, name string) (ext.Trait, error) { + row := db.pool.QueryRow(ctx, ` + select id, guid, name, trait_type, description, instruction, tags::text[], created_at, updated_at + from agent_traits where name = $1 + `, name) + t, err := scanTrait(row) + if err != nil { + return ext.Trait{}, fmt.Errorf("get trait: %w", err) + } + return t, nil +} + +func (db *DB) AddPersonaTrait(ctx context.Context, personaID, traitID int64) error { + _, err := db.pool.Exec(ctx, ` + insert into agent_persona_traits (persona_id, trait_id) + values ($1, $2) on conflict do nothing + `, personaID, traitID) + if err != nil { + return fmt.Errorf("add persona trait: %w", err) + } + return nil +} + +func (db *DB) RemovePersonaTrait(ctx context.Context, personaID, traitID int64) error { + tag, err := db.pool.Exec(ctx, ` + delete from agent_persona_traits where persona_id = $1 and trait_id = $2 + `, personaID, traitID) + if err != nil { + return fmt.Errorf("remove persona trait: %w", err) + } + if tag.RowsAffected() == 0 { + return fmt.Errorf("persona-trait link not found") + } + return nil +} + +// ────────────────────────────────────────────── +// Character Arcs +// ────────────────────────────────────────────── + +func (db *DB) CreateCharacterArc(ctx context.Context, arc ext.CharacterArc) (ext.CharacterArc, error) { + row := db.pool.QueryRow(ctx, ` + insert into character_arcs (name, description, summary) + values ($1, $2, $3) + returning id, created_at, updated_at + `, arc.Name, arc.Description, arc.Summary) + + created := arc + var id int64 + var createdAt, updatedAt time.Time + if err := row.Scan(&id, &createdAt, &updatedAt); err != nil { + return ext.CharacterArc{}, fmt.Errorf("create character arc: %w", err) + } + created.ID = id + created.CreatedAt = createdAt + created.UpdatedAt = updatedAt + return created, nil +} + +func (db *DB) ListCharacterArcs(ctx context.Context) ([]ext.CharacterArc, error) { + rows, err := db.pool.Query(ctx, ` + select id, name, description, summary, created_at, updated_at + from character_arcs order by name + `) + if err != nil { + return nil, fmt.Errorf("list character arcs: %w", err) + } + defer rows.Close() + + var arcs []ext.CharacterArc + for rows.Next() { + var a ext.CharacterArc + if err := rows.Scan(&a.ID, &a.Name, &a.Description, &a.Summary, &a.CreatedAt, &a.UpdatedAt); err != nil { + return nil, fmt.Errorf("scan character arc: %w", err) + } + arcs = append(arcs, a) + } + return arcs, rows.Err() +} + +func (db *DB) AddArcStage(ctx context.Context, arcName string, stage ext.ArcStage) (ext.ArcStage, error) { + row := db.pool.QueryRow(ctx, ` + insert into arc_stages (arc_id, name, stage_order, description, condition) + values ((select id from character_arcs where name = $1), $2, $3, $4, $5) + returning id, arc_id, created_at + `, arcName, stage.Name, stage.StageOrder, stage.Description, stage.Condition) + + created := stage + if err := row.Scan(&created.ID, &created.ArcID, &created.CreatedAt); err != nil { + return ext.ArcStage{}, fmt.Errorf("add arc stage: %w", err) + } + return created, nil +} + +func (db *DB) AddStagePart(ctx context.Context, stageID int64, partName string) error { + _, err := db.pool.Exec(ctx, ` + insert into arc_stage_parts (stage_id, part_id) + values ($1, (select id from agent_parts where name = $2)) + on conflict do nothing + `, stageID, partName) + if err != nil { + return fmt.Errorf("add stage part: %w", err) + } + return nil +} + +func (db *DB) RemoveStagePart(ctx context.Context, stageID int64, partName string) error { + tag, err := db.pool.Exec(ctx, ` + delete from arc_stage_parts + where stage_id = $1 and part_id = (select id from agent_parts where name = $2) + `, stageID, partName) + if err != nil { + return fmt.Errorf("remove stage part: %w", err) + } + if tag.RowsAffected() == 0 { + return fmt.Errorf("stage-part link not found") + } + return nil +} + +func (db *DB) AssignPersonaArc(ctx context.Context, personaName, arcName, startStageName string) error { + _, err := db.pool.Exec(ctx, ` + insert into persona_arc (persona_id, arc_id, current_stage_id) + values ( + (select id from agent_personas where name = $1), + (select id from character_arcs where name = $2), + (select id from arc_stages where arc_id = (select id from character_arcs where name = $2) and name = $3) + ) + on conflict (persona_id) do update + set arc_id = excluded.arc_id, + current_stage_id = excluded.current_stage_id, + updated_at = now() + `, personaName, arcName, startStageName) + if err != nil { + return fmt.Errorf("assign persona arc: %w", err) + } + return nil +} + +func (db *DB) AdvancePersonaStage(ctx context.Context, personaName string) (ext.ArcStage, error) { + row := db.pool.QueryRow(ctx, ` + with current as ( + select pa.persona_id, pa.arc_id, pa.current_stage_id, s.stage_order + from persona_arc pa + join arc_stages s on s.id = pa.current_stage_id + where pa.persona_id = (select id from agent_personas where name = $1) + ), + next_stage as ( + select s.id, s.arc_id, s.name, s.stage_order, s.description, s.condition, s.created_at + from arc_stages s + join current c on c.arc_id = s.arc_id + where s.stage_order > c.stage_order + order by s.stage_order + limit 1 + ) + update persona_arc pa + set current_stage_id = next_stage.id, updated_at = now() + from next_stage, current + where pa.persona_id = current.persona_id + returning next_stage.id, next_stage.arc_id, next_stage.name, next_stage.stage_order, next_stage.description, next_stage.condition, next_stage.created_at + `, personaName) + + var s ext.ArcStage + if err := row.Scan(&s.ID, &s.ArcID, &s.Name, &s.StageOrder, &s.Description, &s.Condition, &s.CreatedAt); err != nil { + return ext.ArcStage{}, fmt.Errorf("advance persona stage: %w", err) + } + return s, nil +} + +func (db *DB) ResetPersonaStage(ctx context.Context, personaName string) (ext.ArcStage, error) { + row := db.pool.QueryRow(ctx, ` + with first_stage as ( + select s.id, s.arc_id, s.name, s.stage_order, s.description, s.condition, s.created_at + from arc_stages s + join persona_arc pa on pa.arc_id = s.arc_id + where pa.persona_id = (select id from agent_personas where name = $1) + order by s.stage_order + limit 1 + ) + update persona_arc pa + set current_stage_id = first_stage.id, updated_at = now() + from first_stage + where pa.persona_id = (select id from agent_personas where name = $1) + returning first_stage.id, first_stage.arc_id, first_stage.name, first_stage.stage_order, first_stage.description, first_stage.condition, first_stage.created_at + `, personaName) + + var s ext.ArcStage + if err := row.Scan(&s.ID, &s.ArcID, &s.Name, &s.StageOrder, &s.Description, &s.Condition, &s.CreatedAt); err != nil { + return ext.ArcStage{}, fmt.Errorf("reset persona stage: %w", err) + } + return s, nil +} + +// ────────────────────────────────────────────── +// Internal helpers +// ────────────────────────────────────────────── + +type rawPart struct { + ID int64 + Name string + PartType string + Description string + Summary string + Content string + Tags []string + Order int + Priority int + Source string +} + +func (db *DB) listPersonaParts(ctx context.Context, personaID int64) ([]rawPart, error) { + rows, err := db.pool.Query(ctx, ` + select ap.id, ap.name, ap.part_type, ap.description, ap.summary, ap.content, ap.tags::text[], + app.part_order, app.priority + from agent_parts ap + join agent_persona_parts app on app.part_id = ap.id + where app.persona_id = $1 + order by app.part_order, ap.part_type, ap.name + `, personaID) + if err != nil { + return nil, fmt.Errorf("list persona parts: %w", err) + } + defer rows.Close() + + var parts []rawPart + for rows.Next() { + var rp rawPart + var tags []string + if err := rows.Scan(&rp.ID, &rp.Name, &rp.PartType, &rp.Description, &rp.Summary, &rp.Content, &tags, &rp.Order, &rp.Priority); err != nil { + return nil, fmt.Errorf("scan persona part: %w", err) + } + rp.Tags = nilToEmptyStrings(tags) + rp.Source = "persona" + parts = append(parts, rp) + } + return parts, rows.Err() +} + +func (db *DB) getArcStateAndParts(ctx context.Context, personaID int64) (*ext.PersonaArcState, []rawPart, error) { + row := db.pool.QueryRow(ctx, ` + select ca.name, s.id, s.name, s.stage_order, s.description, s.condition + from persona_arc pa + join character_arcs ca on ca.id = pa.arc_id + join arc_stages s on s.id = pa.current_stage_id + where pa.persona_id = $1 + `, personaID) + + var arcName, stageName, stageDesc, stageCond string + var stageID int64 + var stageOrder int + if err := row.Scan(&arcName, &stageID, &stageName, &stageOrder, &stageDesc, &stageCond); err != nil { + // No arc assigned — not an error + return nil, nil, nil + } + + arcState := &ext.PersonaArcState{ + ArcName: arcName, + StageName: stageName, + StageOrder: stageOrder, + Description: stageDesc, + Condition: stageCond, + } + + rows, err := db.pool.Query(ctx, ` + select ap.id, ap.name, ap.part_type, ap.description, ap.summary, ap.content, ap.tags::text[] + from agent_parts ap + join arc_stage_parts asp on asp.part_id = ap.id + where asp.stage_id = $1 + order by ap.part_type, ap.name + `, stageID) + if err != nil { + return nil, nil, fmt.Errorf("list stage parts: %w", err) + } + defer rows.Close() + + var parts []rawPart + for rows.Next() { + var rp rawPart + var tags []string + if err := rows.Scan(&rp.ID, &rp.Name, &rp.PartType, &rp.Description, &rp.Summary, &rp.Content, &tags); err != nil { + return nil, nil, fmt.Errorf("scan stage part: %w", err) + } + rp.Tags = nilToEmptyStrings(tags) + rp.Source = "arc_stage" + parts = append(parts, rp) + } + return arcState, parts, rows.Err() +} + +// assembleParts merges base parts with override parts. Override parts replace all +// base parts of the same part_type. +func assembleParts(base, overrides []rawPart, overrideSource string) []rawPart { + if len(overrides) == 0 { + return append([]rawPart{}, base...) + } + + overrideTypes := make(map[string]bool) + for _, op := range overrides { + overrideTypes[op.PartType] = true + } + + result := make([]rawPart, 0, len(base)+len(overrides)) + for _, bp := range base { + if !overrideTypes[bp.PartType] { + result = append(result, bp) + } + } + for _, op := range overrides { + op.Source = overrideSource + result = append(result, op) + } + return result +} + +func (db *DB) fetchPartsByNames(ctx context.Context, names []string) ([]rawPart, error) { + if len(names) == 0 { + return nil, nil + } + rows, err := db.pool.Query(ctx, ` + select id, name, part_type, description, summary, content, tags::text[] + from agent_parts where name = any($1) + `, names) + if err != nil { + return nil, fmt.Errorf("fetch parts by names: %w", err) + } + defer rows.Close() + + var parts []rawPart + for rows.Next() { + var rp rawPart + var tags []string + if err := rows.Scan(&rp.ID, &rp.Name, &rp.PartType, &rp.Description, &rp.Summary, &rp.Content, &tags); err != nil { + return nil, fmt.Errorf("scan part by name: %w", err) + } + rp.Tags = nilToEmptyStrings(tags) + parts = append(parts, rp) + } + return parts, rows.Err() +} + +func (db *DB) listPersonaSkills(ctx context.Context, personaID int64) ([]AgentSkillRow, error) { + rows, err := db.pool.Query(ctx, ` + select s.id, s.name, s.description, s.content, s.tags::text[] + from agent_skills s + join agent_persona_skills aps on aps.skill_id = s.id + where aps.persona_id = $1 + order by s.name + `, personaID) + if err != nil { + return nil, fmt.Errorf("list persona skills: %w", err) + } + defer rows.Close() + + var skills []AgentSkillRow + for rows.Next() { + var s AgentSkillRow + var tags []string + if err := rows.Scan(&s.ID, &s.Name, &s.Description, &s.Content, &tags); err != nil { + return nil, fmt.Errorf("scan persona skill: %w", err) + } + s.Tags = nilToEmptyStrings(tags) + skills = append(skills, s) + } + return skills, rows.Err() +} + +func (db *DB) listPersonaGuardrails(ctx context.Context, personaID int64) ([]AgentGuardrailRow, error) { + rows, err := db.pool.Query(ctx, ` + select g.id, g.name, g.description, g.content, g.severity, g.tags::text[] + from agent_guardrails g + join agent_persona_guardrails apg on apg.guardrail_id = g.id + where apg.persona_id = $1 + order by g.name + `, personaID) + if err != nil { + return nil, fmt.Errorf("list persona guardrails: %w", err) + } + defer rows.Close() + + var gs []AgentGuardrailRow + for rows.Next() { + var g AgentGuardrailRow + var tags []string + if err := rows.Scan(&g.ID, &g.Name, &g.Description, &g.Content, &g.Severity, &tags); err != nil { + return nil, fmt.Errorf("scan persona guardrail: %w", err) + } + g.Tags = nilToEmptyStrings(tags) + gs = append(gs, g) + } + return gs, rows.Err() +} + +func (db *DB) listPersonaTraits(ctx context.Context, personaID int64) ([]ext.Trait, error) { + rows, err := db.pool.Query(ctx, ` + select t.id, t.guid, t.name, t.trait_type, t.description, t.instruction, t.tags::text[], t.created_at, t.updated_at + from agent_traits t + join agent_persona_traits apt on apt.trait_id = t.id + where apt.persona_id = $1 + order by t.trait_type, t.name + `, personaID) + if err != nil { + return nil, fmt.Errorf("list persona traits: %w", err) + } + defer rows.Close() + + var traits []ext.Trait + for rows.Next() { + t, err := scanTrait(rows) + if err != nil { + return nil, fmt.Errorf("scan persona trait: %w", err) + } + traits = append(traits, t) + } + return traits, rows.Err() +} + +// Minimal row types used internally (avoid importing full ext types for joins) +type AgentSkillRow struct { + ID int64 + Name string + Description string + Content string + Tags []string +} + +type AgentGuardrailRow struct { + ID int64 + Name string + Description string + Content string + Severity string + Tags []string +} + +func overrideValues(m map[string]string) []string { + out := make([]string, 0, len(m)) + for _, v := range m { + out = append(out, v) + } + return out +} + +// ────────────────────────────────────────────── +// Scanners +// ────────────────────────────────────────────── + +type personaScanner interface{ Scan(dest ...any) error } +type partScanner interface{ Scan(dest ...any) error } +type traitScanner interface{ Scan(dest ...any) error } + +func scanPersona(row personaScanner) (ext.Persona, error) { + var p ext.Persona + var tags []string + if err := row.Scan(&p.ID, &p.GUID, &p.Name, &p.Description, &p.Summary, &p.Detail, + &p.CompiledSummary, &p.CompiledDetail, &p.CompiledAt, &tags, &p.CreatedAt, &p.UpdatedAt); err != nil { + return ext.Persona{}, err + } + p.Tags = nilToEmptyStrings(tags) + return p, nil +} + +func scanPart(row partScanner) (ext.Part, error) { + var p ext.Part + var tags []string + if err := row.Scan(&p.ID, &p.GUID, &p.Name, &p.PartType, &p.Description, &p.Summary, &p.Content, &tags, &p.CreatedAt, &p.UpdatedAt); err != nil { + return ext.Part{}, err + } + p.Tags = nilToEmptyStrings(tags) + return p, nil +} + +func scanTrait(row traitScanner) (ext.Trait, error) { + var t ext.Trait + var tags []string + if err := row.Scan(&t.ID, &t.GUID, &t.Name, &t.TraitType, &t.Description, &t.Instruction, &tags, &t.CreatedAt, &t.UpdatedAt); err != nil { + return ext.Trait{}, err + } + t.Tags = nilToEmptyStrings(tags) + return t, nil +} diff --git a/internal/tools/agent_personas.go b/internal/tools/agent_personas.go new file mode 100644 index 0000000..8f6d9b6 --- /dev/null +++ b/internal/tools/agent_personas.go @@ -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 +} diff --git a/internal/types/agent_persona.go b/internal/types/agent_persona.go new file mode 100644 index 0000000..f06e4df --- /dev/null +++ b/internal/types/agent_persona.go @@ -0,0 +1,178 @@ +package types + +import ( + "time" + + "github.com/google/uuid" +) + +// ────────────────────────────────────────────── +// Core entities +// ────────────────────────────────────────────── + +type Persona struct { + ID int64 `json:"id"` + GUID uuid.UUID `json:"guid"` + Name string `json:"name"` + Description string `json:"description,omitempty"` + Summary string `json:"summary"` + Detail string `json:"detail,omitempty"` + CompiledSummary string `json:"compiled_summary,omitempty"` + CompiledDetail string `json:"compiled_detail,omitempty"` + CompiledAt *time.Time `json:"compiled_at,omitempty"` + Tags []string `json:"tags"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +type Part struct { + ID int64 `json:"id"` + GUID uuid.UUID `json:"guid"` + Name string `json:"name"` + PartType string `json:"part_type"` + Description string `json:"description,omitempty"` + Summary string `json:"summary"` + Content string `json:"content,omitempty"` + Tags []string `json:"tags"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +type Trait struct { + ID int64 `json:"id"` + GUID uuid.UUID `json:"guid"` + Name string `json:"name"` + TraitType string `json:"trait_type"` + Description string `json:"description,omitempty"` + Instruction string `json:"instruction,omitempty"` + Tags []string `json:"tags"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +type CharacterArc struct { + ID int64 `json:"id"` + Name string `json:"name"` + Description string `json:"description,omitempty"` + Summary string `json:"summary,omitempty"` + Stages []ArcStage `json:"stages,omitempty"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +type ArcStage struct { + ID int64 `json:"id"` + ArcID int64 `json:"arc_id"` + Name string `json:"name"` + StageOrder int `json:"stage_order"` + Description string `json:"description,omitempty"` + Condition string `json:"condition,omitempty"` + CreatedAt time.Time `json:"created_at"` +} + +// ────────────────────────────────────────────── +// Assembled persona (get_agent_persona result) +// ────────────────────────────────────────────── + +// PersonaFull is returned by get_agent_persona. Parts, skills, guardrails, and +// traits are assembled according to priority order: +// +// 1. runtime overrides (highest) +// 2. active arc-stage parts +// 3. persona-linked parts (base) +type PersonaFull struct { + Name string `json:"name"` + Description string `json:"description,omitempty"` + Body string `json:"body"` // summary or detail, depending on mode + CompiledSummary string `json:"compiled_summary,omitempty"` + Tags []string `json:"tags"` + Parts []AssembledPart `json:"parts"` + Skills []PersonaSkillEntry `json:"skills"` + Guardrails []PersonaGuardrailEntry `json:"guardrails"` + Traits []PersonaTraitEntry `json:"traits"` + Arc *PersonaArcState `json:"arc,omitempty"` + Detail bool `json:"detail"` // whether full detail was requested +} + +type AssembledPart struct { + Name string `json:"name"` + PartType string `json:"part_type"` + Description string `json:"description,omitempty"` + Body string `json:"body"` // summary or content, depending on detail mode + Tags []string `json:"tags"` + Source string `json:"source"` // "persona" | "arc_stage" | "override" +} + +type PersonaSkillEntry struct { + ID int64 `json:"id"` + Name string `json:"name"` + Description string `json:"description,omitempty"` + Content string `json:"content,omitempty"` // only when detail=true + Tags []string `json:"tags"` +} + +type PersonaGuardrailEntry struct { + ID int64 `json:"id"` + Name string `json:"name"` + Description string `json:"description,omitempty"` + Content string `json:"content,omitempty"` // only when detail=true + Severity string `json:"severity,omitempty"` // only when detail=true + Tags []string `json:"tags"` +} + +type PersonaTraitEntry struct { + ID int64 `json:"id"` + Name string `json:"name"` + TraitType string `json:"trait_type"` + Description string `json:"description,omitempty"` + Instruction string `json:"instruction,omitempty"` // only when detail=true + Tags []string `json:"tags"` +} + +type PersonaArcState struct { + ArcName string `json:"arc_name"` + StageName string `json:"stage_name"` + StageOrder int `json:"stage_order"` + Description string `json:"description,omitempty"` + Condition string `json:"condition,omitempty"` +} + +// ────────────────────────────────────────────── +// Manifest (get_persona_manifest result) +// ────────────────────────────────────────────── + +type PersonaManifest struct { + Name string `json:"name"` + Description string `json:"description,omitempty"` + Parts []ManifestPart `json:"parts"` + Traits []ManifestTrait `json:"traits"` + Skills []ManifestSkill `json:"skills"` + Guardrails []ManifestGuardrail `json:"guardrails"` + Arc *PersonaArcState `json:"arc,omitempty"` + OnDemandTools []string `json:"on_demand_tools"` +} + +type ManifestPart struct { + Name string `json:"name"` + PartType string `json:"part_type"` + Description string `json:"description,omitempty"` +} + +type ManifestTrait struct { + Name string `json:"name"` + TraitType string `json:"trait_type"` + Description string `json:"description,omitempty"` +} + +type ManifestSkill struct { + ID int64 `json:"id"` + Name string `json:"name"` + Description string `json:"description,omitempty"` +} + +type ManifestGuardrail struct { + ID int64 `json:"id"` + Name string `json:"name"` + Description string `json:"description,omitempty"` + Severity string `json:"severity"` +} diff --git a/migrations/020_generated_schema.sql b/migrations/020_generated_schema.sql index a947e11..11ea378 100644 --- a/migrations/020_generated_schema.sql +++ b/migrations/020_generated_schema.sql @@ -3,6 +3,48 @@ -- Generated by RelSpec -- Sequences for schema: public +CREATE SEQUENCE IF NOT EXISTS public.identity_agent_personas_id + INCREMENT 1 + MINVALUE 1 + MAXVALUE 9223372036854775807 + START 1 + CACHE 1; + +CREATE SEQUENCE IF NOT EXISTS public.identity_agent_parts_id + INCREMENT 1 + MINVALUE 1 + MAXVALUE 9223372036854775807 + START 1 + CACHE 1; + +CREATE SEQUENCE IF NOT EXISTS public.identity_agent_traits_id + INCREMENT 1 + MINVALUE 1 + MAXVALUE 9223372036854775807 + START 1 + CACHE 1; + +CREATE SEQUENCE IF NOT EXISTS public.identity_character_arcs_id + INCREMENT 1 + MINVALUE 1 + MAXVALUE 9223372036854775807 + START 1 + CACHE 1; + +CREATE SEQUENCE IF NOT EXISTS public.identity_arc_stages_id + INCREMENT 1 + MINVALUE 1 + MAXVALUE 9223372036854775807 + START 1 + CACHE 1; + +CREATE SEQUENCE IF NOT EXISTS public.identity_persona_arc_persona_id + INCREMENT 1 + MINVALUE 1 + MAXVALUE 9223372036854775807 + START 1 + CACHE 1; + CREATE SEQUENCE IF NOT EXISTS public.identity_thoughts_id INCREMENT 1 MINVALUE 1 @@ -123,6 +165,99 @@ CREATE SEQUENCE IF NOT EXISTS public.identity_project_guardrails_id CACHE 1; -- Tables for schema: public +CREATE TABLE IF NOT EXISTS public.agent_personas ( + compiled_at timestamptz, + compiled_detail text NOT NULL DEFAULT '', + compiled_summary text NOT NULL DEFAULT '', + created_at timestamptz NOT NULL DEFAULT now(), + description text NOT NULL DEFAULT '', + detail text NOT NULL DEFAULT '', + guid uuid NOT NULL DEFAULT gen_random_uuid(), + id bigserial NOT NULL, + name text NOT NULL, + summary text NOT NULL, + tags text[] NOT NULL DEFAULT '{}', + updated_at timestamptz NOT NULL DEFAULT now() +); + +CREATE TABLE IF NOT EXISTS public.agent_parts ( + content text NOT NULL DEFAULT '', + created_at timestamptz NOT NULL DEFAULT now(), + description text NOT NULL DEFAULT '', + guid uuid NOT NULL DEFAULT gen_random_uuid(), + id bigserial NOT NULL, + name text NOT NULL, + part_type text NOT NULL, + summary text NOT NULL, + tags text[] NOT NULL DEFAULT '{}', + updated_at timestamptz NOT NULL DEFAULT now() +); + +CREATE TABLE IF NOT EXISTS public.agent_persona_parts ( + part_id bigint NOT NULL, + part_order integer NOT NULL DEFAULT 0, + persona_id bigint NOT NULL, + priority integer NOT NULL DEFAULT 0 +); + +CREATE TABLE IF NOT EXISTS public.agent_persona_skills ( + persona_id bigint NOT NULL, + skill_id bigint NOT NULL +); + +CREATE TABLE IF NOT EXISTS public.agent_persona_guardrails ( + guardrail_id bigint NOT NULL, + persona_id bigint NOT NULL +); + +CREATE TABLE IF NOT EXISTS public.agent_traits ( + created_at timestamptz NOT NULL DEFAULT now(), + description text NOT NULL DEFAULT '', + guid uuid NOT NULL DEFAULT gen_random_uuid(), + id bigserial NOT NULL, + instruction text NOT NULL DEFAULT '', + name text NOT NULL, + tags text[] NOT NULL DEFAULT '{}', + trait_type text NOT NULL, + updated_at timestamptz NOT NULL DEFAULT now() +); + +CREATE TABLE IF NOT EXISTS public.agent_persona_traits ( + persona_id bigint NOT NULL, + trait_id bigint NOT NULL +); + +CREATE TABLE IF NOT EXISTS public.character_arcs ( + created_at timestamptz NOT NULL DEFAULT now(), + description text NOT NULL DEFAULT '', + id bigserial NOT NULL, + name text NOT NULL, + summary text NOT NULL DEFAULT '', + updated_at timestamptz NOT NULL DEFAULT now() +); + +CREATE TABLE IF NOT EXISTS public.arc_stages ( + arc_id bigint NOT NULL, + condition text NOT NULL DEFAULT '', + created_at timestamptz NOT NULL DEFAULT now(), + description text NOT NULL DEFAULT '', + id bigserial NOT NULL, + name text NOT NULL, + stage_order integer NOT NULL DEFAULT 0 +); + +CREATE TABLE IF NOT EXISTS public.arc_stage_parts ( + part_id bigint NOT NULL, + stage_id bigint NOT NULL +); + +CREATE TABLE IF NOT EXISTS public.persona_arc ( + arc_id bigint NOT NULL, + current_stage_id bigint NOT NULL, + persona_id bigint NOT NULL, + updated_at timestamptz NOT NULL DEFAULT now() +); + CREATE TABLE IF NOT EXISTS public.thoughts ( archived_at timestamptz, content text NOT NULL, @@ -315,6 +450,786 @@ CREATE TABLE IF NOT EXISTS public.project_guardrails ( ); -- Add missing columns for schema: public +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = 'agent_personas' + AND column_name = 'compiled_at' + ) THEN + ALTER TABLE public.agent_personas ADD COLUMN compiled_at timestamptz; + END IF; +END; +$$; + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = 'agent_personas' + AND column_name = 'compiled_detail' + ) THEN + ALTER TABLE public.agent_personas ADD COLUMN compiled_detail text NOT NULL DEFAULT ''; + END IF; +END; +$$; + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = 'agent_personas' + AND column_name = 'compiled_summary' + ) THEN + ALTER TABLE public.agent_personas ADD COLUMN compiled_summary text NOT NULL DEFAULT ''; + END IF; +END; +$$; + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = 'agent_personas' + AND column_name = 'created_at' + ) THEN + ALTER TABLE public.agent_personas ADD COLUMN created_at timestamptz NOT NULL DEFAULT now(); + END IF; +END; +$$; + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = 'agent_personas' + AND column_name = 'description' + ) THEN + ALTER TABLE public.agent_personas ADD COLUMN description text NOT NULL DEFAULT ''; + END IF; +END; +$$; + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = 'agent_personas' + AND column_name = 'detail' + ) THEN + ALTER TABLE public.agent_personas ADD COLUMN detail text NOT NULL DEFAULT ''; + END IF; +END; +$$; + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = 'agent_personas' + AND column_name = 'guid' + ) THEN + ALTER TABLE public.agent_personas ADD COLUMN guid uuid NOT NULL DEFAULT gen_random_uuid(); + END IF; +END; +$$; + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = 'agent_personas' + AND column_name = 'id' + ) THEN + ALTER TABLE public.agent_personas ADD COLUMN id bigserial NOT NULL; + END IF; +END; +$$; + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = 'agent_personas' + AND column_name = 'name' + ) THEN + ALTER TABLE public.agent_personas ADD COLUMN name text NOT NULL; + END IF; +END; +$$; + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = 'agent_personas' + AND column_name = 'summary' + ) THEN + ALTER TABLE public.agent_personas ADD COLUMN summary text NOT NULL; + END IF; +END; +$$; + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = 'agent_personas' + AND column_name = 'tags' + ) THEN + ALTER TABLE public.agent_personas ADD COLUMN tags text[] NOT NULL DEFAULT '{}'; + END IF; +END; +$$; + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = 'agent_personas' + AND column_name = 'updated_at' + ) THEN + ALTER TABLE public.agent_personas ADD COLUMN updated_at timestamptz NOT NULL DEFAULT now(); + END IF; +END; +$$; + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = 'agent_parts' + AND column_name = 'content' + ) THEN + ALTER TABLE public.agent_parts ADD COLUMN content text NOT NULL DEFAULT ''; + END IF; +END; +$$; + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = 'agent_parts' + AND column_name = 'created_at' + ) THEN + ALTER TABLE public.agent_parts ADD COLUMN created_at timestamptz NOT NULL DEFAULT now(); + END IF; +END; +$$; + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = 'agent_parts' + AND column_name = 'description' + ) THEN + ALTER TABLE public.agent_parts ADD COLUMN description text NOT NULL DEFAULT ''; + END IF; +END; +$$; + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = 'agent_parts' + AND column_name = 'guid' + ) THEN + ALTER TABLE public.agent_parts ADD COLUMN guid uuid NOT NULL DEFAULT gen_random_uuid(); + END IF; +END; +$$; + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = 'agent_parts' + AND column_name = 'id' + ) THEN + ALTER TABLE public.agent_parts ADD COLUMN id bigserial NOT NULL; + END IF; +END; +$$; + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = 'agent_parts' + AND column_name = 'name' + ) THEN + ALTER TABLE public.agent_parts ADD COLUMN name text NOT NULL; + END IF; +END; +$$; + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = 'agent_parts' + AND column_name = 'part_type' + ) THEN + ALTER TABLE public.agent_parts ADD COLUMN part_type text NOT NULL; + END IF; +END; +$$; + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = 'agent_parts' + AND column_name = 'summary' + ) THEN + ALTER TABLE public.agent_parts ADD COLUMN summary text NOT NULL; + END IF; +END; +$$; + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = 'agent_parts' + AND column_name = 'tags' + ) THEN + ALTER TABLE public.agent_parts ADD COLUMN tags text[] NOT NULL DEFAULT '{}'; + END IF; +END; +$$; + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = 'agent_parts' + AND column_name = 'updated_at' + ) THEN + ALTER TABLE public.agent_parts ADD COLUMN updated_at timestamptz NOT NULL DEFAULT now(); + END IF; +END; +$$; + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = 'agent_persona_parts' + AND column_name = 'part_id' + ) THEN + ALTER TABLE public.agent_persona_parts ADD COLUMN part_id bigint NOT NULL; + END IF; +END; +$$; + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = 'agent_persona_parts' + AND column_name = 'part_order' + ) THEN + ALTER TABLE public.agent_persona_parts ADD COLUMN part_order integer NOT NULL DEFAULT 0; + END IF; +END; +$$; + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = 'agent_persona_parts' + AND column_name = 'persona_id' + ) THEN + ALTER TABLE public.agent_persona_parts ADD COLUMN persona_id bigint NOT NULL; + END IF; +END; +$$; + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = 'agent_persona_parts' + AND column_name = 'priority' + ) THEN + ALTER TABLE public.agent_persona_parts ADD COLUMN priority integer NOT NULL DEFAULT 0; + END IF; +END; +$$; + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = 'agent_persona_skills' + AND column_name = 'persona_id' + ) THEN + ALTER TABLE public.agent_persona_skills ADD COLUMN persona_id bigint NOT NULL; + END IF; +END; +$$; + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = 'agent_persona_skills' + AND column_name = 'skill_id' + ) THEN + ALTER TABLE public.agent_persona_skills ADD COLUMN skill_id bigint NOT NULL; + END IF; +END; +$$; + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = 'agent_persona_guardrails' + AND column_name = 'guardrail_id' + ) THEN + ALTER TABLE public.agent_persona_guardrails ADD COLUMN guardrail_id bigint NOT NULL; + END IF; +END; +$$; + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = 'agent_persona_guardrails' + AND column_name = 'persona_id' + ) THEN + ALTER TABLE public.agent_persona_guardrails ADD COLUMN persona_id bigint NOT NULL; + END IF; +END; +$$; + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = 'agent_traits' + AND column_name = 'created_at' + ) THEN + ALTER TABLE public.agent_traits ADD COLUMN created_at timestamptz NOT NULL DEFAULT now(); + END IF; +END; +$$; + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = 'agent_traits' + AND column_name = 'description' + ) THEN + ALTER TABLE public.agent_traits ADD COLUMN description text NOT NULL DEFAULT ''; + END IF; +END; +$$; + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = 'agent_traits' + AND column_name = 'guid' + ) THEN + ALTER TABLE public.agent_traits ADD COLUMN guid uuid NOT NULL DEFAULT gen_random_uuid(); + END IF; +END; +$$; + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = 'agent_traits' + AND column_name = 'id' + ) THEN + ALTER TABLE public.agent_traits ADD COLUMN id bigserial NOT NULL; + END IF; +END; +$$; + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = 'agent_traits' + AND column_name = 'instruction' + ) THEN + ALTER TABLE public.agent_traits ADD COLUMN instruction text NOT NULL DEFAULT ''; + END IF; +END; +$$; + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = 'agent_traits' + AND column_name = 'name' + ) THEN + ALTER TABLE public.agent_traits ADD COLUMN name text NOT NULL; + END IF; +END; +$$; + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = 'agent_traits' + AND column_name = 'tags' + ) THEN + ALTER TABLE public.agent_traits ADD COLUMN tags text[] NOT NULL DEFAULT '{}'; + END IF; +END; +$$; + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = 'agent_traits' + AND column_name = 'trait_type' + ) THEN + ALTER TABLE public.agent_traits ADD COLUMN trait_type text NOT NULL; + END IF; +END; +$$; + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = 'agent_traits' + AND column_name = 'updated_at' + ) THEN + ALTER TABLE public.agent_traits ADD COLUMN updated_at timestamptz NOT NULL DEFAULT now(); + END IF; +END; +$$; + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = 'agent_persona_traits' + AND column_name = 'persona_id' + ) THEN + ALTER TABLE public.agent_persona_traits ADD COLUMN persona_id bigint NOT NULL; + END IF; +END; +$$; + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = 'agent_persona_traits' + AND column_name = 'trait_id' + ) THEN + ALTER TABLE public.agent_persona_traits ADD COLUMN trait_id bigint NOT NULL; + END IF; +END; +$$; + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = 'character_arcs' + AND column_name = 'created_at' + ) THEN + ALTER TABLE public.character_arcs ADD COLUMN created_at timestamptz NOT NULL DEFAULT now(); + END IF; +END; +$$; + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = 'character_arcs' + AND column_name = 'description' + ) THEN + ALTER TABLE public.character_arcs ADD COLUMN description text NOT NULL DEFAULT ''; + END IF; +END; +$$; + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = 'character_arcs' + AND column_name = 'id' + ) THEN + ALTER TABLE public.character_arcs ADD COLUMN id bigserial NOT NULL; + END IF; +END; +$$; + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = 'character_arcs' + AND column_name = 'name' + ) THEN + ALTER TABLE public.character_arcs ADD COLUMN name text NOT NULL; + END IF; +END; +$$; + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = 'character_arcs' + AND column_name = 'summary' + ) THEN + ALTER TABLE public.character_arcs ADD COLUMN summary text NOT NULL DEFAULT ''; + END IF; +END; +$$; + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = 'character_arcs' + AND column_name = 'updated_at' + ) THEN + ALTER TABLE public.character_arcs ADD COLUMN updated_at timestamptz NOT NULL DEFAULT now(); + END IF; +END; +$$; + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = 'arc_stages' + AND column_name = 'arc_id' + ) THEN + ALTER TABLE public.arc_stages ADD COLUMN arc_id bigint NOT NULL; + END IF; +END; +$$; + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = 'arc_stages' + AND column_name = 'condition' + ) THEN + ALTER TABLE public.arc_stages ADD COLUMN condition text NOT NULL DEFAULT ''; + END IF; +END; +$$; + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = 'arc_stages' + AND column_name = 'created_at' + ) THEN + ALTER TABLE public.arc_stages ADD COLUMN created_at timestamptz NOT NULL DEFAULT now(); + END IF; +END; +$$; + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = 'arc_stages' + AND column_name = 'description' + ) THEN + ALTER TABLE public.arc_stages ADD COLUMN description text NOT NULL DEFAULT ''; + END IF; +END; +$$; + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = 'arc_stages' + AND column_name = 'id' + ) THEN + ALTER TABLE public.arc_stages ADD COLUMN id bigserial NOT NULL; + END IF; +END; +$$; + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = 'arc_stages' + AND column_name = 'name' + ) THEN + ALTER TABLE public.arc_stages ADD COLUMN name text NOT NULL; + END IF; +END; +$$; + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = 'arc_stages' + AND column_name = 'stage_order' + ) THEN + ALTER TABLE public.arc_stages ADD COLUMN stage_order integer NOT NULL DEFAULT 0; + END IF; +END; +$$; + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = 'arc_stage_parts' + AND column_name = 'part_id' + ) THEN + ALTER TABLE public.arc_stage_parts ADD COLUMN part_id bigint NOT NULL; + END IF; +END; +$$; + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = 'arc_stage_parts' + AND column_name = 'stage_id' + ) THEN + ALTER TABLE public.arc_stage_parts ADD COLUMN stage_id bigint NOT NULL; + END IF; +END; +$$; + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = 'persona_arc' + AND column_name = 'arc_id' + ) THEN + ALTER TABLE public.persona_arc ADD COLUMN arc_id bigint NOT NULL; + END IF; +END; +$$; + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = 'persona_arc' + AND column_name = 'current_stage_id' + ) THEN + ALTER TABLE public.persona_arc ADD COLUMN current_stage_id bigint NOT NULL; + END IF; +END; +$$; + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = 'persona_arc' + AND column_name = 'persona_id' + ) THEN + ALTER TABLE public.persona_arc ADD COLUMN persona_id bigint NOT NULL; + END IF; +END; +$$; + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = 'persona_arc' + AND column_name = 'updated_at' + ) THEN + ALTER TABLE public.persona_arc ADD COLUMN updated_at timestamptz NOT NULL DEFAULT now(); + END IF; +END; +$$; + DO $$ BEGIN IF NOT EXISTS ( @@ -2136,6 +3051,174 @@ END; $$; -- Primary keys for schema: public +DO $$ +DECLARE + auto_pk_name text; +BEGIN + -- Drop auto-generated primary key if it exists + SELECT constraint_name INTO auto_pk_name + FROM information_schema.table_constraints + WHERE table_schema = 'public' + AND table_name = 'agent_personas' + AND constraint_type = 'PRIMARY KEY' + AND constraint_name IN ('agent_personas_pkey', 'public_agent_personas_pkey'); + + IF auto_pk_name IS NOT NULL THEN + EXECUTE 'ALTER TABLE public.agent_personas DROP CONSTRAINT ' || quote_ident(auto_pk_name); + END IF; + + -- Add named primary key if it doesn't exist + IF NOT EXISTS ( + SELECT 1 FROM information_schema.table_constraints + WHERE table_schema = 'public' + AND table_name = 'agent_personas' + AND constraint_name = 'pk_public_agent_personas' + ) THEN + ALTER TABLE public.agent_personas ADD CONSTRAINT pk_public_agent_personas PRIMARY KEY (id); + END IF; +END; +$$; + +DO $$ +DECLARE + auto_pk_name text; +BEGIN + -- Drop auto-generated primary key if it exists + SELECT constraint_name INTO auto_pk_name + FROM information_schema.table_constraints + WHERE table_schema = 'public' + AND table_name = 'agent_parts' + AND constraint_type = 'PRIMARY KEY' + AND constraint_name IN ('agent_parts_pkey', 'public_agent_parts_pkey'); + + IF auto_pk_name IS NOT NULL THEN + EXECUTE 'ALTER TABLE public.agent_parts DROP CONSTRAINT ' || quote_ident(auto_pk_name); + END IF; + + -- Add named primary key if it doesn't exist + IF NOT EXISTS ( + SELECT 1 FROM information_schema.table_constraints + WHERE table_schema = 'public' + AND table_name = 'agent_parts' + AND constraint_name = 'pk_public_agent_parts' + ) THEN + ALTER TABLE public.agent_parts ADD CONSTRAINT pk_public_agent_parts PRIMARY KEY (id); + END IF; +END; +$$; + +DO $$ +DECLARE + auto_pk_name text; +BEGIN + -- Drop auto-generated primary key if it exists + SELECT constraint_name INTO auto_pk_name + FROM information_schema.table_constraints + WHERE table_schema = 'public' + AND table_name = 'agent_traits' + AND constraint_type = 'PRIMARY KEY' + AND constraint_name IN ('agent_traits_pkey', 'public_agent_traits_pkey'); + + IF auto_pk_name IS NOT NULL THEN + EXECUTE 'ALTER TABLE public.agent_traits DROP CONSTRAINT ' || quote_ident(auto_pk_name); + END IF; + + -- Add named primary key if it doesn't exist + IF NOT EXISTS ( + SELECT 1 FROM information_schema.table_constraints + WHERE table_schema = 'public' + AND table_name = 'agent_traits' + AND constraint_name = 'pk_public_agent_traits' + ) THEN + ALTER TABLE public.agent_traits ADD CONSTRAINT pk_public_agent_traits PRIMARY KEY (id); + END IF; +END; +$$; + +DO $$ +DECLARE + auto_pk_name text; +BEGIN + -- Drop auto-generated primary key if it exists + SELECT constraint_name INTO auto_pk_name + FROM information_schema.table_constraints + WHERE table_schema = 'public' + AND table_name = 'character_arcs' + AND constraint_type = 'PRIMARY KEY' + AND constraint_name IN ('character_arcs_pkey', 'public_character_arcs_pkey'); + + IF auto_pk_name IS NOT NULL THEN + EXECUTE 'ALTER TABLE public.character_arcs DROP CONSTRAINT ' || quote_ident(auto_pk_name); + END IF; + + -- Add named primary key if it doesn't exist + IF NOT EXISTS ( + SELECT 1 FROM information_schema.table_constraints + WHERE table_schema = 'public' + AND table_name = 'character_arcs' + AND constraint_name = 'pk_public_character_arcs' + ) THEN + ALTER TABLE public.character_arcs ADD CONSTRAINT pk_public_character_arcs PRIMARY KEY (id); + END IF; +END; +$$; + +DO $$ +DECLARE + auto_pk_name text; +BEGIN + -- Drop auto-generated primary key if it exists + SELECT constraint_name INTO auto_pk_name + FROM information_schema.table_constraints + WHERE table_schema = 'public' + AND table_name = 'arc_stages' + AND constraint_type = 'PRIMARY KEY' + AND constraint_name IN ('arc_stages_pkey', 'public_arc_stages_pkey'); + + IF auto_pk_name IS NOT NULL THEN + EXECUTE 'ALTER TABLE public.arc_stages DROP CONSTRAINT ' || quote_ident(auto_pk_name); + END IF; + + -- Add named primary key if it doesn't exist + IF NOT EXISTS ( + SELECT 1 FROM information_schema.table_constraints + WHERE table_schema = 'public' + AND table_name = 'arc_stages' + AND constraint_name = 'pk_public_arc_stages' + ) THEN + ALTER TABLE public.arc_stages ADD CONSTRAINT pk_public_arc_stages PRIMARY KEY (id); + END IF; +END; +$$; + +DO $$ +DECLARE + auto_pk_name text; +BEGIN + -- Drop auto-generated primary key if it exists + SELECT constraint_name INTO auto_pk_name + FROM information_schema.table_constraints + WHERE table_schema = 'public' + AND table_name = 'persona_arc' + AND constraint_type = 'PRIMARY KEY' + AND constraint_name IN ('persona_arc_pkey', 'public_persona_arc_pkey'); + + IF auto_pk_name IS NOT NULL THEN + EXECUTE 'ALTER TABLE public.persona_arc DROP CONSTRAINT ' || quote_ident(auto_pk_name); + END IF; + + -- Add named primary key if it doesn't exist + IF NOT EXISTS ( + SELECT 1 FROM information_schema.table_constraints + WHERE table_schema = 'public' + AND table_name = 'persona_arc' + AND constraint_name = 'pk_public_persona_arc' + ) THEN + ALTER TABLE public.persona_arc ADD CONSTRAINT pk_public_persona_arc PRIMARY KEY (persona_id); + END IF; +END; +$$; + DO $$ DECLARE auto_pk_name text; @@ -2613,6 +3696,21 @@ END; $$; -- Indexes for schema: public +CREATE INDEX IF NOT EXISTS idx_agent_persona_parts_persona_id_part_id + ON public.agent_persona_parts USING btree (persona_id, part_id); + +CREATE INDEX IF NOT EXISTS idx_agent_persona_skills_persona_id_skill_id + ON public.agent_persona_skills USING btree (persona_id, skill_id); + +CREATE INDEX IF NOT EXISTS idx_agent_persona_guardrails_persona_id_guardrail_id + ON public.agent_persona_guardrails USING btree (persona_id, guardrail_id); + +CREATE INDEX IF NOT EXISTS idx_agent_persona_traits_persona_id_trait_id + ON public.agent_persona_traits USING btree (persona_id, trait_id); + +CREATE INDEX IF NOT EXISTS idx_arc_stage_parts_stage_id_part_id + ON public.arc_stage_parts USING btree (stage_id, part_id); + CREATE INDEX IF NOT EXISTS idx_thought_links_from_id_to_id_relation ON public.thought_links USING btree (from_id, to_id, relation); @@ -2653,6 +3751,97 @@ CREATE INDEX IF NOT EXISTS idx_project_guardrails_project_id_guardrail_id ON public.project_guardrails USING btree (project_id, guardrail_id); -- Unique constraints for schema: public +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.table_constraints + WHERE table_schema = 'public' + AND table_name = 'agent_personas' + AND constraint_name = 'ukey_agent_personas_guid' + ) THEN + ALTER TABLE public.agent_personas ADD CONSTRAINT ukey_agent_personas_guid UNIQUE (guid); + END IF; +END; +$$; + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.table_constraints + WHERE table_schema = 'public' + AND table_name = 'agent_personas' + AND constraint_name = 'ukey_agent_personas_name' + ) THEN + ALTER TABLE public.agent_personas ADD CONSTRAINT ukey_agent_personas_name UNIQUE (name); + END IF; +END; +$$; + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.table_constraints + WHERE table_schema = 'public' + AND table_name = 'agent_parts' + AND constraint_name = 'ukey_agent_parts_guid' + ) THEN + ALTER TABLE public.agent_parts ADD CONSTRAINT ukey_agent_parts_guid UNIQUE (guid); + END IF; +END; +$$; + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.table_constraints + WHERE table_schema = 'public' + AND table_name = 'agent_parts' + AND constraint_name = 'ukey_agent_parts_name' + ) THEN + ALTER TABLE public.agent_parts ADD CONSTRAINT ukey_agent_parts_name UNIQUE (name); + END IF; +END; +$$; + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.table_constraints + WHERE table_schema = 'public' + AND table_name = 'agent_traits' + AND constraint_name = 'ukey_agent_traits_guid' + ) THEN + ALTER TABLE public.agent_traits ADD CONSTRAINT ukey_agent_traits_guid UNIQUE (guid); + END IF; +END; +$$; + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.table_constraints + WHERE table_schema = 'public' + AND table_name = 'agent_traits' + AND constraint_name = 'ukey_agent_traits_name' + ) THEN + ALTER TABLE public.agent_traits ADD CONSTRAINT ukey_agent_traits_name UNIQUE (name); + END IF; +END; +$$; + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.table_constraints + WHERE table_schema = 'public' + AND table_name = 'character_arcs' + AND constraint_name = 'ukey_character_arcs_name' + ) THEN + ALTER TABLE public.character_arcs ADD CONSTRAINT ukey_character_arcs_name UNIQUE (name); + END IF; +END; +$$; + DO $$ BEGIN IF NOT EXISTS ( @@ -2825,6 +4014,230 @@ $$; -- Check constraints for schema: public -- Foreign keys for schema: public DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.table_constraints + WHERE table_schema = 'public' + AND table_name = 'agent_persona_parts' + AND constraint_name = 'fk_agent_persona_parts_part_id' + ) THEN + ALTER TABLE public.agent_persona_parts + ADD CONSTRAINT fk_agent_persona_parts_part_id + FOREIGN KEY (part_id) + REFERENCES public.agent_parts (id) + ON DELETE NO ACTION + ON UPDATE NO ACTION; + END IF; +END; +$$;DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.table_constraints + WHERE table_schema = 'public' + AND table_name = 'agent_persona_parts' + AND constraint_name = 'fk_agent_persona_parts_persona_id' + ) THEN + ALTER TABLE public.agent_persona_parts + ADD CONSTRAINT fk_agent_persona_parts_persona_id + FOREIGN KEY (persona_id) + REFERENCES public.agent_personas (id) + ON DELETE NO ACTION + ON UPDATE NO ACTION; + END IF; +END; +$$;DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.table_constraints + WHERE table_schema = 'public' + AND table_name = 'agent_persona_skills' + AND constraint_name = 'fk_agent_persona_skills_persona_id' + ) THEN + ALTER TABLE public.agent_persona_skills + ADD CONSTRAINT fk_agent_persona_skills_persona_id + FOREIGN KEY (persona_id) + REFERENCES public.agent_personas (id) + ON DELETE NO ACTION + ON UPDATE NO ACTION; + END IF; +END; +$$;DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.table_constraints + WHERE table_schema = 'public' + AND table_name = 'agent_persona_skills' + AND constraint_name = 'fk_agent_persona_skills_skill_id' + ) THEN + ALTER TABLE public.agent_persona_skills + ADD CONSTRAINT fk_agent_persona_skills_skill_id + FOREIGN KEY (skill_id) + REFERENCES public.agent_skills (id) + ON DELETE NO ACTION + ON UPDATE NO ACTION; + END IF; +END; +$$;DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.table_constraints + WHERE table_schema = 'public' + AND table_name = 'agent_persona_guardrails' + AND constraint_name = 'fk_agent_persona_guardrails_guardrail_id' + ) THEN + ALTER TABLE public.agent_persona_guardrails + ADD CONSTRAINT fk_agent_persona_guardrails_guardrail_id + FOREIGN KEY (guardrail_id) + REFERENCES public.agent_guardrails (id) + ON DELETE NO ACTION + ON UPDATE NO ACTION; + END IF; +END; +$$;DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.table_constraints + WHERE table_schema = 'public' + AND table_name = 'agent_persona_guardrails' + AND constraint_name = 'fk_agent_persona_guardrails_persona_id' + ) THEN + ALTER TABLE public.agent_persona_guardrails + ADD CONSTRAINT fk_agent_persona_guardrails_persona_id + FOREIGN KEY (persona_id) + REFERENCES public.agent_personas (id) + ON DELETE NO ACTION + ON UPDATE NO ACTION; + END IF; +END; +$$;DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.table_constraints + WHERE table_schema = 'public' + AND table_name = 'agent_persona_traits' + AND constraint_name = 'fk_agent_persona_traits_persona_id' + ) THEN + ALTER TABLE public.agent_persona_traits + ADD CONSTRAINT fk_agent_persona_traits_persona_id + FOREIGN KEY (persona_id) + REFERENCES public.agent_personas (id) + ON DELETE NO ACTION + ON UPDATE NO ACTION; + END IF; +END; +$$;DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.table_constraints + WHERE table_schema = 'public' + AND table_name = 'agent_persona_traits' + AND constraint_name = 'fk_agent_persona_traits_trait_id' + ) THEN + ALTER TABLE public.agent_persona_traits + ADD CONSTRAINT fk_agent_persona_traits_trait_id + FOREIGN KEY (trait_id) + REFERENCES public.agent_traits (id) + ON DELETE NO ACTION + ON UPDATE NO ACTION; + END IF; +END; +$$;DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.table_constraints + WHERE table_schema = 'public' + AND table_name = 'arc_stages' + AND constraint_name = 'fk_arc_stages_arc_id' + ) THEN + ALTER TABLE public.arc_stages + ADD CONSTRAINT fk_arc_stages_arc_id + FOREIGN KEY (arc_id) + REFERENCES public.character_arcs (id) + ON DELETE NO ACTION + ON UPDATE NO ACTION; + END IF; +END; +$$;DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.table_constraints + WHERE table_schema = 'public' + AND table_name = 'arc_stage_parts' + AND constraint_name = 'fk_arc_stage_parts_part_id' + ) THEN + ALTER TABLE public.arc_stage_parts + ADD CONSTRAINT fk_arc_stage_parts_part_id + FOREIGN KEY (part_id) + REFERENCES public.agent_parts (id) + ON DELETE NO ACTION + ON UPDATE NO ACTION; + END IF; +END; +$$;DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.table_constraints + WHERE table_schema = 'public' + AND table_name = 'arc_stage_parts' + AND constraint_name = 'fk_arc_stage_parts_stage_id' + ) THEN + ALTER TABLE public.arc_stage_parts + ADD CONSTRAINT fk_arc_stage_parts_stage_id + FOREIGN KEY (stage_id) + REFERENCES public.arc_stages (id) + ON DELETE NO ACTION + ON UPDATE NO ACTION; + END IF; +END; +$$;DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.table_constraints + WHERE table_schema = 'public' + AND table_name = 'persona_arc' + AND constraint_name = 'fk_persona_arc_arc_id' + ) THEN + ALTER TABLE public.persona_arc + ADD CONSTRAINT fk_persona_arc_arc_id + FOREIGN KEY (arc_id) + REFERENCES public.character_arcs (id) + ON DELETE NO ACTION + ON UPDATE NO ACTION; + END IF; +END; +$$;DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.table_constraints + WHERE table_schema = 'public' + AND table_name = 'persona_arc' + AND constraint_name = 'fk_persona_arc_current_stage_id' + ) THEN + ALTER TABLE public.persona_arc + ADD CONSTRAINT fk_persona_arc_current_stage_id + FOREIGN KEY (current_stage_id) + REFERENCES public.arc_stages (id) + ON DELETE NO ACTION + ON UPDATE NO ACTION; + END IF; +END; +$$;DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.table_constraints + WHERE table_schema = 'public' + AND table_name = 'persona_arc' + AND constraint_name = 'fk_persona_arc_persona_id' + ) THEN + ALTER TABLE public.persona_arc + ADD CONSTRAINT fk_persona_arc_persona_id + FOREIGN KEY (persona_id) + REFERENCES public.agent_personas (id) + ON DELETE NO ACTION + ON UPDATE NO ACTION; + END IF; +END; +$$;DO $$ BEGIN IF NOT EXISTS ( SELECT 1 FROM information_schema.table_constraints @@ -3242,6 +4655,120 @@ BEGIN END; $$;-- Set sequence values for schema: public DO $$ +DECLARE + m_cnt bigint; +BEGIN + IF EXISTS ( + SELECT 1 FROM pg_class c + INNER JOIN pg_namespace n ON n.oid = c.relnamespace + WHERE c.relname = 'identity_agent_personas_id' + AND n.nspname = 'public' + AND c.relkind = 'S' + ) THEN + SELECT COALESCE(MAX(id), 0) + 1 + FROM public.agent_personas + INTO m_cnt; + + PERFORM setval('public.identity_agent_personas_id'::regclass, m_cnt); + END IF; +END; +$$; +DO $$ +DECLARE + m_cnt bigint; +BEGIN + IF EXISTS ( + SELECT 1 FROM pg_class c + INNER JOIN pg_namespace n ON n.oid = c.relnamespace + WHERE c.relname = 'identity_agent_parts_id' + AND n.nspname = 'public' + AND c.relkind = 'S' + ) THEN + SELECT COALESCE(MAX(id), 0) + 1 + FROM public.agent_parts + INTO m_cnt; + + PERFORM setval('public.identity_agent_parts_id'::regclass, m_cnt); + END IF; +END; +$$; +DO $$ +DECLARE + m_cnt bigint; +BEGIN + IF EXISTS ( + SELECT 1 FROM pg_class c + INNER JOIN pg_namespace n ON n.oid = c.relnamespace + WHERE c.relname = 'identity_agent_traits_id' + AND n.nspname = 'public' + AND c.relkind = 'S' + ) THEN + SELECT COALESCE(MAX(id), 0) + 1 + FROM public.agent_traits + INTO m_cnt; + + PERFORM setval('public.identity_agent_traits_id'::regclass, m_cnt); + END IF; +END; +$$; +DO $$ +DECLARE + m_cnt bigint; +BEGIN + IF EXISTS ( + SELECT 1 FROM pg_class c + INNER JOIN pg_namespace n ON n.oid = c.relnamespace + WHERE c.relname = 'identity_character_arcs_id' + AND n.nspname = 'public' + AND c.relkind = 'S' + ) THEN + SELECT COALESCE(MAX(id), 0) + 1 + FROM public.character_arcs + INTO m_cnt; + + PERFORM setval('public.identity_character_arcs_id'::regclass, m_cnt); + END IF; +END; +$$; +DO $$ +DECLARE + m_cnt bigint; +BEGIN + IF EXISTS ( + SELECT 1 FROM pg_class c + INNER JOIN pg_namespace n ON n.oid = c.relnamespace + WHERE c.relname = 'identity_arc_stages_id' + AND n.nspname = 'public' + AND c.relkind = 'S' + ) THEN + SELECT COALESCE(MAX(id), 0) + 1 + FROM public.arc_stages + INTO m_cnt; + + PERFORM setval('public.identity_arc_stages_id'::regclass, m_cnt); + END IF; +END; +$$; +DO $$ +DECLARE + m_cnt bigint; +BEGIN + IF EXISTS ( + SELECT 1 FROM pg_class c + INNER JOIN pg_namespace n ON n.oid = c.relnamespace + WHERE c.relname = 'identity_persona_arc_persona_id' + AND n.nspname = 'public' + AND c.relkind = 'S' + ) THEN + SELECT COALESCE(MAX(persona_id), 0) + 1 + FROM public.persona_arc + INTO m_cnt; + + PERFORM setval('public.identity_persona_arc_persona_id'::regclass, m_cnt); + END IF; +END; +$$; +DO $$ DECLARE m_cnt bigint; BEGIN @@ -3574,6 +5101,17 @@ $$; + + + + + + + + + + + diff --git a/schema/agent_personas.dbml b/schema/agent_personas.dbml new file mode 100644 index 0000000..c433591 --- /dev/null +++ b/schema/agent_personas.dbml @@ -0,0 +1,132 @@ +Table agent_personas { + id bigserial [pk] + guid uuid [unique, not null, default: `gen_random_uuid()`] + name text [unique, not null] + description text [not null, default: ''] + summary text [not null] + detail text [not null, default: ''] + compiled_summary text [not null, default: ''] + compiled_detail text [not null, default: ''] + compiled_at timestamptz + tags "text[]" [not null, default: `'{}'`] + created_at timestamptz [not null, default: `now()`] + updated_at timestamptz [not null, default: `now()`] +} + +Table agent_parts { + id bigserial [pk] + guid uuid [unique, not null, default: `gen_random_uuid()`] + name text [unique, not null] + part_type text [not null] + description text [not null, default: ''] + summary text [not null] + content text [not null, default: ''] + tags "text[]" [not null, default: `'{}'`] + created_at timestamptz [not null, default: `now()`] + updated_at timestamptz [not null, default: `now()`] +} + +Table agent_persona_parts { + persona_id bigint [not null, ref: > agent_personas.id] + part_id bigint [not null, ref: > agent_parts.id] + part_order int [not null, default: 0] + priority int [not null, default: 0] + + indexes { + (persona_id, part_id) [pk] + persona_id + } +} + +Table agent_persona_skills { + persona_id bigint [not null, ref: > agent_personas.id] + skill_id bigint [not null, ref: > agent_skills.id] + + indexes { + (persona_id, skill_id) [pk] + persona_id + } +} + +Table agent_persona_guardrails { + persona_id bigint [not null, ref: > agent_personas.id] + guardrail_id bigint [not null, ref: > agent_guardrails.id] + + indexes { + (persona_id, guardrail_id) [pk] + persona_id + } +} + +Table agent_traits { + id bigserial [pk] + guid uuid [unique, not null, default: `gen_random_uuid()`] + name text [unique, not null] + trait_type text [not null] + description text [not null, default: ''] + instruction text [not null, default: ''] + tags "text[]" [not null, default: `'{}'`] + created_at timestamptz [not null, default: `now()`] + updated_at timestamptz [not null, default: `now()`] +} + +Table agent_persona_traits { + persona_id bigint [not null, ref: > agent_personas.id] + trait_id bigint [not null, ref: > agent_traits.id] + + indexes { + (persona_id, trait_id) [pk] + persona_id + } +} + +Table character_arcs { + id bigserial [pk] + name text [unique, not null] + description text [not null, default: ''] + summary text [not null, default: ''] + created_at timestamptz [not null, default: `now()`] + updated_at timestamptz [not null, default: `now()`] +} + +Table arc_stages { + id bigserial [pk] + arc_id bigint [not null, ref: > character_arcs.id] + name text [not null] + stage_order int [not null, default: 0] + description text [not null, default: ''] + condition text [not null, default: ''] + created_at timestamptz [not null, default: `now()`] +} + +Table arc_stage_parts { + stage_id bigint [not null, ref: > arc_stages.id] + part_id bigint [not null, ref: > agent_parts.id] + + indexes { + (stage_id, part_id) [pk] + } +} + +Table persona_arc { + persona_id bigint [pk, ref: > agent_personas.id] + arc_id bigint [not null, ref: > character_arcs.id] + current_stage_id bigint [not null, ref: > arc_stages.id] + updated_at timestamptz [not null, default: `now()`] +} + +// Cross-file refs (for relspecgo merge) +Ref: agent_persona_parts.persona_id > agent_personas.id [delete: cascade] +Ref: agent_persona_parts.part_id > agent_parts.id [delete: cascade] +Ref: agent_persona_skills.persona_id > agent_personas.id [delete: cascade] +Ref: agent_persona_skills.skill_id > agent_skills.id [delete: cascade] +Ref: agent_persona_guardrails.persona_id > agent_personas.id [delete: cascade] +Ref: agent_persona_guardrails.guardrail_id > agent_guardrails.id [delete: cascade] +Ref: agent_persona_traits.persona_id > agent_personas.id [delete: cascade] +Ref: agent_persona_traits.trait_id > agent_traits.id [delete: cascade] +Ref: arc_stages.arc_id > character_arcs.id [delete: cascade] +Ref: arc_stage_parts.stage_id > arc_stages.id [delete: cascade] +Ref: arc_stage_parts.part_id > agent_parts.id [delete: cascade] +Ref: persona_arc.persona_id > agent_personas.id [delete: cascade] +Ref: persona_arc.arc_id > character_arcs.id +Ref: persona_arc.current_stage_id > arc_stages.id