# 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 `.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/<id>/knowledge/
    dbPath string   // path a agents/<id>/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.
