diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..7d771b9 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,101 @@ +## What This Project Is + +AMCS (Avalon Memory Control Service) is an MCP server written in Go that provides memory, knowledge, and task management for AI agents. It exposes 56+ tools via HTTP/SSE MCP transport and includes a Svelte 5 admin UI embedded in the binary. + +# Agent Rules + +Keep your answers short. Question everything, never assume or guess. Ask the user if you are unsure about anything. +You must use the AMCS MCP system tools. Set Origin as the active project and always capture summaries of what was done as thoughts using the capture_thought tool. If amcs is not available write files to doc/llm/log/yyyymmdd_hh.md +When working on postgresql use the postgresql skill. On go/api/backend the go-skill and for the frontend the "svelte-architect" skill. + +## Commands + +### Build & Run +```bash +make build # Build server binary (outputs to bin/amcs-server), embeds UI +make build-cli # Build CLI binary (bin/amcs-cli) +make clean # Remove bin/ + +# Run locally (builds UI then starts server) +./scripts/run-local.sh configs/dev.yaml + +# UI hot-reload dev server (proxy to backend on :8080) +make ui-dev # http://localhost:5173 +``` + +### Testing +```bash +make test # All tests (svelte-check + go test ./...) +go test ./internal/tools -run TestFunctionName -v # Single test +go test ./internal/tools -v # Package tests +``` + +### Database +```bash +make migrate # Apply SQL migrations +make check-schema-drift # Verify no drift between DBML and SQL +``` + +## Architecture + +### Key Pattern: Store → Tool → MCP Handler + +Every tool domain follows the same three-layer pattern: + +1. **Store** (`internal/store/`) — database access via BUN ORM + pgx. One file per domain (e.g., `thoughts.go`, `plans.go`). +2. **Tool** (`internal/tools/`) — MCP tool handler structs with a `Handle(ctx, req, input)` method. Input/output types are plain Go structs; JSON schemas are auto-generated from struct tags. +3. **Registration** (`internal/mcpserver/handlers.go`) — each domain has a `registerXxxTools(server, logger, ts)` function called from `NewHandlers`. + +### Adding a New Tool Domain + +Follow the checklist in `internal/tools/` (see `project_conventions.md` in memory, or look at any existing domain like `skills.go` as a reference): +- Define input/output structs with `jsonschema` tags +- Implement a struct with `Handle` method +- Add the struct to `ToolSet` in `mcpserver/handlers.go` +- Wire the store in `internal/store/db.go` +- Add a `registerXxxTools` call in `NewHandlers` + +### AI Provider Architecture + +Configured in YAML under `ai.providers`. Each role (embeddings, metadata extraction) supports a primary provider and a fallback chain. Providers are named references (litellm, ollama, openrouter) that resolve to OpenAI-compatible endpoints. Health tracking per provider with cooldown on transient failures. + +### Error Handling + +All tool errors return structured `mcperrors.RPCError` with: +- `data.type` — machine-readable category (`invalid_input`, `project_required`, `entity_not_found`, etc.) +- `data.field` / `data.detail` / `data.hint` — structured validation data + +Use helpers in `internal/mcperrors/` rather than raw errors. + +### Authentication + +- API key via `x-brain-key` header (dev/simple deployments) +- OAuth 2.0 client credentials for production +- Session store tracks active project per client connection + +### Database Schema + +DBML files in `schema/` are the source of truth for the database schema. Never edit the generated SQL or Go models directly — always edit the DBML, then regenerate: + +```bash +make generate-migrations # DBML → SQL migration files +make generate-models # DBML → Go model structs +make migrate # Apply pending migrations to the database +``` + +All primary keys are `bigserial int64` (migrated from UUID in migration 015). + +### Frontend + +Svelte 5 + Tailwind + Skeleton UI in `ui/`. Built via pnpm and embedded into the Go binary as a static filesystem. The Vite dev server proxies API calls to the backend on `:8080`. + +The UI uses **resolvespec-js** for admin CRUD views. The backend API for the UI follows the **ResolveSpec** convention — resource endpoints, filtering, and pagination are structured according to that spec. + +## Configuration + +YAML config with schema `version: 2`. Key env var overrides: +- `DATABASE_URL` — PostgreSQL connection string +- `AMCS_LITELLM_API_KEY`, `AMCS_OPENROUTER_API_KEY` — AI provider keys +- `AMCS_SERVER_PORT` — defaults to 8080 + +See `configs/config.example.yaml` for the full schema. Config auto-migrates v1→v2 on startup. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..2214409 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +Read the AGENTS.md file \ No newline at end of file diff --git a/internal/generatedmodels/sql_public_agent_skills.go b/internal/generatedmodels/sql_public_agent_skills.go index b0bcf1f..b71f01f 100644 --- a/internal/generatedmodels/sql_public_agent_skills.go +++ b/internal/generatedmodels/sql_public_agent_skills.go @@ -13,7 +13,11 @@ type ModelPublicAgentSkills struct { 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"` diff --git a/internal/mcpserver/server.go b/internal/mcpserver/server.go index 80cd551..792de0f 100644 --- a/internal/mcpserver/server.go +++ b/internal/mcpserver/server.go @@ -464,10 +464,16 @@ func registerSkillTools(server *mcp.Server, logger *slog.Logger, toolSet ToolSet } if err := addTool(server, logger, &mcp.Tool{ Name: "list_skills", - Description: "List all agent skills, optionally filtered by tag.", + Description: "List agent skills (metadata only by default). Set include_content=true to get full content, or use get_skill for a single skill.", }, toolSet.Skills.ListSkills); err != nil { return err } + if err := addTool(server, logger, &mcp.Tool{ + Name: "get_skill", + Description: "Fetch a single agent skill with full content by id or name.", + }, toolSet.Skills.GetSkill); err != nil { + return err + } if err := addTool(server, logger, &mcp.Tool{ Name: "add_guardrail", Description: "Store an agent guardrail (constraint or safety rule).", @@ -486,6 +492,12 @@ func registerSkillTools(server *mcp.Server, logger *slog.Logger, toolSet ToolSet }, toolSet.Skills.ListGuardrails); err != nil { return err } + if err := addTool(server, logger, &mcp.Tool{ + Name: "get_guardrail", + Description: "Fetch a single agent guardrail with full content by id or name.", + }, toolSet.Skills.GetGuardrail); err != nil { + return err + } if err := addTool(server, logger, &mcp.Tool{ Name: "add_project_skill", Description: "Link a skill to a project. Pass project if client is stateless.", @@ -637,12 +649,14 @@ func BuildToolCatalog() []tools.ToolEntry { {Name: "search_maintenance_history", Description: "Search the maintenance log by task name, category, or date range.", Category: "maintenance"}, // skills - {Name: "add_skill", Description: "Store a reusable agent skill (behavioural instruction or capability prompt).", Category: "skills"}, + {Name: "add_skill", Description: "Store a reusable agent skill. Supports language_tags, library_tags, framework_tags, and domain_tags for precise retrieval.", Category: "skills"}, {Name: "remove_skill", Description: "Delete an agent skill by id.", Category: "skills"}, - {Name: "list_skills", Description: "List all agent skills, optionally filtered by tag.", Category: "skills"}, + {Name: "list_skills", Description: "List agent skills (metadata only by default). Filter by tag (searches all tag fields). Set include_content=true for full bodies, or use get_skill to load one.", Category: "skills"}, + {Name: "get_skill", Description: "Fetch a single agent skill with full content by id or name. Prefer this over list_skills when you know which skill you need.", Category: "skills"}, {Name: "add_guardrail", Description: "Store a reusable agent guardrail (constraint or safety rule).", Category: "skills"}, {Name: "remove_guardrail", Description: "Delete an agent guardrail by id.", Category: "skills"}, {Name: "list_guardrails", Description: "List all agent guardrails, optionally filtered by tag or severity.", Category: "skills"}, + {Name: "get_guardrail", Description: "Fetch a single agent guardrail with full content by id or name.", Category: "skills"}, {Name: "add_project_skill", Description: "Link an agent skill to a project. Pass project explicitly when your client does not preserve MCP sessions.", Category: "skills"}, {Name: "remove_project_skill", Description: "Unlink an agent skill from a project. Pass project explicitly when your client does not preserve MCP sessions.", Category: "skills"}, {Name: "list_project_skills", Description: "List all skills linked to a project. Call this at the start of every project session to load agent behaviour instructions before generating new ones. Only create new skills if none are returned. Pass project explicitly when your client does not preserve MCP sessions.", Category: "skills"}, diff --git a/internal/store/skills.go b/internal/store/skills.go index 0844023..4919fb1 100644 --- a/internal/store/skills.go +++ b/internal/store/skills.go @@ -15,11 +15,24 @@ func (db *DB) AddSkill(ctx context.Context, skill ext.AgentSkill) (ext.AgentSkil if skill.Tags == nil { skill.Tags = []string{} } + if skill.LanguageTags == nil { + skill.LanguageTags = []string{} + } + if skill.LibraryTags == nil { + skill.LibraryTags = []string{} + } + if skill.FrameworkTags == nil { + skill.FrameworkTags = []string{} + } + if skill.DomainTags == nil { + skill.DomainTags = []string{} + } row := db.pool.QueryRow(ctx, ` - insert into agent_skills (name, description, content, tags) - values ($1, $2, $3, $4) + insert into agent_skills (name, description, content, tags, language_tags, library_tags, framework_tags, domain_tags) + values ($1, $2, $3, $4, $5, $6, $7, $8) returning id, guid, created_at, updated_at - `, skill.Name, skill.Description, skill.Content, skill.Tags) + `, skill.Name, skill.Description, skill.Content, skill.Tags, + skill.LanguageTags, skill.LibraryTags, skill.FrameworkTags, skill.DomainTags) created := skill var model generatedmodels.ModelPublicAgentSkills @@ -45,11 +58,11 @@ func (db *DB) RemoveSkill(ctx context.Context, id int64) error { } func (db *DB) ListSkills(ctx context.Context, tag string) ([]ext.AgentSkill, error) { - q := `select id, name, description, content, tags::text[], created_at, updated_at from agent_skills` + q := `select id, name, description, content, tags::text[], language_tags::text[], library_tags::text[], framework_tags::text[], domain_tags::text[], created_at, updated_at from agent_skills` args := []any{} if t := strings.TrimSpace(tag); t != "" { args = append(args, t) - q += fmt.Sprintf(" where $%d = any(tags)", len(args)) + q += fmt.Sprintf(" where $%d = any(tags) or $%d = any(language_tags) or $%d = any(library_tags) or $%d = any(framework_tags) or $%d = any(domain_tags)", len(args), len(args), len(args), len(args), len(args)) } q += " order by name" @@ -61,52 +74,91 @@ func (db *DB) ListSkills(ctx context.Context, tag string) ([]ext.AgentSkill, err var skills []ext.AgentSkill for rows.Next() { - var model generatedmodels.ModelPublicAgentSkills - var tags []string - if err := rows.Scan(&model.ID, &model.Name, &model.Description, &model.Content, &tags, &model.CreatedAt, &model.UpdatedAt); err != nil { + s, err := scanSkill(rows) + if err != nil { return nil, fmt.Errorf("scan agent skill: %w", err) } - s := ext.AgentSkill{ - ID: model.ID.Int64(), - Name: model.Name.String(), - Description: model.Description.String(), - Content: model.Content.String(), - Tags: tags, - CreatedAt: model.CreatedAt.Time(), - UpdatedAt: model.UpdatedAt.Time(), - } - if s.Tags == nil { - s.Tags = []string{} - } skills = append(skills, s) } return skills, rows.Err() } -func (db *DB) GetSkill(ctx context.Context, id int64) (ext.AgentSkill, error) { - row := db.pool.QueryRow(ctx, ` - select id, name, description, content, tags::text[], created_at, updated_at - from agent_skills where id = $1 - `, id) +const skillSelectCols = `id, name, description, content, tags::text[], language_tags::text[], library_tags::text[], framework_tags::text[], domain_tags::text[], created_at, updated_at` +type skillScanner interface { + Scan(dest ...any) error +} + +func scanSkill(row skillScanner) (ext.AgentSkill, error) { var model generatedmodels.ModelPublicAgentSkills - var tags []string - if err := row.Scan(&model.ID, &model.Name, &model.Description, &model.Content, &tags, &model.CreatedAt, &model.UpdatedAt); err != nil { + var tags, langTags, libTags, fwTags, domTags []string + if err := row.Scan(&model.ID, &model.Name, &model.Description, &model.Content, &tags, &langTags, &libTags, &fwTags, &domTags, &model.CreatedAt, &model.UpdatedAt); err != nil { + return ext.AgentSkill{}, err + } + nilToEmpty := func(s []string) []string { + if s == nil { + return []string{} + } + return s + } + return ext.AgentSkill{ + ID: model.ID.Int64(), + Name: model.Name.String(), + Description: model.Description.String(), + Content: model.Content.String(), + Tags: nilToEmpty(tags), + LanguageTags: nilToEmpty(langTags), + LibraryTags: nilToEmpty(libTags), + FrameworkTags: nilToEmpty(fwTags), + DomainTags: nilToEmpty(domTags), + CreatedAt: model.CreatedAt.Time(), + UpdatedAt: model.UpdatedAt.Time(), + }, nil +} + +func (db *DB) GetSkill(ctx context.Context, id int64) (ext.AgentSkill, error) { + row := db.pool.QueryRow(ctx, `select `+skillSelectCols+` from agent_skills where id = $1`, id) + s, err := scanSkill(row) + if err != nil { return ext.AgentSkill{}, fmt.Errorf("get agent skill: %w", err) } - s := ext.AgentSkill{ + return s, nil +} + +func (db *DB) GetSkillByName(ctx context.Context, name string) (ext.AgentSkill, error) { + row := db.pool.QueryRow(ctx, `select `+skillSelectCols+` from agent_skills where name = $1`, name) + s, err := scanSkill(row) + if err != nil { + return ext.AgentSkill{}, fmt.Errorf("get agent skill by name: %w", err) + } + return s, nil +} + +func (db *DB) GetGuardrailByName(ctx context.Context, name string) (ext.AgentGuardrail, error) { + row := db.pool.QueryRow(ctx, ` + select id, name, description, content, severity, tags::text[], created_at, updated_at + from agent_guardrails where name = $1 + `, name) + + var model generatedmodels.ModelPublicAgentGuardrails + var tags []string + if err := row.Scan(&model.ID, &model.Name, &model.Description, &model.Content, &model.Severity, &tags, &model.CreatedAt, &model.UpdatedAt); err != nil { + return ext.AgentGuardrail{}, fmt.Errorf("get agent guardrail by name: %w", err) + } + g := ext.AgentGuardrail{ ID: model.ID.Int64(), Name: model.Name.String(), Description: model.Description.String(), Content: model.Content.String(), + Severity: model.Severity.String(), Tags: tags, CreatedAt: model.CreatedAt.Time(), UpdatedAt: model.UpdatedAt.Time(), } - if s.Tags == nil { - s.Tags = []string{} + if g.Tags == nil { + g.Tags = []string{} } - return s, nil + return g, nil } // Agent Guardrails @@ -253,7 +305,7 @@ func (db *DB) RemoveProjectSkill(ctx context.Context, projectID, skillID int64) func (db *DB) ListProjectSkills(ctx context.Context, projectID int64) ([]ext.AgentSkill, error) { rows, err := db.pool.Query(ctx, ` - select s.id, s.name, s.description, s.content, s.tags::text[], s.created_at, s.updated_at + select s.`+skillSelectCols+` from agent_skills s join project_skills ps on ps.skill_id = s.id where ps.project_id = $1 @@ -266,23 +318,10 @@ func (db *DB) ListProjectSkills(ctx context.Context, projectID int64) ([]ext.Age var skills []ext.AgentSkill for rows.Next() { - var model generatedmodels.ModelPublicAgentSkills - var tags []string - if err := rows.Scan(&model.ID, &model.Name, &model.Description, &model.Content, &tags, &model.CreatedAt, &model.UpdatedAt); err != nil { + s, err := scanSkill(rows) + if err != nil { return nil, fmt.Errorf("scan project skill: %w", err) } - s := ext.AgentSkill{ - ID: model.ID.Int64(), - Name: model.Name.String(), - Description: model.Description.String(), - Content: model.Content.String(), - Tags: tags, - CreatedAt: model.CreatedAt.Time(), - UpdatedAt: model.UpdatedAt.Time(), - } - if s.Tags == nil { - s.Tags = []string{} - } skills = append(skills, s) } return skills, rows.Err() diff --git a/internal/tools/skills.go b/internal/tools/skills.go index 537d0cb..0ece92d 100644 --- a/internal/tools/skills.go +++ b/internal/tools/skills.go @@ -23,10 +23,14 @@ func NewSkillsTool(db *store.DB, sessions *session.ActiveProjects) *SkillsTool { // add_skill type AddSkillInput struct { - Name string `json:"name" jsonschema:"unique skill name"` - Description string `json:"description,omitempty" jsonschema:"short description of what the skill does"` - Content string `json:"content" jsonschema:"the full skill instruction or prompt content"` - Tags []string `json:"tags,omitempty" jsonschema:"optional tags for grouping or filtering"` + Name string `json:"name" jsonschema:"unique skill name"` + Description string `json:"description,omitempty" jsonschema:"short description of what the skill does"` + Content string `json:"content" jsonschema:"the full skill instruction or prompt content"` + Tags []string `json:"tags,omitempty" jsonschema:"general tags for grouping or filtering"` + LanguageTags []string `json:"language_tags,omitempty" jsonschema:"programming languages this skill applies to (e.g. go, python, typescript)"` + LibraryTags []string `json:"library_tags,omitempty" jsonschema:"libraries or packages this skill applies to (e.g. bunrouter, pgx, axios)"` + FrameworkTags []string `json:"framework_tags,omitempty" jsonschema:"frameworks this skill applies to (e.g. gin, nextjs, django)"` + DomainTags []string `json:"domain_tags,omitempty" jsonschema:"topic or problem domains this skill applies to (e.g. auth, testing, migrations)"` } type AddSkillOutput struct { @@ -44,10 +48,14 @@ func (t *SkillsTool) AddSkill(ctx context.Context, _ *mcp.CallToolRequest, in Ad in.Tags = []string{} } skill, err := t.store.AddSkill(ctx, ext.AgentSkill{ - Name: strings.TrimSpace(in.Name), - Description: strings.TrimSpace(in.Description), - Content: strings.TrimSpace(in.Content), - Tags: in.Tags, + Name: strings.TrimSpace(in.Name), + Description: strings.TrimSpace(in.Description), + Content: strings.TrimSpace(in.Content), + Tags: in.Tags, + LanguageTags: in.LanguageTags, + LibraryTags: in.LibraryTags, + FrameworkTags: in.FrameworkTags, + DomainTags: in.DomainTags, }) if err != nil { return nil, AddSkillOutput{}, err @@ -72,10 +80,41 @@ func (t *SkillsTool) RemoveSkill(ctx context.Context, _ *mcp.CallToolRequest, in return nil, RemoveSkillOutput{Removed: true}, nil } +// get_skill + +type GetSkillInput struct { + ID int64 `json:"id,omitempty" jsonschema:"skill id (preferred; use instead of name when available)"` + Name string `json:"name,omitempty" jsonschema:"skill name (alternative to id)"` +} + +type GetSkillOutput struct { + Skill ext.AgentSkill `json:"skill"` +} + +func (t *SkillsTool) GetSkill(ctx context.Context, _ *mcp.CallToolRequest, in GetSkillInput) (*mcp.CallToolResult, GetSkillOutput, error) { + var ( + skill ext.AgentSkill + err error + ) + switch { + case in.ID != 0: + skill, err = t.store.GetSkill(ctx, in.ID) + case strings.TrimSpace(in.Name) != "": + skill, err = t.store.GetSkillByName(ctx, strings.TrimSpace(in.Name)) + default: + return nil, GetSkillOutput{}, errRequiredField("id or name") + } + if err != nil { + return nil, GetSkillOutput{}, err + } + return nil, GetSkillOutput{Skill: skill}, nil +} + // list_skills type ListSkillsInput struct { - Tag string `json:"tag,omitempty" jsonschema:"filter by tag"` + Tag string `json:"tag,omitempty" jsonschema:"filter by tag (searches all tag fields: tags, language_tags, library_tags, framework_tags, domain_tags)"` + IncludeContent bool `json:"include_content,omitempty" jsonschema:"include full skill content in results (default false — use get_skill to load content for a specific skill)"` } type ListSkillsOutput struct { @@ -90,6 +129,11 @@ func (t *SkillsTool) ListSkills(ctx context.Context, _ *mcp.CallToolRequest, in if skills == nil { skills = []ext.AgentSkill{} } + if !in.IncludeContent { + for i := range skills { + skills[i].Content = "" + } + } return nil, ListSkillsOutput{Skills: skills}, nil } @@ -182,6 +226,36 @@ func (t *SkillsTool) ListGuardrails(ctx context.Context, _ *mcp.CallToolRequest, return nil, ListGuardrailsOutput{Guardrails: guardrails}, nil } +// get_guardrail + +type GetGuardrailInput struct { + ID int64 `json:"id,omitempty" jsonschema:"guardrail id (preferred; use instead of name when available)"` + Name string `json:"name,omitempty" jsonschema:"guardrail name (alternative to id)"` +} + +type GetGuardrailOutput struct { + Guardrail ext.AgentGuardrail `json:"guardrail"` +} + +func (t *SkillsTool) GetGuardrail(ctx context.Context, _ *mcp.CallToolRequest, in GetGuardrailInput) (*mcp.CallToolResult, GetGuardrailOutput, error) { + var ( + g ext.AgentGuardrail + err error + ) + switch { + case in.ID != 0: + g, err = t.store.GetGuardrail(ctx, in.ID) + case strings.TrimSpace(in.Name) != "": + g, err = t.store.GetGuardrailByName(ctx, strings.TrimSpace(in.Name)) + default: + return nil, GetGuardrailOutput{}, errRequiredField("id or name") + } + if err != nil { + return nil, GetGuardrailOutput{}, err + } + return nil, GetGuardrailOutput{Guardrail: g}, nil +} + // add_project_skill type AddProjectSkillInput struct { diff --git a/internal/types/extensions.go b/internal/types/extensions.go index c428b2b..2c95b52 100644 --- a/internal/types/extensions.go +++ b/internal/types/extensions.go @@ -217,14 +217,18 @@ type ContactHistory struct { // Agent Skills & Guardrails type AgentSkill struct { - ID int64 `json:"id"` - GUID uuid.UUID `json:"guid"` - Name string `json:"name"` - Description string `json:"description,omitempty"` - Content string `json:"content"` - Tags []string `json:"tags"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` + ID int64 `json:"id"` + GUID uuid.UUID `json:"guid"` + Name string `json:"name"` + Description string `json:"description,omitempty"` + Content string `json:"content,omitempty"` + Tags []string `json:"tags"` + LanguageTags []string `json:"language_tags"` + LibraryTags []string `json:"library_tags"` + FrameworkTags []string `json:"framework_tags"` + DomainTags []string `json:"domain_tags"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` } type AgentGuardrail struct { diff --git a/migrations/020_generated_schema.sql b/migrations/020_generated_schema.sql index 0f868f4..a947e11 100644 --- a/migrations/020_generated_schema.sql +++ b/migrations/020_generated_schema.sql @@ -277,8 +277,12 @@ CREATE TABLE IF NOT EXISTS public.agent_skills ( content text NOT NULL, created_at timestamptz NOT NULL DEFAULT now(), description text NOT NULL DEFAULT '', + domain_tags text[] NOT NULL DEFAULT '{}', + framework_tags text[] NOT NULL DEFAULT '{}', guid uuid NOT NULL DEFAULT gen_random_uuid(), id bigserial NOT NULL, + language_tags text[] NOT NULL DEFAULT '{}', + library_tags text[] NOT NULL DEFAULT '{}', name text NOT NULL, tags text[] NOT NULL DEFAULT '{}', updated_at timestamptz NOT NULL DEFAULT now() @@ -1793,6 +1797,32 @@ BEGIN END; $$; +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = 'agent_skills' + AND column_name = 'domain_tags' + ) THEN + ALTER TABLE public.agent_skills ADD COLUMN domain_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_skills' + AND column_name = 'framework_tags' + ) THEN + ALTER TABLE public.agent_skills ADD COLUMN framework_tags text[] NOT NULL DEFAULT '{}'; + END IF; +END; +$$; + DO $$ BEGIN IF NOT EXISTS ( @@ -1819,6 +1849,32 @@ BEGIN END; $$; +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = 'agent_skills' + AND column_name = 'language_tags' + ) THEN + ALTER TABLE public.agent_skills ADD COLUMN language_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_skills' + AND column_name = 'library_tags' + ) THEN + ALTER TABLE public.agent_skills ADD COLUMN library_tags text[] NOT NULL DEFAULT '{}'; + END IF; +END; +$$; + DO $$ BEGIN IF NOT EXISTS ( diff --git a/schema/skills.dbml b/schema/skills.dbml index 725cbea..ebe0116 100644 --- a/schema/skills.dbml +++ b/schema/skills.dbml @@ -5,6 +5,10 @@ Table agent_skills { description text [not null, default: ''] content text [not null] tags "text[]" [not null, default: `'{}'`] + language_tags "text[]" [not null, default: `'{}'`] + library_tags "text[]" [not null, default: `'{}'`] + framework_tags "text[]" [not null, default: `'{}'`] + domain_tags "text[]" [not null, default: `'{}'`] created_at timestamptz [not null, default: `now()`] updated_at timestamptz [not null, default: `now()`] }