MCPcopy Index your code
hub / github.com/betta-tech/byo-coding-agent

github.com/betta-tech/byo-coding-agent @main

Chat with this repo
repository ↗ · DeepWiki ↗ · + Follow
376 symbols 1,068 edges 45 files 207 documented · 55%
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

Construye Tu Propio Agente de Código

ci

📖 Lee el libro online: byoharness.dev/es · byoharness.dev

🌐 Idiomas: English · Español

Una introducción práctica a la ingeniería de harness — la disciplina de construir el andamiaje alrededor de un LLM que lo convierte en un agente útil. Vas a construir un agente de código funcional en Go, y luego experimentar con las partes que importan: proveedores, herramientas, estrategias de compactación y permisos.

¿Qué es la ingeniería de harness?

El modelo es el motor. El harness es todo lo demás: el bucle que lo invoca, las herramientas que puede usar, cómo se moldea su conversación con el tiempo, qué tiene permitido hacer, cómo el usuario habla con él.

Si el harness está bien diseñado, un modelo de gama media se siente excelente. Si está mal diseñado, un modelo de vanguardia se siente roto. La mayoría de las decisiones interesantes en herramientas como Claude Code, OpenCode y Aider viven en sus harness, no en sus modelos.

La ingeniería de harness ocurre en tres niveles — construir el bucle y las abstracciones, extenderlas con herramientas o integraciones nuevas, y configurar el comportamiento mediante archivos como AGENTS.md y mcp.json. La mayoría del tiempo de quienes trabajan con esto se va al nivel superior; este libro se enfoca en construir porque ahí es donde se forma el modelo mental. Una vez que has construido un harness, lees cada archivo de configuración con ojos nuevos.

Este proyecto es una versión simplificada y legible de esas herramientas, diseñada para ser explorada.

Qué vas a construir

Un agente de código terminal (~600 líneas de Go) que:

  • Habla con Claude (o cualquier LLM que conectes)
  • Llama herramientas — bash, read_file, write_file — para actuar sobre tu sistema de archivos
  • Pide aprobación antes de cada llamada a una herramienta
  • Compacta conversaciones largas usando estrategias intercambiables
  • Soporta comandos de barra (/help, /model, /compact, /verbose, …)
  • Tiene un input TUI con historial, edición de línea y prompt estilizado

Requisitos previos

  • Go 1.21+ (el proyecto usa genéricos y la función max)
  • Una API key de Anthropic — console.anthropic.com → Settings → API Keys

Inicio rápido

git clone git@github.com:betta-tech/byo-coding-agent.git
cd byo-coding-agent
export ANTHROPIC_API_KEY=sk-ant-...
go run .

Para ejecutarlo contra OpenAI también, exporta la clave una vez:

export OPENAI_API_KEY=sk-...
go run .

Después cambia de proveedor a mitad de sesión con el comando de barra — sin reiniciar:

/provider              # muestra el proveedor actual y las opciones disponibles
/provider openai       # cambia a OpenAI (por defecto usa gpt-5-codex)
/provider openai gpt-4o-mini
/provider anthropic
/model gpt-5           # cambia solo el modelo del proveedor actual

Ambos proveedores implementan la misma interfaz Provider; el resto del harness no cambia al hacer el swap. Las variables de entorno LLM_PROVIDER/LLM_MODEL siguen funcionando como valores por defecto al arranque si prefieres no tener que escribir el comando en cada sesión.

Escribe /help para ver los comandos. Prueba con:

  • list the files here
  • write a hello.txt with a haiku in it
  • read main.go and tell me how the agent loop works

Arquitectura en 60 segundos

El harness está construido alrededor de tres puntos de extensión ortogonales. Cada uno vive en su propio paquete bajo internal/, con una interfaz pequeña y una implementación de referencia. Cambiar implementaciones es una sola línea.

┌─────────────────────────────────────────────────────┐
│  main.go        cableado · bucle REPL · bucle agente│
│  commands.go    /help · /model · /compact · …       │
└─────────────────────────────────────────────────────┘
        │
        ├── internal/api/             tipos compartidos (Message, Block, ToolDef, …)
        │
        ├── internal/provider/        interfaz Provider + impl Anthropic
        │     Envía mensajes → recibe respuesta. Cámbialo para añadir OpenAI, etc.
        │
        ├── internal/tool/            interfaz Tool + Registry + un archivo por herramienta
        │     Se auto-registra vía init() — añades un archivo y aparece.
        │
        ├── internal/compact/         interfaz CompactionStrategy + estrategias
        │     SlidingWindow, Summarize, NoCompaction, decorador WithLogging.
        │
        └── internal/ui/              banner, spinner, input Bubble Tea, estilos
              Affordances de TUI. Legible pero menos interesante.

Estructura del proyecto

.
├── main.go              cableado + REPL + bucle del agente + wrapper executeTool
├── commands.go          registro de comandos de barra
└── internal/
    ├── api/             Message, Block, ToolDef, Response, RenderTranscript
    ├── provider/        interfaz Provider + AnthropicProvider
    ├── tool/            interfaz Tool + Registry + bash / readfile / writefile
    ├── compact/         CompactionStrategy + SlidingWindow / Summarize / Logging
    └── ui/              banner, spinner, input (Bubble Tea), helpers de estilo

internal/ es reforzado por el compilador de Go — los paquetes dentro solo pueden ser importados por código del mismo módulo, lo cual es la señal correcta de "esto no está diseñado para ser reutilizado como librería."

Los tres puntos de extensión

1. Proveedores

¿Quieres usar OpenAI, Bedrock o un modelo local? Implementa Provider:

type Provider interface {
    Send(ctx context.Context, messages []Message, tools []ToolDef) (Response, error)
    Model() string
    SetModel(name string)
}

Luego cambia una línea en main.go:

llm = provider.NewOpenAIProvider(...)  // en lugar de provider.NewAnthropicProvider

El adaptador es el único lugar que conoce el formato del SDK. El resto del harness maneja tipos genéricos Message / Block / ToolDef. Mira internal/provider/anthropic.go como referencia.

2. Herramientas

¿Quieres agregar git_diff, web_search, kubectl? Crea un solo archivo bajo internal/tool/:

// internal/tool/gitdiff.go
package tool

import (
    "os/exec"

    "github.com/betta-tech/byo-coding-agent/internal/api"
)

type GitDiffTool struct{}

func init() { Default.Register(&GitDiffTool{}) }

func (GitDiffTool) Definition() api.ToolDef {
    return api.ToolDef{
        Name:        "git_diff",
        Description: "Muestra los cambios sin commitear en el repo actual.",
        InputSchema: map[string]any{},
        Required:    []string{},
    }
}

func (GitDiffTool) Execute(_ string) (string, bool) {
    out, err := exec.Command("git", "diff").CombinedOutput()
    if err != nil { return string(out), true }
    return string(out), false
}

Añade el archivo. Corre go run .. Escribe /toolsgit_diff aparece en la lista, el modelo puede llamarla. Sin modificaciones a main.gomain ya importa internal/tool, así que el init() del nuevo archivo se ejecuta cuando se carga el paquete.

3. Estrategias de compactación

¿Quieres probar diferentes formas de manejar conversaciones largas? Implementa CompactionStrategy:

type CompactionStrategy interface {
    Compact(ctx context.Context, messages []Message) ([]Message, error)
}

Vienen incluidas tres:

Estrategia Qué hace
compact.NoCompaction{} Por defecto — nunca modifica los mensajes
&compact.SlidingWindow{KeepLast: 10} Conserva los últimos N mensajes, descarta los anteriores
&compact.Summarize{Provider: llm, Threshold: 20, KeepRecent: 6} Le pide al modelo que resuma turnos antiguos cuando el historial supera Threshold

Envuelve cualquiera con compact.WithLogging(inner, "compactions.log") para grabar diffs antes/después a un archivo — útil para comparar estrategias.

Cambia una línea en main.go:

compactor = &compact.Summarize{Provider: llm, Threshold: 20, KeepRecent: 6}

Detalle sutil: una truncación ingenua puede dejar un bloque tool_use sin su tool_result correspondiente, y la API responderá 400. El helper SafeSplitPoint en internal/compact/strategy.go retrocede hasta encontrar un límite "limpio". Todas las estrategias pasan por él.

Comandos

Comando Efecto
/help Lista todos los comandos
/provider [anthropic\|openai] [modelo] Muestra o cambia el proveedor de LLM
/model [nombre] Muestra el modelo actual o lo cambia (sugerencias según proveedor)
/tokens Muestra tokens acumulados de entrada/salida y coste estimado
/debug [on\|off\|clear] Alterna el panel de debug en vivo (llamadas al proveedor, dispatch de herramientas, compactación)
/clear Borra el historial de conversación
/tools Lista las herramientas registradas
/subagents Lista subagentes registrados y en vuelo
/compact [sliding\|summarize\|none] Ejecuta compactación (estrategia configurada o ad-hoc)
/verbose [on\|off] Activa/desactiva la impresión del antes/después al compactar
/exit Salir

Ahora intenta

En orden aproximado de dificultad:

  1. Agrega una herramienta git_diff. Lee tool_bash.go para entender el patrón, escribe un nuevo tool_git_diff.go. Verifica que /tools la lista.
  2. Agrega una estrategia de compactación TokenBudget que descarte los mensajes más antiguos hasta que el conteo estimado de tokens esté bajo un umbral configurable. Empieza con una aproximación basada en bytes; después intercámbiala por una llamada real a count_tokens.
  3. Agrega una abstracción PermissionPolicy. Actualmente cada llamada a herramienta pasa por confirm. Refactoriza para que una política decida — AlwaysAllow, AlwaysAsk, AllowList{names}. La política se enchufa en main.go como los otros puntos de extensión.
  4. Agrega un segundo proveedor. OpenAI, un Ollama local, o un MockProvider que registre las llamadas (muy útil para el siguiente ejercicio).
  5. Agrega tests. Con MockProvider puedes probar el bucle del agente de extremo a extremo sin una llamada a la API. Las estrategias de compactación son fáciles de probar con historiales sintéticos.

Qué no está incluido todavía

  • Streaming. El modelo devuelve una respuesta completa antes de que renderizemos algo. Los agentes de código reales transmiten tokens conforme llegan.
  • Tests. Nada está automatizado todavía — ver ejercicio 5.
  • Prompt caching. Cada turno reenvía el historial completo a precio completo.
  • Input multilínea. El textarea de Bubble Tea desbloquearía Shift-Enter para saltos de línea.
  • Políticas de permisos. La aprobación está hardcodeada como "preguntar cada vez" — ver ejercicio 3.
  • Soporte para MCP. Sin servidores de herramientas externos.

Cada uno es un capítulo siguiente válido.

Documentación

Tres sabores de documentación, para las tres razones por las que alguien lee este repositorio:

  • examples/minimal/ — una versión en un solo archivo (~130 líneas) del bucle del agente, sin abstracciones. La forma más rápida de ver "la esencia" antes de que aparezca cualquier maquinaria del harness. Ejecuta con go run ./examples/minimal.
  • follow_along/ — narrativa del tamaño de capítulos sobre el por qué de cada capa del harness, en el orden en que se construyó. Léela en orden; alrededor de una hora en total. Disponible en inglés y español.
  • how-to/ — referencias breves en formato receta para las tareas de extensión más comunes: añadir una herramienta, añadir un proveedor, añadir una política de permisos. También bilingüe.

Si quieres una probada rápida, empieza por examples/minimal/. Si quieres la historia completa, lee follow_along/ en orden. Si ya lo construiste y quieres extenderlo, salta a how-to/.

Reconocimientos

La estructura toma decisiones arquitectónicas visibles en Claude Code, OpenCode y Aider. El marco "build your own X" viene de proyectos como Build Your Own Redis, Crafting Interpreters y Writing An Interpreter In Go de Thorsten Ball.

Extension points exported contracts — how you extend this code

Tool (Interface)
(no doc) [9 implementers]
internal/tool/registry.go
CompactionStrategy (Interface)
(no doc) [4 implementers]
internal/compact/strategy.go
Store (Interface)
Store is the persistence layer. Three operations cover the natural rhythm of agent memory: write (Save), search (Recall) [2 …
internal/memory/store.go
Subagent (Interface)
Subagent is one focused agent type. Run takes a task description and returns the final text — same shape as a tool but w [1 …
internal/subagent/registry.go
Provider (Interface)
(no doc) [3 implementers]
internal/provider/provider.go
ProgressFunc (FuncType)
ProgressFunc reports background progress while Register is iterating the server list. server is the current server's nam
internal/mcp/register.go
UsageFunc (FuncType)
UsageFunc returns the session's cumulative token usage and estimated cost in USD. Cost is -1 if unknown (e.g. unrecogniz
internal/ui/program.go
AgentRunner (FuncType)
AgentRunner is what the program calls when the user submits a line. It covers both slash commands and normal agent turns
internal/ui/program.go

Core symbols most depended-on inside this repo

Dimmed
called by 56
internal/ui/styles.go
Register
called by 21
internal/tool/registry.go
SetEnabled
called by 19
internal/debug/debug.go
Cyan
called by 15
internal/ui/styles.go
Send
called by 14
internal/provider/provider.go
refreshDebugContent
called by 13
internal/ui/program.go
Enabled
called by 12
internal/debug/debug.go
RenderTranscript
called by 12
internal/api/types.go

Shape

Function 179
Method 122
Struct 58
TypeAlias 8
Interface 6
FuncType 3

Languages

Go100%

Modules by API surface

internal/ui/program.go45 symbols
cmd/sitegen/main.go29 symbols
commands.go22 symbols
internal/debug/debug.go17 symbols
internal/memory/sessionfiles.go15 symbols
internal/ui/input.go14 symbols
main.go12 symbols
internal/mcp/register.go12 symbols
internal/compact/strategy_test.go12 symbols
internal/subagent/registry.go11 symbols
internal/provider/openai.go11 symbols
internal/provider/anthropic.go11 symbols

For agents

$ claude mcp add byo-coding-agent \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact