papersmith-ai
Una forja de papers: convierte PDFs de referencia en Markdown de alta fidelidad y legible por un agente (ecuaciones en LaTeX, tablas como tablas, figuras como archivos), y conduce la deliberación formal —matemática y de diseño experimental— hasta el código que se prueba solo y el paper compilado.
Este README es el manual completo, en español: instalación, el workspace por dentro, los doce comandos del CLI, las once skills, los diecinueve agentes, cómputo remoto, desarrollo y solución de problemas.
Índice
- Si es tu primera vez
- Flujo de uso — el camino corto
- Puesta en marcha
- Cómo funciona — el orden
- El workspace por dentro
- Harnesses y proyección de skills
- Los agentes
- Anatomía de cada skill
- Los comandos del CLI
- Cómputo remoto, con cuidado
- Arquitectura
- Desarrollo y pruebas
- Solución de problemas
- Documentación relacionada
- Licencia
Si es tu primera vez
Bienvenido. Esta forja está pensada para que puedas ir de un PDF a un paper compilado sin adivinar el orden. El camino mínimo:
- Instala el CLI y prepara el entorno — ver Puesta en marcha.
- Crea tu workspace —
papersmith init ~/papers/mi-paper --title "…" --topic "…". Qué te deja adentro, en El workspace por dentro. - Pon un PDF de referencia en
guidance/reference-papers/y ejecutapapersmith ingest <pdf>— o pide/paper-ingestiondentro de tu harness. - Abre la deliberación con
/proposal-deliberationy discute la matemática con el agente.
No hace falta usar todas las skills ni en este orden: cada una declara qué
necesita antes y se niega antes de hacer algo a medias. Si le falta una
entrada no la inventa: sale con un código de rechazo que nombra qué falta. Un
PAPER_ABSENT no es un error tuyo; es la skill diciéndote qué le falta.
El mapa completo está en Flujo de uso; la ruta con detalle, en Cómo funciona — el orden. Y si vas a usar un agente de IA como copiloto, los que ya vienen definidos están en Los agentes.
Flujo de uso — el camino corto
Once skills. Se invocan por nombre en Claude Code (/paper-ingestion) o se piden en
castellano ("ingiere los papers"). No hace falta usarlas todas ni en este orden: cada
una declara qué necesita antes y se niega si falta, así que si empiezas por el medio te
lo va a decir ella.
El camino completo, de un PDF a un paper compilado:
| # | Invocás | Qué hace | Dónde deja el resultado |
|---|---|---|---|
| 1 | /paper-ingestion |
Convierte los PDFs de referencia a Markdown legible (ecuaciones en LaTeX, tablas como tablas, figuras como archivos) | guidance/<carpeta>/ |
| 2 | /plausibility |
Explora la idea pre-ingesta en tres tramos: rastrea ~25 referencias por sus abstracts, mapea la constelación en un HTML único y discute la plausibilidad de la hipótesis contra ese SOTA | sota-pool/ (ignorado, como guidance/) |
| 3 | /proposal-deliberation |
Discute la matemática contigo y publica cada acuerdo como una revisión gestionada, con la constelación del SOTA al lado | proposals/ (la revisión y su graph.html) |
| 4 | /proposal-implementation |
Prueba de concepto: convierte la propuesta en Python que se verifica contra el documento | implementations/<repo>/ |
| 5 | /experimental-deliberation |
Discute el diseño experimental que va a poner a prueba esa matemática | experiments/ |
| 6 | /experimental-implementation |
Convierte el protocolo en código y corre sus mediciones | el mismo repo destino |
| 7 | /kaggle-accounts |
Prueba que las credenciales de Kaggle autentican de verdad | store/ (nunca sale del disco) |
| 8 | /remote-execution |
Manda trabajo a un worker remoto y lleva el registro de lo que volvió | el ledger del repo destino |
| 9 | /paper-writing |
Escribe el paper bloque por bloque, con cada afirmación atada a su evidencia | paper/ |
| 10 | /figure-review |
Mira el render: rasteriza cada Figures/<id>.pdf y mide lo que un raster puede sostener (tinta fuera del canvas, colisiones) |
un informe visual, nunca un cambio |
| 11 | /skill-audit |
Audita cualquiera de las anteriores: qué acepta el código contra qué promete su documentación | un informe, nunca un cambio |
Lo mínimo para empezar. Si sólo quieres probar la forja, empieza por el paso 2
con tu idea en dos oraciones: /plausibility rastrea el SOTA y te devuelve la
constelación más el top-5. Solo después, si quieres, pon un PDF en
guidance/reference-papers/, ejecuta /paper-ingestion, y delibera con
/proposal-deliberation. Los pasos 4 y 6 sólo tienen sentido cuando ya hay una propuesta
publicada y un repositorio destino donde implementarla.
El flujo completo, dibujado. El diagrama muestra cada etapa en este orden con
su comando, sus agentes (con las herramientas que cada uno declara en Pi), sus
compuertas humanas y lo que deja en disco. La versión interactiva, con enlaces a
la fuente de cada nodo, está en
docs/diagrams/papersmith-pi-flow.html
(se abre en el navegador; generada con Archify,
y su fuente y su receta están versionadas en
docs/diagrams/).
Tres cosas que conviene saber antes de la primera corrida.
- Nada de lo que produzcas se sube.
proposals/,experiments/,paper/,implementations/ysota-pool/viajan a GitHub como carpetas vacías y nada más;guidance/versiona sus carpetas y nunca los PDFs ni el Markdown de adentro. El andamiaje se versiona, el contenido no. Un paper a medio escribir es material de investigación, y publicarlo por accidente es una fuga. - Cada skill se niega antes de hacer algo a medias. Si le falta una entrada, no la
inventa ni la saltea: sale con un código de rechazo que nombra qué falta. Un
PAPER_ABSENTo unSECTION_CONTRACTS_UNREADABLEno es un error tuyo, es la skill diciéndote qué le falta. - La deliberación la cierras tú. Las dos skills de deliberación proponen, discuten y preparan la edición, pero ninguna publica sin que aceptes. Eso no es una cortesía: está en el contrato de cada una y el motor lo hace cumplir.
Puesta en marcha
Nada de esto requiere claves ni servicios externos: la forja corre localmente.
0. Lo que tu máquina necesita tener
Dos runtimes, y los dos son obligatorios. No es una recomendación: la forja usa Python para el CLI y Node para el motor de deliberación, y sin el segundo la mitad del framework no arranca.
| Runtime | Versión | Para qué | Si falta |
|---|---|---|---|
| Python | ≥ 3.11 | el CLI papersmith y todas las skills de Python |
no hay instalación posible |
| Node.js | ≥ 21 | el motor de deliberación (jiti, typebox) y el tablero |
init avisa y deliberate se niega |
npm |
el que trae Node | instalar esas dos dependencias en el workspace | igual que arriba |
Comprueba las dos de una vez:
python3 --version && node --version && npm --version
Qué pasa exactamente si Node no está. papersmith init no falla: avisa con
npm was not found; deliberate requires npm install for jiti/typebox y sigue
adelante, porque el resto del workspace sí se puede crear. Pero
proposal-deliberation y experimental-deliberation —las dos skills que
proponen y discuten antes de escribir— no corren sin esas dependencias. Si
quieres crear el workspace a propósito sin ellas, init --no-npm lo dice
explícitamente en lugar de dejarlo al azar.
El runtime de ingestión del paso 2 es aparte y sí es opcional: solo hace falta para extraer PDFs con OCR.
1. Instala el CLI y prepara el entorno
# 1a. El CLI, siempre desde la última versión de la rama `main`
pipx install git+https://github.com/LIAUNAL/papersmith-ai.git
# lo mismo con pip, si no usas pipx
pip install "papersmith-ai @ git+https://github.com/LIAUNAL/papersmith-ai.git"
# 1b. O desde un clon de `main` (si vas a desarrollar el framework)
git clone https://github.com/LIAUNAL/papersmith-ai.git && cd papersmith-ai
pipx install . # con pip: pip install .
# 1c. Actualizar el CLI cuando sale una release nueva
pipx upgrade papersmith-ai
# si `upgrade` no ve el cambio, fuerza la reinstalación
pipx install --force git+https://github.com/LIAUNAL/papersmith-ai.git
# con pip, `--upgrade` sobre una URL de git no siempre vuelve a clonar
pip install --upgrade --force-reinstall "papersmith-ai @ git+https://github.com/LIAUNAL/papersmith-ai.git"
# 1d. Y llevar a esa versión los workspaces que ya existen
papersmith upgrade --plan-migrations ~/papers/sparse-ae # qué artefactos faltan, sin escribir nada
papersmith upgrade ~/papers/sparse-ae # sincroniza los archivos y migra los artefactos
papersmith status ~/papers/sparse-ae # deriva y migraciones pendientes
# 2. Runtime aislado de ingestión (micromamba: Python 3.12, PyTorch, Surya OCR, llama-server)
python3 scripts/setup_env.py install
# 3. Dependencias de Node y proyección de skills a los harnesses
npm install
npm run setup:harnesses
No hay pip install papersmith-ai a secas. El paquete no está publicado en
PyPI, así que la instalación va por git, como arriba. Y cuidado con el nombre
vecino: papersmith —sin el -ai— sí está en PyPI, pero es otro proyecto
(generación de documentos Word), así que pip install papersmith no instala
esta forja.
Instala siempre desde main: es la única rama que se mantiene. La forma 1a
recibe exactamente lo que main tiene hoy; la 1b toma el clon tal como está,
incluidos cambios sin commitear, así que para reproducir un build usa 1a. Cada
release queda además con su tag, así que si necesitas congelar un build exacto
fíjalo en la URL — por ejemplo
pipx install --force "git+https://github.com/LIAUNAL/papersmith-ai@v0.13.0".
El historial de cambios está en CHANGELOG.md.
Actualizar son dos pasos y son dos objetos distintos. El 1c reinstala el
CLI desde main; confirma qué quedó con papersmith --version. El 1d
mueve cada workspace que ya existe, y no es opcional: un release puede
cambiar la forma de un artefacto que el workspace ya produjo —el atlas.html
del pool de grafos, por ejemplo—, así que sincronizar los archivos del
framework sin migrar los artefactos deja el andamiaje nuevo leyendo estado
viejo.
papersmith upgrade sincroniza el workspace con la versión instalada y aplica
las migraciones de artefactos de esa versión. La versión que el workspace
registra no avanza hasta que esas migraciones están hechas, así que
papersmith status puede decirte que todavía no llegó al release que acabas de
instalar. --plan-migrations informa lo pendiente y termina sin escribir nada;
no se llama --dry-run a propósito, porque no previsualiza la copia del kit.
--no-migrate sincroniza los archivos y deja los artefactos como están, con el
marcador de versión donde estaba.
Ojo con no confundir los dos upgrade: ninguno de los dos toca al otro.
papersmith upgrade se niega a llevar un workspace a una versión anterior a la
que él registra, salvo que se lo pidas con --allow-downgrade.
scripts/setup_env.py install provisiona todo el stack de ingestión en un
entorno micromamba aparte (CPU o CUDA, según lo que detecte), incluido el
binario llama-server que el OCR necesita — no es Ollama ni una
instalación de Homebrew. npm run setup:harnesses enlaza el árbol canónico
skills/ dentro de .claude/skills, .pi/skills, .opencode/skills
y .agents/skills, con enlaces relativos e idempotentes (ver
Harnesses y proyección de skills). La
primera corrida de papersmith ingest descarga ~1.5 GB de pesos de Surya si el
entorno no quedó pre-provisionado.
2. Crea un workspace
papersmith init ~/papers/sparse-ae \
--title "Sparse Autoencoder Audit" \
--topic "mechanistic interpretability" \
--remote kaggle
cd ~/papers/sparse-ae
papersmith status
papersmith status --json # snapshot legible por máquina
El workspace queda desacoplado del checkout del framework: el andamiaje se
sincroniza con papersmith upgrade y tus artefactos de investigación están
protegidos por contrato. Qué crea init exactamente —y qué se versiona— en
El workspace por dentro.
3. Ingiere literatura
papersmith ingest https://arxiv.org/abs/2309.08600 # arXiv, OpenReview o URL
papersmith ingest ~/Downloads/reference-paper.pdf # PDF local
papersmith ingest ~/Downloads/escaneo.pdf --ocr # escaneado: modo OCR balanceado
Cada PDF suelto termina en su propia carpeta, junto a su Markdown (texto +
LaTeX + tablas, con la bibliografía quitada) y las imágenes de sus figuras. El
detalle del pipeline, en la sección de paper-ingestion de la
Anatomía.
4. Delibera, implementa y ejecuta
papersmith deliberate . --action status # estado de la propuesta gestionada
papersmith implement . --action verify --target implementations/mi-repo # sobre un repo ya materializado
papersmith run smoke_and_invariants --dry-run # planifica; no despacha nada
Dentro de un harness, las mismas capacidades se piden por nombre de skill:
/paper-ingestion, /proposal-deliberation, /experimental-deliberation,
/proposal-implementation, /experimental-implementation, /paper-writing.
Y las que no dependen de un dominio — /kaggle-accounts, /remote-execution,
/skill-audit — hacen de portero, memoria y auditor de lo que envías afuera.
Cómo funciona — el orden
Paso 1 — Colocar cada PDF en la carpeta según su rol
| Carpeta | Qué va aquí |
|---|---|
guidance/paper-guide/ |
Papers guía — las referencias metodológicas / de estilo. proposal-deliberation las carga como contexto al inicio de cada deliberación. |
guidance/reference-papers/ |
Corpus de referencia — papers de apoyo, ingeridos a Markdown para consulta. No se cargan automáticamente en la deliberación. |
guidance/data-paper/ |
Paper de datos — obligatorio para experimental-deliberation. Describe la base de datos de la investigación y es el techo: acota qué se puede afirmar. Una afirmación que los datos no sostienen no es un experimento. Sin esta carpeta poblada, CREATE_INITIAL_REVISION no puede arrancar. |
Las tres carpetas viajan vacías. Cada una llega a un clon con su .gitkeep y nada más: guidance/*/* está ignorado, con !guidance/*/.gitkeep como única escapatoria. El andamiaje se versiona, los PDFs y su Markdown no. tests/test_forge_scaffolding.py deriva las dos mitades —lo que los profile.ts declaran como fuente contra lo que git realmente rastrea— y se pone rojo si una carpeta declarada deja de viajar.
Paso 2 — Ingerir los PDFs (PDF → Markdown)
En Claude Code, invocar la skill:
/paper-ingestion
(o simplemente pedir: "ingiere los papers"). Por cada PDF suelto, crea una
carpeta con el nombre del paper, mueve el PDF adentro y escribe un <nombre>.md
liviano (texto + LaTeX + tablas, con la bibliografía quitada) junto con las
imágenes de las figuras:
guidance/reference-papers/computers-13-00176-v2-1/
├── computers-13-00176-v2-1.pdf
├── computers-13-00176-v2-1.md
└── _page_4_Figure_2.jpeg (figuras que el .md referencia)
Un PDF suelto (directamente en una carpeta raíz) está pendiente; un paper
que ya está dentro de su carpeta se saltea. Para re-ingerir, borrar la carpeta
del paper (dejando el PDF suelto) y volver a ejecutar la skill. La configuración
vive en papersmith.yaml (source_roots, mode, strip_references).
Paso 3 — Deliberar sobre una propuesta (proposal-deliberation)
En Claude Code, invocar la skill:
/proposal-deliberation
En el primer turno carga automáticamente los Markdown de guidance/paper-guide/
como contexto y actúa como tutor matemático. Desde ahí se puede:
- describir una idea y pedir una primera versión,
- pedir ediciones a una propuesta gestionada,
- ejecutar el ciclo de vida de revisiones gestionadas.
Las propuestas viven en proposals/, una por revisión gestionada
(research-concept-rNN.md).
Cada revisión publicada viaja con su propia constelación: la del SOTA con la
propuesta adentro, en proposals/research-concept-rNN.graph.html, dibujada por
el mismo visor 3D que el atlas y nombrada desde la revisión. La produce el
tramo de publicación de la deliberación, a partir de la superposición
proposals/research-concept-rNN.sky.json que se redacta durante la discusión:
ahí la idea principal, los temas que la propuesta toma de los papers del SOTA,
su novedad, el problema que resolvería y de tres a cinco conceptos quedan como
nodos y aristas en vez de como frases. Si esa superposición no existe, el tramo
lo reporta como pendiente en lugar de dibujar relaciones que nadie acordó.
Paso 4 — Llevar la propuesta a código (proposal-implementation)
En Claude Code, invocar la skill:
/proposal-implementation
Toma la revisión vigente y la materializa en un repositorio destino, que vive en
implementations/ con su propio git y su propio entorno virtual. El flujo tiene
dos fases separadas:
- Estructura. Si el repo trae contenido, lo lleva al layout y verifica —sin ejecutar nada— que cuadernos, rutas y referencias sigan resolviendo. No audita ni valida el código que ya estaba: es lo que hay, ordenado.
- Materialización. Solo entonces implementa la matemática y la somete a la escalera de validación.
<repo>/
├── <Name>/ Notebooks/ Data/ Results/ Models/
├── src/<Package>/ una implementación por objeto matemático
├── tests/ smoke · invariantes · sintéticos · auditoría · remedios
└── pyproject.toml
La escalera tiene cinco niveles, del más barato al más caro: smoke, invariantes (cada afirmación de la propuesta anclada a un test), sintéticos (deterministas, semilla fija), auditoría (hallazgos sobre la matemática, medidos sobre 200 configuraciones aleatorias) y remedios (cada corrección propuesta validada con el mismo rigor). Un hallazgo sin remedio validado no se reporta.
Eso es todo lo que hace falta para usar la forja. Si quieres entender qué pasa
adentro de cada skill —sus pasos, sus piezas, cómo se conectan entre sí y qué
limitaciones tiene cada una— sigue en Anatomía de cada
skill. Y para el contrato literal, el SKILL.md de cada
una.
El workspace por dentro
papersmith init crea un directorio autocontenido —sin git inicializado y sin
tocar tu checkout— con esta forma:
mi-paper/
├── .papersmith/ # estado del workspace (config, manifest, ledger)
│ ├── config.json # target y perfil activos, rutas del ledger
│ ├── manifest.json # hashes de todo lo que generó el framework
│ ├── runs_ledger.jsonl # cada corrida despachada, en orden
│ └── version # versión del framework que lo creó
├── .claude/agents/ # los diecinueve subagentes (fuente única de verdad)
├── skills/ # la copia del kit: las once skills + _core
│ └── _core/ # los dos motores compartidos (deliberación, implementación)
├── sections/ # los diez contratos de sección (viajan CON contenido, como skills/)
├── guidance/
│ ├── reference-papers/ # drop-zone: deja aquí los PDFs de referencia
│ ├── paper-guide/ # papers guía de método y estilo (opcional pero recomendado)
│ └── data-paper/ # paper de datos (obligatorio para experimental-deliberation)
├── proposals/ # revisiones gestionadas, plano: research-concept-rNN.md
├── paper/ # el LaTeX del paper (paper-writing lo escribe aquí)
├── experiments/ # el protocolo experimental gestionado
├── implementations/ # repos destino; cada uno con su propio git
├── kaggle-inbox/ # lo que vuelve de los workers remotos
├── CLAUDE.md / OPENCODE.md / .agents/AGENTS.md # routing de harnesses
├── .pi/APPEND_SYSTEM.md # routing de harnesses (Pi lo appendea al prompt de sistema)
├── papersmith.yaml # configuración del workspace (la tuya, editable)
├── package.json # dependencias Node del workspace (jiti, typebox), la tuya, editable
├── requirements.txt
├── scripts/setup_env.py # provisión del runtime de ingestión
└── README.md # descripción breve del workspace
Cada carpeta de investigación (guidance/*/, proposals/, paper/, experiments/,
implementations/) llega vacía, con su propio .gitkeep: el .gitignore del
workspace aplica la misma regla que este repositorio se aplica a sí mismo —la
carpeta se versiona, el contenido nunca.
papersmith.yaml — la configuración que sí vas a tocar
El archivo viene con tres targets de ejemplo y tres perfiles de ejecución. Los targets declaran dónde corre el trabajo; los perfiles, qué se corre y cómo se reparte:
compute_targets:
default: "kaggle-gpu-pool"
targets:
local-workstation: { provider: "local", device: "cuda:0" }
kaggle-gpu-pool: { provider: "kaggle", accelerator: "GPU_T4_X2", ... }
slurm-cluster: { provider: "remote-ssh", host: "hpc.university.edu", ... }
execution_profiles:
smoke_and_invariants: { target: "local-workstation", entrypoint: "pytest …/tests/test_invariants.py" }
sweep_training: { target: "kaggle-gpu-pool", sharding: { … }, artifacts: { … } }
benchmark_evaluation: { target: "kaggle-gpu-pool", entrypoint: "papermill …/benchmark.ipynb" }
paper_ingestion:
engine: marker
mode: fast # fast (CPU) | balanced (GPU, máxima fidelidad)
strip_references: true
source_base: guidance
El target y el perfil activos viven en .papersmith/config.json; papersmith target set los cambia. El bloque paper_ingestion lo posee la skill de
ingestión y el orquestador lo trata como opaco (editalo con la skill, no a
mano).
Qué se versiona y qué no
El contrato de preservación de papersmith upgrade es explícito: nunca toca
guidance/, proposals/, paper/, experiments/, implementations/,
kaggle-inbox/, papersmith.yaml, package.json, README.md
ni los archivos .env*. Todo lo demás es andamiaje del framework y upgrade
lo sincroniza —con --force si hace falta— dejando tus artefactos intactos.
En este repositorio la misma regla vale para Git: el andamiaje se versiona, el
contenido no. Las carpetas de investigación viajan con su .gitkeep y nada
más; guidance/ versiona las carpetas y nunca los PDFs ni el Markdown
ingerido. sota-pool/ sigue el mismo contrato: la carpeta viaja, y el
candidates.json, el atlas.json y el atlas.html —que inlinea el visor 3D
completo— se quedan en la máquina que los produjo.
Preservar, eso sí, no es migrar. No tocar un artefacto lo protege mientras su
forma no cambie; el día que un release la cambia, hace falta algo que lo lleve
hasta ella. Eso es trabajo de las migraciones de
artefactos, y es también la razón por la que
.papersmith/version dejó de avanzar por el solo hecho de haber copiado
archivos.
El estado: .papersmith/
manifest.jsonguarda el hash de cada archivo generado.papersmith audit --check-driftcompara contra el disco y nombra lo que se desvió; la reparación espapersmith upgrade, nunca una edición a mano.runs_ledger.jsonles append-only: cada corrida depapersmith runque se despacha queda registrada con su target, su perfil y su resultado.config.jsonguarda el target activo, el perfil activo y las rutas de configuración del ledger.migrations.jsonregistra qué migraciones de artefactos atadas a una versión ya se aplicaron, para queupgradeno las repita. Las de convergencia nunca se anotan: no terminan nunca. Si el archivo se daña,upgradese niega ystatuslo reporta; no lo edites a mano.versiones la versión que el workspace declara para sus artefactos, no para sus archivos. De los dos marcadores es el que se queda atrás cuando una migración falla, y por esostatuspuede decir que el workspace todavía no llegó al release instalado.
Harnesses y proyección de skills
skills/ en la raíz del repositorio es la fuente única de verdad. Cada
harness lee skills desde su propia carpeta, así que el script de proyección
enlaza el árbol canónico en cada una con enlaces relativos e idempotentes:
correrlo de nuevo converge al mismo layout y el checkout sigue siendo
relocalizable.
npm run setup:harnesses # = bash scripts/setup-harnesses.sh
| Harness | Dónde lee las skills | Documento de routing (workspace) |
|---|---|---|
| Claude Code | .claude/skills/ |
CLAUDE.md |
| Pi | .pi/skills/ |
.pi/APPEND_SYSTEM.md |
| OpenCode | .opencode/skills/ |
OPENCODE.md |
| Google Antigravity | .agents/skills/ |
.agents/AGENTS.md |
Qué cablea cada harness
papersmith init y papersmith upgrade generan, por cada harness que declara el
workspace, lo que muestra esta tabla. Es la misma matriz que
HARNESS_CAPABILITIES en src/papersmith/generators.py, y un test
(tests/test_harness_parity.py) verifica que ambas coincidan. wired es lo que
se genera; unsupported es lo que no se genera; opt-in es lo que sólo se
documentas y activas tú.
| Harness | Skills | Comandos | Agentes | Plugins / guards |
|---|---|---|---|---|
| Claude Code | wired | wired (.claude/commands) |
wired (.claude/agents) |
opt-in (ver guard-hooks) |
| OpenCode | wired | wired (.opencode/commands) |
wired (.opencode/agents) |
wired (.opencode/plugins/refuse-offpath-push.js) |
| Pi | wired | wired (.pi/prompts) |
wired (.pi/agents) |
wired (.pi/extensions/refuse-offpath-push.js) |
| Google Antigravity | wired | unsupported — vía skills (workflows deprecados) | wired (.agents/agents) |
unsupported |
Al terminar, init y upgrade imprimen una línea por harness activo con el
estado de cada capacidad (wired, unsupported, failed o blocked) y agregan
la clave wiring a su resultado. Si un enlace de skills no se pudo crear, o hay
un directorio real donde iría el enlace, lo informan como advertencia (failed /
blocked) en lugar de omitirlo en silencio.
Lo que la tabla no promete:
- Pi: los agentes de
.pi/agentssólo los lee el paquete de tercerospi-subagents; el núcleo de Pi no tiene subagentes. Sin ese paquete, los archivos existen pero nadie los usa. - Claude Code: el guard es opt-in. Papersmith nunca escribe ni mezcla
.claude/settings.json; el fragmento para activarlo está endocs/guard-hooks.md. - Antigravity: no hay guard hasta que se obtenga el esquema de entrada de sus
hooks (
.agents/hooks.json). - Antigravity, comandos: no se generan archivos de comandos. Su único
mecanismo de slash commands, los workflows, está deprecado a favor de skills
(retiro 2026-11-01) y la ruta de workspace de los workflows no está verificada
(
.agents/workflowses una suposición). Las skills ya funcionan como/<skill-name>a través de.agents/skills, así que todas siguen invocables como comandos. - Antigravity, sólo ubicaciones documentadas:
.antigravity/rules.mdy.antigravity/skillsya no se generan. Antigravity usa.agents/skills,.agents/agents/y.agents/AGENTS.md(texto plano, sin frontmatter). Los workspaces existentes no se migran: ejecutapapersmith initen un workspace nuevo.
Matriz de soporte verificada
Resumen de docs/harness-support-matrix.md,
leída contra la documentación de cada herramienta el 2026-10-02. Es lo que la
herramienta documenta, no lo que Papersmith genera (eso es la tabla de arriba).
unverified significa que no se leyó una fuente primaria.
.antigravity/skillsy.antigravity/rules.md: unsupported (no source found) y ya no se generan; Antigravity usa.agents/skills,.agents/agents/y.agents/AGENTS.md.- Antigravity deprecó los workflows a favor de skills (2026-11-01, fuente: https://antigravity.google/docs/ide/workflows).
- Pi: los nombres de herramienta
mcpymcpScriptno tienen fuente (unsupported, no source found). - Las docs de estas herramientas cambian rápido; vuelve a verificar una celda antes de apoyarte en ella después de esa fecha.
Las fuentes de cada celda están en
docs/harness-support-matrix.md.
Dentro de un workspace generado, la historia es ligeramente distinta y
conviene saberlo: el workspace embarca el árbol skills/ completo y el de
agentes (.claude/agents/), y sus routing docs apuntan al árbol canónico
skills/*/SKILL.md. Los documentos de routing son generated projections: la
fuente real de cada agente es .claude/agents/*.md y la de cada skill es su
SKILL.md. No edites las proyecciones a mano; se regeneran (y
papersmith audit --check-drift avisa si una se desvió). init y upgrade generan un comando slash por skill
en .claude/commands/, .opencode/commands/ y .pi/prompts/; Antigravity no
recibe archivos de comandos y lee las skills por sus enlaces. Los agentes se
proyectan desde .claude/agents/ a .opencode/agents/, .pi/agents/ y
.agents/agents/. Para que tu harness
liste las skills como comandos /, ejecuta npm run setup:harnesses dentro del
workspace: enlaza el árbol embarcado en .claude/skills, .pi/skills,
.opencode/skills y .agents/skills, igual de relativo e idempotente que
en el checkout. En este checkout, las proyecciones de comandos slash
(.claude/commands/, .opencode/commands/, .pi/prompts/), las proyecciones de agentes
(.opencode/agents/, .pi/agents/, .agents/agents/) y los guards de OpenCode y Pi
se sincronizan con python scripts/sync-repo-harness.py (--check para CI; no
tiene script en package.json porque el kit lo embarca y el script es sólo del
repo).
Los agentes
Un workspace trae diecinueve subagentes en .claude/agents/. No son
reemplazos del CLI: son tiradas cortas de trabajo que tu harness lanza (por
ejemplo, con la Task tool) cuando se lo pides. La forma es siempre la
misma — una tirada entre dos compuertas del operador: el agente decide
nada, pregunta nada, y termina reportando qué encontró y cuánto costó
establecerlo. Las compuertas las abres y cierras tú.
| Agente | Para qué entra | Skill que carga |
|---|---|---|
implementation-build |
Del mapa objeto→módulo aprobado al informe de hallazgos: materializa el scaffolding, escribe un módulo por objeto con su procedencia y sus tests de invariantes, y valida los remedios admitidos. | proposal-implementation |
implementation-walk |
Camina el flujo declarado acto por acto, en su orden: corre los pasos locales, registra cada producto y se detiene en el ensayo previo al lanzamiento. No tiene camino para enviar una campaña. | proposal-implementation |
deliberation-publish |
Desde que aceptas un cambio hasta que la revisión sucesora queda publicada y vigente — la única forma en que la matemática viaja. La deliberación en sí no está aquí: solo tú la cierras. | proposal-deliberation |
experimental-publish |
Lo mismo que deliberation-publish, sobre el documento de experimentos. |
experimental-deliberation |
experimental-validation |
La etapa validated: busca el protocolo de evaluación del área —métricas, baselines, dataset, semillas, test de significancia—, verifica cada baseline contra su repo y su venue, y fecha cada URL que usa. |
experimental-deliberation |
experiments-build |
Del mapa aprobado de pasos del protocolo a comandos ejecutables: materializa lo que haga falta, cablea cada paso e instrumenta cada medición para que se pueda correr y leer. | experimental-implementation |
experiments-walk |
La caminata del protocolo, con las mismas compuertas y el mismo alto en el ensayo. | experimental-implementation |
paper-ingestion |
Convierte un paper a la forma en que se puede usar de verdad: ecuaciones LaTeX, tablas como tablas, figuras como archivos. Etapas: filed, extracted, readable. No le pregunta nada a nadie. |
paper-ingestion |
contract-auditor |
Lee los ## Disqualifiers de un contrato, literales, y juzga un bloque borrador contra cada uno: fires/clear/undecidable, con el pasaje citado en cada fires. |
paper-writing |
redactor |
Redacta el LaTeX de un bloque a partir de exactamente cuatro entradas (la prosa del contrato, su evidence set, su modo y su style set) y devuelve un mapa de binding: de dónde sale cada oración. | paper-writing |
style-sampler |
Resuelve el bloque equivalente, completo, en cada carpeta de estilo de guidance/ y lo devuelve verbatim, nunca recortado. Es la única material que un chequeo de solapamiento puede comparar. |
paper-writing |
diagram-author |
Escribe un diagrama TikZ standalone (<id>.tex + <id>.diagram.json) y lo compila en su propio loop, hasta el presupuesto de reparaciones. Nunca dibuja un gráfico a partir de números medidos — eso es el verbo place. |
paper-writing |
figure-auditor |
Compara los componentes declarados en el manifest contra la prosa de la sección y los pasos del pipeline; reporta veredicto estructurado. | paper-writing |
insumos-observer |
Lee las cuatro fuentes de entrada declaradas del paper —proposals/, experiments/, el código del repo destino y sus propias salidas de corrida— y reporta, por cada hecho observable, si se cumple y con qué evidencia. Nunca decide un valor ni corre declare. |
paper-writing |
novelty-screener |
Discute la hipótesis contra los abstracts del pool, califica novedad y plausibilidad con cita exacta —todo provisional— y rankea el top-5 para la ingesta posterior, cerrando con un párrafo de plausibilidad. Diálogo con reporte al final; no publica, no escribe, no ingiere, no delibera. | plausibility |
sota-scout |
Rastrea la idea en la literatura abierta —25 referencias con abstracts, cada una en su familia (3 a 5 en el pool)— como sota-pool/candidates.json. Termina en el pool; no ingiere ni califica nada final. |
plausibility |
sota-grapher |
Mapea el pool a su constelación —un sistema solar por paper agrupado por familia, 20 planetas máximo, aristas entre sistemas hermanos— como atlas.json más el atlas.html único, corriendo checker y renderer hasta el verde. |
plausibility |
audit-report |
Audita un sujeto que enumera un conjunto cerrado —operaciones, subcomandos, códigos, assets— buscando la brecha entre lo que su código acepta y lo que su documentación promete. Reporta; nunca repara. | skill-audit |
Tres reglas que ordenan todo lo demás:
- La fuente de verdad es
.claude/agents/. Los routing docs de cada harness (CLAUDE.md,.pi/APPEND_SYSTEM.md,OPENCODE.md,.agents/AGENTS.md) listan a los diecinueve enteros, y se generan: si quieres cambiar un agente, se cambia ahí, no en la proyección. - Cada agente declara la skill que carga (
skills/<nombre>/SKILL.md), y esa atadura se verifica: un agente que apunte a una skill que el workspace no embarca no llega. - Algunos agentes no son invocados por ningún camino de código
(
contract-auditor,redactor,style-sampler): los lanzas tú, y es el verbowriteel que juzga lo que devuelven — nunca confía en tu relato ni en el borrador por sí solos.
Anatomía de cada skill
Las secciones de arriba cuentan qué haces. Esta cuenta qué pasa adentro. Está escrita para alguien que nunca vio el proyecto: cada skill se explica desde cero y entera —sus pasos, sus piezas, sus conexiones con las demás, sus limitaciones conocidas y su diagrama— sin mandarte a otra parte del documento.
Antes de entrar, dos cosas.
Qué es una skill aquí, porque no es un programa que ejecutas y se acabó. Cada una tiene dos mitades que hacen cosas distintas:
| Mitad | Qué es | Quién la ejecuta |
|---|---|---|
SKILL.md |
Un contrato en prosa: cuándo activarse, qué preguntar, qué no hacer nunca. No es código: es la instrucción de conducta. | El agente lo lee y se comporta según eso. |
El motor (scripts/, engine/) |
Código determinista: mismas entradas, misma salida. Sin modelo, sin red, sin claves. | La máquina lo ejecuta y devuelve un veredicto que el agente no puede negociar. |
Esa división es la idea central de toda la forja: el agente decide, el motor verifica. El agente propone un cambio; el motor comprueba que ese cambio no rompió nada y, si lo rompió, se niega. Un agente puede equivocarse. Un motor determinista no cambia de opinión.
Y cómo se encadenan. Las once no son islas: cada una recibe algo concreto de otra y le entrega algo concreto a la siguiente. Este es el mapa; cada skill explica su propia costura en detalle, en su apartado. Las flechas dicen qué pasa por la costura, no sólo que existe — y cada una de las etiquetas de abajo está leída de la declaración de la skill que la recibe, no inferida del nombre de la carpeta.
flowchart TD
PDF["PDFs que dejas en guidance/"] --> PI["1. paper-ingestion"]
IDEA["tu idea, en dos oraciones"] --> PL["2. plausibility"]
PI -- "guidance/paper-guide (opcional)" --> PD["3. proposal-deliberation"]
PL -- "top-5 + reporte provisional" --> PD
PI -- "guidance/data-paper (obligatoria)" --> ED["4. experimental-deliberation"]
PI -- "guidance/paper-guide (opcional)" --> ED
PD -- "proposals/ — la revisión publicada" --> ED
PD -- "STATUS + el texto de la revisión" --> IMP["5. proposal-implementation"]
ED -- "experiments/ — el protocolo publicado" --> EIM["8. experimental-implementation"]
PD -- "proposals/ — el documento 1 del par" --> EIM
IMP -- "correcciones, detrás de compuerta" --> PD
EIM -- "correcciones, detrás de compuerta" --> ED
IMP -- "el repo destino en implementations/" --> EIM
IMP -- "carpeta del trabajo en tools/" --> RE["7. remote-execution"]
EIM -- "carpeta del trabajo en tools/" --> RE
KA["6. kaggle-accounts"] -- "worker + ruta al token, nunca el valor" --> RE
RE -- "el registro, sólo lectura" --> IMP
PD -- "proposals/" --> PW["9. paper-writing"]
ED -- "experiments/" --> PW
EIM -- "el código del destino y lo que sus corridas devolvieron" --> PW
PW --> PAPER["paper/main.tex + Figures/"]
PAPER -- "Figures/<id>.pdf ya compilado" --> FR["10. figure-review"]
SA["11. skill-audit"] -. "informa, nunca cambia" .-> PI
SA -. " " .-> PL
SA -. " " .-> PD
SA -. " " .-> IMP
SA -. " " .-> KA
SA -. " " .-> RE
SA -. " " .-> ED
SA -. " " .-> EIM
SA -. " " .-> PW
SA -. " " .-> FR
SA -. "y a sí misma" .-> SA
Tres cosas que el mapa dice y conviene leer despacio.
Los dos lazos hacia arriba son el corazón de la forja. La matemática baja a código y lo que el código descubre sube de vuelta al documento; lo mismo entre el protocolo experimental y su implementación. En los dos casos la vuelta pasa por una compuerta que abres tú: el código propone la corrección, nunca la publica.
Las cuatro de en medio son dos pares sobre dos motores. La 2 y la 6 comparten un
motor de deliberación; la 3 y la 7, un motor de implementación. Lo único que cambia
entre los dos huéspedes de cada par es su profile.ts o su impl_profile.py — por
eso un defecto en un motor aparece en dos skills a la vez, y por eso las
limitaciones del motor están anotadas en las dos.
La 8 es la única que lee de todo lo anterior a la vez, y la 9 no está en la cadena: se para al costado y mira a cualquiera, incluida a sí misma.
1. paper-ingestion — de PDF a Markdown
Para qué está. Los papers llegan como PDF, y un PDF es, para un agente, una caja cerrada: el texto está mezclado con la maquetación, las ecuaciones son dibujos y las tablas son líneas sueltas. Esta skill convierte cada PDF en Markdown limpio —ecuaciones en LaTeX, tablas en Markdown, figuras como archivos de imagen aparte— para que el resto de la forja pueda leer el paper en vez de intentar descifrarlo. Todo corre localmente: sin claves, sin servicio externo.
De dónde recibe y a quién le entrega. Es el principio de la cadena: lo único que
recibe son los PDFs que dejas en guidance/. Lo que entrega tiene una
consecuencia que conviene entender antes de usarla:
guidance/paper-guide/— lo que cae aquí es lo queproposal-deliberationcarga automáticamente, desde la constanteGUIDE_DIRECTORY = "guidance/paper-guide"de su motor. Pero lo carga una sola vez en toda la vida de la propuesta: en la creación de la v1, y nunca más.guidance/reference-papers/— corpus de consulta. Nunca se carga solo. Está ahí para que tú o el agente lo lean cuando haga falta.
El orden importa, y esto no es obvio. Si ingieres un paper guía después de haber creado la v1 de la propuesta, ese paper ya no entra por esa vía: la carga es irrepetible por diseño. Ingiere primero, delibera después.
Qué necesita antes. Una única preparación por máquina: correr
./.claude/skills/paper-ingestion/setup.sh. Es idempotente y hace dos cosas:
instala el binario llama-server (el motor de OCR; no es un paquete de pip) y crea
el entorno virtual con marker-pdf adentro. La primera ingesta real descarga los
modelos (~1,5 GB) y los cachea; de ahí en más funciona offline.
Las etapas que declara. El flujo no es la lista de comandos: es lo que cada
etapa deja establecido, y cuándo puedes darla por superada. Esta tabla está leída
de OBJECTIVE_FLOW en el script, que un test mantiene igual al SKILL.md.
| Etapa | Qué deja establecido | La tienes detrás cuando |
|---|---|---|
filed |
El PDF está dentro de una carpeta temática propia — eso es lo que lo vuelve un paper y no una descarga | deja de aparecer entre los sueltos |
extracted |
El Markdown existe al lado, con sus figuras escritas como archivos en vez de quedar dentro de la página | la carpeta del paper tiene el documento y sus imágenes |
readable |
Las ecuaciones son LaTeX y las tablas son Markdown, así un lector posterior puede citar una fórmula en vez de describir una foto de una | es la llegada; no está detrás de nadie |
Llegada: un documento que una persona puede leer y una sesión posterior puede citar — que un PDF no es. Sus ecuaciones son dibujos y sus tablas son tinta.
Una tensión declarada, que no es un error tuyo si la ves. El campo
humanStops de esta skill está vacío: ninguna etapa se detiene a esperar a
una persona. Pero su Activation Contract dice "Never ingest without asking
first", y la confirmación de la Fase 2 es obligatoria. Las dos cosas conviven
porque la confirmación ocurre antes de que el flujo arranque, y el script no
tiene mecanismo de consentimiento propio — la regla vive en el SKILL.md y la
hace cumplir el agente. Vale anotar que kaggle-accounts, que también escribe,
sí declara "consentir la validación" como parada de una persona: las dos skills
no describen igual el mismo consentimiento.
El flujo, paso a paso. El punto clave es que son dos comandos, no uno, y esa división existe para que exista un momento de consentimiento.
Fase 1 — Descubrir (gratis, no toca nada):
- El agente corre el script con
--list. Esto leepapersmith.yaml, valida la configuración, descubre las carpetas fuente y busca PDFs sueltos. No carga ningún modelo y no mueve ningún archivo: el código corta antes. - Se imprime la lista: ruta relativa y cantidad de páginas de cada PDF.
- Si hay PDFs tirados directamente en
guidance/—fuera de toda carpeta temática— se reportan aparte, junto con los temas existentes como opciones.
Fase 2 — Confirmar (obligatorio):
- El agente te pregunta cuáles ingerir, como selección múltiple. Esta regla vive
en
SKILL.md, no en el script: el script no tiene mecanismo de consentimiento. La división en dos comandos es lo que hace posible que exista el paso de aprobación. - Si no apruebas nada, se termina ahí. Nada se movió.
Fase 3 — Ejecutar:
- El agente vuelve a correr el script con exactamente las rutas aprobadas. Cada ruta
se valida antes de cargar el motor: que exista, que sea un
.pdf, que siga suelta. Así un argumento mal escrito no cuesta una carga de modelo. - Se construye el conversor de Marker, explícitamente sin servicio de LLM — eso es lo que lo mantiene local y keyless.
- Por cada paper, la conversión es transaccional: se arma todo en un directorio
temporal al lado del PDF, y sólo cuando el
.mdy las figuras están completos se crea la carpeta final, se mueven los archivos, y el PDF se mueve último. Hasta que eso pasa, el paper sigue suelto y reintentar es simplemente volver a correr. - Si un paper falla, se reporta y el lote sigue con los demás. Si falla a mitad, se borra la carpeta sólo si la creó esta corrida.
Reingerir: no hay bandera de "forzar" ni archivo de estado. La idempotencia es
puramente estructural: un PDF está suelto cuando el nombre de su carpeta padre no
coincide con su propio nombre. Una vez que vive en <nombre>/<nombre>.pdf, dejó de
estar suelto y nadie lo vuelve a tocar. Para reingerir, borras la carpeta del
paper y el PDF vuelve a quedar suelto para la próxima corrida.
Los módulos.
| Archivo | Qué hace y por qué existe |
|---|---|
SKILL.md |
El contrato de conducta: correr --list primero, pedir aprobación por paper, cómo tratar un PDF sin archivar. El script no dice nada de esto — sólo devuelve códigos de salida. |
setup.sh |
Provisión del entorno, idempotente: instala llama-server y arma el .venv. Existe porque el OCR necesita un binario de sistema que pip no puede instalar. |
requirements.txt |
Fija marker-pdf==2.0.0. Sin ese pin, una versión nueva podría cambiar la API del conversor y romper la extracción en silencio. |
scripts/extract_pdf.py |
El motor completo: validación de configuración, descubrimiento, listado, archivado, conversión transaccional, quitado de bibliografía, escritura de figuras, y el contrato de códigos de salida. |
Las funciones que importan, si vas a leer el código: is_loose() —el chequeo de
idempotencia entero, en una línea—, discover_source_roots(), find_loose_pdfs(),
resolve_single_target() (la validación previa a cargar el motor),
build_converter(), ingest_loose() (la transacción) y file_into() (el archivado
de un PDF sin tema).
Qué escribe en el disco.
guidance/reference-papers/<nombre>/
├── <nombre>.pdf el PDF original, movido aquí al final
├── <nombre>.md texto + LaTeX + tablas, sin bibliografía
└── _page_4_Figure_2.jpeg las figuras que el .md referencia
Claves que respeta de papersmith.yaml, bajo el bloque paper_ingestion:: engine
(sólo marker), mode (fast o balanced), strip_references (bool, por defecto
true), source_base (por defecto guidance) y source_roots.
Los seguros.
- Nunca convierte sin que se haya preguntado. Protocolo del
SKILL.md, habilitado por la división en dos comandos. - Falla cerrado ante configuración inválida. Un
papersmith.yamlmalformado sale con código 2 sin tocar nada — elegido por sobre reportar "no hay nada que hacer", que sería indistinguible de un caso sano. - Nunca fusiona con una carpeta existente. Si ya hay contenido, se niega. El único camino a reingerir es borrarla, que es explícito y se ve destructivo a propósito.
- Nunca adivina el tema de un PDF sin archivar. Convertirlo donde está lo convertiría permanentemente en una "carpeta temática" propia.
- Sólo revierte lo que creó. Una carpeta que ya existía nunca se borra.
Limitaciones conocidas. Ninguna abierta hoy. La que figuraba aquí
—.claude/agents/paper-ingestion.md describía una interfaz que el script no tiene:
--output-dir, --force, un manifiesto versionado y extracción con PyMuPDF— ya no
aplica: esa definición se corrigió y hoy sólo nombra la skill y delega el contrato en
ella. Medido: el script expone --list, --file e --into, no importa PyMuPDF y no
escribe manifiesto; la única aparición de la palabra manifest en la definición del
agente es la nota que explica que dejó de restatear el contrato. Una regla escrita dos
veces son dos copias que se separan, y la vieja se lee tan autorizada como la nueva.
Diagrama.
flowchart TD
A["Usuario pide ingerir"] --> B["Script con --list"]
B --> C["Lee papersmith.yaml"]
C --> D{"¿Config válida?"}
D -- No --> E["Sale con código 2. Nada tocado"]
D -- Sí --> F["Descubre carpetas fuente y PDFs sueltos"]
F --> G{"¿Hay PDFs sueltos en carpetas temáticas?"}
G -- No --> H["Reporta: nada que ingerir + PDFs sin archivar"]
H --> I["Agente pregunta en qué tema va cada uno"]
I --> J["Script con --file y --into: sólo mueve"]
G -- Sí --> K["Imprime la lista con páginas"]
K --> L["Agente pregunta cuáles ingerir"]
L --> M{"¿El usuario aprueba alguno?"}
M -- No --> N["Fin. Nada se movió"]
M -- Sí --> O["Script con las rutas aprobadas"]
O --> P{"¿Cada ruta existe, es PDF y sigue suelta?"}
P -- No --> E
P -- Sí --> Q["Carga Marker sin servicio de LLM"]
Q --> R["Por cada paper"]
R --> S{"¿Ya existe la carpeta del paper?"}
S -- Sí --> T["Se niega este paper. El lote sigue"]
S -- No --> U["Directorio temporal de staging"]
U --> V["Convierte, quita bibliografía, escribe figuras y .md"]
V --> W{"¿Salió bien?"}
W -- No --> X["Borra lo que creó. El PDF sigue suelto"]
W -- Sí --> Y["Mueve lo generado y el PDF al final"]
Y --> Z["Reporte final y código de salida"]
T --> Z
X --> Z
Z --> AA["El Markdown queda listo para proposal-deliberation"]
2. proposal-deliberation — discutir la matemática y publicar revisiones
Para qué está. Escribir una propuesta de paper matemático es un ida y vuelta largo: se discute una idea, se corrige una ecuación, se reordena una sección. El riesgo es que en ese ida y vuelta un modelo reescriba una ecuación "de paso", sin que nadie lo note. Esta skill hace dos cosas a la vez: convierte al agente en tutor matemático que discute contigo, y pone un motor determinista entre esa discusión y el archivo, para que nada cambie salvo lo que aprobaste.
De dónde recibe y a quién le entrega.
Recibe de paper-ingestion: los Markdown de guidance/paper-guide/, y sólo en
la creación de la v1. El motor los carga él mismo en esa única operación; el agente
no se los pasa. Para cualquier revisión que ya existe, esa carga no se repite — el
SKILL.md lo dice sin matices: ese ingreso se gasta una sola vez, en la creación
verdadera de la v1.
Recibe de proposal-implementation: correcciones. Cuando la implementación
encuentra un defecto en la matemática y lo valida, vuelve aquí a publicarlo. Pero
entra por la puerta normal, sin atajo: ubica la sección, arma el reemplazo, y pasa
por la misma vista previa, la misma puerta de integridad matemática y la misma
auditoría que cualquier otro cambio. Que la corrección venga de una medición no la
exime de nada.
Le entrega a todo el resto: dos cosas. El archivo publicado
(proposals/research-concept-rNN.md) y —tanto o más importante— la operación
STATUS, que es de dónde toda la forja saca la respuesta a "cuál es la revisión
vigente". Nadie inspecciona el directorio a simple vista. Esa es una regla dura, y existe porque el
listado del directorio puede mostrar archivos que el motor considera inválidos.
Qué necesita antes. Nada más que Node. No usa claves de API ni llama a ningún modelo: el motor es keyless. El "modelo" de esta skill es el agente que ya está en la conversación — por eso el motor no necesita uno propio.
Las etapas que declara. Leídas del campo objective de su propio
profile.ts, que un test mantiene igual al SKILL.md. STATUS las reporta
arriba del inventario, y las dos rutas de error del motor también las llevan.
| Etapa | Qué deja establecido | La tienes detrás cuando |
|---|---|---|
bound |
Cuál revisión es la actual y cuál entrada de ella toca el cambio | STATUS nombró la última y el objetivo resolvió a una entrada |
deliberated |
El cambio se discutió, no se tipeó | lo dijiste tú — aquí nada lo mide, y nada puede medirlo |
composed |
El reemplazo existe escrito COMO matemática —la ecuación, con su tag— y no como una descripción de ella | existe un bloque que lleva la ecuación y el tag donde aterriza |
published |
La sucesora existe con el marcador del artefacto y es la revisión actual | es la llegada; no está detrás de nadie |
Llegada: la revisión sucesora publicada y actual, que es la única forma en la que viaja la matemática.
deliberated es la parada de una persona, y es deliberado que no se pueda
medir. El motor puede comprobar que existe un bloque; no puede comprobar que
lo discutiste. Por eso esa etapa avanza sólo cuando lo dices.
El flujo, paso a paso.
- Arranque. Lo primero que hace el agente es pedirle al motor
STATUS—una operación de sólo lectura— para saber cuál es la revisión vigente. No abreproposals/por su cuenta. - Bifurcación. Si no hay ninguna revisión gestionada, el camino es crear la v1.
Si ya hay una (
r01,r05,r17…), el camino es editar. - Crear la v1. El agente te pide la idea y manda la creación inicial. Tu idea
necesita al menos dos oraciones: el motor saca el título de la primera y el
encabezado de sección de la segunda, y con una sola los dos salen idénticos byte a
byte — lo que dejaría el documento imposible de editar después, porque toda consulta
que nombre un lugar sería ambigua. Con una sola oración se niega en el acto
(
INITIAL_IDEA_SINGLE_SENTENCE) y no escribe nada. Una oración termina en.,!o?; un salto de línea no alcanza. El motor carga los papers deguidance/paper-guide/una sola vez —aquí y nunca más—, redacta el documento a partir de tu idea, toma un candado único de proyecto (para que dos ideas en paralelo no puedan crear dos v1) y escribeproposals/research-concept-r01.md. La v1 se publica directo: no hay nada previo que proteger. - Deliberar. Con una revisión ya existente, la discusión pasa en la conversación, no en el motor. El agente propone, refuta, exige necesidad matemática antes de formalizar. El motor no se entera de nada de esto, y así debe ser: el estado de la charla vive donde puedes verlo.
- Ubicar el cambio. Cuando algo queda aprobado hay que decirle al motor dónde aplica. Se lo nombra con palabras del encabezado, y el motor las puntúa contra la estructura real del documento. Si los dos mejores candidatos quedan demasiado cerca, se bloquea y pregunta en vez de elegir. Nunca adivina.
- Vista previa. El agente arma la decisión concreta —
replace,insert,delete,moveocopy— y pide el sucesor. Aquí el motor no publica: compila el documento candidato pegando los bytes aprobados en el offset exacto, lo revalida entero, y devuelve un token de un solo uso junto con —lo importante— la lista de qué matemática desaparecería. - La puerta. El agente te muestra en castellano llano cada ecuación, cada
\tagy cada cita(Ec. N)que se perdería. Tú confirmas. Solo entonces se reenvía la misma operación con el token y con cada pérdida reconocida por nombre. Si falta una sola, se rechaza y no se escribe nada. - Publicar. El motor vuelve a leer el archivo fuente, verifica que su hash no
cambió desde el paso 6, aplica los parches, valida el resultado, verifica el fuente
una segunda vez justo antes de escribir, y solo entonces escribe
research-concept-r<N+1>.mdde forma atómica. - Contabilidad. Reconstruye los índices derivados y escribe un recibo con el sha256 del documento antes y después.
- Auditoría. Después de cada publicación se releen todas las revisiones y se
comprueba que cada archivo siga coincidiendo byte a byte con su recibo. Si algo no
cuadra, el resultado se degrada a
blockedaunque los bytes ya estén escritos — y eso es a propósito: el trabajo no está terminado hasta que la contabilidad cierra.
Además existe un ciclo de vida de revisiones: retirar una revisión la copia a una
cuarentena inmutable, mueve los artefactos públicos, marca la operación como pendiente
de auditoría y sólo la confirma si la auditoría pasa; restaurar es el espejo exacto.
La r01 no se puede retirar nunca, ni tampoco una revisión que tenga descendientes.
Los módulos. El motor son unos 50 archivos TypeScript. Agrupados por trabajo:
| Grupo | Archivos | Qué hace y por qué existe |
|---|---|---|
| Puerta de entrada | engine/cli.mjs |
El único punto de acceso al motor. Atiende STATUS y la resolución de objetivo él mismo, y despacha el resto. Se levanta una vez por deliberación para no pagar el arranque en cada llamada. |
| Enrutador público | proposal-workspace.ts |
Registra la herramienta que el agente llama y decide a qué etapa va cada pedido. Contiene además las primitivas de escritura segura en disco. |
| Coordinador | orchestrator.ts |
La máquina de estados: resuelve, previsualiza, acepta y publica. Su publish() es el único lugar del sistema por donde puede pasar una escritura. |
| Ubicación | target-resolver.ts, ambiguity-gate.ts, document-index.ts |
Convierten "la sección de normalización" en un rango de bytes exacto. ambiguity-gate es el que se niega cuando hay empate. |
| Aplicación de bytes | patch-compiler.ts, successor-composite-engine.ts, ambient-supplied-planner.ts |
Pegan el texto aprobado en el offset exacto y verifican, por separado, que todo lo que quedó afuera del cambio siga idéntico. |
| Puerta de preservación | preservation.ts (neutral) + proposal-deliberation/preservation-math.ts (extractor matemático) |
Enumera los átomos que el perfil del dominio reconoce (para matemática: ecuaciones, \tag, macros y citas) antes y después. Reporta lo perdido, y bloquea por su cuenta ante violaciones de forma canónica: $$ desbalanceado, delimitadores \(...\), un símbolo Unicode de matemática metido adentro de un $...$. |
| Validación del candidato | candidate-validator.ts |
Re-parsea el documento entero resultante: Markdown bien formado, etiquetas únicas, referencias que resuelven, símbolos sin conflicto, bytes de afuera intactos. |
| Auditoría | consistency-audit.ts, self-audit.ts |
Recalculan el sha256 de cada revisión y lo cruzan contra su manifiesto y su recibo. De aquí salen RECEIPT_SHA_MISMATCH, ORPHAN_STATE y compañía. |
| Recibos y estado | revision-receipt.ts, derived-state-store.ts, derived-state-builder.ts |
Escriben y releen la contabilidad: qué se publicó, desde qué, con qué hash. |
| Ciclo de vida | revision-lifecycle-store.ts, revision-lifecycle-transaction.ts |
Retiro y restauración transaccionales, con reversión en orden inverso si algo falla a mitad. |
| Concurrencia | mutation-lock.ts |
Impide dos publicaciones simultáneas sobre el mismo archivo. |
| Token de aceptación | successor-acceptance-registry.ts |
Ata una vista previa a su aceptación. Vive en memoria, es de un solo uso y muere con el proceso: no se puede aceptar mañana una previa de hoy. |
| Agente delegado | .claude/agents/deliberation-publish.md |
El tramo terminal, y arranca solo después de que aceptaste: resuelve la entrada, compone el reemplazo sustituyendo adentro de ella en vez de devolver un bloque pelado, y publica. Read, Bash, Glob, Grep — sin Write ni Edit, porque sólo el motor escribe. La deliberación misma no está en su tramo y no puede estarlo: eso no lo cierra nadie más que tú. |
Qué escribe en el disco.
proposals/research-concept-rNN.md la revisión, con un marcador de artefacto al inicio
.proposal-deliberation/
├── state/research-concept-rNN.md.json índices derivados + hashes
├── receipts/research-concept-rNN.md.json el recibo: sha antes, sha después, qué parches
├── withdrawn/<uuid>/ cuarentena inmutable de una revisión retirada
└── locks/initial-revision.lock candado de creación de la v1
Los seguros.
- Nada se pierde en silencio. Una ecuación que desaparece exige que la reconozcas por nombre antes de publicar.
- Se bloquea antes que adivinar. Un objetivo ambiguo pregunta; no elige el más probable.
- La auditoría es byte a byte. Editar a mano una revisión publicada rompe su recibo, y el motor lo dice en la siguiente operación.
- Sin claves. Ninguna operación del motor sale a la red.
- Falla cerrado. Una operación desconocida se rechaza; no cae al camino por defecto.
Limitaciones conocidas. Vienen en tres grupos, y no las cuento aquí a propósito: un número escrito a mano al lado de una lista envejece la primera vez que alguien agrega una, y este documento ya tuvo tres casos así. Primero las del motor de edición —qué pasa, qué puedes hacer igual, qué no, y cómo se arreglaría—; después las del alcance de las garantías, o sea qué es lo que esta skill, medida, no puede afirmar; y al final las del motor compartido, que valen igual para el otro dominio que lo usa.
La consulta que ubica un cambio es sensible a cómo la escribes. Para aplicar un
cambio hay que decirle a qué sección apunta. Esa consulta se compara por substring
contra la línea del encabezado, y eso tiene dos filos. Las tildes cuentan:
Normalizacion terminos adaptacion no encuentra ## 5. Normalización de los términos de adaptación, aunque sea la misma frase. Solo molesta cuando la palabra acentuada es
justo la que distingue: si quedan otras palabras distintivas sin tilde, resuelve
igual. Y la consulta se corta en el primer signo de puntuación, así que Sección 3. Formulación… se reduce a 3 antes de buscar nada. Qué puedes hacer: escribir
la consulta como palabras distintivas del encabezado, sin puntuación, sin el
número de sección, y con las tildes tal como están escritas. Normalización términos adaptación funciona; la frase completa con puntuación, no. Las palabras vacías (de,
los, la) ya se filtran solas. Cómo se arregla: comparando sin tildes de los dos
lados, y quedándose con la consulta completa en vez de cortarla en la puntuación.
Mover o copiar nombrando una sección puede quedar ambiguo. En un move o un copy,
la sección se busca con un puntuador distinto al de las ediciones normales: ese mira el
cuerpo entero de cada entrada, no solo su encabezado. Como los párrafos de una
sección contienen las mismas palabras que su título, la sección y sus propios párrafos
empatan y la operación se bloquea pidiéndote que desambigües. Qué puedes hacer y qué
no: se bloquea, no se equivoca — nunca vas a mover algo distinto de lo que pediste
sin enterarte. Para desambiguar, nombra el bloque concreto que quieres mover en vez de
la sección completa. Cómo se arregla: haciendo que move/copy puntúe la línea
del encabezado, igual que ya lo hacen las ediciones normales.
Un cambio se aplica sobre la sección completa. La unidad mínima que se puede apuntar
es una sección ##. Para corregir una sola ecuación, la skill entrega la sección
entera reescrita. Nada dentro de esa sección está protegido byte a byte: la garantía de
bytes idénticos cubre lo que queda fuera del cambio. Qué puedes hacer y qué no:
lo que sí protege lo de adentro es la puerta de integridad matemática — antes de
publicar, la skill te lista cada ecuación, cada símbolo, cada \tag y cada cita (Ec. N) que existía antes y ya no está, y no publica hasta que esa desaparición se
reconozca explícitamente. Una ecuación no se puede perder en silencio; sí puede cambiar
prosa alrededor sin que nadie lo señale. Cómo se arregla: con loci más finos —
poder apuntar a un párrafo o a una ecuación concreta, no solo a la sección que la
contiene.
Mover contenido al lugar equivocado no lo detecta nadie. La puerta de integridad
matemática compara qué había antes y qué hay después. Un move que se lleva el bloque
equivocado no pierde matemática: la reubica intacta. Como no falta nada, la puerta
no tiene nada que objetar. Qué puedes hacer y qué no: el riesgo real bajó bastante
—hoy una consulta ambigua se bloquea en vez de resolver a lo que no era— pero aun así,
revisa el resultado de un move antes de seguir construyendo encima. Cómo se
arregla: comparando también dónde está cada bloque, no solo si sigue existiendo.
Editar una revisión publicada a mano rompe la auditoría. Abres
proposals/research-concept-r14.md en el editor, corriges una palabra, guardas. La
próxima operación de la skill reporta auditStatus: FAIL y no te deja seguir. Por
qué: cada publicación deja un recibo en .proposal-deliberation/receipts/ con el
sha256 del documento. Antes de cualquier operación, la auditoría relee el archivo, lo
vuelve a hashear y exige que sea byte-idéntico a lo que el motor publicó
(consistency-audit.ts, RECEIPT_SHA_MISMATCH). Una palabra distinta cambia el hash y
el recibo deja de respaldar nada. No es un defecto: es la garantía funcionando. Sin
recibos el motor no puede afirmar que una revisión sea lo que dice ser, y el linaje
byte-exacto se queda sin respaldo. Sacarlos no es una opción. Qué puedes hacer: hoy,
o reviertes la edición manual hasta que el archivo vuelva a coincidir, o reconcilias el
recibo a mano. Lo segundo es delicado y conviene evitarlo: un recibo actualizado sin
cuidado deja al linaje afirmando algo que nadie comprobó. Cómo se arregla: con una
operación de re-base autorizada —algo como ADOPT_MANUAL_EDIT: "edité esta revisión a
propósito, adopta los bytes actuales como nueva línea base"— que actualice el recibo
tras confirmación explícita. Con eso, editar a mano dejaría de ser una ruptura y
pasaría a ser un acto declarado.
Ninguna edición a mano está impedida; en el mejor caso se detecta después. No hay en
la forja ninguna barrera que frene a alguien —o a un agente— que abra un archivo
gestionado y lo escriba por afuera del motor. .claude/settings.json tiene un solo
hook PreToolUse (refuse_offpath_push.py, con matcher Bash, y es de
remote-execution, no de esta skill), y su clave permissions no tiene ninguna
entrada deny. Qué significa para ti: todo lo que esta skill opone a una edición
manual llega después del hecho — la auditoría del punto anterior te avisa en la
operación siguiente, con los bytes ya escritos. Qué no cubre: el momento de la
escritura, ni nada de lo que pase entre esa escritura y la próxima vez que alguien
invoque el motor. Cómo se arregla: con una regla deny sobre proposals/ y sobre
.proposal-deliberation/, para que la escritura por afuera del motor no llegue a
ocurrir.
El índice persistido es un caché que se cura solo, no un guardia. Es tentador leer
.proposal-deliberation/state/<archivo>.json —un manifiesto con el documentSha256 del
documento entero y, por cada entrada de la estructura, su propio textSha256 sobre un
rango de bytes— como una defensa contra una edición manual. No lo es.
loadDocumentState reconstruye siempre el estado a partir de los bytes que hay en
el disco, y sólo usa el guardado si valida contra ese mismo documento; si no valida —que
es exactamente lo que pasa después de una edición a mano— lo sobrescribe en silencio
con la reconstrucción fresca. Ningún error, ningún reporte, nada que llegue a quien
llamó. Qué significa para ti: el estado guardado no contradice una edición manual;
se acomoda a ella. Qué no cubre: consistency-audit.ts tiene un
MANIFEST_SHA_MISMATCH, pero no hay ninguna operación que puedas invocar para
preguntarlo, y para cuando la auditoría corre el caché ya se curó. Lo que de verdad nota
una edición a mano es el recibo —la limitación de más arriba—, no el índice. Qué
lo contiene: la skill de al lado. El bloque de posición de proposal-implementation
guarda el sha256 de la revisión contra la que se derivó, así que cambiarle los bytes a
una propuesta levanta POSITION_STALE en gate y en close —comprobado por
ejecución—. El motor de deliberación no lo detecta; la implementación sí. Cómo se
arregla: haciendo que el caché, cuando no valida, lo diga antes de curarse.
El estado no sobrevive a un clon. .proposal-deliberation/ es una entrada de
.gitignore (línea 40), así que nada de lo que hay adentro —ni los índices, ni los
recibos, ni la cuarentena— viaja en un clon nuevo. Qué significa para ti: un clon
fresco arranca sin contabilidad y la reconstruye a partir de los bytes que encuentra en
proposals/, aceptándolos tal como están. Si esos bytes venían editados a mano, el clon
no tiene contra qué notarlo: para él ese es el documento, y la auditoría cierra. Qué
no cubre: la verificación byte a byte es una propiedad de la máquina donde se
publicó, no del repositorio. Un linaje verificado aquí no llega verificado allá. Cómo
se arregla: no está decidido. Habría que separar qué mitad de esa contabilidad es
historia del proyecto y cuál es estado de máquina, y versionar sólo la primera. Por
ahora está anotado, no resuelto.
Nada prueba quién decidió. El motor deja constancia de lo que se publicó, contra qué base y con qué hash. Lo que no deja —ni puede dejar— es constancia de que la confirmación de la puerta la haya dado una persona: el token de aceptación es de un solo uso y ata una vista previa a su aprobación, pero lo consume quien llame al motor, y el agente que armó la vista previa puede llamarlo. Qué significa para ti: una decisión registrada prueba que llegó al registro, nunca que alguien la tomó. Un agente puede abrir la pregunta y contestársela solo, y nadie más adelante en la cadena puede distinguir ese caso del otro. Qué no cubre: cualquier lectura del registro como "esto fue aprobado". Lo que dice es "esto quedó registrado". Cómo se arregla: no con más registro. Haría falta que la confirmación entre por un canal que el motor no pueda originar, y hoy no existe.
Deuda de mantenimiento. No limita a quien usa la skill; limita a quien la
modifique. El nombre del directorio de estado está repetido. La carpeta
.proposal-deliberation/ guarda la contabilidad —receipts/ y state/— en la raíz
del repositorio, y su nombre está escrito como texto literal en 21 puntos repartidos
en 7 archivos del motor (consistency-audit.ts sola tiene 7). Los 21 dicen lo mismo,
así que la carpeta siempre se encuentra. Qué sí funciona: dos propuestas distintas
conviven sin problema, porque tanto los recibos como el estado se guardan en un
archivo por revisión, nombrado con el archivo de la propuesta
(state/research-concept-r01.md.json). Nombres distintos, archivos distintos, cero
colisión. Qué no se puede: tener el mismo documento bajo dos deliberaciones
independientes —dos estados aislados sobre los mismos archivos, para explorar dos
caminos en paralelo— ni mover el estado fuera de la raíz del repositorio. Las dos cosas
necesitan que la ubicación sea configurable, y hoy no lo es. Y el modo de fallar es
feo: si alguien renombra la carpeta y se olvida de uno de los 21 lugares, no falla
nada — la mitad de la contabilidad queda escribiéndose en la carpeta vieja, en
silencio, hasta que alguien nota que faltan recibos. Cómo se arregla: con un único
stateRoot(root) del que salgan los 21. Es un refactor chico y desbloquea las dos
cosas de arriba.
Resuelto. proseReferenceText era obligatorio a nivel de motor sin tener ningún lector
en el núcleo —su único lector real siempre fue el lado matemático
(preservation-math.ts)—, y el otro host lo declaraba porque debía, sin invocarlo jamás.
Ahora es opcional en domain-profile.ts; preservation-math.ts exige su propia
necesidad localmente (falla con PROSE_REFERENCE_TEXT_REQUIRED si no está), y el perfil
experimental dejó de declararlo. En el mismo archivo de entrada, cli.mjs importaba
pathToFileURL sin usarlo salvo por un void pathToFileURL; de cierre; ambos se
borraron.
Diagrama.
flowchart TD
A["Usuario invoca /proposal-deliberation"] --> B["Agente pide STATUS al motor"]
B --> C{"¿Existe una revisión gestionada?"}
C -- No --> D["Agente pide la idea"]
D --> E["Crea la v1: carga paper-guide UNA sola vez"]
E --> F["Escribe research-concept-r01.md"]
C -- Sí --> G["Deliberación en la conversación. El motor no participa"]
G --> H["Resolver objetivo: ubicar la sección"]
H --> I{"¿Candidato único?"}
I -- "No, empate" --> J["Bloqueado: el motor pregunta cuál"]
J --> H
I -- Sí --> K["Vista previa del sucesor"]
K --> L["Compila el candidato y lo valida entero"]
L --> M{"¿Validación OK?"}
M -- No --> N["Bloqueado: candidato rechazado"]
M -- Sí --> O["Token + lista de matemática que se perdería"]
O --> P["El agente muestra qué desaparecería"]
P --> Q{"¿Confirma cada pérdida?"}
Q -- No --> R["Rechazado. No se escribe nada"]
Q -- Sí --> S["Revalida, rehashea el fuente, escribe atómico"]
S --> T["Escribe research-concept-r-siguiente.md"]
T --> U["Reconstruye estado y escribe el recibo"]
U --> V["Auditoría de consistencia byte a byte"]
V --> W{"¿Pasa?"}
W -- No --> X["status: blocked. La contabilidad no cierra"]
W -- Sí --> Y["Publicado. STATUS ahora reporta esta revisión"]
F --> U
Z["Corrección que llega desde proposal-implementation"] --> H
3. proposal-implementation — de la matemática al código que se prueba solo
Para qué está. Una propuesta publicada es un documento. Esta skill la convierte en un repositorio Python que funciona, y después demuestra mecánicamente —no por afirmación— que ese código es fiel a la matemática: que corre, que sus invariantes se cumplen, que los defectos que reporta son reales, que las correcciones que propone están validadas, y que sus informes dicen lo que los números dicen.
La conexión con proposal-deliberation
Es la costura más importante de la forja, va en los dos sentidos, y se comporta distinto en cada uno de los dos flujos. Vale la pena verla entera antes que nada.
De deliberación hacia aquí, tres cosas distintas entran:
- Cuál es la revisión vigente. Paso 1 de los dos flujos, sin excepción:
node .claude/skills/proposal-deliberation/cli.mjs '{ "operation": "STATUS" }'→ se tomalatest. La skill nunca adivina la base y nunca miraproposals/a simple vista. - El texto de la revisión. El motor sí lee el archivo:
revision_source()lo levanta del directorio de propuestas. Lo usa para juzgar admisibilidad: cada hallazgo declara qué notación usa —que tiene que aparecer textualmente en la revisión— y qué notación introduce. Una corrección que cita una ecuación que no existe, o que se apoya en notación que el documento nunca definió, no es un defecto a validar con un barrido: es una decisión que le pertenece a la deliberación, y la skill tiene prohibido reportarla como resuelta. (La ruta es configurable por entorno conIMPLEMENTATION_PROPOSALS, y la razón está escrita en el código: una forja de papers no puede tener su suite de tests atada a la investigación de alguien.) - El sello de procedencia. Cada módulo escrito declara contra qué revisión se
escribió, y ese string tiene que ser igual a
latest. Ese sello es lo que hace que la deriva sea medible en vez de opinable.
De aquí hacia deliberación, una sola vía, y con compuerta: cuando la auditoría
encuentra un defecto real en la matemática y lo valida, la corrección vuelve al
documento. Detrás de una autorización explícita, esta sesión maneja el motor de
deliberación: handoff dimensiona cada hallazgo, se ubica la entrada, compose arma
el texto de reemplazo empatando por el \tag{n} de la ecuación, y se publica el
sucesor. Está detrás de compuerta porque publicar avanza tu linaje real. Y no hay
atajo: la corrección entra por la misma vista previa, la misma puerta de integridad
matemática y la misma auditoría que cualquier otro cambio.
Y ahora lo que distingue a los dos flujos. La skill enruta por existencia, no por
fidelidad: mira si src/ tiene una implementación, y nada más. Eso es deliberado —
preguntar por fidelidad aquí mandaría un repositorio atado a r14, con latest en
r16, a una primera pasada completa, reimplementando desde cero algo que sólo necesita
ponerse al día. La deriva es el cuarto paso del flujo B, no una razón para empezar de
nuevo.
| Flujo A — primera pasada | Flujo B — toda pasada posterior | |
|---|---|---|
| Cuándo | src/ no tiene implementación |
src/ ya tiene una, atada a la revisión que sea |
Qué hace con latest |
Ata: se estampa como procedencia en cada módulo que se escribe | Mide: verify cruza lo declarado contra lo vigente y reporta la distancia |
| La vuelta a deliberación | Paso 14, detrás de compuerta: se publican las correcciones que salieron de la auditoría | Paso 5, si hay diferencias, y primero pregunta de quién son |
| Cómo termina | Paso 16: si es fiel, no para — sigue en el paso 3 del flujo B | En probe, que dice qué es lo siguiente |
Ese último renglón importa: el flujo A desemboca en el flujo B. Terminar en A haría que la respuesta dependa de cómo llegaste en vez de qué hay en el repositorio.
Y la compuerta más importante de toda la skill está en el flujo B, cuando la fidelidad no da: se te pregunta si esos cambios los hiciste tú.
- Los hiciste tú → el código va adelante de la propuesta. Se te recuerda actualizar la matemática y se te entrega el prompt que lo hace. Nunca se edita tu código para que coincida con una propuesta vieja.
- No los hiciste tú → el código derivó. Se corrige y se revalida, con un tope de tres pasadas.
Con un matiz fino que evita trabajo inventado: el reporte de deriva cruza qué secciones cambiaron de verdad con qué secciones declara cada módulo. Un módulo atado a una revisión vieja cuyas propias secciones nunca se movieron necesita re-atarse, no reescribirse — contabilidad, no matemática. Decir "nueve módulos están viejos" cuando cambió una ecuación informa que hay trabajo y nada sobre dónde.
La conexión con remote-execution
Mucho más angosta y de una sola dirección: sólo lectura. La skill importa por ruta
exactamente dos módulos —el registro y la puerta de entrada— y nunca el adaptador
ni nada bajo adapters/, que es el único lugar de aquella skill que nombra un
servicio. Con eso pliega el registro y reporta qué se mandó, qué volvió y qué quedó en
cuarentena. Nunca envía, nunca reconcilia: reconciliar es territorio de un humano
corriendo el comando de aquella skill, jamás de verify. Y nunca nombra un worker: sólo
reporta un conteo. De ese pliegue sale el peldaño poll-first.
Qué necesita antes. Una revisión publicada, y un repositorio destino bajo
implementations/ que ya sea un repositorio git.
El flujo, por etapas. Esta sección no tenía ninguno. Las etapas están leídas
de OBJECTIVE_FLOW, que un test mantiene igual al SKILL.md, y son
deliberadamente independientes de lo que haya en disco: tienen que leerse
igual en un repositorio vacío que en uno a mitad de campaña. Existen para el
momento en que algo se rompe — una sesión que choca con un error se ubica aquí,
resuelve lo que la bloquea y se reengancha, en vez de improvisar hacia adelante.
| Etapa | Qué deja establecido | La tienes detrás cuando |
|---|---|---|
standing |
Un repositorio donde escribir la matemática: aislado bajo implementations/, con intérprete propio, con la disposición que esta skill espera, los destinos del kit materializados, y aprobado el mapa de objeto matemático a módulo |
structure no reporta huecos de andamiaje y la declaración del benchmark lleva la revisión y las premisas con las que se aprobó el mapa — que es lo que materialize --stage objects se niega a hacer sin ellas |
fidelity |
El código dice lo que dice la revisión ligada, y cada afirmación lleva un invariante con su test | fidelity está limpio y la suite propia del destino está verde bajo su propio intérprete |
audit |
Qué cosas la formulación hace mal, establecido sobre el barrido declarado, cada remedio juzgado admisible antes de medirlo y validado después | audit deja de estar incomplete |
declaration |
Qué compara el experimento, sobre qué unidad estadística, con qué métrica, y qué produce | la declaración del benchmark está contestada en vez de sentada en su valor vacío de andamiaje |
rehearsal |
El flujo declarado corre punta a punta con sus propios notebooks, y el documento que lee una persona coincide con la corrida | el piloto está completo y report da ok |
full-scale |
Cada paso ruteado a donde se decidió que corra, y ejecutado ahí a la escala que declara el protocolo | es la llegada; no está detrás de nadie |
Llegada: corridas completas a la escala declarada, locales o remotas según lo declare cada paso, con el registro que dejan. No una verificación verde, no un ensayo que pasó.
Cuatro paradas son de una persona, y ninguna es un defecto a reparar. Dos están en la primera etapa y una sesión que arranca de cero las encuentra antes que nada: autorizar que se escriba código, sin lo cual nada debajo de esa compuerta puede empezar, y aprobar el mapa de objeto matemático a módulo, con la revisión y las premisas anotadas al lado. Las otras dos están al final: publicar el commit que un worker clonaría, y autorizar un lanzamiento. Un agente que lea cualquiera de ellas como un bloqueo se va a trabar o la va a saltear — las dos cosas están mal.
Los comandos. Todos con la misma forma:
python3 .../implementation_cli.py <comando> --target implementations/<repo> [--name <Name>] [--revision research-concept-rNN.md].
Ese python3 tiene que ser 3.10 o más nuevo — y no está forzado: bajo uno más viejo,
verify alcanza el adaptador de remote-execution, que evalúa tuple[int, int] | None al
importar, y el comando muere con un TypeError en vez de negarse. Peor: main() anota esa
excepción como un defecto abierto, y un defecto abierto niega otros ocho comandos hasta que
alguien lo cierre. No se cuentan aquí a propósito: un número escrito a mano al lado de un
roster que la máquina publica envejece la primera vez que alguien agrega un comando, y esta
tabla decía nueve cuando ya eran veinte.
| Comando | Qué hace |
|---|---|
env |
Crea y verifica el entorno virtual del repositorio destino (se niega si lo ejecutas desde un intérprete de la forja). Reporta también el estado de los punteros de Git LFS — aquí, porque es el primer comando después de un clon, justo cuando un repositorio lleno de marcadores parece completo. |
name |
Función pura, sin repositorio. Normaliza lo que escribiste a la forma de carpeta <Name>/ y a la forma importable src/<Package>/. Rechaza cualquier carácter que no sea ASCII alfanumérico (NAME_NOT_ALPHANUMERIC, nombrando el carácter): antes esos caracteres se caían en silencio y el nombre salía mutilado — Ñandú daba And. Separadores y camelCase siguen funcionando igual. |
plan |
Plan de migración de sólo lectura: qué se renombra, qué se mueve, qué directorios faltan, qué referencias hay que reescribir, qué conflictos hay y qué archivos no sabe clasificar. Se niega sobre un árbol sucio. |
apply |
Ejecuta un plan ya aprobado como un único commit atómico. Revalida que el plan no haya quedado viejo; ante cualquier falla revierte duro y reporta. Nunca deja un árbol a medio migrar. |
admit |
Decide la admisibilidad de una corrección antes de medir si funciona: comprueba contra el texto de la revisión que la notación que cita exista de verdad. El veredicto se escribe en el destino; el texto de la propuesta se queda en la forja. |
handoff |
Mide cuánto alcance tiene cada hallazgo dentro del documento y decide si se puede resolver en el momento o si merece su propia sesión de deliberación. |
compose |
Arma el texto de reemplazo para llevar una corrección de vuelta a la propuesta, empatando por el \tag{n} de la ecuación. |
probe |
Informe de sólo lectura de qué falta para poder correr el benchmark. Aquí vive la escalera de nextStep. |
verify |
El gran lector estático: cumplimiento del layout, fidelidad a la revisión, integridad del trabajo previo, acuerdos, prosa desactualizada, declaraciones de búsqueda, estado de la ejecución remota, contrato del informe, auditoría y escalera de validación. Todo en un solo JSON. |
Las dos fases. Están separadas a propósito:
- Estructura.
plan→apply: puramente mecánico. Renombres, creación de directorios, reescritura de referencias, un commit. No toca la semántica del código. Y si la reorganización necesita más decisiones de las que alguien puede leer de verdad, se niega a aplicarla directo y te la entrega como trabajo para una sesión aparte — porque una lista así de larga se aprueba sin leerla, y una aprobación sin lectura no es una aprobación. Lo que cuenta son las decisiones que lees tú, no los archivos arrastrados: renombrar una carpeta de doscientos archivos es una línea para leer y un comando para deshacer. - Materialización. Solo entonces se escribe la matemática, con aprobación humana en cada bisagra: el mapa de objeto a módulo, el nombre, y la autorización para implementar.
La escalera de nextStep. probe responde una sola pregunta: ¿qué es lo
siguiente? Los peldaños base, en orden:
nothing-to-compare— no hay línea base. Y sin línea base el backend no es asunto de nadie: numpy es donde la matemática se prueba —sin autograd, sin dispositivo, sin optimizador que tape una fórmula equivocada— y para una propuesta que nadie va a entrenar, ahí es donde pertenece y donde puede quedarse. Es un estado legítimo, no un error.convert— hay línea base pero la implementación calcula en numpy, o sea no es entrenable, o sea la comparación no puede ocurrir. Proponer el benchmark primero sería pedirte que apruebes una corrida que no puede pasar.piloted— hay resultados, pero sólo a escala piloto. Nunca se lee como "listo", y tampoco se te ofrece un menú de tres botones: el piloto existe justamente para que alguien mire, agregue un test, mueva una proporción y lo vuelva a correr corto.already-benchmarked— hay un registro completo y vigente.benchmark— el caso por defecto cuando todo lo demás está limpio.
Encima de eso se aplican cuatro bloqueos, y el orden entre ellos está argumentado, no es una preferencia:
wiring-first— un brazo declara matemática que nunca llama. Va primero porque cualquier número producido bajo un cableado roto está contestando la pregunta equivocada desde el arranque.poll-first— ya hay un envío afuera, en un worker remoto, sin nada terminal registrado. Existe para que no se mande un duplicado quemando cuota real por una pregunta que ya está en vuelo. Va después dewiring-first(esperar no arregla un cableado roto) y antes desearch-first(el envío en vuelo puede ser justamente la búsqueda que ese peldaño te mandaría a repetir).search-first— la búsqueda declarada no tiene su registro en el disco: una corrida cuyo escalar de gobierno todavía no se eligió no tiene configuración con la cual arrancar.report-first— el informe no concuerda con la corrida. Va último porque es la falla más angosta —describe mal una corrida sana, se arregla con una frase— pero igual bloquea, porque una frase equivocada impresa con la autoridad de treinta repeticiones atrás es peor que ninguna frase.
La escalera de validación, cinco niveles. Del más barato al más caro, y ese orden es el diseño: cada nivel falla más barato de diagnosticar que el siguiente, así que la plata se gasta sólo cuando lo barato ya está limpio.
| Nivel | Qué prueba | Por qué está donde está |
|---|---|---|
| 1. Smoke | ¿Arranca? Sólo que el paquete importe y que cada módulo declare su procedencia. | No afirma nada matemático. Cuesta cero y agarra roturas de andamiaje antes de intentar nada serio. |
| 2. Invariantes | Un test por cada afirmación matemática de la propuesta, atado por nombre a la procedencia declarada del módulo. | Deterministas y baratos. verify cruza que toda invariante declarada tenga su test. |
| 3. Sintéticos | Deterministas, semilla fija, verdad conocida por construcción. La expectativa se escribe en el docstring antes de la afirmación. | Así un test que pasa no se puede confundir con una hipótesis ajustada después de ver el resultado. |
| 4. Auditoría | Que cada hallazgo declarado sea un defecto real: un barrido de 200 configuraciones independientes por hallazgo, desplazadas por corrida para que dos corridas auditen configuraciones disjuntas. | El primer paso genuinamente caro. Sólo corre cuando ya se sabe que el código al menos importa y cumple lo que declara. |
| 5. Remedios | Cada corrección validada con el mismo rigor, y obligada a mostrar los dos polos: que el remedio cumple el criterio y que la formulación original no lo cumple. | Cerrado detrás de admit: nada caro se mide sobre un remedio inadmisible. Sin el polo de control, un test de remedio se estaría midiendo a sí mismo. |
Y hay una regla que verify no puede reemplazar: correr la suite y verificar son dos
preguntas distintas. verify lee —que cada módulo declare su revisión, que cada
invariante tenga test, que ninguna afirmación sea infalsificable, que el cuaderno se
haya ejecutado de verdad—; lo que no puede decirte es si alguno de esos tests pasa.
Saltear la corrida es exactamente cómo un repositorio llega a un benchmark con una
invariante rota: toda la procedencia intacta, todos los ids empatados, fidelidad limpia,
y una afirmación fallando abajo. Esa brecha es más ancha que nunca justo después de un
cambio de backend, que reescribe cómo se computa cada número dejando cada declaración
igual.
Los módulos.
| Archivo | Qué hace y por qué existe |
|---|---|
scripts/implementation_cli.py |
El motor entero. Biblioteca estándar solamente, keyless, offline. Nunca importa ni ejecuta el código del destino: lee las declaraciones estáticamente con ast. |
scripts/materialize.py |
El andamiero de la propia forja, no un paso del Flujo A: el agente rellena los huecos leyendo el paso 5, y este script hace lo mismo para que la suite pueda examinar un destino recién andamiado. Copia el kit sustituyendo los marcadores, y sólo escribe las plantillas que ya se pueden escribir: las del paso 9 esperan al mapa de objetos. Acepta un kit alternativo, y la razón está escrita en su docstring: una forja de papers no puede cargar con el contenido de un paper. |
references/usage.md |
Invocaciones reales, copiables, de cada comando, con salidas de ejemplo y la tabla de códigos de rechazo. Existe para que el agente no invente banderas. |
assets/pyproject.template.toml |
El marcador de aislamiento. Sin su configuración de pythonpath, la suite del destino no puede importar su propio paquete offline. |
assets/requirements-dev.txt |
Lo que se instala en el .venv del destino, nunca en el de la forja. |
assets/kit/src/module.py |
Plantilla de un módulo: docstring, procedencia declarada (revisión, secciones, ecuaciones, invariantes) y un stub. La procedencia es lo que hace detectable la deriva contra la revisión. |
assets/kit/tests/* |
La escalera de cinco niveles completa, más sus fixtures compartidas y la compuerta de admisibilidad. Cada archivo que falte quita exactamente un peldaño. |
assets/kit/nb/benchmark.py |
Entrena las dos implementaciones bajo una misma reducción acotada. Se niega a correr bajo un intérprete ajeno —porque el tiempo de pared y la memoria pico son la medición— y se niega a correr sin cableado declarado. |
assets/kit/nb/verdict.py |
La lógica de juicio: sólo concede un ganador cuando las medias difieren más que el error estándar combinado, y por debajo de tres repeticiones no da veredicto, sólo imprime una estimación puntual. |
assets/kit/nb/report_digest.py |
Hashea todo src/ en un sello que el informe imprime y que verify recalcula, para poder probar que un informe está atado al código exacto que lo produjo. |
.claude/agents/implementation-build.md |
El tramo entre dos compuertas tuyas: del mapa objeto-a-módulo aprobado al informe de hallazgos que decides. Materializa el andamiaje, escribe un módulo por objeto matemático con su procedencia y sus tests de invariante, barre las configuraciones declaradas, y falla sobre la admisibilidad de cada remedio ANTES de medirlo. Read, Write, Edit, Bash, Glob, Grep. No decide ni pregunta: termina reportando qué encontró y cuánto costó establecerlo. |
.claude/agents/implementation-walk.md |
El tramo de las colocaciones ya decididas al lanzamiento que tienes que autorizar: camina el flujo declarado acto por acto en su propio orden, corre los pasos locales, commitea el producto de cada uno, refresca la posición, y genera las carpetas de trabajo que un paso remoto necesita. Read, Bash, Glob, Grep — sin Write. Se detiene en el lanzamiento y no tiene camino para enviar una campaña, ni para ejecutar un ensayo: ningún acto del motor realiza un ensayo, y walk dejó de prometerlo — se detiene ahí y te dice que lo corras a mano, que es lo que la doctrina prescribió siempre. |
walk |
Camina el flujo declarado hacia el rung al que apunta el encabezado de posición. Ejecuta los pasos locales, commitea el producto de cada uno y genera las carpetas de trabajo que un paso remoto necesita. Se detiene en el lanzamiento, y también en un ensayo: ningún acto del motor realiza uno. |
step |
Corre un paso local declarado, aislado, bajo el venv del propio destino. Nunca el intérprete de la forja. |
position |
El único escritor de la sección de posición de <Name>/AGREED.md. Marca qué se midió, qué falta y qué quedó sin medir, y ata cada marca a la revisión y a su sha256 — porque una revisión puede reescribirse en su lugar bajo el mismo nombre de archivo, y sólo el hash lo detecta. |
discuss |
La discusión como una operación con valor de retorno: publica una pregunta y devuelve el bucket en el que cae, en vez de dejarla en la conversación. |
settle |
Coloca un acuerdo saldado, y sólo uno, bajo un rótulo que nombra el llamador. |
close |
La precondición de cierre: escribe la posición y deja el ciclo cerrado. Una segunda llamada no vuelve a cerrar. |
propose |
La propuesta de campaña: qué trabajos, con qué workers, con qué justificación. Registra, no envía. |
gate |
El registro de autorización de lanzamiento. Es la compuerta que separa "listo para correr" de "corriendo", y la abre una persona. |
offer |
El menú de acciones derivado del estado: un conjunto cerrado de lo que puede pasar a continuación, calculado y no ofrecido de memoria. |
defect |
Declara que algún archivo de la forja está roto ahora mismo. Mientras siga abierto, ocho comandos se niegan con FORGE_DEFECT_OPEN; probe, verify, position, plan, compose, handoff, discuss y propose siguen alcanzables. main() también lo anota solo, cuando cualquier otra excepción lo alcanza. |
materialize |
Escribe el andamiaje aprobado por etapas (scaffold, objects, harness). --plan es una aprobación que da una persona, y --seed el número del que saca el experimento: ninguna skill elige eso por un repositorio. |
Qué escribe en el disco.
implementations/<repo>/ git propio, .venv propio, ignorado por la forja
├── <Name>/ Notebooks/ Data/ Results/ Models/
├── src/<Package>/ una implementación por objeto matemático, cada una con su procedencia,
│ más __implementation__ (revisión/premisas) y las declaraciones propias
│ del flujo (__levels__/__steps__/__records__)
├── src/<Package>_Benchmark/ el arnés de la comparación — sólo existe una vez aceptada
├── tests/ la escalera de cinco niveles
├── tools/ sólo si hace falta: opera corridas, no implementa ecuaciones
└── pyproject.toml
src/<Package>_Benchmark/ no es un destino del andamiaje inicial (materialize --stage scaffold): un destino recién andamiado no tiene ese directorio. Lo escribe únicamente
materialize --stage harness, y sólo después de que se acepta una comparación —nunca
si se la rechaza, y nunca por correr el ensayo de validación de un solo brazo en su
lugar—. Declara qué secciones y ecuaciones ejercita cada brazo. No declara
procedencia a propósito: no implementa ninguna ecuación, y estamparle una
falsificaría justamente el chequeo que ata código a matemática. Lo escribe el agente
al cablear; lo leen verify y probe, siempre de forma estática. Contra qué revisión
se construyó el método, y bajo qué premisas, vive aparte, en __implementation__
dentro de src/<Package>/__init__.py —son hechos sobre el MÉTODO, no sobre la
comparación.
Los seguros.
- Nunca ejecuta el código del destino para inspeccionarlo. Todo se lee con
ast. - Guardia de ruta. El destino tiene que resolver dentro de
implementations/y ya ser un repositorio git. - Nunca migra un árbol sucio. Ni aplica un plan que quedó viejo.
- Entorno aislado. Se niega a construir el venv del destino desde un intérprete de la forja.
- Un hallazgo sin remedio validado no se reporta. Está impuesto estructuralmente: la
auditoría queda
incompletemientras haya hallazgos sin remedio o sin test que lo valide. - Nunca escribe en
proposals/. Publicar es territorio exclusivo deproposal-deliberation, y sólo se llega ahí por la puerta descrita arriba. - Nunca nombra un worker remoto. Sólo reporta un conteo.
Limitaciones conocidas.
Los archivos bajo Git LFS llegan vacíos. Clonás un repositorio que usa Git LFS y
parece completo: todas las rutas están, todos los archivos existen. Pero cada archivo
bajo LFS pesa unos cientos de bytes — es un marcador de texto, no el archivo. Lo primero
que lo abra como datos falla con un error sobre el formato del archivo, que no se
parece en nada a la causa real. Por qué es a propósito: el clon salta el filtro que
materializa esos archivos, y el salto queda fijado en la configuración local del clon.
Los marcadores alcanzan para reorganizar el repositorio y leer el código, y bajar
gigabytes sólo para moverlos de carpeta gasta una cuota de LFS que no vuelve. Qué hace
la forja: env te los reporta apenas termina el clon —el momento en que la ilusión es
más fuerte— y verify lo repite en cada pasada posterior, porque lo único peor que no
saberlo es olvidarlo. Cada marcador declara el tamaño del archivo real, así que el
reporte trae un total, no una advertencia. En este repositorio, por ejemplo: 18
marcadores, 5,09 GiB si se bajaran. Nada del flujo los lee, ninguna prueba ni
cuaderno se escribe contra ellos, y la descarga nunca se hace sola: el comando se
imprime en vez de ejecutarse. Ojo con el atajo que no existe: bajarlos desde la
página de GitHub con el botón de descarga cuesta exactamente lo mismo. GitHub cuenta
todas las descargas contra el ancho de banda del dueño del repositorio, por cualquier
vía — el comando, el navegador, y hasta el zip del código fuente si contiene esos
objetos. La franquicia gratuita es de 1 GiB por mes. No hay ruta que la evite, y creer
que la hay es la forma más común de gastarse el mes sin querer. Qué puedes hacer antes
de gastarla: mira lo que probe reporta bajo acquisition — el material que el
repositorio se baja, clona o desempaqueta por su cuenta no cuesta cuota, y lo que
salió de un entrenamiento se vuelve a producir entrenando. La cuota se gasta sólo en lo
que de verdad no existe en ningún otro lado. Qué no hace: no puede impedir que un
cableado escrito a mano intente cargar uno —si pasa, el error habla del formato y el
reporte de env es donde está la razón— y no distingue un marcador de un archivo
genuinamente corrupto: los dos se leen como material ausente, que es la lectura
conservadora.
Una corrida larga en esta máquina es invisible mientras corre. probe sabe decir
que hay una submisión afuera cuya respuesta no volvió —es el peldaño poll-first— y lo
puede decir porque remote-execution mantiene un registro append-only que la forja lee.
Consultas en mitad de un envío, te lo dice, y te vas a hacer otra cosa. Por qué en
local no pasa lo mismo: una corrida en tu propia máquina deja lo que el repositorio
destino haya decidido dejar —un parcial, un checkpoint, un lock— con un nombre que sólo
ese repositorio conoce, y mirarlo obligaría a cablear el vocabulario de un paper dentro
de una herramienta que tiene que servir a todos. Qué pasa entonces: lanzas una
búsqueda o una campaña larga, consultas mientras corre, y verify lee que el registro
declarado no existe — reporta el trabajo como no empezado y probe te ofrece lanzarlo
otra vez. Qué puedes hacer mientras tanto: mirar si el registro que tu declaración
nombra en record ya existe, o si al lado quedó un parcial; dos ls contestan la
pregunta. Qué no hace: no borra nada ni pisa la corrida en curso, y si el
repositorio destino sabe retomar desde su parcial el segundo lanzamiento salta lo ya
medido. Lo que falta no es la protección: es el aviso, que es justamente para lo que uno
consulta. Qué necesitaría para cerrarse: que el hecho sea declarado y no
adivinado —igual que hoy se declara dónde vive el registro— y que distinga no hay
nada corriendo de este repositorio no lo declara, porque un hecho cuyo valor vacío no
separa esos dos casos no sirve para gatillar nada. Es la misma razón por la que
smokeReady se reporta y nunca decide.
verify compara nombres de revisión, no contenido. El sello de procedencia que cada
módulo declara se contrasta contra la revisión vigente con una sola comparación:
module["stale"] = bool(revision) and module["revision"] != revision. Es una
comparación de strings, y el __provenance__ del módulo no lleva ningún hash del
texto contra el que se escribió. Qué significa para ti: si una revisión se
reescribe bajo su mismo nombre —se corrige una ecuación y el archivo se sigue
llamando igual—, verify no ve nada: ningún módulo queda marcado como viejo y la pasada
sale limpia. La deriva aparece solo más tarde, en gate o en close, como
POSITION_STALE —"atada a una revisión cuyos bytes ya no coinciden"—, que sí compara
bytes. Qué no cubre, y es lo importante: un verify limpio no significa que la
matemática se haya sostenido. Significa que ningún módulo nombra una revisión distinta
de la vigente. Cómo se arregla: haciendo que el sello lleve también el hash del
contenido, para que la comparación sea contra los bytes y no contra la etiqueta.
Un testigo prueba que el test existe, nunca que pasa. Un acuerdo puede declarar qué
test lo respalda, con un token test_<id>. La CLI no corre ninguna suite: para
resolver ese token hace un recorrido ast sobre tests/ y junta los nombres de las
funciones. Encontrar el nombre prueba que existe una función así, y nada más. Qué
significa para ti: un testigo bien formado cuyo test existe se reporta unmeasured,
y unmeasured es un estado terminal — lo único que puede sacarlo de ahí es que el
test desaparezca, y solo entonces, con el ítem tildado, pasa a disagrees. Cruzar un
testigo contra el resultado de una corrida no existe. Qué no cubre: un test que
existe y falla, o que existe y no prueba lo que dice. Los dos se leen igual que uno que
pasa. Cómo se arregla: con un cruce contra el resultado real de la suite, que hoy no
tiene por dónde entrar.
Ninguna edición a mano está impedida, y aquí ni siquiera se detecta. Vale la misma
observación que en proposal-deliberation —un solo hook PreToolUse, que es de
remote-execution, y ninguna entrada deny—, pero la diferencia entre las dos skills
importa. Allá una edición manual rompe un recibo y se nota en la operación siguiente;
aquí no hay recibo que romper. El SKILL.md de esta skill lo dice sin adornos sobre el
token de testigo: escribirlo a mano es doctrina no soportada, no una prevención
técnica — el parser no puede distinguir, y no distingue, un token escrito por la skill
de uno escrito a mano, y verify y close evalúan los dos exactamente igual. Qué
significa para ti: una marca o un testigo puestos a mano en el archivo de acuerdos
son indistinguibles de los que puso la herramienta. Qué no cubre: ni el momento de
la escritura, ni ninguna lectura posterior que los separe. Qué lo contiene: el
lanzamiento no depende de ese archivo. gate lee la escalera de posición, la
autorización, la propuesta de campaña y la elección — el archivo de acuerdos, cero
veces. Un acuerdo editado a mano corrompe el registro de lo que se decidió, pero no
puede provocar un lanzamiento ni un gasto: no llega a la acción. Cómo se arregla:
con una regla deny sobre el archivo de acuerdos, o con una contabilidad byte a byte
como la que sí tiene la otra skill.
El registro no viaja en un clon. .implementation/ es una entrada obligatoria del
.gitignore del repositorio destino (en el destino de referencia de esta forja, la
línea 61). Para una mitad de lo que guarda eso es lo correcto: una autorización de
lanzamiento que aparece en un clon es una autorización que nadie en ese clon dio. Para
la otra mitad no lo es: la deliberación —lo que se preguntó, lo que se respondió, y por
eso los acuerdos dicen lo que dicen— es historia del proyecto, y el clon no recibe nada.
Qué significa para ti: un clon fresco arranca sin registro y toma el archivo de
acuerdos tal como está, sin nada con qué contrastarlo. Qué no cubre: todo lo que
dependa de haber visto la deliberación previa. Cómo se arregla: separando las dos
mitades, que es un cambio en lo que todo lector del registro espera encontrar en un solo
lugar. Está anotado en el SKILL.md de la skill, no resuelto.
Nada prueba quién decidió. La precondición de que un acuerdo haya sido discutido antes de colocarse se satisface con cualquier evento de discusión respondido. Nada comprueba que la respuesta haya venido de una persona. Qué significa para ti: vale lo mismo que del otro lado de la costura — un acuerdo registrado prueba que llegó al registro, nunca que alguien lo decidió. Un agente puede abrir la pregunta y contestársela solo, y nadie más adelante en la cadena puede distinguir ese caso del otro. Qué no cubre: cualquier lectura del registro como prueba de consentimiento. Cómo se arregla: hace falta un canal de confirmación que la skill no pueda originar, y hoy no existe.
Deuda de mantenimiento. El inventario del SKILL.md nombra 5 de los 9 comandos.
En su sección de referencias, la línea que describe implementation_cli.py lista env,
plan, apply, admit y verify — se quedaron afuera name, handoff, compose y
probe. No rompe nada, y la línea inmediatamente anterior ya apunta a
references/usage.md, que sí los documenta todos con ejemplos. Pero es una lista que
envejeció sin avisar, y la próxima que se agregue va a envejecer igual. Cómo se
arregla: completándola, o borrando la enumeración y dejando sólo el puntero a
references/usage.md — un inventario que se mantiene solo es mejor que uno que hay que
acordarse de actualizar.
Resuelto: una ausencia ya no se lee como "no está vieja". Bajo dos documentos,
el chequeo de vigencia de admissibility_record hacía continue cuando faltaba la
entrada de un documento declarado o cuando su fuente no se podía leer, y el registro
incompleto caía al retorno de abajo como vigente. Ahora cada uno de esos dos casos
responde unknown con su detalle. La forma de la falla es la que este repositorio
más repite: una ausencia que hereda el valor bueno.
La rama "sin posición" devuelve una clave menos, y el candado que dice sostener la forma
no puede verla. position_state devuelve 13 claves cuando no hay posición y 14 cuando
la hay: falta unmeasurable. Un consumidor que la lea directo revienta con KeyError —
sequence_block_detail ya se defiende con .get, que es la pista. El docstring dice que
la forma la sostiene una regla de acuerdo entre retornos, pero ese helper sólo lee
retornos que son diccionarios literales, y esta rama devuelve un nombre. Cómo se
arregla: que la rama ausente devuelva la clave, o que el candado sepa leer un return <nombre>.
Resuelto: __benchmark__ tiene un solo lector otra vez. resolve_benchmark_declaration
se llamaba a sí mismo "el único lugar del que todo lector saca __benchmark__" mientras
el script INTROSPECT, que corre dentro del intérprete del destino, lo leía del atributo
del módulo importado. Una declaración que vivía en config.py sin re-exportarse era real
para el resolver e inexistente para INTROSPECT: un hecho, dos lectores, veredictos
opuestos. INTROSPECT ahora sigue el mismo orden __init__ → config que el resolver.
"El único lugar" donde se leen accelerator/localBudget era falso al nacer.
cmd_gate los arma por su cuenta desde su propio run_config y se los pasa al mismo
clasificador. Los dos sitios citan el mismo cambio de diseño, así que no es deriva: el
cuantificador nunca fue cierto. Hoy leen el mismo archivo igual, y nada los ata.
Cómo se arregla: un lector, o un test que falle si se separan.
Resuelto: las tres piezas sueltas del kit, cada una con su propia respuesta.
ruled_revision se cableó, porque cerraba una ventana real: require_admissible
miraba el veredicto y nunca la revisión, así que un fallo viejo pasaba al medir y solo
aparecía en verify. resolve_device se borró: cero referencias en toda la forja,
incluidas las notebooks. Y el fixture rng se cableó donde entra —test_synthetic.py,
que sí es un ítem que pytest recolecta— dejando el generador local de sweep.py con la
razón escrita: es una función plana, no puede recibir un fixture, y necesita semilla por
índice. Cablear, borrar y separar con su motivo son tres respuestas distintas a la misma
forma, y elegir una sola habría sido el error.
Resuelto: seis justificaciones muertas y seis imports muertos. Cuatro docstrings del
motor justificaban un patrón apelando a raised_refusal_codes, un barrido que no veía un
rechazo escondido dentro de un helper — y que ya había sido reemplazado por
reachable_refusal_codes, que sí lo ve. La ubicación del patrón estaba bien; la razón
escrita al lado estaba muerta. Un comentario falso es un defecto con vida propia,
porque el próximo que pasa le cree. Queda una pieza, y es de la misma clase: el test que
mantiene vivo a latest_revision se justifica con "cinco tests unitarios y un llamador
de producción", y ese llamador ya no existe.
Seis imports muertos, y un test que mantiene vivo un símbolo citando un llamador que no
existe. Tres líneas de import del motor traen siete nombres que no usa. Y
latest_revision está muerta en producción desde que un cambio reemplazó a su único
llamador por revision_discovery — nada la llama, y ningún despacho dinámico la alcanza.
Se mantiene viva a propósito, por una spec y ocho tests unitarios; el defecto está en el
pin mismo: el test que la sostiene se justifica diciendo "cinco tests unitarios y un
llamador de producción la leen como str | None". Ese llamador se fue. Es prosa que
sobrevivió a su mecanismo dentro del test que mantiene vivo al símbolo. Cómo se
arregla: corregir la justificación del pin —o retirar el pin— y borrar los imports.
Diagrama.
flowchart TD
A["Usuario pide implementar o verificar"] --> B["STATUS a proposal-deliberation → latest"]
B --> C{"¿src/ ya tiene implementación?<br/>Se enruta por EXISTENCIA, no por fidelidad"}
C -- No --> D["FLUJO A: clonar, env, plan"]
C -- Sí --> E["FLUJO B: leer src/ y medir"]
D --> F{"¿El plan es revisable o grande?"}
F -- Grande --> G["No se aplica. Se entrega como sesión aparte"]
F -- Revisable --> H["apply: un commit atómico de migración"]
H --> I["Compuerta: confirmar Name y Package"]
I --> J["Paso 5: el agente rellena cada hueco del andamio<br/>desde assets/kit/, a src/, tests/ y Notebooks/"]
J --> K["Compuerta: autorización para implementar"]
K --> L["Escribe módulos ATANDO latest como procedencia"]
L --> M["Barrido de auditoría: 200 configuraciones"]
M --> N["admit: lee el texto de la revisión.<br/>¿La notación citada existe?"]
N --> O["Valida remedios con los dos polos"]
O --> P["Compuerta: ¿publicamos las correcciones?"]
P -- Sí --> Q["handoff → ubicar → compose → publicar sucesor<br/>VUELVE a proposal-deliberation"]
P -- No --> R["Corre la suite Y verify. Son preguntas distintas"]
Q --> R
E --> R
R --> S{"¿Suite verde y fidelidad limpia?"}
S -- "Test rojo" --> T["Ese ES el hallazgo. Se corta aquí"]
S -- "Hay diferencias" --> U{"Compuerta: ¿esos cambios los hiciste tú?"}
U -- Sí --> V["El código va adelante.<br/>Se te da el prompt para actualizar la matemática.<br/>NUNCA se edita tu código hacia atrás"]
U -- No --> W["El código derivó. Corregir, máximo 3 pasadas"]
W --> R
S -- Sí --> X["probe: escalera de nextStep"]
X --> Y{"¿Qué es lo siguiente?"}
Y -- wiring-first --> Z["Arreglar el cableado brazo a módulo"]
Z --> X
Y -- poll-first --> AA["Hay un envío en vuelo: esperar, no duplicar"]
Y -- search-first --> AB["Falta el registro de la búsqueda"]
Y -- report-first --> AC["El informe no concuerda: reejecutar"]
AC --> X
Y -- convert --> AD["Convertir a un backend entrenable"]
AD --> X
Y -- benchmark --> AE["Compuerta: ¿corremos el benchmark?"]
AE --> AF["Entrena ambos brazos y emite veredicto"]
4. kaggle-accounts — el portero de las credenciales
Para qué está. Los tokens de Kaggle vencen y rotan sin avisar: una cuenta que funcionaba ayer puede fallar hoy a mitad de una corrida. Esta skill responde la única pregunta que la máquina puede verificar de verdad —¿esta credencial todavía autentica?— y mantiene un depósito chico, ignorado por git, con las que pasan.
De dónde recibe y a quién le entrega. Recibe archivos que dejas en
kaggle-inbox/: un kaggle.json, o un .txt/.md con una credencial por línea. Le
entrega a remote-execution dos cosas, y sólo dos:
- Identidad de worker, por su comando
list, que devuelve nada más que usuarios. El diccionario de salida se reconstruye desde cero con sólo ese campo, así que una clave no puede llegar ahí ni por accidente. - Una ruta a un archivo de token, nunca el valor. Su comando de entrega escribe el
token como archivo suelto con permisos
0600en un directorio0700e imprime la ruta.
Del otro lado, remote-execution no abre ese depósito jamás: corre estos dos comandos
como procesos hijo y lee su salida. Es una costura de una sola dirección y de una sola
forma.
Qué necesita antes. Python 3.10+ y red. Nada más: no hay entorno virtual, no hay
pip install kaggle. Todo se hace con la biblioteca estándar y una llamada HTTPS cruda.
Las etapas que declara. Leídas de OBJECTIVE_FLOW, que un test mantiene
igual al SKILL.md. list contesta dónde estoy; esto contesta para qué es,
que ninguna lista implica: una cuenta está en el almacén porque alguien la puso,
no porque funcione.
| Etapa | Qué deja establecido | La tienes detrás cuando |
|---|---|---|
taken-in |
Lo que entregaste está en el almacén, de a una fila por vez, así una fila mala nunca cuesta las de al lado | el inbox soltó todo lo que tenía y se consumió, o quedó porque todavía hay filas adentro |
proven |
Cada cuenta guardada se le preguntó al servicio, bajo cualquiera de los dos esquemas de token que use de verdad | cada cuenta lleva un veredicto de esta corrida |
decided |
Qué pasa con las que dejaron de funcionar — que es tu decisión y nunca un efecto secundario de preguntar | es la llegada; no está detrás de nadie |
Llegada: cada cuenta con un veredicto actual, y las que fallan
respondidas, no apenas reportadas. Un veredicto se vence: los tokens se rotan y
expiran, así que "autenticó una vez" tampoco es llegada. Una sesión que corre
validate, ve un fallo reportado y para, no llegó.
Dos paradas son de una persona: consentir la validación, porque escribe — guarda credenciales y consume el inbox—, y decidir qué pasa con una cuenta que dejó de funcionar. Una credencial muerta se reporta y nunca se borra sola, a propósito: borrarla es tu otra opción, no una consecuencia de preguntar.
El flujo. Al activarse corre list en silencio para saber qué hay guardado, y
después hace una sola pregunta: validar o eliminar. La opción de eliminar sólo
aparece si hay algo que eliminar.
Validar:
- Carga el depósito. Si está corrupto falla cerrado y no escribe nada — nunca lo trata como "vacío", porque eso permitiría que una escritura posterior pisara credenciales todavía buenas.
- Re-verifica cada cuenta ya guardada contra la API real. Cada una da su propia línea de pase o falla.
- Decide de dónde salen las nuevas: por defecto, lo que haya en
kaggle-inbox/. - Parsea cada archivo. Un
kaggle.jsonda una credencial; un.txto.mdda una por línea, y una línea mala se marca sin arruinar las demás. - Valida cada una: primero autenticación básica y, sólo si vuelve un 401, reintenta como token bearer. Un timeout de red no reintenta nada: una falla de red no es evidencia sobre la credencial.
- La que pasa se guarda o reemplaza su clave; la que falla se reporta por número de línea.
- Escribe el depósito de forma atómica, y sólo si el archivo está efectivamente ignorado por git.
- Un archivo del inbox se borra sólo si entró entero. Si alguna fila quedó afuera, el archivo se conserva y te dice cuántas.
- Una cuenta que dejó de autenticar se reporta, no se borra. Eliminar es una decisión tuya, aparte.
Eliminar: se listan las cuentas reales, eliges de una lista —nunca escribes un nombre a mano— y si alguna de las elegidas no existe se rechaza el lote entero. Un borrado parcial por un typo es peor que no borrar nada.
Los módulos.
| Archivo | Qué hace y por qué existe |
|---|---|
SKILL.md |
El contrato de conducta: cuándo preguntar, qué no leer nunca, cómo reportar. Sin esto, el agente no tendría motivo para preguntar antes de escribir. |
scripts/accounts_cli.py |
La implementación entera, sin dependencias externas. Subcomandos: list, discover, validate, remove y materialize. Los cuatro primeros son para una persona; materialize es para código: escribe la credencial guardada de un worker a un archivo de texto plano y devuelve dónde, nunca qué. Ese es el contrato que deja que remote-execution use un token sin que ninguna skill lo lea ni lo imprima. |
store/accounts.json |
El depósito. Permisos 0600, escritura atómica. |
store/.gitignore |
Ignora todo el contenido de store/ por regla de contenido, no por nombre — así cubre también el temporal de la escritura atómica y cualquier archivo futuro. Está commiteado para que la regla exista antes de que se escriba la primera credencial. |
store/workers/<usuario>/token |
Se crea solo en la primera entrega. Contiene sólo el token, sin envoltorio JSON: es la forma que el cliente de Kaggle espera. |
Los seguros.
- La credencial nunca cruza como valor.
listdevuelve sólo usuarios; la entrega devuelve sólo una ruta. - No hay bandera para pasar la clave. Un secreto como argumento quedaría en la lista de procesos y en el historial del shell. La entrada interactiva usa entrada oculta y se niega si no hay una terminal real — que es exactamente cómo detecta a un agente intentando hacerlo por ti.
- No escribe si git no lo ignora. Verifica la regla de ignorado antes de crear ningún directorio, así una negativa no deja ni el andamio.
- Escritura atómica siempre, con los permisos puestos antes del primer byte.
- Nunca lee el depósito para contestar "qué cuentas hay". Para eso está
list.
Limitaciones conocidas.
El auditor la mira, pero sólo de un lado. La validación toca la API real, así que lo
que reporta es evidencia y no inferencia, y el punto flojo de la cadena está del otro lado
de la costura, en el adaptador de remote-execution (ver su apartado). Esta skill ya tiene
su receta accepted-operations en skill-audit, que deriva sus cinco operaciones del
propio rechazo del CLI. Lo que todavía no tiene es la otra mitad: su SKILL.md declara ese
conjunto en prosa y no en una tabla cerrada, así que el auditor responde
comparison: not-run — deriva lo que el código acepta y no puede compararlo contra lo que
el documento promete. Cómo se arregla: una tabla cerrada en el SKILL.md, como la que
remote-execution ya tiene.
Diagrama.
flowchart TD
A["Usuario invoca la skill"] --> B["list en silencio"]
B --> C{"¿El depósito está vacío?"}
C -- Sí --> D["Pregunta: sólo validar"]
C -- No --> E["Pregunta: validar o eliminar"]
D --> F["validate"]
E -- validar --> F
E -- eliminar --> G["Lista de cuentas para elegir"]
G --> H{"¿Todas las elegidas existen?"}
H -- No --> I["Rechaza el lote entero"]
H -- Sí --> J["Reescribe el depósito y reporta"]
F --> K["Re-verifica cada cuenta guardada"]
K --> L["Lee kaggle-inbox/"]
L --> M["Parsea cada credencial"]
M --> N["Autenticación básica contra la API"]
N --> O{"¿Devolvió 401?"}
O -- Sí --> P["Reintenta como bearer"]
O -- "No, otra falla" --> Q{"¿Pasa?"}
P --> Q
Q -- No --> R["Reporta por número de línea. No guarda"]
Q -- Sí --> S{"¿git ignora el depósito?"}
S -- No --> T["Se niega a escribir"]
S -- Sí --> U["Escritura atómica 0600"]
U --> V{"¿El archivo del inbox entró entero?"}
V -- Sí --> W["Borra el archivo del inbox"]
V -- No --> X["Lo conserva y dice cuántas faltaron"]
W --> Y["Reporte final al usuario"]
X --> Y
R --> Y
U --> Z["remote-execution pide un worker:<br/>recibe usuario y ruta, nunca el valor"]
5. remote-execution — la memoria de lo que se mandó afuera
Para qué está. Mandar un entrenamiento a una máquina remota, hoy, se hace copiando un comando, corriéndolo a mano y recordando de memoria si salió, si volvió, y si lo que volvió sigue correspondiéndose con el código que lo produjo. Esta skill reemplaza esa memoria humana por un registro durable y de sólo agregado: qué se mandó, qué volvió, si ese resultado todavía es confiable contra el código actual, y cuántos trabajos puede aceptar un worker a la vez.
De dónde recibe y a quién le entrega.
Recibe de kaggle-accounts: la identidad de los workers y la ruta de la credencial,
por las dos costuras descritas en aquel apartado. Vale la pena seguir el recorrido
entero del secreto, porque es el diseño más cuidado de la forja: el valor del token vive
en el depósito de kaggle-accounts; la entrega lo escribe como archivo suelto; esta
skill lee sólo la ruta de la salida de ese proceso hijo; esa ruta viaja adentro de un
handle congelado; y su único destino es una variable de entorno en el proceso hijo que
ejecuta el cliente del servicio. El valor del secreto nunca entra en la memoria de esta
skill ni en la del agente.
Recibe de proposal-implementation: la carpeta del trabajo, que vive bajo
<destino>/tools/<servicio>/<trabajo>/ dentro del repositorio de la implementación.
Le entrega a proposal-implementation: el registro. Aquella skill lo pliega en modo
sólo lectura, reutilizando este mismo código de pliegue en vez de reimplementarlo —
dos definiciones de "este resultado ya no es vigente" sería una que deriva de la otra en
silencio. De ese pliegue sale su peldaño poll-first.
Qué necesita antes. Python 3.10+, biblioteca estándar, sin entorno virtual. Para hablar con un servicio real hace falta un adaptador registrado y una credencial.
El flujo, por etapas. Esta sección tampoco tenía uno. Leídas de
OBJECTIVE_FLOW, independientes de todo ledger, carpeta de job o resultado en
disco: status contesta dónde estoy plegando lo que pasó, esto contesta para
qué es. Las catorce rutas de rechazo de este CLI la llevan, y para eso hubo que
crear el único lugar por el que todas llegan a un lector.
| Etapa | Qué deja establecido | La tienes detrás cuando |
|---|---|---|
reachable |
Una carpeta de job fijada a un commit que el remoto declarado puede servir de verdad, que es lo que un worker clona | generate-job escribió la carpeta — prueba el pin contra el remoto antes de escribir un byte, así que una carpeta que existe es un pin que se publicó |
wire |
Que este worker, en este pin, lleva corriente — pagado en minutos en vez de en las horas que cuesta una campaña | readiness dice listo, con un veredicto derivado de la evidencia de un ensayo, nunca afirmado |
authorized |
Un lanzamiento atado a este commit, este entrypoint y esta lista de unidades, con un token acuñado para él y consumido una sola vez | un registro de gate coincide; un hecho ligado que se movió lo vuelve stale, que no es lo mismo que pasar el tiempo |
sent |
La entrega salió y el ledger lo sabe, así ninguna segunda contesta una pregunta que ya se está contestando | el ledger lo tiene con un estado terminal |
returned |
El resultado está en disco, actual, y no en cuarentena | es la llegada; no está detrás de nadie |
Llegada: el resultado en disco y verificado como el actual — lo único de lo que alguien puede leer un número. No la entrega aceptada: una entrega que el servicio tomó es un recibo, no un resultado.
reconcile no es una etapa y nunca avanza ninguna. Es el camino de vuelta
cuando el ledger y el servicio dejan de coincidir —drift, o una línea que no
se pudo leer— y devuelve a sent.
Dos paradas son de una persona: publicar el commit que un worker
clonaría, sin el cual generate-job se niega y lo nombra con precisión, y
autorizar el lanzamiento, que son horas de la cuota de alguien.
Los comandos.
| Comando | Qué hace | Cuándo se usa |
|---|---|---|
generate-job |
Arma la carpeta del trabajo: configuración de corrida, cuaderno ejecutor y el archivo de metadatos del servicio. Se construye en un directorio parcial y se renombra atómicamente al terminar — una carpeta a medio escribir no puede existir. | Una vez, antes del primer envío. |
submit |
El camino de envío completo, en orden fijo: guardia de ruta → resolver el producto → digest fresco del fuente → clamp de capacidad → envío real → y solo entonces anotar en el registro. Anotar último significa que nunca se registra algo que no salió. --smoke marca la corrida como ensayo y la manda al registro de ensayos. |
Cada vez que envías trabajo. |
status |
Pliega el registro y muestra el estado por punto de entrada: pendiente, devuelto, con error, en cuarentena, en vuelo hace demasiado. No recibe adaptador: es estructuralmente incapaz de resolver nada, sólo reporta. | Cuando quieres saber dónde estás parado. |
poll |
Pregunta por una entrega y re-valida que el estado devuelto esté dentro del vocabulario de cinco valores del seam. Una defensa contra un adaptador que se porte mal. | Mientras esperas. |
fetch |
Trae el resultado. Evalúa la vigencia antes de escribir nada; si el resultado ya no corresponde al código actual, se redirige a cuarentena. Materializa en un directorio parcial, verifica que esté completo, renombra, y sólo entonces anota la devolución. | Cuando la corrida terminó. |
reconcile |
Compara lo que el servicio dice tener contra lo que el registro cree, en las dos direcciones. Reporta huérfanos; nunca los adopta ni los cancela solo. | Cuando algo no cuadra. Siempre lo corre un humano. |
smoke record |
Anota el veredicto de un ensayo, y ese veredicto sale de la evidencia del artefacto traído, no de que alguien diga que anduvo. | Después de traer el ensayo. |
readiness |
Reporta —nunca envía— si un trabajo está listo para su corrida completa. Ata el último ensayo a tres cosas: que pasó, mismo commit, mismo worker. No lee ningún reloj: un ensayo caduca porque cambió el commit o el worker, jamás por tiempo transcurrido. | Antes de gastar una corrida grande. |
El registro y su pliegue. El registro es un archivo de una línea JSON por evento, sólo de agregado. Se escribe con un descriptor crudo en modo de anexado, verificando que la cantidad de bytes escritos sea la esperada, con un tope por evento. Nada se guarda como "estado actual mutable": el estado se deriva releyendo el log en dos pasadas — la primera resuelve cuál es el último envío de cada punto de entrada, la segunda decide si cada resultado devuelto sigue vigente. Hacen falta dos pasadas porque un resultado que era vigente cuando llegó deja de serlo en el instante en que aterriza un reenvío posterior.
El clamp de capacidad. El empaquetador lee el tope directamente del adaptador —nunca
lo recibe por parámetro ni lo tiene escrito a mano—, arranca de lo pendiente según el
registro, y lo refina preguntándole al servicio qué tiene activo. La cuenta es
otorgado = max(0, min(pedido, tope) - en_vuelo), y se reporta como cuatro números
distintos para que nadie confunda "pide 5 y me dieron 2 porque el tope es 2" con "pide
exactamente 2".
Los módulos.
| Archivo | Qué hace y por qué existe |
|---|---|
scripts/adapter.py |
El seam: una clase abstracta con exactamente seis operaciones, las formas congeladas que la cruzan, y un registro de nombre a clase. Gracias a esto, todo lo que está arriba es ciego respecto de qué servicio hay abajo. Un backend nuevo entra sin tocar nada más. |
scripts/ledger.py |
El registro: el camino de escritura confiable y el pliegue que deriva el estado, incluida la vigencia. Es el módulo que proposal-implementation importa. |
scripts/packer.py |
El clamp de capacidad. El único lugar que combina lo que sabe el repositorio con lo que sabe el servicio. |
scripts/remote_cli.py |
La puerta de entrada. También la guardia de ruta, que resuelve los enlaces simbólicos antes de comprobar contención y admite exactamente dos formas de ruta. |
scripts/credentials.py |
El único productor de un handle de credencial. Importa nada más que subprocess, json, pathlib y el seam: estructuralmente no puede leer un secreto. |
scripts/jobfolder.py |
Genera y lee la carpeta del trabajo, y calcula la única condición de obsolescencia: que el fuente se haya movido más allá del commit fijado, dentro de las rutas declaradas. |
scripts/shard_io.py |
Un único predicado de completitud de evidencia, compartido por el registro de ensayos y por el paso de fusión posterior. Una sola definición, cero deriva. |
scripts/adapters/kaggle.py, colab.py |
Los adaptadores de servicio autorizados a nombrar un backend. Invocan el CLI oficial correspondiente (kaggle o google-colab-cli); nunca importan paquetes privados ni leen secretos directamente. |
assets/runner_bootstrap.py |
La celda 0 de todo cuaderno generado: valida la configuración, clona el commit fijado, pone src/ en el path e importa los módulos declarados verificando que resuelvan dentro del clon. Corre en la máquina remota, antes que cualquier código tuyo. |
assets/runner_invoke.py |
La celda 1: elige el bloque normal o el de ensayo y llama al punto de entrada declarado. |
distribute |
Reparte las unidades declaradas entre los workers disponibles y devuelve el reparto, sin enviar nada. Es la respuesta a "¿en cuántos pedazos entra esto y quién corre cada uno?", separada de submit a propósito: decidir el reparto y gastar cuota son dos actos distintos. |
Qué escribe en el disco.
<destino>/<Producto>/.remote-execution/
├── ledger.jsonl el registro principal
├── smoke.jsonl los ensayos: archivo FÍSICAMENTE distinto
└── quarantine/<id>/ resultados que llegaron fuera de vigencia
<destino>/tools/<servicio>/<trabajo>/
├── run-config.json qué se corre, con qué argumentos, en qué commit
├── runner.ipynb el cuaderno que clona el commit fijado y ejecuta
└── <metadatos del servicio>
Los ensayos van a un archivo aparte y no a un cuarto tipo de evento en el registro principal, y la razón es concreta: un envío de ensayo se convertiría en el "último envío" del punto de entrada y taparía en silencio una corrida real todavía pendiente.
Los seguros.
- Sólo se agrega. Un envío es un hecho una vez que ocurrió; un reenvío agrega una línea nueva, jamás borra la que supera.
- Falla cerrado. Un proceso hijo que sale con error o vence su tiempo es una negativa, nunca un estado inventado.
- La cuarentena no se puede olvidar. Un resultado fuera de vigencia se escribe estructuralmente afuera del único árbol que el lector de resultados recorre: no hay filtro que alguien pueda olvidarse de aplicar.
- La preparación se prueba, no se declara. El veredicto de un ensayo sale de la evidencia traída, y no hay ningún reloj involucrado en ninguna parte.
- Sólo los adaptadores nombran un servicio.
scripts/adapters/kaggle.pyyscripts/adapters/colab.pyson los únicos lugares autorizados; hay guardas a nivel de código fuente que verifican que el resto de la skill sea ciego respecto del backend.
Limitaciones conocidas.
El adaptador de Kaggle nunca se probó contra un servicio real. Está verificado contra
la fuente del driver —hoy kagglesdk==0.1.37 con token Bearer, no el camino Basic-auth
del CLI, que la skill retiró— y contra un binario falso en los tests, pero ninguna
prueba de esta skill toca la red ni una cuenta real. Eso último no es un descuido: un
test tripwire falla si aparece una llamada de red a nivel de módulo, y se prueba a sí
mismo contra un módulo envenenado. Qué significa en
la práctica: todo lo que está arriba del seam —el registro, el pliegue, la vigencia,
el clamp, la cuarentena— sí está probado, y esas son las partes cuyo error sería
silencioso. Lo que no está probado en vivo es el archivo de abajo, cuyos errores son
ruidosos: un comando mal formado falla y se ve. Qué puedes hacer: si algo falla en
vivo por primera vez, empieza a buscar ahí y no en el seam. Cómo se arregla: con un
ensayo real —para eso existe submit --smoke—, que es exactamente el camino más barato
para descubrirlo antes de gastar una corrida grande.
Resuelto: los nueve sitios de impresión llevan red. status reventaba con
TypeError: Object of type PosixPath is not JSON serializable en cada invocación, porque
alguien le agregó un sub-bloque anidado y nadie revisó los sitios de impresión. El arreglo
—json.dumps(..., default=str)— llegó a cuatro de los nueve: exactamente los cuatro que
tenían test de regresión. Los otros cinco quedaron pelados, y tres de ellos se probaron
crasheables por mutación. Ahora los nueve tienen red y los nueve tienen su test. El
defecto nunca fue "tres comandos pueden reventar": era un arreglo aplicado donde había
cobertura en vez de donde se repite la forma.
Diagrama.
flowchart TD
KA["kaggle-accounts entrega worker + ruta"] --> C
B0["proposal-implementation deja la carpeta en tools/"] --> A
A["generate-job"] --> B["Carpeta del trabajo: config + cuaderno + metadatos"]
B --> C["submit"]
C --> D{"¿La ruta pasa la guardia?"}
D -- No --> E["Rechazado"]
D -- Sí --> F["Digest fresco del fuente"]
F --> G["Clamp de capacidad"]
G --> H{"¿Otorgado mayor que cero?"}
H -- No --> I["No se envía: el worker está al tope"]
H -- Sí --> J["El adaptador envía vía el CLI del servicio"]
J --> K{"¿El servicio confirmó un id?"}
K -- No --> L["Falla cerrado. Nada se anota"]
K -- Sí --> M["Anota el envío"]
M --> N["ledger.jsonl o smoke.jsonl"]
N --> O["status: pliega el registro y reporta"]
N --> P["poll: pregunta por una entrega"]
P --> Q["fetch"]
Q --> R{"¿El resultado sigue vigente?"}
R -- No --> S["Cuarentena, fuera del árbol de resultados"]
R -- Sí --> T["Materializa en un directorio parcial"]
T --> U{"¿Llegó completo?"}
U -- No --> V["No se anota nada. Se puede reintentar"]
U -- Sí --> W["Renombra atómico al destino"]
W --> X["Anota la devolución"]
X --> Y["smoke record: veredicto por evidencia"]
Y --> Z["readiness: ¿pasó, mismo commit, mismo worker?"]
N --> AA["reconcile: registro contra servicio. Lo corre un humano"]
N --> AB["proposal-implementation pliega esto en sólo lectura"]
6. experimental-deliberation — discutir el diseño experimental y publicar el plan, nunca el resultado
Para qué está. Un paper matemático necesita experimentos que sostengan sus afirmaciones,
y ese diseño tiene su propia tentación: que un modelo escriba "el término adaptado supera al
baseline" antes de que nadie haya corrido nada, o que invente una URL de repositorio porque
suena plausible. Esta skill es el segundo dominio sobre el mismo motor que
proposal-deliberation — convierte al agente en tutor de diseño experimental y aplica los
cambios acordados a través del mismo motor determinista, byte-exacto. Comparten el andamio de
escritura entero: resolver un locus, parchear, previsualizar, reconocer, publicar. Lo que
no comparten es qué hace "suficiente" un cambio. Para la matemática, alcanza con derivarlo
y cerrarlo adentro del documento — una demostración no vence. Para un experimento, alcanza sólo
lo que una fuente real y vigente todavía dice — un repositorio se muda, un baseline queda
superado, un protocolo cambia. Esa diferencia es la razón de ser de esta skill, y por eso su
etapa central se llama validated, no deliberated a secas: el sitio donde el motor hermano no
tiene equivalente en absoluto.
De dónde recibe y a quién le entrega.
Recibe de tres fuentes, dos obligatorias:
guidance/data-paper(obligatoria) — el paper de datos. Es el techo: acota qué se puede afirmar, y una afirmación que los datos no sostienen no es un experimento, es un deseo.proposals(obligatoria) — el directorio gestionado deproposal-deliberation, leído aquí y nunca escrito. Pero el motor sólo exige que el directorio exista; el contenido nunca entra por el motor, porque los revisiones gestionadas son archivos.mdsueltos y el cargador sólo desciende un nivel dentro de cada fuente. Las afirmaciones de la propuesta llegan al documento porque las llevas tú, en la idea que escribes y en la deliberación que sigue.
Le entrega a experimental-implementation: el archivo publicado
(experiments/experiments-<slug>-vNN.md) es, medido, el documento 0 que esa skill lee como su
"protocolo declarado" — así se llama en su propio perfil (impl_profile.py). Ninguna operación
del motor empuja ese archivo hacia el otro lado; experimental-implementation lo va a buscar
cuando corre.
Le entrega a todo el resto: la operación STATUS, de dónde sale la respuesta a "cuál es la
revisión vigente". Nadie abre el directorio a simple vista.
Qué necesita antes. Nada más que Node. El motor es keyless — no llama a ningún modelo ni
usa ANTHROPIC_API_KEY — porque el "modelo" de esta deliberación es el agente ya sentado en la
conversación.
Las etapas que declara. Leídas del campo objective de su propio
profile.ts. Este dominio no dice el norte de su hermano matemático, y la
prueba es estructural: tiene una etapa, validated, de la que el hermano no
tiene equivalente ninguno.
| Etapa | Qué deja establecido | La tienes detrás cuando |
|---|---|---|
bound |
Cuál revisión es la actual y cuál entrada de ella toca el cambio | STATUS nombró la última y el objetivo resolvió a una entrada |
validated |
El protocolo, la métrica, el baseline, el dataset y el esquema están dichos y salieron de una búsqueda, no de una suposición | frena esta etapa: una URL sin tag fechado, un baseline sin repositorio o sin año de venue, una línea **Dataset:** no dada o dada dos veces, o un esquema que no muestre test, semillas o repeticiones |
deliberated |
El cambio se discutió, no se tipeó | lo dijiste tú — nada aquí lo mide, y nada puede |
composed |
El reemplazo existe escrito como el experimento, no como una descripción de él | existe un bloque que lleva el experimento y lo que debe cumplir |
published |
La sucesora existe con el marcador del artefacto y es la revisión actual | es la llegada; no está detrás de nadie |
Llegada: la revisión sucesora publicada y actual, que es la única forma en la que viajan los experimentos.
validated es la etapa que justifica que esta skill exista aparte. Un
protocolo inventado de memoria se lee igual que uno buscado; la diferencia sólo
aparece cuando alguien pide la fuente. Por eso la validación externa corre
antes de cualquier borrador, no después.
El flujo.
- Arranque.
STATUSsobreexperiments/, de sólo lectura. Nunca se mira el directorio a mano. - Bifurcación. Cero revisiones gestionadas → crear v1. Ya hay una → editar.
- Crear v1. El motor carga las tres fuentes una sola vez, las incluye verbatim bajo
## Reference Sources(encabezado propio de este dominio; el motor compartido escribía## Paper Guide Referencepara cualquier dominio hasta que este perfil declaró el suyo, finding L6), y compone el documento a partir de tu idea de texto. La idea necesita al menos dos oraciones. El título sale de la primera y el encabezado de sección de la segunda; con una sola oración las dos calculan el mismo texto,# Xy## Xquedan byte-idénticos, y cada consulta de locus posterior queda ambigua para siempre.CREATE_INITIAL_REVISIONrechaza esto de entrada conINITIAL_IDEA_SINGLE_SENTENCE, sin escribir nada — no hay reparación posterior, porque el motor no puede escribir un encabezado que el autor no escribió. Y como v1 no tiene esqueleto ni rellena nada, el**Dataset:**y el**Validation scheme:**que el documento exige ya tienen que estar en la idea o en una fuente requerida, o v1 se rechaza igual, por la misma puerta de forma canónica que corre en cada sucesor — nunca una segunda puerta. - Validar (delegado al agente
experimental-validation). Busca en la web el protocolo estándar del área, las métricas aceptadas, los baselines vigentes con repositorio, venue y año, y el esquema de validación que el área espera. La búsqueda está acotada a los tres años anteriores a hoy, calculados desde la fecha de la corrida, nunca de memoria. El acotamiento es sobre lo que se busca, no sobre lo que se puede citar: un protocolo fijado antes sigue siendo el que el área usa, siempre que la cita diga qué año lo fijó. - Deliberar, en la conversación, nunca en el motor. El agente propone, refuta, exige el baseline que un reviewor va a pedir.
- Ubicar el cambio con
RESOLVE_TARGET: palabras distintivas del encabezado, sin puntuación, sin número de sección. - Vista previa con
CREATE_SUCCESSOR. El motor compila el candidato completo, lo valida contra la forma canónica, y devuelve un token de un solo uso junto con qué se perdería (preservationDelta) y qué conflicto de fuente detectó (ACHIEVED_RESULT, ver Los seguros). - La puerta. El agente muestra en castellano llano qué desaparece y qué frase leyó como un resultado logrado. Confirmas cada una por nombre.
- Publicar (delegado al agente
experimental-publish, que empieza solo después de que aceptaste). Reenvía la misma operación con el token,acknowledgedRemovalsyacknowledgedSourceConflicts. Escribeexperiments-<slug>-vNN+1.mdde forma atómica. - Auditoría. Recibo con sha256 antes/después, y una relectura byte a byte de todas las revisiones.
Los módulos.
| Archivo | Qué hace y por qué existe |
|---|---|
SKILL.md |
El contrato completo de esta deliberación: north de cinco etapas, qué delega y a quién, la puerta de validación externa, las reglas de forma canónica. Nada de esto vive en el motor compartido. |
references/usage.md |
Ejemplos de request/response y un transcript trabajado. Sostenido por su propio guard de deriva (tests/experimental-deliberation-usage-doc-drift.test.mjs): todo enlace ../SKILL.md#ancla tiene que resolver a un encabezado real, y el ejemplo trabajado se extrae del documento y se corre de verdad, comparando la respuesta contra lo que el motor devuelve hoy. |
profile.ts |
El perfil de dominio: directorio experiments, rótulo de revisión v (no r), el detector ACHIEVED_RESULT, las tres fuentes con sus obligatoriedades, y el objetivo de cinco etapas (bound → validated → deliberated → composed → published). El motor bajo _core/ no ganó ni una línea para admitir este segundo dominio. |
preservation-experimental.ts |
extractAtoms/violations: los seis átomos que no pueden desaparecer en silencio (tabla, baseline, criterio de éxito, figura, URL, dataset) y las reglas duras de forma — tag de verificación, celda vacía, baseline con URL y año, dataset/scheme declarados exactamente una vez. |
reference-experimental.ts |
declares/cites: un experimento declara [exp:E1]; una afirmación cita [tests:E1] o (Exp. E1) en prosa. Hace visible, mecánicamente, tanto la afirmación sin experimento como el experimento que no sostiene ninguna. |
cli.mjs |
13 líneas. Fija DELIBERATION_DOMAIN_PROFILE a este profile.ts y delega el resto entero — argumentos, modo stdin, códigos de salida — al cli.mjs del motor compartido. |
.claude/agents/experimental-validation.md |
El tramo validated: busca y verifica, nunca compone. Termina cuando ningún hallazgo depende de la memoria en vez de una búsqueda de esta misma corrida. |
.claude/agents/experimental-publish.md |
El tramo terminal: empieza donde el operador ya aceptó el cambio, termina con el sucesor publicado y vigente. No tiene Write ni Edit — sólo el motor escribe. |
_core/deliberation/engine/ (~50 archivos TS) |
El motor compartido con proposal-deliberation, sin cambios para admitir este dominio. Ver el desglose completo en la sección de proposal-deliberation; un solo archivo — revision-lifecycle-store.ts — todavía escribe el marcador de artefacto como literal, a propósito, para un guard Python de cruce de lenguaje (ver Limitaciones conocidas). |
Los seguros.
- El tag de verificación es obligatorio, y su ausencia es la violación. Toda URL externa
lleva
[pending-verification]o[verified: YYYY-MM-DD]inmediatamente después. Los bytes no pueden saber si alguien buscó; por eso el silencio es lo que se rechaza. - Ningún número en una celda de tabla de reporte. Una tabla de reporte es una promesa sobre
una corrida futura; un número ahí es un resultado que nadie midió. Las tablas de baselines
están exentas — son referencia, no promesa — y se reconocen sólo porque su primer encabezado
dice
Baseline/Baselines. - El detector de resultado logrado es advertencia, no bloqueo duro.
ACHIEVED_RESULTbuscaoutperform,beats,state-of-the-art, etc. Bloquea el turno de aceptar, no la vista previa — y existe la salida de reconocer el conflicto por escrito, para cuando el documento legítimamente cita el resultado publicado de otro. - Dataset y esquema de validación, exactamente una vez cada uno. Cero y dos-o-más se rechazan igual — dos líneas son una ambigüedad sobre qué corre el plan, y el motor no elige por ti.
- Una idea de una sola oración no llega a ser v1. Ver el paso 3 de El flujo.
- Nunca se inventa un repositorio, una URL, un venue o un año. Lo que no salió de una
búsqueda de esta corrida se marca
[pending-verification]; una URL plausible es peor que una ausente, porque parece verificada.
Limitaciones conocidas. Las tres de abajo estaban abiertas y ya se resolvieron.
Resuelto: el marcador del artefacto ya no está duplicado sin motivo. patch-compiler.ts y
draft-materialization.ts escribían <!-- proposal-workspace:artifact:v1 --> como constante
literal en vez de leerla de DOMAIN.artifact.marker — el segundo, además, sin ningún lector: la
constante estaba muerta. El primero ahora lee artifact.marker como los demás sitios del motor;
el segundo perdió la constante entera. Sólo revision-lifecycle-store.ts:19 sigue escribiendo el
literal, y a propósito: un guard Python de cruce de lenguaje lo lee por regex.
Resuelto: el cargador del perfil de dominio ya no acepta secciones enteras vacías.
domain-profile.ts revisaba que REQUIRED no tuviera claves de nivel superior undefined, y
volvía a revisar artifact.*/objective.* explícitamente, pero la misma lección no llegaba a
vocabulary, preservation, references ni sources, y la comprobación de stages[].establishes/
.behindWhen sólo probaba === undefined (una cadena vacía la atravesaba). Ahora los ocho campos
de vocabulary, las funciones de preservation/references, una lista de sources no vacía y
bien formada, y cadenas no vacías en objective.arrival/.purpose/.humanStops/stages[].* se
revisan igual de explícitamente, con el mismo código de error nombrando el campo anidado. Esta
skill nunca lo sufrió — declara los ocho campos de vocabulary llenos — pero el motor ya se lo
exige a la próxima.
Resuelto (decisión tomada): los siete contadores de auto-auditoría que nacían en cero se
retiraron. runtime-metrics.ts definía recordScientificMetric, con cero llamadores de
producción y cero tests — la única ocurrencia del símbolo era su propia definición. Los siete
contadores que alimentaba viajaban sin cambiar hasta el bloque metrics que devuelve cada
auto-auditoría, indistinguibles de una medición real. Se retiraron (scientificMetrics,
recordScientificMetric y el tipo que los declaraba) en vez de inventarles sitios de llamada:
decidir DÓNDE instrumentar cada uno es una pregunta de diseño real que este arreglo no puede
responder por adivinanza, y una colocación equivocada seguiría siendo señal falsa, sólo que ya no
visiblemente en cero. recordLifecycleMetric (dos sitios de llamada reales, dos tests) queda como
el patrón a seguir si alguna vez se diseñan productores reales para estos siete.
Diagrama.
flowchart TD
A["Agente pide STATUS a experiments/"] --> B{"¿Existe una revisión gestionada?"}
B -- No --> C["Pide la idea al usuario"]
C --> D{"¿La idea tiene al menos dos oraciones?"}
D -- No --> E["Rechazado: INITIAL_IDEA_SINGLE_SENTENCE. Nada escrito"]
E --> C
D -- Sí --> F["Compone v1 con las 3 fuentes cargadas una vez"]
F --> G{"¿Pasa la forma canónica?<br/>dataset x1, scheme x1, sin números en celdas"}
G -- No --> H["Rechazado: INITIAL_REVISION_CANONICAL_FORM_VIOLATION"]
H --> C
G -- Sí --> I["Escribe experiments-slug-v01.md"]
B -- Sí --> J["Delegado: experimental-validation<br/>busca protocolo, métricas, baselines<br/>ventana: 3 años antes de hoy"]
J --> K["Deliberación en la conversación.<br/>El motor no participa"]
K --> L["RESOLVE_TARGET: ubicar el locus"]
L --> M["CREATE_SUCCESSOR: vista previa"]
M --> N{"¿Se pierde algún átomo o<br/>se detecta ACHIEVED_RESULT?"}
N -- Sí --> O["El agente muestra qué se pierde<br/>y qué frase lee como resultado logrado"]
O --> P{"¿El usuario confirma cada una?"}
P -- No --> Q["No se publica"]
N -- No --> R["Delegado: experimental-publish"]
P -- Sí --> R
R --> S["Reenvía con acceptSuccessor + token +<br/>acknowledgedRemovals/SourceConflicts"]
S --> T["Escribe experiments-slug-vN+1.md"]
T --> U["Recibo + auditoría byte a byte"]
I --> V["experimental-implementation lee esta revisión<br/>como su documento 0"]
U --> V
7. experimental-implementation — el segundo huésped, dos documentos, un motor sin tocar
Para qué está. Un protocolo de experimentos publicado (experiments/experiments-<slug>-vNN.md)
también es un documento. Esta skill lo convierte en un repositorio Python que corre, y
demuestra —no afirma— que ese código hace lo que el protocolo dice y que sus corridas
completas dejan un registro que concuerda con lo que el protocolo declara. Hasta ahí sería
un clon de proposal-implementation. Lo que la distingue es que verifica dos documentos
declarados, cada uno bajo su propio vocabulario de reclamo: documents[0] es
experiments/, con claves experiments/remedy_experiments; documents[1] es
proposals/ —la matemática que ese protocolo responde—, con claves equations/
remedy_equations, la misma nomenclatura que ya usa el hermano. El propio
impl_profile.py lo deja escrito en su comentario: el documento 1 declara su overlay
completo (claim_key, locus_key, remedy_locus_key, notation_keys,
citation_pattern) sin heredar nunca los valores de experiments del documento 0.
De dónde recibe y a quién le entrega. Recibe un protocolo publicado bajo
experiments/ y, a través de él, la propuesta matemática que ese protocolo cita. Sobre
remote-execution la costura es idéntica a la del hermano: sólo lectura, dos módulos por
ruta —registro y puerta de entrada—, nunca el adaptador. Y hacia experimental-deliberation
entrega hallazgos locales por handoff, nunca publica ella misma: la corrección vuelve al
documento por la misma compuerta que cualquier otro cambio.
Qué necesita antes. Un protocolo de experimentos publicado, y un repositorio destino
bajo implementations/ que ya sea un repositorio git. Nada de entorno virtual propio de
la forja: el .venv es siempre del destino.
Las etapas que declara. Leídas de OBJECTIVE_FLOW, con la misma propiedad
que el norte de proposal-implementation: independientes de lo que haya en
disco, para que una sesión que chocó con un error se pueda ubicar y reenganchar.
| Etapa | Qué deja establecido | La tienes detrás cuando |
|---|---|---|
standing |
Un repositorio armado como esta skill espera, con intérprete propio y los pasos declarados del protocolo puestos como comandos ejecutables | structure no reporta huecos de andamiaje y el protocolo al que responde la corrida está nombrado y es legible |
binding |
El código que corre dice lo que dice la revisión de experimentos ligada, con cada paso declarado trazado a un comando ejecutable | fidelity está limpio contra esa revisión y la suite propia del destino está verde bajo su propio intérprete |
instrumentation |
Cada medición que el protocolo declara tiene dónde aterrizar — una métrica, un registro, un chequeo que pueda fallar | cada medición declarada resuelve a algo que se puede correr y volver a leer |
rehearsal |
El flujo declarado corre punta a punta a escala chica, y el registro que deja coincide con el documento que lee una persona | el ensayo está completo y su propio reporte da ok |
full-scale |
Cada paso ruteado a donde se decidió que corra, y ejecutado ahí a la escala que declara el protocolo | es la llegada; no está detrás de nadie |
Llegada: corridas completas a la escala declarada por el protocolo, con un registro contrastable contra la revisión de experimentos. No una verificación verde, no un ensayo que pasó.
Cuatro paradas son de una persona, ninguna es un defecto a reparar: autorizar que se escriba código, aprobar el mapa de pasos declarados a comandos ejecutables, publicar el commit que un worker clonaría, y autorizar un lanzamiento — que son horas de la cuota de alguien.
El flujo. Esta skill es casi enteramente el motor compartido en
_core/implementation/engine/ —unas 21.000 líneas en 12 archivos, la misma base que
sostiene a proposal-implementation— y lo que agrega son unos 300 bytes propios: un
perfil de dominio (impl_profile.py, 278 líneas), una copia byte-a-byte del lanzador del
hermano (scripts/implementation_cli.py, 24 líneas), este SKILL.md, dos agentes y su
propio corpus sellado bajo tests/. Ninguna línea del motor cambió para admitir este
segundo huésped.
Enruta igual que el hermano: por existencia, no por fidelidad. Si src/ no tiene
implementación, Flujo A; si ya tiene una, Flujo B, que ahora mide deriva contra los dos
documentos antes de decidir nada.
Flujo A —primera pasada. Esta skill no envía assets/kit/: materialize --stage scaffold no está disponible, así que el andamio del destino se escribe a mano. El resto
sigue la forma del hermano: clonar, env, pedir el nombre, compuerta de autorización,
presentar el mapa de objeto a módulo para ambos documentos —qué módulo implementa qué
sección del experimento y qué módulo implementa qué ecuación de la propuesta que ese
experimento responde—, escribir código con la clave de procedencia que corresponda
(experiments, equations, o ambas), y entregar a los dos agentes propios cuando toca.
Flujo B —toda pasada posterior. Lee src/ y toma la revisión más nueva de cada
documento declarado por separado —nunca un solo nombre asumido para los dos—. Corre la
suite del destino y después verify. Una diferencia de fidelidad en cualquiera de los
dos documentos abre la misma compuerta del hermano ("¿ese cambio lo hiciste tú?"), sin
esperar a que el otro documento también discrepe. Con los dos limpios, agree —disponible
sólo porque esta skill siempre declara más de un documento— nombra cada citación cruzada
que un documento hace y el otro no reconoce, en las dos direcciones, y nunca falla un
veredicto sobre cuál lado debe cambiar. Solo con agree sin discrepancias pendientes,
handoff dimensiona los hallazgos abiertos hacia experimental-deliberation.
Los módulos.
| Archivo | Qué hace y por qué existe |
|---|---|
impl_profile.py |
El perfil de dominio entero: dos documentos, cada uno con su propio overlay de vocabulario. Cada ruta se deriva de la ubicación de este archivo, nunca de la del motor —así la misma forma que usa el hermano resuelve a este perfil sin una sola línea específica de dominio. |
scripts/implementation_cli.py |
El lanzador, 24 líneas, byte a byte idéntico al del hermano. No hace nada propio: sólo fija IMPLEMENTATION_DOMAIN_PROFILE a este perfil antes de entregarle el control al motor compartido. Todo argumento, subcomando y código de salida es del motor. |
SKILL.md |
La doctrina de esta skill: los dos documentos, su vocabulario, el flujo A y B propios, y qué no está disponible (materialize --stage scaffold) y por qué. |
tests/test_experimental_implementation.py, tests/test_experimental_implementation_mutation.py |
El corpus sellado propio, fuera de esta carpeta (vive en tests/ del repositorio), que ancla el comportamiento de dos documentos contra el motor compartido. |
Los comandos. Veintiuno, medidos con implementation_cli.py -h: walk, env,
name, plan, apply, admit, handoff, compose, probe, verify, position,
discuss, propose, gate, offer, close, step, settle, defect, materialize,
agree. El hermano sirve exactamente los mismos veinte y esta skill agrega el vigésimo
primero, agree, habilitado sólo cuando el perfil declara más de un documento —lo que
esta skill siempre hace y el hermano nunca.
Los seguros.
- Nunca ejecuta el código del destino para inspeccionarlo. Todo se lee con
ast, igual que el hermano —es una propiedad del motor, no de este perfil. - Aislamiento no negociable. Cada corrida trabaja dentro de
implementations/<repo>/y usa el.venvde ese repositorio, nunca el de la forja ni el sistema. Data/es demandable por producto, no por raíz. Si la revisión de experimentos declara un**Dataset:**,verifyexige{Name}/Dataigual que ya exigeNotebooks/,Results/yModels/—pero atado al método (--name), nunca al repositorio entero, porque el mismo clon puede alojar otro método sin esos datos.- Un hallazgo sin remedio validado no se reporta. Estructural, compartido con el
motor: la auditoría queda
incompletemientras algo quede sin medir. agreenunca falla un veredicto. Nombra las dos direcciones de discrepancia y deja la decisión de qué lado corregir a la conversación, nunca a la máquina.
Limitaciones conocidas.
El vocabulario de dos documentos no llega a cuatro consumidores del motor compartido —
cmd_admit, finding_impact, cmd_handoff y las filas de auditoría de verify. Estos
cuatro leían un hallazgo bajo las claves del documento 0 sin importar cuál documento
nombrara: un hallazgo de documents[1] que cita equations: ['99'] se buscaba bajo
experiments, no encontraba nada, y una ausencia se leía como "sin objeción" —admitiendo
hallazgos falsos y rechazando hallazgos legítimos con razones inventadas. Ya está
corregido, en tres commits fechados el mismo día que esta sección: e88afec extrajo el
lector correcto (finding_locus_scopes/finding_named_source) que ya existía en
remedy_compatibility y lo puso a llamar cmd_admit; 5e8d277 hizo lo mismo para
finding_impact, con un matiz que el primer intento no tenía —leer la clave propia del
documento cuando el hallazgo la declara, y remedy_loci en el caso general—; y el
mismo par de lectores ahora alimenta cmd_handoff y las filas de auditoría de verify.
Se documenta aquí como el mecanismo vigente, no como historia.
walk ya no promete un acto rehearse que nada ejecutaba. Su roster incluía
rehearse entre los actos que "realiza", pero el despacho sólo tenía dos formas de
comando —una para pasos locales, otra para generar carpetas de trabajo— y la segunda
nunca pasaba --regenerate, así que en el único estado para el que rehearse existe
(la carpeta ya está creada y no está lista para correr) el comando se negaba a sí mismo.
Corregido en 7fdec85: el acto se movió de "lo que walk realiza" a "lo que walk
se detiene a pedir", y el agente experiments-walk ya lo describe así —"generar las
carpetas de trabajo que un paso remoto necesita y detenerse en el ensayo, que ningún acto
aquí realiza y la doctrina deja en manos de una persona". El ensayo se hace a mano, como
la doctrina siempre prescribió; lo que cambió es que la herramienta ya no promete
automatizarlo.
-h ya no se presenta con el nombre del hermano. El motor lee su propio docstring de
módulo para construir la ayuda de argparse, y ese docstring estaba escrito para
proposal-implementation —así que el frente de esta skill se introducía con el nombre
equivocado y describía 4 de sus 21 subcomandos. Corregido en 8e92d40: el nombre
ahora sale de la raíz del perfil cargado (kit.root), así que un tercer huésped futuro
recibe su propio nombre el día que se escriba, sin nada que actualizar a mano; y la lista
de comandos se retiró en vez de corregirse, porque una copia hecha a mano de un roster que
argparse ya construye solo sólo puede estar bien por accidente.
admit todavía puede admitir un hallazgo cuya notación citada no aparece en el
documento que nombra, si esa notación pertenece a otro documento válido. Medido: el
motor comparte con proposal-implementation la comprobación de que la notación citada
exista en el texto, pero esa comprobación no distingue el documento correcto del
incorrecto salvo por las claves ya corregidas arriba; un hallazgo bien formado que cita
la notación real de un documento distinto al que nombra sigue sin tener una prueba
cruzada dedicada más allá de las que ya ejercitan la ruta corregida. Cómo se
arregla: un test que fije, con dos documentos reales y distintos, que una cita válida en
uno y ausente en el otro se rechace nombrando cuál.
El seguro de "corpus sellado sin tocar" compara contra el índice de git, no contra lo
escrito en disco. Igual que en el hermano, las pruebas que protegen tests/seal/ corren
git diff --exit-code, así que un archivo plantado y nunca agregado al índice, o una
edición ya puesta en stage, pasan sin marcarse. Como este huésped comparte el mismo motor
y el mismo patrón de prueba, hereda la misma ventana. Cómo se arregla: cambiar la
comprobación a git status --porcelain (o git diff HEAD más un chequeo de no
rastreados), igual que se anota para el hermano.
Resuelto: el guardia de intérprete ajeno ya se puede poner en rojo.
require_non_forge_interpreter —la doctrina que los dos SKILL.md llaman "aislamiento no
negociable"— tenía una única llamada de producción y ninguna prueba capaz de fallar: se
podía retargetear su condición, dejando el raise intacto en el código fuente para que los
barridos de roster no lo notaran, y aceptaba en silencio un sys.prefix de la forja con
todas las suites verdes. Ahora hay un test que muta exactamente eso y se pone rojo.
El módulo de reescritura de referencias puede volverse un no-op y ninguna suite lo
nota. scan_reference_updates, scan_stale_references y el patrón de referencia
pueden vaciarse simultáneamente sin que ninguna de las suites que ejercitan este huésped
se ponga en rojo. Bajo esa mutación, apply dejaría de reescribir referencias tras un
renombre y un repositorio migrado conservaría imports rotos en silencio, y
structure.staleReferences de verify quedaría permanentemente vacío. Como el módulo
vive en el motor compartido, este huésped hereda la misma ventana ciega que el hermano.
Cómo se arregla: una prueba que mute cada una de las tres piezas por separado y
verifique que alguna suite se rompa.
Resuelto: ese refusal ahora puede disparar, y lo que arreglaba era peor que publicarlo
de más. NAME_NOT_ALPHANUMERIC estaba detrás de un patrón que sólo emitía tokens ya
alfanuméricos, así que no podía alcanzarse. El daño no era el refusal de adorno: los
caracteres que ningún patrón capturaba se caían en silencio, y el nombre salía
mutilado — Ñandú se convertía en And, münchen en M-Nchen, test@here en
Testhere. Se hizo alcanzable en vez de retirarlo, porque perder letras sin avisar es
peor que rechazar el nombre. Con un detalle que sin él no arreglaba nada: str.isalnum()
es Unicode-aware y aceptaba é, así que la comprobación necesitó isascii() además.
Diagrama.
flowchart TD
A["Usuario invoca la skill sobre un protocolo de experimentos"] --> B{"¿src/ ya tiene una implementación?"}
B -- No --> C["FLUJO A"]
B -- Sí --> D["FLUJO B"]
C --> C1["Clonar bajo implementations/, env con el intérprete del destino"]
C1 --> C2["[GATE] Confirmar name"]
C2 --> C3["[GATE] Autorización para implementar"]
C3 --> C4["Mapa objeto→módulo para AMBOS documentos:<br/>secciones del experimento y ecuaciones de la propuesta"]
C4 --> C5["Escribir módulos con su propia __provenance__<br/>(experiments, equations, o ambas)"]
C5 --> C6["Delega a experiments-build hasta instrumentation"]
C6 --> C7["Delega a experiments-walk hasta el lanzamiento"]
D --> D1["Leer la revisión más nueva de CADA documento por separado"]
D1 --> D2["Correr la suite del destino, luego verify"]
D2 --> D3{"¿Fidelidad limpia en los DOS documentos?"}
D3 -- No, en cualquiera --> D4["[GATE] ¿Ese cambio lo hiciste tú?"]
D4 -- Sí --> D5["El código va adelante; se actualiza la matemática o el protocolo"]
D4 -- No --> D6["Corregir y revalidar"]
D6 --> D2
D3 -- Sí --> D7["agree: nombra citaciones cruzadas no reconocidas, en las dos direcciones"]
D7 --> D8{"¿Discrepancias sin reconocer?"}
D8 -- Sí --> D9["Resolver en conversación con --acknowledge"]
D9 --> D7
D8 -- No --> D10["handoff: dimensiona hallazgos abiertos"]
D10 --> D11["settleInline vía compose, o agenda para experimental-deliberation"]
C7 --> E["remote-execution: probe dice qué sigue"]
D11 --> E
8. paper-writing — de los contratos de sección al PDF, sin que nada se asuma
Para qué está. Escribir un paper a mano tiene un problema silencioso: una frase
sobrevive al dato que la sostenía, una cita queda sin resolver, un diagrama se
declara "compilado" sin haber compilado nunca. Esta skill lleva paper/ de un
andamio vacío a un documento renderizado donde cada bloque, cada decisión y cada
cita quedan grabados y verificables — nunca asumidos. Es el reverso de
paper-ingestion: esa skill mete papers hacia adentro como Markdown legible; esta
saca prosa hacia afuera como LaTeX que compila, con la misma regla de fondo
(no-claim-without-a-source-that-holds-it) aplicada del otro lado.
De dónde recibe y a quién le entrega. Lee tres cosas que no escribe: los
contratos de sección en sections/*.md (front-matter JSON versionado, diez
archivos), lo que proposals/ y experiments/ declaran, y el código de un
repositorio de implementación objetivo. De ahí resuelve citas contra OpenAlex,
Crossref y arXiv, y le entrega al operador un paper/main.tex con cada bloque
sustituido, su refs.bib reconstruido desde metadata cacheada, y un PDF
compilado vía latexmk. No hay una skill "después" — verify es el último paso
antes de que el documento salga del repositorio.
Qué necesita antes. Python 3.10+ de biblioteca estándar únicamente — sin
pip install para nada del CLI. render/place necesitan latexmk instalado
(medido: /Library/TeX/texbin/latexmk, presente). resolve necesita red hacia
api.openalex.org, api.crossref.org y export.arxiv.org — sin API key, sin
secreto: OpenAlex sólo pide un contacto de cortesía (papersmith.yaml,
paper_writing.contact), nunca enviado como credencial. Verificado en vivo:
$ paper_cli.py resolve --identifier 10.1038/nature14539 --resolver crossref
{"status": "ok", "title": "Deep learning", "doi": "10.1038/nature14539", ...}
El flujo, por grupos de verbos. Esta skill no declara una tabla de etapas:
declara diecinueve verbos detrás de una sola puerta (scripts/paper_cli.py),
y el flujo es el orden en que esos grupos se habilitan entre sí.
| Grupo | Verbos | Qué deja establecido |
|---|---|---|
| El documento existe | scaffold, status |
paper/main.tex, refs.bib, Figures/. scaffold es idempotente: una segunda corrida nunca toca un byte existente, ediciones a mano incluidas |
| El contrato de sección | contract, readiness, order |
Qué debe cumplir cada sección. El contrato es dato, no código |
| Las decisiones del paper | declare, observe, plan |
Lo que el paper decide sobre sí mismo. observe valida un informe de insumos-observer contra el esquema antes de que una persona corra declare |
| La evidencia que sostiene | resolve, bib build, validate |
Resolución de citas, bibliografía con fuente, y la compuerta de veredicto/colocación: ninguna afirmación sin una fuente que la sostenga |
| Escribir y dibujar | write, render, place, figure optimize, figure audit |
Redacción atada a evidencia, auditoría de contrato y prueba de fuga de estilo. Optimización de TikZ y auditoría semántica de figuras. render/place: un diagrama que compila o dice por qué, con su presupuesto de reparación |
| Comprobar | verify |
Verificación de acoplamientos de sólo lectura, integridad de citas, consistencia semántica de figuras y vigencia del contrato |
El motor de sustitución son cuatro de esos diecinueve —scaffold, status,
open, substitute— y tienen una regla que conviene saber: no existe
--force. Nada aquí descarta jamás el texto en disco de una persona a favor de
un cuerpo entrante. La única salida de un bloque editado a mano es --adopt,
que rebasa el digest y deja el cuerpo intacto.
Y nunca afirma que el documento compile. Un substitute exitoso siempre
agrega "rendering": "unproven": este motor no tiene toolchain de LaTeX y no
hace ninguna afirmación sobre cómo queda la página.
El flujo. Siete etapas, declaradas en paper_objective.OBJECTIVE_FLOW y
leídas por los tests de los agentes, no por prosa suelta: scaffold crea
paper/main.tex, refs.bib y Figures/ de forma idempotente, sin pisar jamás
un edit a mano; plan reporta de sólo lectura la clase de cada carpeta de
guidance/, el estado de cada declaración y hecho, y la procedencia de cada
bloque ya escrito; declare fija esas declaraciones y resoluciones de hecho —
siempre una decisión humana, nunca inferida de evidencia; cite resuelve,
reconstruye y valida cada cita que un bloque exige; write sustituye cada
bloque escribible, en el orden que order deriva del grafo de bloques, pero
nunca invocando un agente él mismo — eso lo hace un procedimiento de traslado
(shuttle) que corre fuera del CLI (ver Los seguros); render/place prueban
que el documento compila de verdad, no que "debería"; y verify cierra con un
reporte de sólo lectura sobre acoplamientos, integridad de citas y vigencia del
contrato, corrido de nuevo después de cualquier substitute, render, place
o declare. La llegada declarada: un documento renderizado y decisión-completo
donde nada quedó afirmado sin que una corrida lo haya chequeado.
Los módulos.
| Archivo | Qué hace y por qué existe |
|---|---|
SKILL.md |
El contrato de conducta: los 19 verbos, sus refusals, el procedimiento de traslado hacia los 6 agentes. |
scripts/paper_cli.py |
El front door — 19 verbos: scaffold, status, open, substitute, contract, readiness, order, declare, observe, resolve, bib, validate, plan, write, render, verify, place, figure optimize, figure audit. Sin dependencias externas. |
scripts/paper_scaffold.py |
Crea y re-entra paper/ sin clobbing — mismo patrón que proposals//experiments/. |
scripts/paper_block.py |
Gramática de marcadores: delimita, ubica, sustituye y rechaza sobre bloques nombrados dentro de main.tex, byte a byte. |
scripts/paper_region.py |
La gramática compartida en la que se escriben las regiones declarations/provenance. |
scripts/paper_declarations.py |
La región declarations — valores del operador y resoluciones de hecho, fijados una vez grabados, reabiertos sólo por nombre. |
scripts/paper_provenance.py |
La región provenance — digest de contrato y generación de declaración con la que se escribió cada bloque, nunca reconstruida. |
scripts/paper_contract.py |
Gramática del front-matter y validación de esquema sobre sections/*.md. |
scripts/paper_graph.py |
Ensambla el corpus, el namespace plano de ids, resuelve los edges after, deriva el orden de escritura. |
scripts/paper_readiness.py |
Por bloque: escribible o bloqueado, dados los hechos y declaraciones ya satisfechos. |
scripts/paper_vocabulary.py |
Los tres vocabularios cerrados que un header de contrato puede usar — sin I/O, sin estado. |
scripts/paper_guidance.py |
Clasifica cada carpeta de guidance/ como style-reference o evidence por su marcador propio. |
scripts/paper_evidence.py |
El registro claim↔fuente: un span citado verbatim de un .md ya ingerido, lo que vuelve una verdad de cita una medición y no una opinión. |
scripts/paper_bindings.py |
El contrato de entrada del redactor y su binding map — detecta "nada afirmado fuera del evidence set", no sólo lo instruye. |
scripts/paper_audit.py |
Extrae los ## Disqualifiers verbatim y reconcilia la cuenta del auditor contra el borrador real. |
scripts/paper_style.py |
Resolución y registro del bloque equivalente en cada carpeta style-reference — el registro, no el contenido. |
scripts/paper_leak.py |
La prueba de tres borradores (distancia de registro + solapamiento de n-gramas) y el tripwire de ocho tokens — separados por firma a propósito, para que ajustar el tripwire nunca mueva la prueba. |
scripts/paper_write.py |
El pipeline write: readiness, gate, borrador, auditoría de evidencia, auditoría de contrato, substitute --contract — con exactamente un re-borrador acotado, controlado por una ledger en disco. |
scripts/paper_resolve.py |
Cliente urllib puro para resolver citas y metadata contra OpenAlex, Crossref y arXiv. Sin llave. |
scripts/paper_bib.py |
Reconstruye paper/refs.bib exclusivamente desde metadata cacheada y resuelta — "nunca a mano" es una propiedad chequeada. |
scripts/paper_validate.py |
El único gate de citas: veredicto, colocación por régimen, y un loop de búsqueda acotado a tres rondas que nunca deja pasar un bloque a medio citar. |
scripts/paper_obligation.py |
Qué debe contener un diagrama, leído de la declaración figure: del contrato — nunca hardcodeado contra un id de sección o bloque. |
scripts/paper_figure.py |
Layout de fuente/manifiesto de diagramas, el cruce de dos vías del manifiesto, y la ledger de presupuesto de reparación por figura. |
scripts/paper_tikz.py |
Optimizador de código TikZ: poda librerías no usadas, detecta librerías faltantes y factoriza estilos repetidos en \tikzset. |
scripts/paper_figure_audit.py |
Auditor semántico de figuras: compara nodos del manifiesto contra la prosa de la sección y las pipeline steps. |
scripts/paper_latex.py |
El único módulo al que esta skill le permite importar subprocess — descubrimiento de toolchain, la única llamada a latexmk, parseo de log, veredicto de tres señales. |
scripts/paper_coupling_evidence.py |
Toda lectura de disco que verify necesita, aislada aquí y en ningún otro lado. |
scripts/paper_verify.py |
Ocho chequeos puros sobre esa evidencia (incluyendo figure-semantics) — cero I/O propio, reporte de sólo lectura. |
scripts/paper_objective.py |
El norte declarado: OBJECTIVE_FLOW, leído por tests/test_agents.py vía ast.literal_eval — literales puros, sin llamadas ni imports. |
sections/*.md |
Los diez contratos reales, versionados: front-matter JSON + prosa. Entrada real, no fixture. |
.claude/agents/insumos-observer.md |
Lee las cuatro fuentes declaradas del paper —proposals/, experiments/, el código del repo destino y lo que devolvieron sus corridas— y reporta, por cada hecho observable, si está satisfecho y con qué evidencia. Read, Glob, Grep. Nunca decide un valor y nunca corre declare: su informe es lo que una persona lee antes de correrlo ella. El informe se pasa por archivo y observe lo valida contra el esquema — nunca se le cree a un agente su propia cuenta. |
.claude/agents/style-sampler.md |
Resuelve el bloque equivalente, entero, de cada carpeta de guidance/ que el registro clasifica como style-reference, y lo devuelve verbatim, nunca recortado. Read, Glob, Grep. Lo que devuelve es el único material contra el cual una prueba posterior de solapamiento puede comparar un borrador con estilo: si recortara, la prueba compararía contra algo que nadie escribió. |
.claude/agents/figure-auditor.md |
Audita diagramas TikZ contra la prosa de su sección y el manifiesto declarado; corre figure audit y reporta el veredicto estructurado. |
Los seguros.
- El CLI nunca invoca un agente. Un AST scan (
NoSubprocessScanTests) certifica que ningún módulo bajoscripts/importasubprocess,os.system,os.popen,os.exec*nimultiprocessing— con una única excepción nombrada y hoy sin uso, reservada paralatexmk.writeno puede correr desatendido por construcción: el agente orquestador arma los cuatro insumos del redactor, delega, escribe el JSON a un archivo, y solo entonces correwrite --draft <path> --audit <path>. - Un solo re-borrador, contado en disco.
AUDIT_EXHAUSTEDrefuta un tercer intento bajo el mismo contrato/evidencia/mode — el conteo vive en una ledger de archivo, no en memoria del proceso que podría reiniciarse. - La prueba de estilo y su tripwire están separados por firma a propósito
(
paper_leak.py, Decision D6): ajustar el tripwire de ocho tokens nunca puede mover la prueba de tres borradores. Un canal de estilo todo-noEquivalentreportaunmeasured, no un pase silencioso. paper_verify.pyes puro: ceroPath(,open(ni.read_en el archivo — confirmado por grep directo, no por confiar en su docstring.- Descubrimiento de citas falla por nombre, no en silencio. Con
paper_writing.roles.discovery: []enpapersmith.yamly.mcp.json→"mcpServers": {}, cualquier intento de descubrimiento se rechazaDISCOVERY_UNAVAILABLE; un rol de resolución vacío se rechazaRESOLVER_ROLE_EMPTY. Nunca degrada a "no encontré nada" indistinguible de "no busqué". - Resolución de citas hoy anda:
resolutionenpapersmith.yamltraeopenalex,crossrefyarxivcableados — verificado en vivo arriba, no sólo declarado.
Limitaciones conocidas.
SKILL.md afirma un conteo que ya no es cierto sobre sus propios datos
shipeados. SKILL.md:453-454 dice hoy: "A header declaring neither is
schema-valid — none of the ten shipped contracts carry mode yet". Medido
directamente: los 10 de 10 sections/*.md ya cargan mode en la forma exacta
que el mismo documento describe dos párrafos antes (rg -n '"mode"' sections/*.md
da diez matches). La deriva viene de dos commits (d99ee93, 9986e11) que
llevaron la cobertura a 10/10 antes de que un commit posterior enfocado
explícitamente en "stale SKILL.md content" (eab6496) tocara ese mismo archivo
sin corregir esta frase.
Cómo se arregla: corregir la oración a "los 10 shipped contracts ya cargan
mode" y, si se quiere que no vuelva a driftear, que un test lea el conteo real
de sections/*.md en vez de que SKILL.md lo repita a mano.
El build acumula símbolos sin un llamador de producción, y el conteo de cuántos
hay ya no es confiable ni citando el propio historial de commits. Cuatro casos
recientes (Verdict.__post_init__, register_distance_holds,
relative_overlap_holds, RedactorInput) se auditaron y no son defectos:
son pruebas de mecanismo o contratos de forma para un fixture, documentados así
en su propio código. Pero el patrón es real y tiene historia: el commit
eab6496 registró a paper_contract.install_header como "la undécima
instancia de esa forma" — sin embargo esa misma función ya había sido
eliminada en el commit ab10c21, anterior al que la citó como viva. El
número "once" quedó fosilizado en un mensaje de commit que ya estaba
desactualizado el día que se escribió.
Cómo se arregla: no volver a citar un conteo de un mensaje de commit; una
barrida por ocurrencia de identificador (def/class de nivel superior que no
aparece fuera de su propia línea de definición, sobre los 27 scripts + los
tests/test_paper_*.py) da el número real en el momento en que se corre, y ese
es el único número que vale la pena escribir.
Diagrama.
flowchart TD
A["scaffold: crea paper/main.tex, refs.bib, Figures/"] --> B["plan: clases de guidance/,\nestado de declaraciones/hechos, procedencia"]
B --> C{"¿Algún bloque bloqueado?"}
C -- Sí --> D["declare: fija declaración o hecho\n(siempre decisión humana)"]
D --> B
C -- No --> E{"¿Alguna cita pendiente?"}
E -- Sí --> F["resolve contra OpenAlex/Crossref/arXiv"]
F --> G["bib build: refs.bib desde cache resuelta"]
G --> H["validate: veredicto + colocación,\n3 rondas acotadas"]
H --> E
E -- No --> I["order: deriva el orden de escritura del grafo"]
I --> J["Procedimiento de traslado (shuttle) --\nel CLI nunca invoca un agente"]
J --> K["redactor arma el LaTeX + binding map"]
K --> L["contract-auditor reconcilia contra ## Disqualifiers"]
L --> M["write --draft --audit: reconcilia"]
M -- audit-fired, 1er intento --> K
M -- AUDIT_EXHAUSTED, 3er intento --> N["Refuse: no se sustituye"]
M -- written --> O{"¿Bloque tiene figura?"}
O -- Sí --> P["diagram-author: TikZ + render (latexmk)"]
P --> Q["place: figura ya medida"]
O -- No --> R["Siguiente bloque en el orden"]
Q --> R
R -- quedan bloques --> J
R -- ninguno queda --> S["render/place: compila el documento entero"]
S --> T["verify: acoplamientos, integridad de citas,\nvigencia del contrato"]
T -- falla --> D
T -- ok --> U["Documento decisión-completo,\nnada afirmado sin corrida que lo cheque"]
9. skill-audit — el auditor que deriva las dos mitades de un conjunto cerrado, nunca lee ninguna
Para qué está. Cualquier skill o CLI que declara un conjunto cerrado —los subcomandos que acepta, los códigos de error que puede devolver, los archivos que dice que embarca— corre el mismo riesgo: que ese conjunto se repita a mano en más de un lugar y que, con el tiempo, esas repeticiones se separen entre sí y de lo que el código realmente acepta. Esta skill busca exactamente eso, y sólo eso. No lee el código para adivinar qué acepta, ni lee la documentación para confiar en lo que dice: deriva el lado del código conduciendo el sujeto como un proceso real y tomando el roster de su propio mensaje de rechazo, y deriva el lado documentado parseando una tabla. Compara las dos mitades y se detiene ahí — el reporte es lo que entrega, y la reparación queda para quien es dueño del sujeto auditado.
De dónde recibe y a quién le entrega. Recibe un sujeto nombrado —una
skill, un CLI, cualquier cosa que enumere un conjunto cerrado— y una receta de
prueba (--probe-spec) que le dice cómo conducirlo y dónde leer su tabla
doctrinal. El trabajo real corre en el agente audit-report, delegado en un
contexto propio: el motivo no es cosmético, es de método. Un contexto que ya
cargó al sujeto ya vio una de las dos mitades antes de derivarla, lo mismo que
una fixture escrita y leída por la misma mano. El agente carga SKILL.md
entero antes de moverse, tiene Read, Write, Bash, Glob, Grep y no
tiene Edit — así que "reporta, nunca repara" deja de ser una frase y pasa a
ser un hecho sobre lo que puede alcanzar. Le entrega el reporte a quien pidió
la auditoría; no vuelve a tocar el sujeto después.
Qué necesita antes. Un shell real. Sin Bash la skill se niega —
nombra la capacidad que falta y no emite reporte, hallazgo, candidato ni
veredicto parcial. No hay entorno virtual: todo corre con la biblioteca
estándar. No toca red, con una sola excepción declarada: el paso driver
opcional de structure. La negativa sin shell no es una falla del audit, es
su salida correcta — un audit cuyas afirmaciones son puras lecturas no le dice
al usuario nada que no pudiera haber leído solo.
El flujo, por etapas. Una auditoría diferencial contra un segundo sujeto corre en seis etapas, de la más barata a la más cara. Cada una es una fila de una tabla, nunca un encabezado numerado, y todo informe lleva las seis filas, sea diferencial o no.
| # | Etapa | Modelos que gasta | Qué exige |
|---|---|---|---|
| 0 | Congelar el sujeto | 0 | frozen |
| 1 | Decidir por herramienta | 0 | undecidable |
| 2 | Manejarlo desde la ignorancia | 1 | user-drive |
| 3 | Dos lecturas ciegas, sólo sobre esa lista | 2 | reading-diff |
| 4 | Manejo diferencial: una caja con la skill y una sin ella | 2 | drives |
| 5 | Partir las dos transcripciones | 1 | found-by |
Seis corridas de modelo en total, y sólo si cada etapa después de las tres primeras efectivamente corre. Las dos primeras no gastan ninguna: el orden existe para que lo que se puede decidir con una herramienta nunca cueste un modelo.
La etapa 2 no es diferencial. Exige que el sujeto se maneje desde la ignorancia siempre que haya algo alcanzable que manejar — es lo que separa "la documentación dice X" de "el código hace X".
El flujo. roster es el verbo central y el que reconstruye las dos
mitades: probe_code_side (audit_cli.py:308-372) corre el argv propio del
sujeto con un nonce que no puede aceptar (__AUDIT_NONCE__), y saca el roster
del texto de su propio rechazo por regex — nunca abre ni parsea un archivo
fuente del sujeto para esto. doctrine_side (audit_cli.py:92-135) hace lo
simétrico del otro lado: parsea una tabla Markdown nombrada por su encabezado
y devuelve una columna, sin correr ningún proceso. El resultado compara
code contra doctrine y publica unregistered, phantom, duplicated,
numeralMismatch. Los otros ocho subcomandos —medidos vía --help, ver
tabla abajo— cubren dimensiones distintas del mismo problema: structure
compara lo declarado, lo que hay en disco y lo que un scaffold desde cero
produce; walkthrough conduce una receta paso a paso hasta el índice donde se
traba; reading-diff compara dos lecturas ya dadas de una misma superficie de
prosa; sensitivity varía una entrada declarada y mide si la salida se mueve;
inversion invierte un hecho guardado por un lock y observa si el lock
dispara; exits conduce, por cada estado que una receta declara, el acto
publicado que le corresponde; enumeration-reach clasifica si la fuente de
iteración de cada chequeo declarado está acotada o deriva del sujeto.
Los módulos.
| Archivo | Qué hace y por qué existe |
|---|---|
SKILL.md |
La doctrina completa: los nueve subcomandos, sus códigos de salida, la tabla de archivos embarcados y las puertas de decisión. El agente la carga entera antes de moverse. |
scripts/audit_cli.py |
La implementación entera de los nueve verbos, sin dependencias externas. doctrine_side y probe_code_side son las dos derivaciones que nunca se leen entre sí. |
references/usage.md |
Una invocación resuelta por subcomando. |
references/example-report.md |
Un reporte que check-report acepta como válido — la forma contra la que se valida cualquier reporte nuevo. |
references/probes/skill-audit.*.json (ocho archivos) |
Las recetas de autoauditoría: subcommands, structure, first-run, reading-a/reading-b, sensitivity, self-guarded-facts (para inversion), exits, enumeration-reach. skill-audit es el único sujeto con las nueve dimensiones cubiertas. |
references/probes/proposal-deliberation.accepted-operations.json, proposal-implementation.accepted-operations.json |
Recetas roster contra sujetos ajenos, con su propia tabla doctrinal como lado documentado. |
references/probes/remote-execution.accepted-operations.json, remote-execution.smoke-subcommands.json |
Recetas roster contra remote-execution, top-level y anidada en smoke record. Las únicas dos que declaran minInterpreterVersion (ver Los seguros). |
.claude/agents/audit-report.md |
El agente que corre el stretch. Sin Edit; sin stretch: en su frontmatter, porque esta skill no declara un norte al que un agente pueda amarrar una etapa — es legítimo, no un descuido, y así lo dice el propio archivo. |
Los seguros.
- Sin shell, refusal, no degradación. No existe un modo "leer el código y
describirlo" cuando
Bashfalta; eso produciría un documento con apariencia de audit y ninguna autoridad de audit. - Una extracción que no matchea nada, revienta.
rosternunca convierte "no encontré nada" en un conjunto vacío — devolver vacío convertiría cada fila documentada en un phantom finding, una probe rota disfrazada de resultado. - La inhabilidad de mirar nunca comparte código de salida con un hallazgo
vacío. Cada uno de los nueve subcomandos separa explícitamente sus
motivos de exit
2(no pudo mirar) de sus veredictos exit0(miró y reporta, incluso cuando el reporte es "no hay nada"). - El agente no tiene
Edit. "Reporta, nunca repara" es una restricción de herramienta, no una promesa de conducta. - Un piso de intérprete, nombrado en vez de fallar en silencio. Las dos
recetas contra
remote-executioncorrían conargv[0]="python3"a secas; en esta máquina eso resuelve a 3.9.6, yadapter.py:373del sujeto necesita ≥3.10 al importar, así que moría antes de llegar a su propio rechazo yrostercontestaba{"status": "unprobeable"}sin decir por qué. El commit0275fa6lo resolvió con un campo declarado,minInterpreterVersion, queprobe_code_sideusa para buscar enPATHel nombre y sus variantes con sufijo de versión y sustituir la primera que satisface el piso — o fallar nombrando qué versión hacía falta y cuáles encontró. El roster nunca estuvo mal; sólo la selección de intérprete.
Limitaciones conocidas.
Una de nueve skills se audita entera; las otras ocho, a medias. Medido el 2026-09-13
manejando cada receta: sólo remote-execution responde comparison: run —nueve
operaciones del código contra nueve de su tabla, sin sobrantes ni fantasmas—. Las otras
ocho responden comparison: not-run y nombran el motivo, no-closed-roster: sus
documentos declaran el roster en prosa, y la prosa es justo de donde el lado
documentado no puede leerse. Conviene leer bien lo que eso significa: el unregistered: []
que aparece al lado no es un veredicto limpio, es un campo vacío de una comparación que
nunca corrió. El auditor es honesto; lo que falta es la otra mitad del sujeto. Cómo se
arregla: cada SKILL.md publicando su roster en una tabla cerrada — trabajo dentro de
cada skill, no del auditor.
Una receta ata una mutación a un número de línea exacto.
references/probes/skill-audit.self-guarded-facts.json fija la mutación de
inversion en la línea 3873 de audit_cli.py (REPORT_SCHEMA_VERSION = 1 →
= 97). Cualquier edición por encima de esa línea corre el número real y
rompe la receta sin tocar el hecho que dice guardar — ya pasó una vez: el
propio commit 0275fa6, al agregar ochenta líneas más arriba, desplazó el
pin y dejó dos tests en rojo hasta que alguien lo corrigió a mano. La
fragilidad es real y sigue en pie; el commit la nombra explícitamente como
"dejada de pie porque rediseñarla no es este cambio". Cómo se arregla:
que la receta ancle por contenido (el literal REPORT_SCHEMA_VERSION = 1
buscado en el archivo) en vez de por número de línea, o que inversion
resuelva la línea buscando el literal antes de mutar.
Diagrama.
flowchart TD
A["Se nombra un sujeto + una receta --probe-spec"] --> B{"¿Hay shell (Bash)?"}
B -- No --> C["Refusal: nombra Bash, sin reporte ni hallazgo"]
B -- Sí --> D["audit-report arranca en contexto propio"]
D --> E["probe_code_side: corre el argv del sujeto\ncon un nonce, lee su rechazo"]
D --> F["doctrine_side: parsea la tabla\ndoctrinal del sujeto"]
E --> G{"¿Exit code y extracción\ncomo la receta espera?"}
G -- No --> H["unprobeable / exit 2\n(inhabilidad de mirar, no hallazgo)"]
G -- Sí --> I["code = roster derivado del rechazo"]
F --> J["doctrine = roster derivado de la tabla"]
I --> K["Compara code vs doctrine"]
J --> K
K --> L["unregistered / phantom / duplicated / numeralMismatch"]
L --> M["Reporte al dueño del sujeto"]
H --> M
Limitaciones conocidas. La skill de auditoría no prueba contención por
contenido: dos comandos quedaron fuera por presupuesto y siguen fuera —
manifest --root <dir> [--baseline <f>] (huella sha256 por archivo sobre una
carpeta declarada y, contra una foto previa, qué se agregó, se borró y cambió) y
counts --before <f> --after <f> (conteo de tests antes y después, por separado
en las dos suites, con el número que no sube tratado como hallazgo). Mientras no
estén, la prueba de que una carpeta de trabajo quedó limpia y de que
implementations/Domain_Adaptation no se tocó descansa en git status, que sobre
un directorio ignorado sale vacío por construcción: no prueba contención, solo
lo parece; y "la suite quedó verde" sigue valiendo como evidencia de cobertura
agregada, cuando lo que prueba es que el conteo suba. La decisión de diferirlos
está registrada en el cambio archivado the-skill-that-audits-the-others y es
explícita: "The budget decision is made — do not reopen." El cambio de
seguimiento que iba a recogerlos nunca se creó, y esa nota apuntaba a la nada;
este párrafo la reemplaza.
Los comandos del CLI
Todos los comandos corren como papersmith <comando> [opciones] [<dir>]. <dir>
es el directorio actual por defecto y debe apuntar a un workspace inicializado
(salvo init). Flag global: --version.
| Comando | Qué hace |
|---|---|
init |
Crea un workspace de investigación standalone |
upgrade |
Sincroniza el andamiaje del framework preservando tu investigación |
status |
Muestra salud del workspace, propuestas, corridas e inbox |
ingest |
Ingesta un PDF o una URL de literatura a Markdown |
deliberate |
Habla con el motor de proposal-deliberation |
implement |
Delega al harness de proposal-implementation |
run |
Ejecuta un perfil de cómputo (local / Kaggle / Slurm) |
remote |
Empaca, envía y sigue trabajos remotos directamente |
target |
Lista, selecciona y chequea targets de cómputo |
audit |
Audita la estructura y detecta drift de lo generado |
ui |
Sirve el Paper Command Center local (DAG, compuertas, secciones, wiring) |
mcp |
Expone el orquestador y los verbos del paper por Model Context Protocol |
papersmith init <dir>
papersmith init ~/papers/sparse-ae \
--title "Sparse Autoencoder Audit" \
--topic "mechanistic interpretability" \
--remote kaggle
| Flag | Efecto |
|---|---|
--title TEXT |
Título del paper, escrito en los documentos del workspace |
--topic TEXT |
Tema de investigación |
--tools TEXT |
Runtimes a provisionar, separados por coma |
--remote {kaggle,local,slurm} |
Target de cómputo por defecto (por defecto: kaggle) |
--no-npm |
Saltea el npm install best-effort (uso offline/hermético) |
--no-env |
Saltea el aprovisionamiento del entorno Python del workspace; papersmith ui se niega hasta que corras python3 scripts/setup_env.py install |
--progress |
Fuerza un log de progreso en texto plano, con estimación del tiempo restante; útil en CI, donde la barra es ruido pero el log es lo importante |
--no-progress |
No dibuja progreso aunque la salida sea un terminal |
El paso que domina el tiempo es el aprovisionamiento del entorno, y hasta la
0.9.0 corría con la salida capturada: varios minutos sin ninguna señal. Ahora
init dibuja una barra con estimación del tiempo restante en un terminal, e
imprime el log de los dos procesos hijos que ejecuta. La estimación arranca de
unos valores declarados y se recalibra con el ritmo que la propia corrida mide;
si un paso se pasa de su parte, el número sube en vez de contradecirse. Cuando
la salida no es un terminal — una tubería, el servidor MCP, la suite — no se
imprime nada, para no ensuciar una salida que otro programa está leyendo.
Crea .papersmith/, guidance/{paper-guide,reference-papers,data-paper}/,
proposals/, paper/, experiments/, implementations/, kaggle-inbox/,
más la copia del kit y su manifest. La forma completa del árbol está en
El workspace por dentro.
papersmith upgrade [<dir>]
papersmith upgrade ~/papers/sparse-ae
| Flag | Efecto |
|---|---|
--tools TEXT |
Reemplaza los generators de runtime activos |
--force |
Fuerza la escritura de archivos del framework |
--allow-downgrade |
Permite instalar una versión del framework anterior a la que registra el workspace |
--no-migrate |
Sincroniza los archivos del framework sin migrar artefactos; la versión registrada no avanza |
--plan-migrations |
Informa las migraciones de artefactos pendientes y termina sin escribir nada |
Sólo toca archivos gestionados por el framework — jamás guidance/,
proposals/, paper/, experiments/, implementations/, kaggle-inbox/,
papersmith.yaml, package.json, README.md ni los .env*.
Contra un checkout del kit sin reinstalar:
PAPERSMITH_KIT_ROOT=/ruta/al/papersmith-ai papersmith upgrade <dir>.
Migraciones de artefactos
Sincronizar archivos no alcanza. Un release puede cambiar la forma de un
artefacto que el workspace ya produjo, y entonces el andamiaje nuevo queda
leyendo estado viejo sin que nada lo note. Por eso upgrade corre migraciones
de artefactos en un único lugar: después de copiar el kit, porque una migración
puede necesitar los scripts y los assets del release que acaba de llegar, y
antes de escribir .papersmith/version.
De ese orden sale una regla sin excepciones. La versión registrada avanza
cuando las migraciones de esa versión están hechas. Si una falla, el marcador
se queda donde estaba, el comando termina con código distinto de cero y la
corrida siguiente reintenta. --no-migrate tampoco mueve el marcador cuando
algo quedaba pendiente: avanzarlo ahí convertiría el flag en una forma de
registrar un release que tus artefactos nunca alcanzaron, y el version_match
de status reportaría esa mentira como acuerdo.
El manifest es el caso opuesto y se escribe siempre. Describe archivos, y los
archivos sí se movieron; retenerlo haría que status reportara como deriva
todo lo que acababa de sincronizar.
Hay dos clases de migración. Una está atada a una versión y corre una sola
vez, cuando el workspace está por debajo de su compuerta y el kit instalado en
ella o más arriba; queda registrada en .papersmith/migrations.json y no se
repite. La otra es de convergencia: no tiene compuerta, pregunta al
sistema de archivos mediante una sonda de sólo lectura y se evalúa en cada
corrida, porque el release siguiente puede cambiar aquello hacia lo que hay que
converger. Un workspace que ya la satisface no escribe nada.
La versión actual trae dos migraciones de convergencia:
sota-pool-scaffoldcreasota-pool/.gitkeepen los workspaces anteriores al release que empezó a andamiar el pool de grafos.atlas-render-refreshvuelve a renderizarsota-pool/atlas.htmlcuando su atlas, su renderizador o el bundle del visor que inlinea cambiaron. La decisión se toma con hashes guardados ensota-pool/.atlas-render.json, no con fechas de modificación: el mtime de una copia dice cuándo se escribió el archivo, yupgradelo reescribe por razones que nada tienen que ver con la página.
Cuando la versión registrada no se puede ordenar —el marcador se perdió, o
dice algo sin número— una migración atada a versión no se puede ubicar a
ninguno de los dos lados de su compuerta. Se reporta como undetermined y no
se aplica nunca: migrar artefactos bajo una relación que nadie estableció es
exactamente el silencio que el guardia de downgrade ya elimina del lado de los
archivos. Las de convergencia no se ven afectadas, porque preguntan al disco.
--plan-migrations no se llama --dry-run a propósito. Ese nombre sugeriría
que también previsualiza la copia del kit, las proyecciones por harness y la
limpieza de huérfanos, y no lo hace: un flag no debe exagerar su alcance.
Un .papersmith/migrations.json dañado hace que upgrade se niegue antes de
escribir un byte, junto al guardia de downgrade y por el mismo motivo. Leerlo
como vacío volvería a aplicar cada migración que estaba registrando. status
responde distinto a propósito: lo reporta como advertencia, porque un comando
que sólo informa y además revienta deja al workspace sin forma de preguntar
qué le pasa.
papersmith status [<dir>]
papersmith status --json
| Flag | Efecto |
|---|---|
--json |
Snapshot legible por máquina (versiones, propuestas, corridas, inbox) |
Reporta los archivos desviados por nombre; el exit code queda en 0 mientras
hay drift reportado (que sea no-cero es un gap conocido, registrado como
follow-up).
papersmith ingest <file_or_url> [<dir>]
papersmith ingest https://arxiv.org/abs/2309.08600
papersmith ingest ~/Downloads/reference-paper.pdf --ocr
| Flag | Efecto |
|---|---|
--ocr |
Modo de extracción orientado a OCR, balanceado (más pesado, para escaneos) |
Descarga/clasifica la referencia, la extrae a Markdown con ecuaciones LaTeX y archivos de figuras (una carpeta por paper) y refresca el índice. La primera corrida descarga ~1.5 GB de pesos de Surya si el entorno no quedó pre-provisionado.
papersmith deliberate [<dir>]
papersmith deliberate . --action status
papersmith deliberate . --action init
papersmith deliberate . --request-file request.json
| Flag | Efecto |
|---|---|
--action ACTION |
Operación real del motor (status, init, sucesoras…) o alias soportado |
--request JSON / --request-file FILE |
Objeto JSON crudo / archivo que lo contiene |
--serve |
Modo persistente por JSON-lines |
--revision FILE |
Archivo fuente gestionado al que aplica la operación |
--instruction TEXT, --query TEXT |
Payloads de instrucción / consulta |
--selected-entry-id ID, --decisions JSON |
Decisiones de edición resueltas, para operaciones de mutación |
--accept, --acceptance-token TOKEN |
Aceptar un preview de CREATE_SUCCESSOR |
--withdrawal-operation-id ID, --withdrawal-reason TEXT |
Retirar una revisión publicada |
--prior-conclusion TEXT |
Conclusión previa para operaciones de seguimiento |
Corre el motor de deliberación determinista y verificado por AST, keyless y local (ninguna operación de estado llama a un modelo).
papersmith implement [<dir>]
papersmith implement . --action verify --target implementations/demo
papersmith implement . --action probe --target implementations/demo
--action es obligatorio. Acciones comunes: env, plan, apply (alias
materialize), admit, handoff, compose, probe (alias benchmark),
verify. Flags de apoyo: --target, --name, --plan, --finding,
--entry-text, --python, --shards, --revision; lo que venga después de
los flags conocidos se reenvía como extra a implementation_cli.py.
papersmith run <profile> [<dir>]
papersmith run smoke_and_invariants --dry-run
papersmith run sweep_training --target kaggle-gpu-pool --consent <token>
| Flag | Efecto |
|---|---|
--target NAME |
Pisa el target de cómputo del perfil |
--dry-run |
Sólo planifica — registra el comando, no despacha nada |
--shard VALUE |
Corre un solo shard de la campaña |
--consent TOKEN |
Token de consentimiento para envío remoto (se reenvía, se hace cumplir aguas abajo) |
Despacha según el provider (subproceso local / submit a Kaggle / sbatch por
ssh) y agrega cada trabajo a .papersmith/runs_ledger.jsonl.
papersmith remote {pack,push,status,pull,sync} [<dir>]
papersmith remote pack --target kaggle-gpu-pool --entrypoint train.py
papersmith remote status --job <job-id>
papersmith remote pull --job <job-id> --dest ./results
Flags clave: --target, --entrypoint, --backend, --account, --job,
--submission-id, --dest, --consent, --smoke, --unit (alcance de
campaña, repetible), --force, --resolve, más los flags de definición de
trabajo (--service, --job-name, --product, --commit, --repo-url,
--repo-ref, --run-module, --run-function, --clone-path,
--regenerate). Los tokens extra se reenvían a remote_cli.py. Un envío real
gasta cuota: prefiere --smoke y --dry-run primero (ver
Cómputo remoto, con cuidado).
papersmith target {list,set,check}
papersmith target list
papersmith target set kaggle-gpu-pool
papersmith target check
papersmith target check slurm-cluster
list [<dir>]— targets configurados, marcando el activo con*.set <name> [<dir>]— persiste el target por defecto en.papersmith/config.json.check [name] [<dir>]— prueba de conectividad:localbusca Python,kagglepasa por el helper de cuentas,remote-sshintentassh -o BatchMode=yes -o ConnectTimeout=5 <host> true.
papersmith audit [<dir>]
papersmith audit --check-drift
| Flag | Efecto |
|---|---|
--check-drift |
Reporta también el drift de archivos generados como findings (nunca repara) |
Audita la estructura y consistencia del workspace. Hoy está atado al sujeto
skill-audit; el drift se reporta, no se arregla.
Nota:
kaggle-accounts(validate/remove/list/discover/materialize) no tiene todavía un subcomandopapersmith— corre comopython3 skills/kaggle-accounts/scripts/accounts_cli.py …o indirectamente víatarget check.
papersmith ui [<dir>]
papersmith ui --port 8080
papersmith ui ~/papers/sparse-ae --no-browser
papersmith ui --export-static ./dashboard-static
| Flag | Efecto |
|---|---|
--host HOST |
Dirección de bind (por defecto 127.0.0.1) |
--port PORT |
Puerto preferido; si está ocupado pasa al siguiente libre |
--no-browser |
No abre el navegador |
--export-static <dir> |
Copia el build del dashboard a <dir> y sale |
--allowed-host HOST:PORT |
Acepta además ese encabezado Host (repetible); solo se reenvía al backend si lo das |
Sirve el Paper Command Center: un dashboard local dentro del workspace con
sus dos flujos, las cuatro compuertas de calidad, la matriz de las diez
secciones y la salud del wiring de harnesses. Se actualiza en vivo por
Server-Sent Events, con un debounce de 300 ms sobre sections/, openspec/,
experiments/ y papersmith.yaml.
El backend vive en skills/_core/command_center/ (parte del kit, así que viaja
con cada workspace) y corre standalone sin Node/npm:
python -m skills._core.command_center.server --port 8080
Los dos flujos. La pestaña Pipeline dibuja el flujo general: los doce
tramos del diagrama de arquitectura, en su orden, más la vía transversal de
auditoría donde aterrizan las cuatro compuertas. Cada tramo se enciende con lo
que hay en disco —sota-pool/, guidance/, proposals/, implementations/,
.experimental-deliberation/, experiments/, el store de credenciales, el
inbox, paper/Figures/ y sections/— y un tramo sin artefacto se reporta
inactivo diciendo qué falta, nunca con un progreso inventado. La pestaña
Writing dibuja el flujo de redacción: una sección por nodo, en orden de
renderizado, cada una con su estado, sus palabras y sus bloques. Las aristas no
viven en el frontend: el extractor las manda en el payload (pipeline_chain y
gate_links), de modo que el tablero no puede contradecir al diagrama.
Endpoints: /api/state, /api/health/wiring, /api/health/run-wiring-smoke
(POST) y /api/events (SSE); el build del dashboard se sirve en /. El
frontend se construye con npm run setup:ui una vez (instala desde el
lockfile) y después npm run build:ui, que emite en
skills/_core/command_center/static/; npm run dev:ui levanta el servidor de
Vite con proxy al backend para trabajar sobre la interfaz. El paso de
instalación usa npm ci y no npm install a propósito: install reescribe el
lockfile con los metadatos del npm local, y ese ruido no pertenece a ningún
cambio de dependencias. El smoke end-to-end
es bash scripts/command-center-smoke.sh.
Tema claro. El dashboard usa un único tema claro, definido por tokens de
color en ui/src/styles.css; un test de Vitest rechaza cualquier color
literal fuera de :root y verifica el contraste (WCAG AA) de los pares de
tokens.
Diagrama clicable y deep links. Cada nodo (etapa, compuerta, sección) y cada
arista del diagrama del pipeline se puede seleccionar con el mouse o con el
teclado (Enter o Espacio sobre el elemento enfocado, Escape para cerrar) y abre
un panel de detalle con sus datos, sus conexiones y su historial. La selección
queda en la URL (#pipeline?el=gate%3Awriting-readiness), así que un enlace
abre directamente ese elemento.
Historial en memoria. La pestaña History muestra lo que cambió desde que
arrancó el dashboard: estado y bloques de las secciones, conteo de palabras,
compuertas, etapas, salud del wiring y el smoke. Vive en memoria (un anillo de
1.000 entradas por proceso; nada se escribe a disco), así que se pierde al
reiniciar el servidor. GET /api/history?boot_id=<id>&after_seq=<n> entrega
páginas de entradas más antiguas primero (element, kind y limit son
opcionales; un element o kind inválido responde 422); la respuesta trae
has_more, reset (otro proceso: hay que recargar) y gap (se descartaron
entradas antiguas). Las entradas nuevas llegan por SSE como history_append.
Lista de hosts permitidos. En un bind a loopback el servidor solo acepta los
encabezados Host 127.0.0.1:<puerto>, localhost:<puerto> y [::1]:<puerto>
y responde 421 a cualquier otro, lo que frena páginas de DNS-rebinding. Para
aceptar otro host (por ejemplo al reenviar un puerto) usa el flag repetible
papersmith ui --allowed-host HOST:PORT. Con npm --prefix ui run dev el proxy
de Vite conserva el Host localhost:5173; arranca el backend así (el módulo
acepta el mismo flag):
python -m skills._core.command_center.server --port 8080 --allowed-host localhost:5173
Vista previa del paper (solo lectura). Dos rutas GET, sin escrituras:
GET /api/paper/preview devuelve las secciones en el mismo orden e ids que
/api/state, con el texto plano de cada bloque de paper/main.tex (marcas
%% paper-writing block <id> begin|end), sus claves \cite{...}, palabras,
written y banderas truncated/duplicate, más el estado del PDF
(pdf.main con stale si main.tex es más nuevo que main.pdf, y la lista de
figuras; ambos llevan mtime en milisegundos, que la UI añade como ?v= para
recargar el PDF y las miniaturas al recompilar) y sections_dir (status:
ok, absent, unsafe si sections/ es un symlink que sale del workspace, y
truncated si hay más de 100 secciones no canónicas; las canónicas siempre se
resuelven por nombre). Las regiones de declaraciones y procedencia se ignoran; un bloque sin
cierre se descarta y, con ids repetidos, gana el primero (marcado duplicate).
GET /api/paper/file?name=<n> sirve solo main.pdf (paper/main.pdf) y
figures/<id>.pdf|png (el directorio real es paper/Figures/, con F
mayúscula). Regla del id: un solo segmento [A-Za-z0-9._-], sin separadores,
NUL ni punto inicial, hasta 100 caracteres (más estricta que paper_figure.py).
Respuestas: 400 nombre mal formado, 404 ausente o no permitido, 413 sobre 25 MiB;
encabezados fijos Content-Type por extensión, nosniff, inline,
X-Frame-Options: SAMEORIGIN y no-store. No lleva CSP sandbox (rompería el
visor de PDF) y no admite Range (se envía el archivo completo). Límites de
lectura: main.tex 2 MiB (si lo supera, estado too_large y sin contenido),
cada contrato de sección 256 KiB, 32 KiB de texto por bloque y 200 KiB de texto
en total (con truncated). Cada lectura resuelve la ruta, exige que quede dentro
del workspace (un symlink que escapa da unsafe/404) y que sea un archivo
regular. papersmith no genera main.pdf: que no exista es lo normal. La
protección Host (421) cubre estas rutas solo en un bind a loopback; con un bind
no loopback no hay filtrado de Host.
Pestaña Preview. Muestra, en una sola vista, el texto de los bloques
escritos y el PDF compilado. A la izquierda van las secciones en orden con su
estado; cada bloque se pinta como texto plano escapado (nunca como HTML: un
<b> o un <script> en main.tex se ve literal), las claves \cite{...}
salen como chips, los bloques sin escribir muestran un marcador con su id y se
avisa de cada truncado o id duplicado, de main.tex ausente, demasiado grande,
inseguro o ilegible, y de "Preview truncated" cuando se alcanza el tope de la
respuesta. A la derecha, si existe paper/main.pdf, se incrusta en un <iframe>
con una insignia stale cuando main.tex es más nuevo que el PDF; si no existe
se explica que papersmith no compila el paper completo y que debes construirlo
tú. Debajo se listan las figuras de paper/Figures/ con enlace Open (los PNG
con miniatura). Se carga al abrir la pestaña (#preview) y se vuelve a pedir
cuando cambia el estado del workspace por SSE, sin solapar peticiones; si falla
muestra el error con Retry.
Atlas SOTA (solo lectura, backend). Dos rutas GET sobre
sota-pool/atlas.json y sota-pool/atlas.html (los escribe la skill
plausibility; el dashboard nunca los escribe ni los regenera).
GET /api/atlas devuelve json y html con su estado (ok, absent,
too_large, unsafe, y para el JSON también unreadable o invalid) y tamaño,
stale (atlas.json más nuevo que atlas.html; null si falta alguno),
mtime en milisegundos (la UI lo añade como ?v= al iframe para recargarlo),
validation (ok, failed con errors, hasta 50, o unavailable con
detail) y summary (sistemas con id, título, número de planetas y familias;
número de enlaces; conteo por rel; evidencia con sistema, planeta, origen y
fecha, hasta 100) o null. Un JSON inválido da json.status: invalid y
validation.status: failed, nunca un 500. GET /api/atlas/view sirve los bytes
de atlas.html (404 si falta, es inseguro o no es un archivo regular; 413 sobre
2 MiB) con Content-Type: text/html; charset=utf-8, Content-Security-Policy: sandbox allow-scripts; default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; img-src data:, nosniff, no-store,
X-Frame-Options: SAMEORIGIN y Referrer-Policy: no-referrer. Cada lectura
resuelve la ruta, exige que quede dentro del workspace (un symlink que escapa es
unsafe) y que sea un archivo regular, y comprueba el tamaño (2 MiB para cada
archivo) antes de leer. Ejecución de Python del workspace: para validar, el
servidor carga skills/plausibility/scripts/check_atlas.py del propio workspace
(ruta fija, nunca derivada de la petición) y llama a su validador dentro del
proceso del servidor, sin escribir bytecode; es una superficie de ejecución de
código, así que no apuntes el dashboard a un workspace en el que no confíes. Si
el script falta, no se puede cargar o falla, validation.status es
unavailable con el motivo. La protección Host (421) cubre estas rutas solo en
un bind a loopback; con un bind no loopback no hay filtrado de Host.
Pestaña Atlas. #atlas consume las rutas anteriores. A la izquierda, un
resumen nativo (texto siempre escapado): tabla de sistemas con planetas y
familias, número de enlaces y desglose por rel, una lista de evidencia acotada
("showing N of evidence_total"), y un chip de validación (valid; failed con
los primeros errores y "N more"; unavailable con el motivo). A la derecha, la
constelación: atlas.html dentro de un <iframe src="/api/atlas/view" sandbox="allow-scripts" referrerpolicy="no-referrer">, sin allow-same-origin
(el HTML corre en un origen opaco y no puede leer el dashboard). Los estados se
explican en pantalla: sin atlas ("No atlas yet. Run the plausibility skill..."),
atlas.json demasiado grande, inseguro, ilegible o inválido, "JSON present, HTML
missing" (hace falta scripts/render_atlas.py), HTML sin JSON, y una insignia
STALE cuando atlas.json es más nuevo que atlas.html. Los archivos del atlas
no se vigilan: se piden al abrir la pestaña y con el botón Refresh (sin
solapar peticiones; si falla muestra el error con Retry y conserva los datos
anteriores). Nota: Chromium no pinta un iframe aislado que queda fuera del
viewport en capturas --full-page (a 700x900 sale en blanco); dentro del
viewport se ve completo.
Línea de decisiones (solo lectura, backend). GET /api/decisions mezcla, al
pedirla, eventos normalizados {ts, source, kind, summary, ref} de registros que
el workspace ya guarda; no escribe nada ni guarda instantáneas. Fuentes:
declarations (la región %% paper-writing declarations de paper/main.tex;
ts es recorded, y esa región solo guarda el estado ACTUAL: los valores
anteriores no se pueden recuperar), proposal y experiment (receipts/*.json
y lifecycle/v1/transitions/*.json de .proposal-deliberation/ y
.experimental-deliberation/) y remote-execution (los ledger.jsonl de
implementations/<repo>/<Nombre>/.remote-execution/, descubiertos exactamente a
dos niveles, sin recursión; kaggle-inbox/ nunca se lee). Los recibos de
edición no tienen marca de tiempo: sus eventos llevan ts: null, van
después de los fechados (por nombre de archivo) y nunca se usa el mtime; el
recibo de la revisión inicial (CREATE_INITIAL_REVISION) sí trae createdAt, que
se usa como ts ("Created initial revision r01"). Las
transiciones usan committedAt; cada resultado (COMMITTED,
ALREADY_COMMITTED, REJECTED, INCONSISTENT, RECOVERY_REQUIRED) tiene su
resumen. Todo se lee como JSON plano sin repetir las comprobaciones de hash y
consistencia del ciclo de vida, así que esos eventos llevan verified: false
(la UI los marcará como unverified). Orden: fechados de más nuevo a más
antiguo, desempate estable por fuente; un ts ilegible cuenta como null.
Cada fuente devuelve status (ok, absent, unreadable, too_large) y
count en sources, de modo que una fuente rota no rompe la respuesta. Topes:
500 eventos por fuente y 1000 en total (truncated y total), main.tex 2 MiB,
256 KiB por recibo o transición, 500 archivos por directorio del sidecar,
20 ledgers de hasta 2 MiB y 2000 líneas cada uno (se conservan las 2000 líneas
más recientes y detail cuenta las más antiguas ignoradas; las líneas mal
formadas se omiten y se cuentan en detail). Filtros opcionales: source= (lista separada
por comas; 422 si hay un nombre desconocido) y limit= (1 a 1000). Cada lectura
resuelve la ruta, exige que quede dentro del workspace, un archivo regular y
comprueba el tamaño antes de leer. No hay campo de autor: nunca se registra. La
protección Host (421) cubre la ruta solo en un bind a loopback.
Pestaña Decisions. #decisions consume /api/decisions al abrirse y con el
botón Refresh (las fuentes no se vigilan; sin solapar peticiones; error en
línea con Retry, conserva los datos anteriores). Muestra los eventos tal como
llegan (fechados de más nuevo a más antiguo; después, bajo el subtítulo
Undated, los recibos de edición sin marca de tiempo). Cada fila lleva la fecha en texto
UTC (AAAA-MM-DD HH:MM:SS UTC), una etiqueta de fuente (declarations, proposal,
experiment, remote ledger), el tipo, el resumen y la ruta ref como texto (no
es un enlace). Las filas con verified: false muestran unverified con el
título "Read from the lifecycle files without hash or consistency checks." y las
que traen note la muestran en gris (las declaraciones solo guardan el valor
actual). Arriba, un chip de estado por fuente (ok, absent, unreadable,
too_large, con su conteo, detail y la marca truncated), casillas para
filtrar por fuente en el cliente y, si la respuesta se truncó, "Showing N of
total". Todo el texto se escapa. No hay enlace a la pestaña Sections: la
pestaña no tiene anclas por elemento y el id de una declaración no permite
derivar la sección sin heurísticas, así que se omite a propósito.
Verificador visual. scripts/command-center-visual-check.mjs abre el
dashboard en Chromium, saca capturas de cada pestaña a 1280x800, 1600x1000 y 700x900 (hoja inferior) y
falla ante errores de consola:
node scripts/command-center-visual-check.mjs --out /tmp/shots --full-page --click-check --drag-check
| Flag | Efecto |
|---|---|
--out <dir> |
Carpeta de capturas (obligatoria) |
--url <url> |
Dashboard a revisar (por defecto http://127.0.0.1:8099/) |
--tabs a,b / --viewports 1280x800,... |
Acota pestañas o tamaños |
--full-page |
Captura la página completa, no solo el viewport |
--drag-check |
Verifica que arrastrar el lienzo no abre el panel |
--click-check |
Hace clic en una etapa, una compuerta, una sección y una arista, y prueba el teclado |
--timeout <s> / --chromium <path> |
Tiempo máximo y ejecutable de Chromium |
Es una herramienta de desarrollo (necesita Chromium); no corre en CI. Los tests
de la UI (npm --prefix ui test) sí corren en CI.
papersmith mcp {serve,inspect,print-config}
papersmith mcp serve --workspace ~/papers/sparse-ae
papersmith mcp inspect
papersmith mcp print-config --workspace ~/papers/sparse-ae
Expone el orquestador del workspace y los verbos de paper-writing a cualquier
cliente del Model Context Protocol sobre stdio (JSON-RPC 2.0).
serve --workspace <dir>— arranca el servidor MCP sobrestdio, enlazado a un único workspace.inspect— imprime el catálogo de capacidades en JSON (fuente de verdad).print-config [--workspace <dir>]— emite el fragmento de configuración para el host que estés configurando (Claude Code, OpenCode).
El catálogo completo de 38 herramientas (15 de sólo lectura y 23 mutantes), los recursos papersmith:// y el modelo de seguridad están documentados en detalle en docs/mcp.md.
Cómputo remoto, con cuidado
Antes de que un trabajo toque un worker real, la forja te da una escalera de ensayos baratos. Bajala en orden; no la saltees:
papersmith target list— qué targets hay y cuál está activo.papersmith target check [nombre]— ¿responde ese target?papersmith run <perfil> --dry-run— planifica y registra el comando en el ledger; no despacha nada.papersmith remote … --smoke— ensayo real contra el backend, sin gastar campaña.- Solo entonces, la corrida real (
--consent <token>cuando corresponda).
Los targets y los perfiles son los de tu papersmith.yaml (ver
El workspace por dentro). El ledger
.papersmith/runs_ledger.jsonl es append-only: cada corrida despachada queda
registrada con su target, su perfil y su resultado. Y papersmith remote status / pull es la memoria de lo que se mandó afuera y lo que volvió — el
detalle de ese contrato vive en la sección de remote-execution de la
Anatomía.
Credenciales y backends. remote-execution soporta múltiples backends (kaggle, colab):
- Kaggle: corre contra el pool de workers configurados. Sus credenciales viven en el store de
kaggle-accounts(skills/kaggle-accounts/store/accounts.json), nunca salen del disco y nunca se imprimen:materializeescribe el token de un worker a un archivo de texto y te dice dónde, jamás qué./kaggle-accountsvalida o remueve cuentas, ypapersmith target check kagglelas prueba de costado. Un subcomandopapersmith accountstodavía no existe (follow-up registrado): se invoca comopython3 skills/kaggle-accounts/scripts/accounts_cli.py {list,discover,validate,remove,materialize}. - Colab: corre contra sesiones administradas por
google-colab-cli(papersmith remote push --backend colab ...). No es un target de workspace (no se pasa ainit --remoteni atarget check), sino un backend directo deremote-execution. Requiere tenergoogle-colab-clienPATHy el token resuelto en~/.config/colab-cli/token.json.
Cuota real. Un envío real gasta cuota y horas de GPU. La doctrina de la casa
es simple: todo lo que se pueda ensayar con --dry-run y --smoke, se ensaya;
lo que quede después de eso, lo decides tú.
Arquitectura
papersmith-ai/
├── docs/ # documentación complementaria (MCP server)
├── src/papersmith/ # el CLI orquestador (Python) y sus bridges de runtime
│ ├── core/ # init, status, ingest, upgrade, executor, ledger
│ ├── bridges/ # Node, Python, deliberación, ejecución remota
│ └── mcp/ # servidor stdio Model Context Protocol
├── skills/ # árbol canónico de skills, proyectado a los harnesses
│ ├── _core/ # motores compartidos de deliberación e implementación
│ ├── paper-ingestion/ # Marker + Surya OCR + llama-server
│ ├── proposal-deliberation/ # motor TypeScript: verificación por AST y máquina de estados
│ ├── proposal-implementation/# generación de código reproducible y sus tests
│ ├── experimental-deliberation/ # segundo dominio; mismo motor, otro norte
│ ├── experimental-implementation/ # segundo huésped de implementación; dos documentos, un motor
│ ├── paper-writing/ # contratos de sección → PDF, sin asumir nada
│ ├── remote-execution/ # despacho a cómputo distribuido (Kaggle T4/P100 / local)
│ ├── kaggle-accounts/ # guardián de credenciales: identidades, nunca valores
│ └── skill-audit/ # meta-auditor de superficies y promesas
├── guidance/ # papers de referencia y lineamientos de dominio
├── tests/ # suites Node.js + pytest (meta-auditorías incluidas)
└── scripts/ # provisión de entorno y setup de harnesses
Garantías de ingeniería
- Invariantes de AST y fronteras: las ecuaciones y secciones se siguen por IDs estables y cadenas de revisión inmutables. No hay borrados, reubicaciones ni fusiones de frontera no autorizadas.
- Keyless y local-first: la ingesta corre completamente offline y local, con
Marker y
llama.cpp. - Proyección agnóstica de harness: el árbol canónico
skills/se proyecta a.claude/skills,.pi/skills,.opencode/skillsy.agents/skills.
Desarrollo y pruebas
Son dos suites, no una. Correr una sola y dar un veredicto es un error que este repositorio ya cometió, así que las dos van aquí con su invocación exacta:
npm test # motor compartido de deliberación
.venv/bin/python -m pytest # todo lo demás
npm run test:all # las dos, en orden
Bucle rápido, no compuerta. npm run test:fast (python3 scripts/fast_tests.py)
corre los tests que tocan tu trabajo sin commitear (staged, unstaged y
untracked; los archivos no-código como .md o .json no seleccionan nada) más
un conjunto fijo de guardas. Las guardas solas tardan unos 40 segundos y la
selección se suma a eso. Tres tests lentos medidos se deseleccionan por defecto
(--with-slow los incluye; la compuerta completa siempre los corre).
--since REF suma lo ya commiteado desde REF, --list muestra la selección, su
base y por qué, y --guards-only corre solo las guardas. Es una heurística
textual que puede perder tests que ejercitan un módulo de forma indirecta:
no reemplaza a npm run test:all, que sigue siendo lo que se corre antes de
integrar.
Python en paralelo. npm run test:py:par corre la misma suite con
pytest -n 8 --dist loadfile (necesita pytest-xdist, que setup_env.py ya
provisiona). Medido el 2026-10-01: 5455 passed en 5 min 9 s contra 10 min 17 s
en serie. Solo loadfile es seguro: load y loadscope compiten por el
directorio compartido implementations/. Un único test (esperar a un proceso
matado, 120 s) fija el piso de tiempo.
npm run typecheck corre tsc sobre el motor de deliberación y sale limpio.
Dos trampas que parecen defectos del repositorio y no lo son.
El script de npm fija el perfil de dominio. npm test exporta
DELIBERATION_DOMAIN_PROFILE antes de invocar node --test. Llamar node --test a secas falla cerrado, porque el motor no sirve ningún dominio propio y
se niega a arrancar sin uno. Es la invocación la que está mal, no el motor. Lo
mismo vale del lado Python: los motores de implementación exigen
IMPLEMENTATION_DOMAIN_PROFILE, y por eso el CLI que los lanza lo fija — y por
eso tests/domain_profile.py lo siembra y lo restaura por import.
tests/ no tiene __init__.py. La forma que uno escribiría de memoria,
discover -s tests -t ., muere con ImportError: Start directory is not importable. La forma que funciona es la de arriba, sin -t ..
Qué prueba cada cosa. tests/ cubre el tooling de la forja: los dos
motores compartidos, los once SKILL.md y sus scripts, la atadura entre cada
skill y sus agentes en las dos direcciones, y una serie from-scratch que
genera un workspace con el CLI y recorre cada comando, cada skill embarcada y
cada subagente dentro de él (tests/test_workspace_{commands,skills,agents}_e2e.py).
Los tests de una implementación materializada viven en su repositorio destino y
en ningún otro lado; borrarlo borra sus tests.
Un ensayo live, opt-in. Todo lo de arriba es hermético. Para probar los
motores reales —npm install, provisión de entorno, una ingesta y un agente—
está scripts/cli-paper-live-smoke.sh, que se niega a correr sin
PAPERSMITH_LIVE=1 (y acepta PAPERSMITH_LIVE_INGEST=<pdf> y
PAPERSMITH_LIVE_AGENT=<nombre> para extender el recorrido).
Dos corpus sellados. tests/seal/ y tests/experiments_seal/ guardan la
salida exacta de una batería de comandos como digests dorados. No son tests de
unidad: existen para que un cambio en el motor compartido no altere en silencio
lo que cada host produce. Valen lo que cuestan — durante el último arreglo,
cuatro digests se movieron y dijeron que la corrección era equivocada, en una
dirección que ningún test de unidad veía.
proposal-implementation se validó además con un arnés de escenarios propio
—las situaciones en las que puede caer un clon, más el ciclo completo hasta
publicar la revisión que produce un hallazgo— construido para cerrar la v1.5 y
retirado al cerrarla. Era andamiaje, no parte de la forja. Queda en la historia:
el commit que lo saca lo dice, y git revert lo trae de vuelta si alguna vez
hay que volver a correrlo.
Solución de problemas
"La skill se negó a arrancar." Es el comportamiento esperado: cada skill declara qué necesita antes y sale con un código de rechazo que nombra lo que falta, en vez de inventarlo o saltearlo. Lee el código como una lista de tareas — dice exactamente qué entrada falta. No es un error tuyo.
"node --test a secas falla." Por diseño: el motor de deliberación se niega
a servir sin un perfil de dominio. Usa npm test (que lo fija) o el CLI de la
skill. Lo mismo vale para los motores de implementación:
IMPLEMENTATION_DOMAIN_PROFILE_REQUIRED significa que los estás invocando
directo en vez de a través de implementation_cli.py, que es quien los arranca
con su perfil.
"El workspace no tiene .claude/skills." Es el estado por defecto: un
workspace embarca el árbol skills/ completo y .claude/agents/, y sus
documentos de routing apuntan al árbol canónico skills/*/SKILL.md; los
agentes leen las skills desde ahí. Si quieres que tu harness las liste como
comandos /, ejecuta npm run setup:harnesses dentro del workspace: crea las
proyecciones de los cuatro harness, relativas e idempotentes.
"papersmith audit reporta drift." Algún archivo generado cambió respecto
del manifest. No lo edites a mano: ejecuta papersmith upgrade (y --force si
hace falta) para volver a la versión del framework.
"No puedo correr papersmith accounts." Todavía no existe (follow-up
registrado). Usa
python3 skills/kaggle-accounts/scripts/accounts_cli.py {list,discover,validate,remove,materialize}.
"status sale con código 0 aunque haya drift." Gap conocido y registrado:
hoy el exit code no es estricto. Para un gate de CI, mira el JSON (--json) en
vez del código de salida.
"La primera ingesta descarga ~1.5 GB." Son los pesos de Surya si el entorno
no quedó pre-provisionado. papersmith init ya provisiona el entorno salvo que
hayas pasado --no-env; en ese caso ejecuta python3 scripts/setup_env.py install
antes de la primera ingesta.
Documentación relacionada
- Cada skill, su contrato literal:
skills/<nombre>/SKILL.md— la fuente de verdad de lo que una skill acepta y promete. - Contexto del proyecto para agentes:
openspec/project-context.md. - Lineamientos de dominio:
guidance/paper-guide/— la drop-zone queproposal-deliberationcarga como contexto al inicio de cada deliberación. - Provisión del entorno y de los harnesses:
scripts/setup_env.pyyscripts/setup-harnesses.sh. - Suites y gates:
tests/y los scripts depackage.json(test,test:all,typecheck). - Servidor MCP y catálogo de herramientas: docs/mcp.md.
- Diagrama del flujo (comandos, skills y agentes en Pi): docs/diagrams/papersmith-pi-flow.html.
Licencia
Apache-2.0 — el texto completo está en LICENSE.
Metadata
Release files for papersmith-ai 0.13.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| papersmith_ai-0.13.0.tar.gz | 3.7 MB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| papersmith_ai-0.13.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 6.0 MB
Release files / papersmith_ai-0.13.0.tar.gz
| Download URL | papersmith_ai-0.13.0.tar.gz |
|---|---|
| Size | 3.7 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
b8fd2080585a48ed29b8dace1b96a8a8a5e05c00efc64383b234aeae6f99b2ed
|
|
BLAKE2b-256 checksum How to use checksums |
7a63f504ad891e4e52043bcf2800830a40ff9913c1451bc0fa3ae2504ab9ad13
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 11, 2026.
Transparency logRelease files / papersmith_ai-0.13.0-py3-none-any.whl
| Download URL | papersmith_ai-0.13.0-py3-none-any.whl |
|---|---|
| Size | 2.3 MB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a3397545166b9667c3a2f9ead30384d510ff3db11be4238ab1b2648612fc8203
|
|
BLAKE2b-256 checksum How to use checksums |
f49c225c3c1b96b023a46352f942e4dc89d0a77e4591f56f5c95907ef128e25e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 11, 2026.
Transparency log