- Return error when both path and content are supplied simultaneously - Improve tool description to clearly state the two valid call forms - Add per-field descriptions so LLMs understand what each parameter requires Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
86 lines
3.2 KiB
Go
86 lines
3.2 KiB
Go
// internal/skills/brain/skill.go
|
|
package brain
|
|
|
|
import (
|
|
"encoding/json"
|
|
|
|
"github.com/mathiasbq/supervisor/internal/registry"
|
|
)
|
|
|
|
// Config holds brain skill configuration.
|
|
type Config struct {
|
|
IngestBaseURL string // base URL of the ingestion HTTP server (brain_query, brain_write)
|
|
IngestSvcURL string // base URL of the ingestion-svc HTTP server (brain_ingest)
|
|
KBRetrievalURL string // base URL of the kb-retrieval server (brain_search)
|
|
}
|
|
|
|
// Skill implements registry.Skill for brain_query and brain_write.
|
|
type Skill struct {
|
|
cfg Config
|
|
}
|
|
|
|
// New constructs a brain Skill.
|
|
func New(cfg Config) *Skill { return &Skill{cfg: cfg} }
|
|
|
|
// Name returns the skill name used for routing.
|
|
func (s *Skill) Name() string { return "brain" }
|
|
|
|
// Tools returns the MCP tool definitions for brain_query and brain_write.
|
|
func (s *Skill) Tools() []registry.ToolDef {
|
|
schema := func(required []string, props map[string]any) json.RawMessage {
|
|
b, _ := json.Marshal(map[string]any{"type": "object", "required": required, "properties": props})
|
|
return b
|
|
}
|
|
str := map[string]any{"type": "string"}
|
|
num := map[string]any{"type": "integer"}
|
|
|
|
tools := []registry.ToolDef{
|
|
{
|
|
Name: "brain_query",
|
|
Description: "BM25 full-text search across brain/knowledge/ and brain/wiki/ markdown files. Fast, no embeddings needed. Call before any significant task.",
|
|
InputSchema: schema([]string{"query"}, map[string]any{
|
|
"query": str,
|
|
"limit": num,
|
|
}),
|
|
},
|
|
{
|
|
Name: "brain_write",
|
|
Description: "Write a raw knowledge note to brain/knowledge/ for later ingestion.",
|
|
InputSchema: schema([]string{"content"}, map[string]any{
|
|
"content": str,
|
|
"type": str,
|
|
"domain": str,
|
|
"filename": str,
|
|
}),
|
|
},
|
|
}
|
|
if s.cfg.IngestSvcURL != "" {
|
|
tools = append(tools, registry.ToolDef{
|
|
Name: "brain_ingest",
|
|
Description: "Ingest content into the brain wiki (brain/wiki/). Calls an LLM to produce structured wiki pages. " +
|
|
"Use for substantial documents, articles, or knowledge worth structuring. " +
|
|
"Provide EITHER (a) path — absolute path to a file or directory, " +
|
|
"OR (b) content + source — raw text and a human-readable name. " +
|
|
"Providing both is an error. Returns the list of wiki pages written.",
|
|
InputSchema: schema([]string{}, map[string]any{
|
|
"content": map[string]any{"type": "string", "description": "raw text to ingest; required when path is not set"},
|
|
"source": map[string]any{"type": "string", "description": "human-readable name for the content, e.g. 'shape-up-book'; required when path is not set"},
|
|
"path": map[string]any{"type": "string", "description": "absolute path to a file or directory to ingest; mutually exclusive with content+source"},
|
|
"dry_run": map[string]any{"type": "boolean"},
|
|
}),
|
|
})
|
|
}
|
|
if s.cfg.KBRetrievalURL != "" {
|
|
tools = append(tools, registry.ToolDef{
|
|
Name: "brain_search",
|
|
Description: "Semantic vector search across the brain wiki using embeddings. Use when brain_query returns no results or you need conceptually-related results rather than keyword matches.",
|
|
InputSchema: schema([]string{"query"}, map[string]any{
|
|
"query": str,
|
|
"collection": str,
|
|
"limit": num,
|
|
}),
|
|
})
|
|
}
|
|
return tools
|
|
}
|