Saltar a contenido

🧩 Diseño · un adaptador para ver también agentes de Codex en la oficina pixel-art

Documento de diseño (demo avanzada) · nivel técnico dentro de bloques de código · pensado para que un directivo entienda la idea y un desarrollador la pueda ejecutar

En la ficha 🎮 Pixel Agents vimos una oficina en pixel-art donde cada agente de Claude Code aparece como un personaje que lee, escribe, ejecuta o piensa. Hoy esa herramienta solo dibuja agentes de Claude Code. Este documento esboza —sin construirlo aún— cómo se podría añadir un segundo inquilino a la oficina: los agentes de Codex (el asistente de línea de comandos de OpenAI).

Qué es esto y qué no es

Esto es un boceto de arquitectura con fragmentos de código ilustrativos, no una función lista para usar. No existe (a fecha de hoy) un adaptador de Codex para pixel-agents. Lo que sigue es un plan honesto: separamos lo verificado de lo plausible/por confirmar, y marcamos con ⚠ SUPUESTO todo lo que habría que comprobar en la documentación de Codex antes de escribir una línea de producción.


1. La idea en una frase (para dirección)

Pixel Agents no crea agentes: los dibuja leyendo el rastro que dejan en tu propio ordenador. Si Codex deja un rastro parecido en disco, podemos enseñar a la oficina a leerlo también —y entonces verás, en la misma escena, a tus agentes de Claude Code y a los de Codex trabajando codo con codo.

La pieza clave es que pixel-agents ya está diseñado para esto: trae una abstracción llamada HookProvider (un "enchufe" estándar) pensada explícitamente como punto de extensión para añadir nuevas herramientas de IA. Añadir Codex = escribir un nuevo enchufe (CodexHookProvider) que hable el mismo idioma que el resto de la oficina.


2. Qué SÍ se puede y qué NO (los límites, sin humo)

El criterio es sencillo: ¿la herramienta deja un rastro local que podamos leer sin pedir permisos a un servidor ajeno?

¿Se puede dibujar en la oficina? Por qué
Claude Code (terminal) ✅ Sí — ya funciona Emite eventos vía su Hooks API y además escribe transcripts locales en ~/.claude/projects/ (JSONL).
Codex CLI (terminal, OpenAI) 🟡 Plausible — es de lo que trata este documento Corre en local y deja sesiones/logs en disco (formato y ruta exactos: ⚠ SUPUESTO, ver §5).
Otros agentes de terminal (p. ej. asistentes que escriben logs locales) 🟡 Caso a caso Mismo patrón: si hay rastro local legible, hay adaptador posible.
ChatGPT web / Gemini web / Copilot en el navegador ❌ No Viven en la nube. No hay transcript local que leer; su actividad no toca tu disco. Sin fuente de datos local, no hay nada que dibujar.
Cualquier IA vía API sin cliente local ❌ No (por sí sola) Si tú no ejecutas un cliente que deje rastro, no hay evento que capturar.

La frontera es 'local vs nube', no 'OpenAI vs Anthropic'

Que Codex sea de OpenAI no es el problema; el problema sería que su actividad no tocara tu máquina. Como Codex CLI sí corre en tu terminal, entra en el lado de "se puede". ChatGPT en el navegador, aunque sea la misma empresa, queda fuera: no deja transcript local.


3. Cómo lo hace hoy pixel-agents (el patrón que vamos a copiar)

Para Claude Code, pixel-agents usa dos rutas de captura y las normaliza a un único formato interno:

  ┌─────────────────────────┐        ┌──────────────────────────────┐
  │ (a) Hooks API de Claude │        │ (b) Transcripts JSONL en      │
  │     Code (eventos push) │        │     ~/.claude/projects/ (poll)│
  └────────────┬────────────┘        └───────────────┬──────────────┘
               │  evento crudo (raw)                 │  línea JSONL cruda
               ▼                                     ▼
        ┌───────────────────────────────────────────────────┐
        │  HookProvider.normalizeHookEvent(raw) -> AgentEvent │  ← "traductor"
        └───────────────────────────┬───────────────────────┘
                                    ▼
                        AgentEvent canónico { state, label, ... }
                                    ▼
                     🎮 Motor de la oficina pixel-art
                 (personaje camina / lee / escribe / ejecuta)
  • (a) Hooks = la herramienta te avisa cuando pasa algo (empujón / push). Es lo ideal: instantáneo y fiable.
  • (b) Polling de logs = si no hay avisos, la oficina vigila los archivos de sesión y detecta líneas nuevas (sondeo / pull). Es el plan B universal.

El corazón es normalizeHookEvent(raw): recibe un evento "crudo" (con el vocabulario propio de cada herramienta) y devuelve un AgentEvent canónico que el motor de dibujo entiende. Añadir Codex es, sobre todo, escribir ese traductor para el vocabulario de Codex.

Detalle de honestidad sobre las interfaces

Que existe HookProvider con un método normalizeHookEvent(raw) como punto de extensión está verificado. Los nombres exactos de los campos de AgentEvent y la firma precisa de la interfaz que uso más abajo son una reconstrucción ilustrativa coherente con la demo del curso (estados escribir/leer/ejecutar/pensar). Antes de programar, hay que mirar el código real en github.com/pixel-agents-hq/pixel-agents y ajustar los tipos a la versión instalada (v1.0.2).


4. Arquitectura del adaptador: CodexHookProvider

La meta es una clase que cumpla la interfaz HookProvider y que la oficina pueda registrar junto al proveedor de Claude Code, sin tocar el motor de dibujo.

4.1. El contrato (interfaz, ilustrativo)

// ⚠ Firmas ILUSTRATIVAS: verificar contra el código real de pixel-agents v1.0.2.
// El objetivo es reflejar el patrón (un "enchufe" con un traductor), no copiar la API exacta.

/** Estados canónicos que el motor de la oficina sabe dibujar.
 *  Coinciden con la leyenda de la demo: escribir / leer / ejecutar / pensar (+ tránsito). */
type AgentState = 'WRITE' | 'READ' | 'RUN' | 'THINK' | 'IDLE' | 'DONE';

/** El evento "canónico": lo que hablan todos los proveedores por igual. */
interface AgentEvent {
  agentId: string;      // id estable de la sesión (para reusar el mismo personaje)
  tool: 'codex' | 'claude-code' | string; // de qué herramienta viene (para el color/etiqueta)
  state: AgentState;    // qué está haciendo ahora mismo
  label: string;        // texto del bocadillo: "Escribiendo app.py", "Ejecutando npm test"…
  timestamp: number;    // ms epoch, para ordenar y descartar eventos viejos
}

/** El "enchufe" que cada herramienta implementa. Claude Code ya trae el suyo. */
interface HookProvider {
  readonly id: string;                          // p. ej. "codex"
  /** Arranca la captura y llama a `emit` por cada evento normalizado. */
  start(emit: (e: AgentEvent) => void): Promise<void> | void;
  /** Detiene watchers/timers y libera recursos. */
  stop(): Promise<void> | void;
  /** Traduce UN evento crudo de la herramienta a un AgentEvent (o null si se ignora). */
  normalizeHookEvent(raw: unknown): AgentEvent | null;
}

4.2. Mapeo de estados (Codex → oficina)

Esta tabla es el núcleo del diseño: qué acción de Codex se convierte en qué personaje.

Lo que hace el agente Codex Señal cruda que buscamos (⚠ SUPUESTO, confirmar) Estado canónico Cómo se ve en la oficina
Aplica un parche / edita un archivo herramienta apply_patch, o evento de escritura/diff WRITE 🟢 camiseta verde, "Escribiendo app.py"
Lee/inspecciona un archivo lectura de fichero, cat, read_file READ 🔵 camiseta azul, "Leyendo README.md"
Ejecuta un comando de shell herramienta shell / exec, command RUN 🟡 camiseta ámbar, "Ejecutando npm test"
Razona / planifica el siguiente paso tokens de reasoning / mensaje del modelo sin acción THINK 🔴 camiseta rosa, "Pensando…"
Sesión inicia / espera turno del usuario apertura de sesión / idle IDLE / WALK 🟣 camina hasta su escritorio
Sesión termina cierre de sesión / EOF del log DONE el personaje se va

Los nombres de herramienta de Codex son SUPUESTOS

apply_patch, shell, read_file… son nombres plausibles basados en cómo suelen exponerse las herramientas de un agente de terminal, no confirmados para Codex. El primer paso real (§7) es capturar eventos/logs de verdad y ver con qué vocabulario exacto los nombra Codex, y luego ajustar el mapeo.


5. Las dos rutas de captura para Codex

Igual que con Claude Code, atacamos por dos frentes. Empezar por (b) suele ser lo más realista, porque los logs en disco casi siempre existen aunque no haya una API de hooks pública.

(a) Hooks / eventos — si Codex los expone

Sería la vía elegante: Codex nos "empuja" un evento por cada acción.

  • Verificado (concepto): este es exactamente el mecanismo que usa pixel-agents con la Hooks API de Claude Code.
  • SUPUESTO (para Codex): que Codex CLI ofrezca una API de hooks, callbacks o notificaciones equivalente. A confirmar en la documentación de Codex. Si no existe hoy, esta ruta queda aparcada y tiramos de (b).

(b) Polling de los logs de sesión en disco — plan robusto

Vigilamos los archivos que Codex escribe mientras trabaja y reaccionamos a cada línea nueva —el equivalente exacto a leer los JSONL de ~/.claude/projects/.

  • Verificado (concepto): Codex CLI corre en local y deja logs/sesiones en disco; el patrón "observar un directorio de sesiones y parsear líneas nuevas" es idéntico al que pixel-agents ya usa para Claude Code.
  • SUPUESTO — NO dar por segura ninguna ruta: la ubicación y el formato exactos de esos logs hay que verificarlos en la documentación de Codex antes de codificar. Como hipótesis de partida a confirmar (no como hecho), algo del estilo de un directorio propio bajo el home del usuario (p. ej. ~/.codex/… con archivos por sesión) es lo esperable por convención, pero debe comprobarse: la ruta, el nombre de los archivos y si son JSONL, JSON, texto u otro formato.

Regla de honestidad: no inventes rutas 'seguras'

En este documento no escribimos ~/.codex/sessions/*.jsonl como si fuera un hecho comprobado. Es una conjetura razonable que sirve para diseñar, pero el diseño debe descubrir la ruta real en tiempo de ejecución (leyendo la config/documentación de Codex, o dejándola configurable), nunca hardcodearla a ciegas.


6. STUB de código: CodexHookProvider (ilustrativo, no producción)

Lo que sigue es un esqueleto comentado para fijar ideas. No compila contra la API real de pixel-agents ni contra la de Codex (que hay que verificar). Sirve para enseñar en clase cómo pensaría un desarrollador este adaptador.

// codex-hook-provider.ts
// ⚠ CÓDIGO ILUSTRATIVO PARA EL CURSO — NO ES PRODUCCIÓN.
//    Rutas, nombres de eventos y formato de log son SUPUESTOS a verificar en la doc de Codex.

import { watch } from 'node:fs';
import { readFile } from 'node:fs/promises';
import { homedir } from 'node:os';
import { join } from 'node:path';

// --- Config: la ruta NO se da por segura. Se lee de env/config y, en último caso,
//     una conjetura razonable que habrá que confirmar (ver §5). ---
const CODEX_SESSIONS_DIR =
  process.env.CODEX_SESSIONS_DIR ??            // 1) lo ideal: configurable por el usuario
  join(homedir(), '.codex', 'sessions');       // 2) ⚠ SUPUESTO: conjetura por convención, VERIFICAR

/** Adaptador de Codex para la oficina pixel-art. Cumple la interfaz HookProvider. */
export class CodexHookProvider /* implements HookProvider */ {
  readonly id = 'codex';
  private stopFns: Array<() => void> = [];
  private offsets = new Map<string, number>(); // cuánto llevamos leído de cada archivo

  // ----------------------------------------------------------------------
  // start(): intenta la RUTA (a) hooks; si no existe, cae a (b) polling de logs.
  // ----------------------------------------------------------------------
  async start(emit: (e: AgentEvent) => void): Promise<void> {
    // (a) HOOKS — solo si Codex expone una API de eventos (⚠ SUPUESTO: a confirmar).
    //     Pseudocódigo: si existiera, nos suscribiríamos y traduciríamos cada evento.
    //
    //   const unsub = codexEvents.on('event', raw => {
    //     const ev = this.normalizeHookEvent(raw);
    //     if (ev) emit(ev);
    //   });
    //   this.stopFns.push(unsub);
    //   return; // si los hooks funcionan, no hace falta el polling

    // (b) POLLING DE LOGS — plan robusto: vigilar el directorio de sesiones.
    const watcher = watch(CODEX_SESSIONS_DIR, async (_type, file) => {
      if (!file) return;
      const full = join(CODEX_SESSIONS_DIR, file.toString());
      await this.readNewLines(full, emit);      // leer solo lo nuevo desde el último offset
    });
    this.stopFns.push(() => watcher.close());
  }

  stop(): void {
    this.stopFns.forEach((fn) => fn());
    this.stopFns = [];
  }

  // Lee las líneas añadidas desde la última vez y las normaliza.
  private async readNewLines(path: string, emit: (e: AgentEvent) => void) {
    const text = await readFile(path, 'utf8').catch(() => '');
    const lines = text.split('\n');
    const from = this.offsets.get(path) ?? 0;
    for (let i = from; i < lines.length; i++) {
      const line = lines[i].trim();
      if (!line) continue;
      let raw: unknown;
      try { raw = JSON.parse(line); } catch { continue; } // ⚠ asume JSONL: VERIFICAR formato
      const ev = this.normalizeHookEvent(raw);
      if (ev) emit(ev);
    }
    this.offsets.set(path, lines.length);
  }

  // ----------------------------------------------------------------------
  // normalizeHookEvent(raw): EL TRADUCTOR. Convierte un evento crudo de Codex
  // en un AgentEvent canónico. Aquí vive todo el "conocimiento" del formato de Codex.
  // ⚠ Toda la forma de `raw` (campos type/tool/name/args…) es SUPUESTA: ajustar tras
  //    inspeccionar eventos/logs REALES de Codex.
  // ----------------------------------------------------------------------
  normalizeHookEvent(raw: unknown): AgentEvent | null {
    const e = raw as any; // en real: validar con zod/typeguards
    const agentId = e.session_id ?? e.conversation_id ?? 'codex-desconocido';
    const ts = e.timestamp ? Date.parse(e.timestamp) : Date.now();

    // El "tipo" de acción de Codex → estado canónico de la oficina.
    // Los nombres del switch son HIPÓTESIS a verificar (ver tabla §4.2).
    const kind: string = e.tool_name ?? e.type ?? e.event ?? '';

    switch (kind) {
      case 'apply_patch':
      case 'edit':
      case 'write_file':
        return { agentId, tool: 'codex', state: 'WRITE', timestamp: ts,
                 label: `Escribiendo ${basename(e)}` };

      case 'read_file':
      case 'read':
        return { agentId, tool: 'codex', state: 'READ', timestamp: ts,
                 label: `Leyendo ${basename(e)}` };

      case 'shell':
      case 'exec':
      case 'command':
        return { agentId, tool: 'codex', state: 'RUN', timestamp: ts,
                 label: `Ejecutando ${shortCmd(e)}` };

      case 'reasoning':
      case 'message':                 // turno del modelo sin acción concreta
        return { agentId, tool: 'codex', state: 'THINK', timestamp: ts,
                 label: 'Pensando…' };

      case 'session_end':
        return { agentId, tool: 'codex', state: 'DONE', timestamp: ts, label: '' };

      default:
        return null;                  // evento que no sabemos dibujar → se ignora
    }
  }
}

// Helpers de etiqueta (defensivos: los campos son SUPUESTOS).
function basename(e: any): string {
  const p = e.path ?? e.file ?? e.args?.path ?? '';
  return String(p).split('/').pop() || 'archivo';
}
function shortCmd(e: any): string {
  const c = e.command ?? e.args?.command ?? e.cmd ?? '';
  return String(Array.isArray(c) ? c.join(' ') : c).slice(0, 24) || 'comando';
}

Lee el stub como un guion, no como una verdad

Cada case del switch es una apuesta sobre cómo nombra Codex sus acciones. El valor del ejercicio está en el patrón (un traductor que colapsa el vocabulario de Codex a cuatro estados: escribir/leer/ejecutar/pensar), no en que estos nombres concretos sean correctos.


7. De boceto a demo real: pasos siguientes

Un orden sensato para quien quiera intentarlo de verdad:

  1. Instalar Codex CLI y usarlo un rato en un proyecto de prueba (editar un archivo, ejecutar un comando).
  2. Encontrar el rastro real: localizar en la documentación de Codex (y en el disco) dónde guarda sus sesiones/logs y en qué formato. Anotar ruta y esquema reales → sustituir todos los ⚠ SUPUESTO de §5.
  3. Capturar 5–10 eventos crudos de verdad y volcarlos a un archivo: son la "verdad sobre el terreno" para diseñar el switch de normalizeHookEvent.
  4. Investigar si Codex ofrece hooks/eventos (ruta a). Si sí, es preferible al polling; si no, seguir con (b).
  5. Leer el código real de pixel-agents (v1.0.2, MIT) para conocer la firma exacta de HookProvider y AgentEvent, y registrar el nuevo proveedor junto al de Claude Code.
  6. Ajustar el mapeo de §4.2 a los nombres reales y distinguir en pantalla las dos herramientas (p. ej. un color de camiseta base o una etiqueta "Codex" vs "Claude" sobre el personaje).
  7. Probar con ambos a la vez: una sesión de Claude Code y una de Codex trabajando en paralelo, las dos visibles en la misma oficina. Ese es el "momento wow".
  8. Contribuir el adaptador al proyecto (PR) o mantenerlo como fork propio.

8. Verificado vs. plausible (resumen honesto)

Afirmación Estado
pixel-agents v1.0.2 solo dibuja agentes de Claude Code hoy ✅ Verificado
Detecta Claude Code vía Hooks API + transcripts JSONL en ~/.claude/projects/ ✅ Verificado
Existe una abstracción HookProvider con normalizeHookEvent(raw) como punto de extensión ✅ Verificado
pixel-agents no trae adaptador de Codex ni de ChatGPT ✅ Verificado
Codex CLI corre en local y deja logs/sesiones en disco ✅ Verificado (a alto nivel)
La ruta y formato exactos de esos logs (~/.codex/…, JSONL, etc.) SUPUESTO — verificar en doc de Codex
Que Codex exponga una API de hooks/eventos SUPUESTO — verificar
Los nombres de herramienta (apply_patch, shell, read_file…) y la forma de raw SUPUESTO — verificar con eventos reales
Los campos exactos de AgentEvent/HookProvider del stub 🟡 Reconstrucción ilustrativa — ajustar al código real
Que ChatGPT/Gemini web puedan dibujarse ❌ No (viven en la nube, sin transcript local)

Esto es un mini-proyecto de desarrollo, no un plug-and-play

Salvo que aparezca un adaptador oficial, no basta con npx pixel-agents para ver Codex. Escribir el CodexHookProvider implica investigar el formato de Codex, capturar eventos reales, ajustar el traductor y probar. Es una práctica avanzada / hackathon interno, ideal para un perfil técnico del equipo —no un paso para la clase general. Como con cualquier herramienta que observa tu actividad local, aplica la guía de gobernanza de datos: instala desde la fuente oficial, entiende qué lee y no lo uses con proyectos sensibles hasta haberlo revisado.


Ver también: 🎮 Pixel Agents (ficha y despliegue) · Demo autocontenida · github.com/pixel-agents-hq/pixel-agents