---
{
  "title": "Coding Agent: REPL is all you need",
  "description": "Dele a su agente de código un REPL dentro de su propio entorno de ejecución y se extenderá a sí mismo para adaptarse a usted, a su proyecto y a la tarea.",
  "date": "2026-04-19",
  "author": "Nikolai Ryzhikov",
  "reading-time": "8 min read",
  "tags": ["AI / Agents", "System Design"]
}
---

> For the complete documentation index, see [llms.txt](https://www.health-samurai.io/llms.txt).
> Use it to discover all available pages before guessing URLs.

---

Hace un año reconocimos que un bucle de agente simple con herramientas es suficiente para tareas de programación.

El proyecto [pi / pi-mono](https://github.com/badlogic/pi-mono) de Mario Zechner lo demuestra: un agente de código no necesita mucho. Una herramienta `bash` es suficiente, y su harness de ~1000 líneas ya se mantiene a la par de Claude Code y Codex — una décima parte del tamaño de los agentes en producción, y la diferencia no está donde uno esperaría.

La razón por la que funciona es que los agentes ya son buenos en dos cosas que se combinan muy bien: componer comandos de shell en pipelines y escribir código. Entonces, ¿para qué enviarlos con una caja de herramientas predefinida cuando podrían escribir las herramientas ellos mismos, justo cuando las necesitan, adaptadas al usuario, al proyecto y a la tarea en cuestión?

¿Le resulta familiar? Es Emacs de nuevo. Emacs nunca fue realmente un editor con plugins — es un entorno de ejecución Lisp que, de paso, edita texto y se reescribe a sí mismo al vuelo; por eso, cuarenta años después, la gente sigue construyendo flujos de trabajo enteros dentro de él. El mismo truco encaja en un agente de código: proporciónele un entorno de ejecución y un REPL, una sola capacidad — **escribir y ejecutar código** — y deténgase ahí. El resto (`read`, `write`, `edit`, `grep`, `subagent`, lo que surja después) el agente lo escribirá por sí solo. ¡El código se compone mucho mejor que MCP o bash!

## Hyper Code

En [hyper-code](https://github.com/niquola/hyper-code2) estoy construyendo exactamente eso — un entorno de ejecución donde el agente puede vivir y extenderse a sí mismo. Es lo suficientemente pequeño como para que el agente mantenga toda la arquitectura en su contexto, y lo suficientemente sencillo como para que pueda ampliar esa arquitectura sin romper nada.

Un esquema rápido de la forma del entorno de ejecución:

* Bun como entorno de ejecución principal — rápido, con muchas bibliotecas integradas, sin dependencias externas.
* Estilo procedural/funcional: estado separado de las funciones. Una función por archivo, recarga en caliente sin necesidad de reiniciar.
* SQLite para sesiones, en el espíritu de [out of the tar pit](https://curtclifton.net/papers/MoseleyMarks06a.pdf).
* Una interfaz web con HTTP y HTML — sin reinventar la rueda con una TUI, sin la complejidad de la API de VS Code. Fácil de extender y personalizar.


## Ejemplo

Para concretar esto, le pedí a Claude que propusiera una tarea realista y no trivial que un agente gestionaría en una sola llamada a `evalCode`. Esto fue lo que respondió. Imagine que un usuario dice: *«extrae las notas de lanzamiento desde la última etiqueta — commits, autores, PRs vinculados, y guarda el resultado en caché para la próxima vez.»*

En el mundo de bash + MCP, serían cinco o seis herramientas separadas que habría que describir y conectar con antelación: `git describe`, `git log`, un analizador de commits convencionales, la API de GitHub para PRs, SQLite para la caché, y formato markdown. Cada una con su propia superficie de API, cada una con un ciclo de ida y vuelta para definir y llamar.

En un agente orientado al REPL es simplemente un `evalCode`:

```js
const repo = "niquola/hyper-code2";
const headers = { Authorization: `Bearer ${process.env.GITHUB_TOKEN}` };

// 1. Last tag and commits since
const tag = (await Bun.$`git describe --tags --abbrev=0`.text()).trim();
const raw = await Bun.$`git log ${tag}..HEAD --pretty=format:%H|%s|%an`.text();

// 2. Local SQLite cache — if we've been here already, return what we have
await ctx.fns.db.exec(ctx, `
    CREATE TABLE IF NOT EXISTS release_notes (
        tag TEXT PRIMARY KEY, head TEXT, body TEXT, at INTEGER
    )
`);
const head = (await Bun.$`git rev-parse HEAD`.text()).trim();
const cached = await ctx.fns.db.select(ctx,
    "SELECT body FROM release_notes WHERE tag = ? AND head = ?", [tag, head]);
if (cached.length) {
    agent.scratchpad.release = { tag, body: cached[0].body, cached: true };
    return { since: tag, cached: true, preview: cached[0].body.slice(0, 300) + "…" };
}

// 3. Parse conventional commits: "feat(scope): message (#42)"
const commits = raw.split("\n").filter(Boolean).map(line => {
    const [sha, subject, author] = line.split("|");
    const m = subject.match(/^(feat|fix|chore|refactor|docs|test)(?:\([^)]+\))?:\s*(.+?)(?:\s*\(#(\d+)\))?$/);
    return { sha, author, type: m?.[1] ?? "other", title: m?.[2] ?? subject, pr: m?.[3] };
});

// 4. Fetch linked PRs in parallel — labels and linked issues come with them
const prNums = [...new Set(commits.map(c => c.pr).filter(Boolean))];
const prs = await Promise.all(prNums.map(n =>
    fetch(`https://api.github.com/repos/${repo}/pulls/${n}`, { headers }).then(r => r.json())
));

// 5. Group by type, build markdown
const byType = Map.groupBy(commits, c => c.type);
const body = [...byType].map(([type, cs]) =>
    `## ${type}\n${cs.map(c =>
        `- ${c.title}${c.pr ? ` (#${c.pr})` : ""} — @${c.author}`
    ).join("\n")}`
).join("\n\n");

// 6. Save the cache — good until HEAD moves
await ctx.fns.db.exec(ctx,
    "INSERT OR REPLACE INTO release_notes VALUES (?, ?, ?, ?)",
    [tag, head, body, Date.now()]);

// 7. Heavy stuff stays in scratchpad. The model gets a short summary.
agent.scratchpad.release = { tag, commits, prs, body };
return {
    since: tag,
    commits: commits.length,
    by_type: Object.fromEntries([...byType].map(([t, cs]) => [t, cs.length])),
    contributors: [...new Set(commits.map(c => c.author))],
    prs_linked: prs.length,
    labels: [...new Set(prs.flatMap(p => p.labels?.map(l => l.name) ?? []))],
    preview: body.slice(0, 300) + "…",
};
```

## Del script puntual a su propia herramienta

La primera vez, el agente escribe esto de memoria. La segunda vez, se da cuenta de que hizo casi lo mismo la semana pasada. Y la tercera vez, en lugar de volver a escribirlo, guarda el código en un archivo y lo convierte en un procedimiento de proyecto propiamente dicho:

```js
// Turn N: pin the pattern
await Bun.write(".hyper/release/notes.ts", `
    export default async function (ctx: Context, repo: string) {
        // ... same logic as above — now a reusable function
        return { tag, commits, prs, body };
    }
`);

// Hot-load into the live runtime and regenerate types — no restart
await ctx.fns.repl.load(ctx, "release");
await ctx.genTypes(ctx);

// Turn N+1: call it like a built-in
await ctx.fns.release.notes(ctx, "niquola/hyper-code2");
```

La próxima vez que alguien pida las notas de lanzamiento, será una línea en lugar de cuarenta. La función vive bajo `.hyper/`, lo que significa que permanece en el proyecto — y a lo largo de unas semanas la base de código crece silenciosamente con una pequeña biblioteca de procedimientos, cada uno adaptado a este repositorio específico y a este autor específico. Eso no se puede obtener de serie con ningún harness fijo, porque ningún harness fijo conoce sus hábitos.

## Pruébelo

La idea en una frase: proporcione al modelo el entorno de ejecución completo y una sola herramienta, y deje que escriba el resto — adaptado a usted y a su proyecto.

El prototipo está en [github.com/niquola/hyper-code2](https://github.com/niquola/hyper-code2). Unas mil líneas sobre Bun: un `evalCode`, archivos recargados en caliente, SQLite para sesiones, un chat web en `/`. Funciona con OpenAI, Anthropic, Groq, OpenRouter y LM Studio local.

Si dispone de cinco minutos, pruebe esto:

1. `bun install && cp .env.test .env` — introduzca la clave de su proveedor.
2. `bun src/$main.ts` → abra `http://localhost:3000`.
3. Pregunte: *«lee tu propia base de código y explica cómo funciona.»*
4. Pregunte: *«¿qué herramientas te faltan para trabajar en ...?»*
5. Pregunte: *«¡escríbela y llámala!»*

El proyecto todavía está en pañales. Si le gusta la idea — ¡únase a la diversión!