Custom Store
Implement a custom persistence backend by satisfying the composite store.Store interface.
Every Cortex persistence operation goes through the composite store.Store interface. This guide shows how to implement your own backend — whether that's MySQL, DynamoDB, or an in-memory store for tests.
The composite interface
The store.Store interface embeds 8 domain-specific sub-interfaces plus 3 lifecycle methods:
import "github.com/xraph/cortex/store"
type Store interface {
agent.Store // 6 methods
skill.Store // 6 methods
trait.Store // 6 methods
behavior.Store // 6 methods
persona.Store // 6 methods
run.Store // 8 methods
memory.Store // 8 methods
checkpoint.Store // 4 methods
Migrate(ctx context.Context) error
Ping(ctx context.Context) error
Close() error
}Total: 47 methods across all sub-interfaces plus 3 lifecycle methods.
Sub-interface breakdown
agent.Store (6 methods)
type Store interface {
Create(ctx context.Context, config *Config) error
Get(ctx context.Context, id id.AgentID) (*Config, error)
GetByName(ctx context.Context, appID, name string) (*Config, error)
Update(ctx context.Context, config *Config) error
Delete(ctx context.Context, id id.AgentID) error
List(ctx context.Context, filter *ListFilter) ([]*Config, error)
}skill.Store (6 methods)
type Store interface {
CreateSkill(ctx context.Context, s *Skill) error
GetSkill(ctx context.Context, id id.SkillID) (*Skill, error)
GetSkillByName(ctx context.Context, appID, name string) (*Skill, error)
UpdateSkill(ctx context.Context, s *Skill) error
DeleteSkill(ctx context.Context, id id.SkillID) error
ListSkills(ctx context.Context, filter *ListFilter) ([]*Skill, error)
}trait.Store (6 methods)
type Store interface {
CreateTrait(ctx context.Context, t *Trait) error
GetTrait(ctx context.Context, id id.TraitID) (*Trait, error)
GetTraitByName(ctx context.Context, appID, name string) (*Trait, error)
UpdateTrait(ctx context.Context, t *Trait) error
DeleteTrait(ctx context.Context, id id.TraitID) error
ListTraits(ctx context.Context, filter *ListFilter) ([]*Trait, error)
}behavior.Store (6 methods)
type Store interface {
CreateBehavior(ctx context.Context, b *Behavior) error
GetBehavior(ctx context.Context, id id.BehaviorID) (*Behavior, error)
GetBehaviorByName(ctx context.Context, appID, name string) (*Behavior, error)
UpdateBehavior(ctx context.Context, b *Behavior) error
DeleteBehavior(ctx context.Context, id id.BehaviorID) error
ListBehaviors(ctx context.Context, filter *ListFilter) ([]*Behavior, error)
}persona.Store (6 methods)
type Store interface {
CreatePersona(ctx context.Context, p *Persona) error
GetPersona(ctx context.Context, id id.PersonaID) (*Persona, error)
GetPersonaByName(ctx context.Context, appID, name string) (*Persona, error)
UpdatePersona(ctx context.Context, p *Persona) error
DeletePersona(ctx context.Context, id id.PersonaID) error
ListPersonas(ctx context.Context, filter *ListFilter) ([]*Persona, error)
}run.Store (8 methods)
type Store interface {
CreateRun(ctx context.Context, r *Run) error
GetRun(ctx context.Context, id id.AgentRunID) (*Run, error)
UpdateRun(ctx context.Context, r *Run) error
ListRuns(ctx context.Context, filter *ListFilter) ([]*Run, error)
CreateStep(ctx context.Context, s *Step) error
ListSteps(ctx context.Context, runID id.AgentRunID) ([]*Step, error)
CreateToolCall(ctx context.Context, tc *ToolCall) error
ListToolCalls(ctx context.Context, stepID id.StepID) ([]*ToolCall, error)
}memory.Store (8 methods)
type Store interface {
SaveConversation(ctx context.Context, agentID id.AgentID, tenantID string, msgs []Message) error
LoadConversation(ctx context.Context, agentID id.AgentID, tenantID string, limit int) ([]Message, error)
ClearConversation(ctx context.Context, agentID id.AgentID, tenantID string) error
SaveWorking(ctx context.Context, agentID id.AgentID, tenantID string, data map[string]any) error
LoadWorking(ctx context.Context, agentID id.AgentID, tenantID string) (map[string]any, error)
ClearWorking(ctx context.Context, agentID id.AgentID, tenantID string) error
SaveSummary(ctx context.Context, agentID id.AgentID, tenantID string, summary string) error
LoadSummaries(ctx context.Context, agentID id.AgentID, tenantID string) ([]string, error)
}checkpoint.Store (4 methods)
type Store interface {
CreateCheckpoint(ctx context.Context, cp *Checkpoint) error
GetCheckpoint(ctx context.Context, id id.CheckpointID) (*Checkpoint, error)
Resolve(ctx context.Context, id id.CheckpointID, decision Decision) error
ListPending(ctx context.Context, filter *ListFilter) ([]*Checkpoint, error)
}Skeleton implementation
package mystore
import (
"context"
"github.com/xraph/cortex/agent"
"github.com/xraph/cortex/behavior"
"github.com/xraph/cortex/checkpoint"
"github.com/xraph/cortex/id"
"github.com/xraph/cortex/memory"
"github.com/xraph/cortex/persona"
"github.com/xraph/cortex/run"
"github.com/xraph/cortex/skill"
"github.com/xraph/cortex/store"
"github.com/xraph/cortex/trait"
)
// MyStore implements the composite store.Store interface.
type MyStore struct {
// your database client, connection pool, etc.
}
// Compile-time check.
var _ store.Store = (*MyStore)(nil)
// Migrate creates tables or runs schema migrations.
func (s *MyStore) Migrate(ctx context.Context) error { return nil }
// Ping verifies database connectivity.
func (s *MyStore) Ping(ctx context.Context) error { return nil }
// Close releases any held resources.
func (s *MyStore) Close() error { return nil }
// ── Agent methods (6) ────────────────────────────
func (s *MyStore) Create(ctx context.Context, config *agent.Config) error { /* ... */ }
func (s *MyStore) Get(ctx context.Context, agentID id.AgentID) (*agent.Config, error) { /* ... */ }
func (s *MyStore) GetByName(ctx context.Context, appID, name string) (*agent.Config, error) { /* ... */ }
func (s *MyStore) Update(ctx context.Context, config *agent.Config) error { /* ... */ }
func (s *MyStore) Delete(ctx context.Context, agentID id.AgentID) error { /* ... */ }
func (s *MyStore) List(ctx context.Context, filter *agent.ListFilter) ([]*agent.Config, error) { /* ... */ }
// ── Skill methods (6) ────────────────────────────
func (s *MyStore) CreateSkill(ctx context.Context, sk *skill.Skill) error { /* ... */ }
func (s *MyStore) GetSkill(ctx context.Context, skillID id.SkillID) (*skill.Skill, error) { /* ... */ }
func (s *MyStore) GetSkillByName(ctx context.Context, appID, name string) (*skill.Skill, error) { /* ... */ }
func (s *MyStore) UpdateSkill(ctx context.Context, sk *skill.Skill) error { /* ... */ }
func (s *MyStore) DeleteSkill(ctx context.Context, skillID id.SkillID) error { /* ... */ }
func (s *MyStore) ListSkills(ctx context.Context, filter *skill.ListFilter) ([]*skill.Skill, error) { /* ... */ }
// ── Trait methods (6) ────────────────────────────
func (s *MyStore) CreateTrait(ctx context.Context, t *trait.Trait) error { /* ... */ }
func (s *MyStore) GetTrait(ctx context.Context, traitID id.TraitID) (*trait.Trait, error) { /* ... */ }
func (s *MyStore) GetTraitByName(ctx context.Context, appID, name string) (*trait.Trait, error) { /* ... */ }
func (s *MyStore) UpdateTrait(ctx context.Context, t *trait.Trait) error { /* ... */ }
func (s *MyStore) DeleteTrait(ctx context.Context, traitID id.TraitID) error { /* ... */ }
func (s *MyStore) ListTraits(ctx context.Context, filter *trait.ListFilter) ([]*trait.Trait, error) { /* ... */ }
// ── Behavior methods (6) ─────────────────────────
func (s *MyStore) CreateBehavior(ctx context.Context, b *behavior.Behavior) error { /* ... */ }
func (s *MyStore) GetBehavior(ctx context.Context, behaviorID id.BehaviorID) (*behavior.Behavior, error) { /* ... */ }
func (s *MyStore) GetBehaviorByName(ctx context.Context, appID, name string) (*behavior.Behavior, error) { /* ... */ }
func (s *MyStore) UpdateBehavior(ctx context.Context, b *behavior.Behavior) error { /* ... */ }
func (s *MyStore) DeleteBehavior(ctx context.Context, behaviorID id.BehaviorID) error { /* ... */ }
func (s *MyStore) ListBehaviors(ctx context.Context, filter *behavior.ListFilter) ([]*behavior.Behavior, error) { /* ... */ }
// ── Persona methods (6) ──────────────────────────
func (s *MyStore) CreatePersona(ctx context.Context, p *persona.Persona) error { /* ... */ }
func (s *MyStore) GetPersona(ctx context.Context, personaID id.PersonaID) (*persona.Persona, error) { /* ... */ }
func (s *MyStore) GetPersonaByName(ctx context.Context, appID, name string) (*persona.Persona, error) { /* ... */ }
func (s *MyStore) UpdatePersona(ctx context.Context, p *persona.Persona) error { /* ... */ }
func (s *MyStore) DeletePersona(ctx context.Context, personaID id.PersonaID) error { /* ... */ }
func (s *MyStore) ListPersonas(ctx context.Context, filter *persona.ListFilter) ([]*persona.Persona, error) { /* ... */ }
// ── Run methods (8) ──────────────────────────────
func (s *MyStore) CreateRun(ctx context.Context, r *run.Run) error { /* ... */ }
func (s *MyStore) GetRun(ctx context.Context, runID id.AgentRunID) (*run.Run, error) { /* ... */ }
func (s *MyStore) UpdateRun(ctx context.Context, r *run.Run) error { /* ... */ }
func (s *MyStore) ListRuns(ctx context.Context, filter *run.ListFilter) ([]*run.Run, error) { /* ... */ }
func (s *MyStore) CreateStep(ctx context.Context, step *run.Step) error { /* ... */ }
func (s *MyStore) ListSteps(ctx context.Context, runID id.AgentRunID) ([]*run.Step, error) { /* ... */ }
func (s *MyStore) CreateToolCall(ctx context.Context, tc *run.ToolCall) error { /* ... */ }
func (s *MyStore) ListToolCalls(ctx context.Context, stepID id.StepID) ([]*run.ToolCall, error) { /* ... */ }
// ── Memory methods (8) ───────────────────────────
func (s *MyStore) SaveConversation(ctx context.Context, agentID id.AgentID, tenantID string, msgs []memory.Message) error { /* ... */ }
func (s *MyStore) LoadConversation(ctx context.Context, agentID id.AgentID, tenantID string, limit int) ([]memory.Message, error) { /* ... */ }
func (s *MyStore) ClearConversation(ctx context.Context, agentID id.AgentID, tenantID string) error { /* ... */ }
func (s *MyStore) SaveWorking(ctx context.Context, agentID id.AgentID, tenantID string, data map[string]any) error { /* ... */ }
func (s *MyStore) LoadWorking(ctx context.Context, agentID id.AgentID, tenantID string) (map[string]any, error) { /* ... */ }
func (s *MyStore) ClearWorking(ctx context.Context, agentID id.AgentID, tenantID string) error { /* ... */ }
func (s *MyStore) SaveSummary(ctx context.Context, agentID id.AgentID, tenantID string, summary string) error { /* ... */ }
func (s *MyStore) LoadSummaries(ctx context.Context, agentID id.AgentID, tenantID string) ([]string, error) { /* ... */ }
// ── Checkpoint methods (4) ───────────────────────
func (s *MyStore) CreateCheckpoint(ctx context.Context, cp *checkpoint.Checkpoint) error { /* ... */ }
func (s *MyStore) GetCheckpoint(ctx context.Context, cpID id.CheckpointID) (*checkpoint.Checkpoint, error) { /* ... */ }
func (s *MyStore) Resolve(ctx context.Context, cpID id.CheckpointID, decision checkpoint.Decision) error { /* ... */ }
func (s *MyStore) ListPending(ctx context.Context, filter *checkpoint.ListFilter) ([]*checkpoint.Checkpoint, error) { /* ... */ }Register with the engine
eng, err := engine.New(
engine.WithStore(&MyStore{db: myDB}),
)Key implementation notes
-
Tenant isolation — Every query must filter by
tenant_idandapp_idfrom context. Usecortex.TenantFromContext(ctx)andcortex.AppFromContext(ctx). -
ID generation — Use
id.NewAgentID(),id.NewSkillID(), etc. to generate TypeID-based identifiers in yourCreatemethods. -
JSONB columns — Complex nested structures (tools, dimensions, triggers, etc.) should be serialized as JSON. The PostgreSQL store uses
bun:"type:jsonb"annotations. -
Migrate() — Should be idempotent. Create tables if they don't exist, add columns if missing.
-
Compile-time check — Always include
var _ store.Store = (*MyStore)(nil)to catch missing methods at compile time.
Existing implementations
| Package | Backend | Notes |
|---|---|---|
store/postgres | PostgreSQL | Production-ready, bun ORM, embedded migrations |
store/memory | In-memory maps | For testing only |