# Tarea 08 — Knowledge por agente ## Objetivo Cada agente tiene una carpeta `knowledge/` donde almacena documentos de conocimiento (markdown). El agente puede buscar, leer, escribir y mejorar su propio conocimiento usando tools siempre disponibles. El conocimiento es archivos reales — inspeccionables por humanos, editables, y se pueden sembrar con contenido inicial. ## Diseño ### Almacenamiento híbrido: archivos + índice FTS5 ``` agents//knowledge/ ← archivos .md reales (human-readable) ├── go-patterns.md ├── user-preferences.md └── matrix-tips.md agents//data/knowledge.db ← índice SQLite FTS5 (búsqueda rápida) ``` - Los documentos viven como archivos `.md` en `knowledge/`. - Un índice FTS5 en SQLite permite búsqueda full-text instantánea. - Al iniciar, se sincroniza: archivos → índice (detecta nuevos, modificados, eliminados). - Al escribir via tool, se actualiza archivo + índice atómicamente. ### Por qué archivos y no solo SQLite 1. **Sembrables**: se puede crear `knowledge/` con documentos iniciales antes de arrancar 2. **Inspeccionables**: un humano puede leer/editar el conocimiento del agente 3. **Git-friendly**: opcionalmente trackeable en el repo 4. **Naturales**: el agente "escribe documentos", no inserta rows --- ## Arquitectura (pure core / impure shell) ### 1. Pure core: `pkg/knowledge/` ```go // pkg/knowledge/types.go package knowledge import "time" // Document represents a knowledge document. type Document struct { Slug string // filename sin extensión, e.g. "go-patterns" Title string // primera línea H1 del markdown, o slug humanizado Content string // contenido completo del archivo UpdatedAt time.Time // mtime del archivo } // SearchResult is a document matched by a search query. type SearchResult struct { Slug string Title string Snippet string // fragmento relevante con match highlights Rank float64 // relevancia FTS5 } ``` ```go // pkg/knowledge/store.go package knowledge import "context" // Store is the pure interface for knowledge operations. // Implemented by shell/knowledge. type Store interface { // Search performs full-text search across all documents. Search(ctx context.Context, query string, limit int) ([]SearchResult, error) // Get retrieves a document by slug. Get(ctx context.Context, slug string) (*Document, error) // Put creates or updates a document (file + index). Put(ctx context.Context, doc Document) error // Delete removes a document (file + index). Delete(ctx context.Context, slug string) error // List returns all document slugs with titles. List(ctx context.Context) ([]Document, error) // Sync re-indexes all files from disk. Called on startup. Sync(ctx context.Context) error // Close releases resources. Close() error } ``` ### 2. Impure shell: `shell/knowledge/` ```go // shell/knowledge/store.go package knowledge // FileStore implements knowledge.Store using files + SQLite FTS5. type FileStore struct { dir string // path a agents//knowledge/ dbPath string // path a agents//data/knowledge.db db *sql.DB logger *slog.Logger } ``` **Schema SQLite:** ```sql CREATE VIRTUAL TABLE IF NOT EXISTS documents USING fts5( slug, title, content, updated_at UNINDEXED ); ``` **Operaciones:** | Método | Archivos | SQLite FTS5 | |--------|----------|-------------| | `Sync()` | Lee todos los `.md` del dir | Reconstruye índice completo | | `Search()` | — | `SELECT slug, title, snippet(...) FROM documents WHERE documents MATCH ?` | | `Get()` | Lee `{slug}.md` | — | | `Put()` | Escribe `{slug}.md` | Upsert en FTS5 | | `Delete()` | Borra `{slug}.md` | Delete en FTS5 | | `List()` | — | `SELECT slug, title FROM documents` | **Sync al startup:** 1. Listar `*.md` en el directorio 2. Para cada archivo: leer contenido, extraer título (primer `# ...`), calcular mtime 3. `DELETE FROM documents` + re-insertar todo (rebuild completo, simple y correcto) 4. Log: `knowledge_sync count=N` **Slug rules:** - Solo `[a-z0-9-]`, máximo 64 chars - Derivado del nombre de archivo sin `.md` - El tool valida antes de escribir ### 3. Tools: `tools/knowledge.go` Cuatro tools que el agente siempre tiene disponibles cuando knowledge está habilitado: #### `knowledge_search` ``` Nombre: knowledge_search Descripción: Search your knowledge base for relevant documents. Returns matching snippets ranked by relevance. Parámetros: - query (string, required): Search terms or phrase - limit (integer, optional): Max results, default 5 Retorna: Lista de resultados con slug, título y snippet ``` #### `knowledge_read` ``` Nombre: knowledge_read Descripción: Read the full content of a knowledge document by its slug. Parámetros: - slug (string, required): Document slug (e.g. "go-patterns") Retorna: Contenido completo del documento ``` #### `knowledge_write` ``` Nombre: knowledge_write Descripción: Create or update a knowledge document. Use this to save new knowledge or improve existing documents. Parámetros: - slug (string, required): Document slug (lowercase, hyphens, e.g. "matrix-tips") - content (string, required): Full markdown content of the document Retorna: Confirmación con slug y tamaño ``` #### `knowledge_list` ``` Nombre: knowledge_list Descripción: List all documents in your knowledge base with their titles. Parámetros: ninguno Retorna: Lista de slugs con títulos y fecha de última actualización ``` > **Nota:** No incluyo `knowledge_delete` por ahora. Los agentes deberían mejorar y ampliar, no borrar. Si se necesita, se añade después. ### 4. Config: `internal/config/schema.go` ```go type KnowledgeCfg struct { Enabled bool `yaml:"enabled"` Dir string `yaml:"dir"` // default: "./knowledge" (relativo al dir del agente) } ``` Añadir a `ToolsCfg`: ```go type ToolsCfg struct { // ... existentes ... Knowledge KnowledgeCfg `yaml:"knowledge"` } ``` Config de ejemplo en `config.yaml`: ```yaml tools: knowledge: enabled: true dir: "./knowledge" # opcional, default relativo al agente ``` ### 5. Registro en runtime: `agents/runtime.go` En `buildToolRegistry()`, después de los memory tools: ```go if cfg.Tools.Knowledge.Enabled { knowledgeDir := resolveKnowledgeDir(cfg) // resolve relative to agent dir knowledgeDBPath := filepath.Join(cfg.Storage.DataDir, "knowledge.db") kStore, err := shellknowledge.New(knowledgeDir, knowledgeDBPath, logger) if err != nil { logger.Error("knowledge_store_init_failed", "err", err) } else { // Sync on startup if err := kStore.Sync(ctx); err != nil { logger.Error("knowledge_sync_failed", "err", err) } reg.Register(tools.NewKnowledgeSearch(kStore)) reg.Register(tools.NewKnowledgeRead(kStore)) reg.Register(tools.NewKnowledgeWrite(kStore)) reg.Register(tools.NewKnowledgeList(kStore)) logger.Debug("registered knowledge tools") } } ``` --- ## Plan de implementación (orden) ### Paso 1 — Pure types (`pkg/knowledge/`) - [ ] `pkg/knowledge/types.go` — Document, SearchResult - [ ] `pkg/knowledge/store.go` — Store interface ### Paso 2 — Config - [ ] Añadir `KnowledgeCfg` a `internal/config/schema.go` dentro de `ToolsCfg` ### Paso 3 — Shell store (`shell/knowledge/`) - [ ] `shell/knowledge/store.go` — FileStore con FTS5 - Constructor `New(dir, dbPath, logger)` - Sync(), Search(), Get(), Put(), Delete(), List(), Close() - Validación de slugs - Extracción de título del markdown (primer `# `) ### Paso 4 — Tools (`tools/knowledge.go`) - [ ] `tools/knowledge.go` — NewKnowledgeSearch, NewKnowledgeRead, NewKnowledgeWrite, NewKnowledgeList - [ ] Interface `KnowledgeStore` en tools (subset de knowledge.Store, como se hizo con MemoryStore) ### Paso 5 — Registro en runtime - [ ] Modificar `buildToolRegistry()` en `agents/runtime.go` - [ ] Resolver directorio de knowledge relativo al agente ### Paso 6 — Activar en agentes existentes - [ ] Crear `agents/assistant-bot/knowledge/` con un documento semilla - [ ] Crear `agents/asistente-2/knowledge/` con un documento semilla - [ ] Actualizar `config.yaml` de ambos agentes: `tools.knowledge.enabled: true` - [ ] Actualizar system prompts para que el agente sepa que tiene knowledge tools ### Paso 7 — Tests - [ ] Test de `shell/knowledge/` — sync, search, put, get, list - [ ] Test de `tools/knowledge.go` — validación de slugs, parámetros - [ ] Build completo: `go build -tags goolm ./...` --- ## Ejemplo de uso por el agente Un usuario le dice al bot: "¿Cómo configuro un webhook en Gitea?" 1. El agente llama `knowledge_search(query="gitea webhook")` 2. Encuentra `gitea-admin.md` con snippet relevante 3. Llama `knowledge_read(slug="gitea-admin")` para leer el documento completo 4. Responde al usuario con la info 5. Si descubre info nueva en la conversación, llama `knowledge_write(slug="gitea-webhooks", content="# Gitea Webhooks\n\n...")` para ampliar su base ## Diferencia con memory tools | Aspecto | Memory (facts) | Knowledge (documents) | |---------|----------------|----------------------| | Granularidad | Key-value individual | Documentos completos | | Búsqueda | Por subject exacto | Full-text search (FTS5) | | Formato | Tripla (subject, key, value) | Markdown libre | | Propósito | Datos puntuales sobre users/temas | Base de conocimiento estructurada | | Persistencia | SQLite rows | Archivos .md + índice FTS5 | | Editable por humanos | No (solo via SQL) | Sí (archivos normales) | --- ## Notas de implementación - **FTS5 y modernc/sqlite**: modernc.org/sqlite soporta FTS5 nativamente, no necesita CGO. - **Slugs**: validar con regexp `^[a-z0-9][a-z0-9-]{0,62}[a-z0-9]$` (min 2 chars). - **Título**: extraer primera línea que empiece con `# `. Si no hay, usar slug humanizado. - **Tamaño máximo por documento**: 64 KB (consistente con read_file tool). - **Directorio knowledge/ en .gitignore**: decisión del usuario. Se puede trackear o no. - **No embeddings**: FTS5 keyword search es suficiente para v1. Embeddings es extensión futura.