chore: initial scaffold with design docs

This commit is contained in:
Victor Hugo Vargas Servin 2026-06-28 16:03:57 -07:00
commit c21173a7f8
12 changed files with 757 additions and 0 deletions

19
.gitignore vendored Normal file
View file

@ -0,0 +1,19 @@
# Go
*.exe
*.test
*.out
*.prof
vendor/
coverage.out
coverage.html
# Editor / OS
.vscode/
.idea/
*.swp
*.swo
*~
.DS_Store
# Local build cache
.cache/

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Victor Hugo Vargas Servín
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

137
README.md Normal file
View file

@ -0,0 +1,137 @@
# go-llm-agent
> 🔑 **Librería core reutilizable** para construir agentes LLM en Go.
Esta librería es el corazón de varios proyectos de Victor Vargas:
- [`harness`](https://github.com/VictorVargas/harness) — AI agent harness para desarrollo de software (TUI)
- [`chat-bot`](https://github.com/VictorVargas/chat-bot) — Chatbot HTTP para portfolios y sitios web
Provee toda la lógica **genérica** de un agente LLM:
| Componente | Ubicación | Responsabilidad |
|---|---|---|
| **Agent loop** | `pkg/agent/` | Bucle iterativo con guardrails |
| **LLM clients** | `pkg/llm/` | Abstracción multi-provider (OpenAI, Anthropic, Ollama) |
| **RAG / memoria** | `pkg/rag/` | Memoria de corto y largo plazo con búsqueda semántica |
| **Persona system** | `pkg/persona/` | System prompts configurables + AGENTS.md discovery |
| **Tool registry** | `pkg/tools/` | JSON Schema + execution sandbox |
| **Config loading** | `pkg/config/` | Carga de YAML con precedencia jerárquica |
## 🎯 Filosofía
- **Reusable, no opinionated.** No fuerza un tipo de UI, deployment, ni use case.
- **Hexagonal.** Ports & adapters permiten sustituir cualquier pieza.
- **Streaming-first.** Usa `iter.Seq2` de Go 1.23+ para streaming sin boilerplate.
- **Seguridad por defecto.** Sandbox de paths con `os.Root` (Go 1.24+).
## 📦 Instalación
```bash
go get github.com/VictorVargas/go-llm-agent
```
## 🔧 Setup del proyecto (si vas a contribuir)
```bash
git clone https://github.com/VictorVargas/go-llm-agent.git
cd go-llm-agent
# El go.mod ya existe con module + go version
# Las dependencias se agregan automáticamente cuando escribes código:
# 1. Escribe tu código importando paquetes
# 2. Ejecuta:
go mod tidy # resuelve imports → actualiza go.mod + crea go.sum
```
## 🚀 Uso básico
```go
package main
import (
"context"
"fmt"
"github.com/VictorVargas/go-llm-agent/pkg/agent"
"github.com/VictorVargas/go-llm-agent/pkg/llm"
"github.com/VictorVargas/go-llm-agent/pkg/persona"
)
func main() {
// 1. Crear cliente LLM
llmClient, _ := llm.NewAnthropicClient(llm.AnthropicConfig{
APIKey: os.Getenv("ANTHROPIC_API_KEY"),
Model: "claude-sonnet-4.5",
})
// 2. Cargar persona
p := persona.Load("./persona.yaml")
// 3. Crear agent loop
loop := agent.New(agent.Config{
LLM: llmClient,
Persona: p,
MaxIters: 50,
Sandbox: agent.NewSandbox("./workspace"),
})
// 4. Ejecutar
resp, err := loop.Run(context.Background(), "Refactoriza auth.go")
if err != nil { panic(err) }
fmt.Println(resp.Content)
}
```
## 🔌 Adapters incluidos
### LLM Providers (`pkg/llm/providers/`)
| Provider | Import | Modelos |
|---|---|---|
| OpenAI | `llm/providers/openai` | gpt-4o, gpt-4o-mini, gpt-4-turbo |
| Anthropic | `llm/providers/anthropic` | claude-sonnet-4.5, claude-haiku-4 |
| Ollama | `llm/providers/ollama` | llama3.1, qwen2.5, mistral, etc. |
| llama.cpp | `llm/providers/llamacpp` | Custom GGUF models |
### Vector DBs (`pkg/rag/backends/`)
| Backend | Estado |
|---|---|
| ChromaDB embedded | ✅ Estable |
| Qdrant embedded | 🚧 En desarrollo |
| SQLite + sqlite-vec | 📋 Planeado |
### Embeddings (`pkg/rag/embeddings/`)
- Ollama embeddings (nomic-embed-text, bge-m3, etc.)
- Local sentence-transformers via ONNX
## 🧪 Testing
```bash
go test ./...
go test -race ./...
go test -bench=. ./pkg/agent/
```
Incluye `MockLLMClient` para tests deterministas sin gastar API calls.
## 📐 Versiones
- **Go mínimo:** 1.26 (usa `os.Root`, `iter.Seq`, `unique.Handle`, container-aware GOMAXPROCS)
- **Política de versionado:** Semver estricto. API breaking changes solo en MAJOR.
## 📄 Licencia
MIT — ver [`LICENSE`](./LICENSE).
## 🔗 Proyectos que usan esta librería
- [`VictorVargas/harness`](https://github.com/VictorVargas/harness) — TUI agent para software dev
- [`VictorVargas/chat-bot`](https://github.com/VictorVargas/chat-bot) — HTTP chatbot
## 📚 Documentación adicional
- [Architecture overview](./docs/README.md)
- [Design decisions](./docs/architecture.md) (próximamente)

93
docs/README.md Normal file
View file

@ -0,0 +1,93 @@
# go-llm-agent — Documentación
Documentación técnica detallada de la librería.
## 📐 Arquitectura
```
┌────────────────────────────────────────────────────────────────┐
│ go-llm-agent (pkg/) │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ │
│ │ agent │ │ persona │ │ tools │ │
│ │ │ │ │ │ │ │
│ │ Agent loop │◄─┤ Persona │ │ Tool registry │ │
│ │ con guard- │ │ + AGENTS.md │ │ + JSON Schema │ │
│ │ rails │ │ discovery │ │ + execution │ │
│ └──────┬───────┘ └──────────────┘ └────────┬─────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ llm │ │ rag │ │
│ │ │ │ │ │
│ │ LLMClient │ │ Memory + │ │
│ │ interface + │ │ Embeddings │ │
│ │ providers │ │ + VectorDB │ │
│ └──────────────┘ └──────────────┘ │
│ │
└────────────────────────────────────────────────────────────────┘
```
## 📦 Paquetes
| Paquete | Responsabilidad | Docs |
|---|---|---|
| `pkg/agent` | Bucle iterativo, termination conditions, approval gates | [Ver](../pkg/agent/README.md) |
| `pkg/llm` | `LLMClient` interface, streaming, providers | [Ver](../pkg/llm/README.md) |
| `pkg/rag` | Memoria, embeddings, búsqueda semántica | [Ver](../pkg/rag/README.md) |
| `pkg/persona` | System prompts, AGENTS.md, few-shot examples | [Ver](../pkg/persona/README.md) |
| `pkg/tools` | Tool registry, JSON Schema, sandboxing | [Ver](../pkg/tools/README.md) |
| `pkg/config` | YAML loading, precedence, env override | [Ver](../pkg/config/README.md) |
## 🎯 Principios de diseño
### 1. Streaming-first
Usa `iter.Seq2[T, error]` de Go 1.23+ para streaming natural:
```go
for token, err := range llmClient.StreamTokens(ctx, req) {
if err != nil { return err }
fmt.Print(token)
}
```
### 2. Hexagonal puro
Cada paquete expone interfaces, las implementaciones concretas están separadas:
```go
// pkg/rag/rag.go (puerto)
type VectorDB interface {
Search(ctx context.Context, embedding []float32, topK int) ([]Document, error)
}
// pkg/rag/backends/chroma/chroma.go (adapter)
type ChromaDB struct { ... }
func (c *ChromaDB) Search(...) { ... }
```
### 3. Seguridad por defecto
- `os.Root` para sandbox de filesystem (Go 1.24+)
- Approval gates antes de tools destructivos
- Bash sandbox con denylist + timeout
- Network egress control opcional
### 4. Zero magic
No hay reflection, no hay code generation, no hay DSLs. Todo es Go idiomático y explícito.
## 🔄 Versionado
- **Semver estricto** (`vMAJOR.MINOR.PATCH`)
- **MAJOR**: breaking changes en `pkg/` (interfaces, signatures, tipos públicos)
- **MINOR**: nuevas features, nuevos paquetes, nuevos adapters
- **PATCH**: bugfixes
Los adapters privados (`pkg/llm/providers/openai/`) pueden cambiar sin bump de MAJOR si la interfaz `LLMClient` no cambia.
## 🚧 Estado actual
⚠️ **Esta librería está en diseño activo.** El código todavía no está implementado. La especificación completa está en los design docs de los proyectos que la consumen:
- [`VictorVargas/harness/docs/architecture.md`](https://github.com/VictorVargas/harness/blob/main/docs/architecture.md) — Define los requirements
- [`VictorVargas/harness/docs/phase2.md`](https://github.com/VictorVargas/harness/blob/main/docs/phase2.md) — Features avanzadas
Una vez que `harness/` esté implementado, esta librería se extraerá como código real.

29
examples/README.md Normal file
View file

@ -0,0 +1,29 @@
# Examples
> Ejemplos de uso de `go-llm-agent` en distintos contextos.
## 📁 Contenido planeado
| Ejemplo | Descripción | Estado |
|---|---|---|
| `simple_chat/` | Chat básico sin tools | 📋 Pendiente |
| `chat_with_tools/` | Chat con tools custom | 📋 Pendiente |
| `rag_qa/` | Q&A sobre documentos | 📋 Pendiente |
| `multi_agent/` | Orquestación de sub-agents | 📋 Pendiente |
| `streaming_ui/` | Integración con TUI | 📋 Pendiente |
## 🎯 Cómo correr los ejemplos (cuando existan)
```bash
cd examples/simple_chat
go mod tidy
export ANTHROPIC_API_KEY=sk-ant-...
go run main.go
```
## 📝 Contribuir
Cada ejemplo debe ser:
- ✅ **Standalone**: `go run main.go` y funciona
- ✅ **Mínimo**: <100 líneas si es posible
- ✅ **Documentado**: README con qué demuestra y cómo extenderlo

3
go.mod Normal file
View file

@ -0,0 +1,3 @@
module github.com/VictorVargas/go-llm-agent
go 1.26

57
pkg/agent/README.md Normal file
View file

@ -0,0 +1,57 @@
# pkg/agent
> El bucle principal que ejecuta un agente LLM con guardrails.
## Responsabilidad
Coordinar el ciclo iterativo entre el LLM y la ejecución de tools:
```
while iteration < MaxIterations:
response = llm.Generate(messages, tools)
if no tool calls: return response
for tool_call in response.ToolCalls:
if needs_approval: ask_user()
result = execute(tool_call)
append tool result to messages
```
## API pública
```go
type Loop interface {
Run(ctx context.Context, input string) (Response, error)
RunStream(ctx context.Context, input string) iter.Seq2[Chunk, error]
}
type Config struct {
LLM llm.LLMClient
Persona persona.Persona
Tools tools.Registry
Sandbox Sandbox
MaxIters int
Approver Approver // nil = auto-approve all
OnIteration func(Iteration) // observability hook
}
type Response struct {
Content string
ToolCalls []tools.Call
Iterations int
Duration time.Duration
TokenUsage llm.TokenUsage
}
```
## Garantías
- **Termination**: Siempre termina (max iterations, error, o respuesta final)
- **Idempotencia**: Re-ejecutar con el mismo input produce el mismo output (dado el mismo LLM)
- **Observabilidad**: Cada iteración emite un span OpenTelemetry
- **Approval**: Tool destructivos (`Ask` permission) requieren confirmación
## Ver también
- [pkg/tools](../tools/README.md) — Tool execution
- [pkg/llm](../llm/README.md) — LLMClient interface
- [pkg/persona](../persona/README.md) — Persona assembly

81
pkg/config/README.md Normal file
View file

@ -0,0 +1,81 @@
# pkg/config
> Carga de configuración YAML con precedencia jerárquica.
## Responsabilidad
Resolver la configuración final del agente combinando múltiples fuentes con orden de precedencia:
1. **Flags CLI** (highest)
2. **Environment variables**
3. **Project config** (`./.rony.yaml`)
4. **Global config** (`~/.config/rony/config.yaml`)
5. **Defaults embebidos** (lowest)
## API pública
```go
type Config struct {
Model string
Persona string
Provider ProviderConfig
Tools ToolPolicy
Logging LoggingConfig
Sandbox SandboxConfig
}
type Loader interface {
Load(ctx context.Context, workdir string) (Config, error)
LoadFromBytes(data []byte, source string) (Config, error)
}
type ProviderConfig struct {
Type string // "openai" | "anthropic" | "ollama" | "llamacpp"
Model string
APIKey string // resuelto de env si es referencia
Endpoint string
}
type ToolPolicy map[string]tools.Permission
```
## Formato YAML
```yaml
# ~/.config/rony/config.yaml
model: claude-sonnet-4.5
persona: pragmatista
provider:
type: anthropic
api_key_env: ANTHROPIC_API_KEY
tools:
bash: ask
read: allow
write: ask
logging:
level: info
format: json
sandbox:
workspace: .
timeout_ms: 30000
```
## Override por env
```bash
RONY_MODEL=gpt-4o rony chat # override model
RONY_LOG_LEVEL=debug rony chat # override log level
```
## Precedencia
El loader resuelve en este orden (mayor prioridad primero):
```
flag > RONY_* env > ./.rony.yaml > ~/.config/rony/config.yaml > defaults
```
## Ver también
- [pkg/agent](../agent/README.md) — Usa Config
- [pkg/llm](../llm/README.md) — ProviderConfig se mapea a LLMClient

91
pkg/llm/README.md Normal file
View file

@ -0,0 +1,91 @@
# pkg/llm
> Abstracción multi-provider para modelos de lenguaje.
## Responsabilidad
Definir una interfaz común (`LLMClient`) y adapters para los principales providers.
## API pública
```go
type LLMClient interface {
Generate(ctx context.Context, req CompletionRequest) (CompletionResponse, error)
Stream(ctx context.Context, req CompletionRequest) iter.Seq2[StreamChunk, error]
Name() string
Capabilities() ProviderCapabilities
}
type CompletionRequest struct {
Messages []Message
Tools []tools.Tool
ToolChoice ToolChoice
Model string
Temperature *float32
MaxTokens *int
}
type CompletionResponse struct {
Content string
ToolCalls []tools.Call
Usage TokenUsage
StopReason string
}
type ProviderCapabilities struct {
SupportsTools bool
SupportsVision bool
MaxContextWindow int
}
```
## Providers incluidos
| Provider | Paquete | Soporte tools |
|---|---|---|
| OpenAI | `providers/openai` | ✅ |
| Anthropic | `providers/anthropic` | ✅ |
| Ollama | `providers/ollama` | ✅ (modelos que lo soporten) |
| llama.cpp | `providers/llamacpp` | ✅ (con grammar) |
## Uso
```go
import "github.com/VictorVargas/go-llm-agent/pkg/llm/providers/anthropic"
client, err := anthropic.New(anthropic.Config{
APIKey: os.Getenv("ANTHROPIC_API_KEY"),
Model: "claude-sonnet-4.5",
})
resp, err := client.Generate(ctx, llm.CompletionRequest{
Messages: []llm.Message{
{Role: llm.RoleUser, Content: "Hola"},
},
})
```
## Streaming
```go
for chunk, err := range client.Stream(ctx, req) {
if err != nil { return err }
fmt.Print(chunk.Delta)
}
```
## Mock para tests
```go
import "github.com/VictorVargas/go-llm-agent/pkg/llm/mock"
mockClient := mock.New(mock.Responses{
{Match: "hola", Response: "¡Hola! ¿Cómo estás?"},
{Match: "*", Response: "default"},
})
```
## Ver también
- [pkg/agent](../agent/README.md) — Usa `LLMClient`
- [pkg/tools](../tools/README.md) — Las `Tool` definitions

71
pkg/persona/README.md Normal file
View file

@ -0,0 +1,71 @@
# pkg/persona
> Sistema de personalidades configurables para agentes LLM.
## Responsabilidad
Ensamblar el system prompt final del agente combinando:
1. **Base prompt** (hardcoded en la librería)
2. **Persona YAML** (configurable por proyecto)
3. **AGENTS.md** (instrucciones del proyecto, descubierto por búsqueda en árbol de directorios)
4. **Working memory context** (resúmenes si hay compaction)
## API pública
```go
type Persona struct {
ID string
Name string
Tone string
Style string
Language string
Constraints []string
FewShot []llm.Message
}
type Loader interface {
Load(ctx context.Context, configPath string) (Persona, error)
Discover(ctx context.Context, workdir string) (Persona, error) // incluye AGENTS.md
}
```
## Formato YAML
```yaml
id: pragmatista
name: Pragmatista
tone: "Directo y profesional"
style: "Enfoque Go idiomatic"
language: "Español, con términos técnicos en inglés"
constraints:
- "No usar interface{} en código nuevo"
- "Siempre wrapped errors con %w"
few_shot:
- role: user
content: "Refactoriza este código"
- role: assistant
content: "Listo. Optimizado. ¿Aplico?"
```
## AGENTS.md discovery
El loader busca `./AGENTS.md`, sube al directorio padre, etc., concatenando todos los encontrados hasta llegar a `~` o `/`. También incluye `~/.config/rony/AGENTS.md` como default global.
```
/home/user/proyecto/AGENTS.md ← incluye
/home/user/AGENTS.md ← incluye
/home/AGENTS.md ← incluye
~/.config/rony/AGENTS.md ← incluye
```
## Uso
```go
p, err := persona.Discover(ctx, "/home/user/mi-proyecto")
// p.Content incluye todo lo anterior concatenado
```
## Ver también
- [pkg/agent](../agent/README.md) — Usa la persona en el system prompt

82
pkg/rag/README.md Normal file
View file

@ -0,0 +1,82 @@
# pkg/rag
> Retrieval-Augmented Generation: memoria, embeddings, y búsqueda semántica.
## Responsabilidad
Proveer memoria persistente y búsqueda semántica sobre el contenido del agente.
## Componentes
| Tipo | Qué guarda | Persistencia |
|---|---|---|
| **Working** | Mensajes de la sesión actual | RAM |
| **Episodic** | Eventos pasados (qué hice el día X) | Vector DB |
| **Semantic** | Conocimiento consolidado | Vector DB (curado) |
| **Procedural** | Patrones de uso (workflows) | Vector DB (auto-aprendido) |
## API pública
```go
type Memory interface {
Add(ctx context.Context, fragment Fragment) error
Search(ctx context.Context, query string, topK int) ([]Fragment, error)
Forget(ctx context.Context, id string) error
}
type Fragment struct {
ID string
Content string
Vector []float32
Metadata map[string]string
Timestamp time.Time
ProjectID string
}
type Embedder interface {
Embed(ctx context.Context, text string) ([]float32, error)
Dimensions() int
}
```
## Backends
| Backend | Cuándo usar |
|---|---|
| ChromaDB embedded | Default. Simple, suficiente para <100k docs |
| Qdrant embedded | Si necesitas >100k docs o queries muy rápidas |
| SQLite + sqlite-vec | Si quieres zero-dependency (sin CGO con `modernc.org/sqlite`) |
## Embeddings
| Provider | Modelo | Dimensiones |
|---|---|---|
| Ollama | `nomic-embed-text` | 768 |
| Ollama | `bge-m3` | 1024 |
| Local ONNX | `all-MiniLM-L6-v2` | 384 |
## Uso
```go
import "github.com/VictorVargas/go-llm-agent/pkg/rag"
import "github.com/VictorVargas/go-llm-agent/pkg/rag/backends/chroma"
backend, _ := chroma.New(chroma.Config{
Path: "~/.local/share/rony/chroma",
})
memory := rag.New(rag.Config{
Backend: backend,
Embedder: ollamaEmbedder,
})
err := memory.Add(ctx, rag.Fragment{
Content: "Refactoricé auth.go usando hexagonal",
ProjectID: "rony",
})
```
## Ver también
- [pkg/agent](../agent/README.md) — Inyecta memoria al loop
- [pkg/llm](../llm/README.md) — Para summarization en compaction

73
pkg/tools/README.md Normal file
View file

@ -0,0 +1,73 @@
# pkg/tools
> Sistema de tools (function calling) con JSON Schema, sandbox, y permisos.
## Responsabilidad
Permitir que el LLM invoque funciones definidas en Go, con validación de schema y sandboxing.
## API pública
```go
type Tool struct {
Name string
Description string
InputSchema json.RawMessage // JSON Schema draft-07+
Handler Handler // func(ctx, args json.RawMessage) (Result, error)
Permission Permission // Allow | Ask | Deny
Examples []Example // few-shot para el LLM
}
type Registry interface {
Register(tool Tool) error
Get(name string) (Tool, bool)
List() []Tool
Filter(policy Policy) []Tool
}
type Call struct {
ID string
Name string
Arguments json.RawMessage
Thought string // opcional: chain-of-thought del LLM
}
type Result struct {
Content string
IsError bool
Metadata map[string]string
Artifacts []Artifact
}
type Permission int
const (
Allow Permission = iota
Ask
Deny
)
```
## Sandbox integrado
`pkg/tools` usa `os.Root` (Go 1.24+) para sandbox de filesystem:
```go
sandbox := tools.NewSandbox("./workspace")
sandbox.Register(myReadTool) // solo puede leer dentro del workspace
```
Ver [pkg/tools/sandbox/](sandbox/) para detalles.
## Tools genéricos incluidos
- `http_fetch` — GET a URL con HTML→markdown
- `json_parse` — Parse JSON arbitrario
- `datetime_now` — Current timestamp
Las tools específicas de cada producto (ej. `read_file`, `bash` para software dev) las define cada consumidor en su propio `internal/tools/`.
## Ver también
- [pkg/agent](../agent/README.md) — Ejecuta tool calls
- [pkg/llm](../llm/README.md) — Las tools se envían al LLM