Reemplaza el scaffold del echobot por la plataforma completa de bots traida desde ~/DataProyects/Github/agents_and_robots tras la operacion Matrix-out: los bots ya no hablan por Matrix sino por el bus unibus (modelo todo-rooms + E2E via shell/transportunibus sobre github.com/enmanuel/unibus/pkg/client). - go.mod: replace de unibus -> ../unibus y de fn-registry -> ../../../.. (paths relativos reajustados a la nueva ubicacion dentro de fn_registry). - app.md: bump a 0.2.0, descripcion + arquitectura + comandos + gotchas reales. - modulo Go conservado como github.com/enmanuel/agents (sin reescribir imports). agents_and_robots queda archivado como museo de la era Matrix.
10 KiB
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/<id>/knowledge/ ← archivos .md reales (human-readable)
├── go-patterns.md
├── user-preferences.md
└── matrix-tips.md
agents/<id>/data/knowledge.db ← índice SQLite FTS5 (búsqueda rápida)
- Los documentos viven como archivos
.mdenknowledge/. - 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
- Sembrables: se puede crear
knowledge/con documentos iniciales antes de arrancar - Inspeccionables: un humano puede leer/editar el conocimiento del agente
- Git-friendly: opcionalmente trackeable en el repo
- Naturales: el agente "escribe documentos", no inserta rows
Arquitectura (pure core / impure shell)
1. Pure core: pkg/knowledge/
// 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
}
// 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/
// shell/knowledge/store.go
package knowledge
// FileStore implements knowledge.Store using files + SQLite FTS5.
type FileStore struct {
dir string // path a agents/<id>/knowledge/
dbPath string // path a agents/<id>/data/knowledge.db
db *sql.DB
logger *slog.Logger
}
Schema SQLite:
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:
- Listar
*.mden el directorio - Para cada archivo: leer contenido, extraer título (primer
# ...), calcular mtime DELETE FROM documents+ re-insertar todo (rebuild completo, simple y correcto)- 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_deletepor ahora. Los agentes deberían mejorar y ampliar, no borrar. Si se necesita, se añade después.
4. Config: internal/config/schema.go
type KnowledgeCfg struct {
Enabled bool `yaml:"enabled"`
Dir string `yaml:"dir"` // default: "./knowledge" (relativo al dir del agente)
}
Añadir a ToolsCfg:
type ToolsCfg struct {
// ... existentes ...
Knowledge KnowledgeCfg `yaml:"knowledge"`
}
Config de ejemplo en config.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:
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, SearchResultpkg/knowledge/store.go— Store interface
Paso 2 — Config
- Añadir
KnowledgeCfgainternal/config/schema.godentro deToolsCfg
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
#)
- Constructor
Paso 4 — Tools (tools/knowledge.go)
tools/knowledge.go— NewKnowledgeSearch, NewKnowledgeRead, NewKnowledgeWrite, NewKnowledgeList- Interface
KnowledgeStoreen tools (subset de knowledge.Store, como se hizo con MemoryStore)
Paso 5 — Registro en runtime
- Modificar
buildToolRegistry()enagents/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.yamlde 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?"
- El agente llama
knowledge_search(query="gitea webhook") - Encuentra
gitea-admin.mdcon snippet relevante - Llama
knowledge_read(slug="gitea-admin")para leer el documento completo - Responde al usuario con la info
- 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.