ontoref/assets/presentation/jj_slides.md
Jesús Pérez 82a358f18d
Some checks failed
Nickel Type Check / Nickel Type Checking (push) Has been cancelled
Rust CI / Security Audit (push) Has been cancelled
Rust CI / Check + Test + Lint (push) Has been cancelled
feat: #[onto_mcp_tool] catalog, OCI credential vault layer, validate ADR-018 mode hierarchy
ontoref-derive: #[onto_mcp_tool] attribute macro registers MCP tool unit-structs in
  the catalog at link time via inventory::submit!; annotated item is emitted unchanged,
  ToolBase/AsyncTool impls stay on the struct. All 34 tools migrated from manual wiring
  (net +5: ontoref_list_projects, ontoref_search, ontoref_describe,
  ontoref_list_ontology_extensions, ontoref_get_ontology_extension).

  validate modes (ADR-018): reads level_hierarchy from workflow.ncl and checks every
  .ncl mode for level declared, strategy declared, delegate chain coherent, compose
  extends valid. mode resolve <id> shows which hierarchy level handles a mode and why.
  --self-test generates synthetic fixtures in a temp dir for CI smoke-testing.

  validate run-cargo: two-step Cargo.toml resolution — workspace layout first
  (crates/<check.crate>/Cargo.toml), single-crate fallback by package name or repo
  basename. Lets the same ADR constraint shape apply to workspace and single-crate repos.

  ontology/schemas/manifest.ncl: registry_topology_type contract — multi-registry
  coordination, push targets, participant scopes, per-namespace capability.

  reflection/requirements/base.ncl: oras ≥1.2.0, cosign ≥2.0.0, sops ≥3.9.0, age
  ≥1.1.0, restic declared as Hard/Soft requirements with version_min, check_cmd, and
  install_hint (ADR-017 toolchain surface).

  ADR-019: per-file recipient routing for tenant isolation without multi-vault. Schema
  additions: sops.recipient_groups + sops.recipient_rules in ontoref-project.ncl.
  secrets-bootstrap generates .sops.yaml from project.ncl in declarative mode. Three
  new secrets-audit checks: recipient-routing-coherent, recipient-routing-coverage,
  no-multi-vault. Adoption templates: single-team/, multi-tenant/, agent-first/.
  Integration templates: domain-producer/, mode-producer/, mode-consumer/.

  UI: project_picker surfaces registry badge (⟳ participant) and vault badge
  (⛁ vault_id · N, green=declarative / amber=legacy) per project card. Expanded panel
  adds collapsible Registry section with namespace, endpoint, and push/pull capability.
  manage.html gains Runtime Services card — MCP and GraphQL toggleable without restart
  via HTMX POST /ui/manage/services/{service}/toggle.

  describe.nu: capabilities JSON includes registry_topology and vault_state per project.
  sync.nu: drift check extended to detect //! absence on newly registered crates.
  qa.ncl: six entries — credential-vault-best-practice (layered data-flow diagram),
  credential-vault-templates (paths A/B/C), credential-vault-troubleshooting (15 named
  errors), integration-what-and-why (ADR-042 OCI federation), integration-how-to-implement,
  integration-troubleshooting.

  on+re: core.ncl + manifest.ncl updated to reflect OCI, MCP, and mode-hierarchy nodes.
  Deleted stale presentation assets (2026-02 slides + voice notes).
2026-05-12 04:46:15 +01:00

1188 lines
32 KiB
Markdown

---
theme: default
title: jj + Radicle — VCS para la era de los agentes
titleTemplate: '%s'
layout: cover
keywords: jj,Jujutsu,Radicle,VCS,Git,AI,Agents,ontoref,vapora
download: true
exportFilename: jj-radicle-agents
monaco: true
remoteAssets: true
selectable: true
record: true
colorSchema: dark
lineNumbers: false
themeConfig:
primary: '#f74c00'
fonts:
mono: 'Victor Mono'
drawings:
enabled: true
persist: false
presenterOnly: false
syncAll: true
scripts:
- setup/image-overlay.ts
class: 'justify-center flex flex-cols'
---
<h1 class="pl-27 font-medium">jj + Radicle</h1>
<h2 class="flex justify-center mt-3 font-medium text-orange-400">
VCS para la era de los agentes
</h2>
<div class="flex justify-center mt-3 text-gray-400 text-lg italic">
Workspaces aislados. Parches soberanos. Sin staging area.
</div>
<div class="flex justify-center">
<img class="mt-8 w-40" src="/jesusperez_w.svg">
</div>
<style scoped>
h1, h2, p { z-index: 10; }
</style>
<Footer />
<!--
Abrir con calma. La audiencia probablemente usa git todos los días.
Vamos a cuestionar cosas que asumen como inamovibles.
-->
---
layout: two-cols
---
<h1 class="w-300"> El problema con Git + agentes IA </h1>
<div>
**Fricción acumulada**
<div class="mt-3 space-y-3">
<div class="flex items-start gap-2">
<span class="text-red-500 font-bold text-sm mt-1">🔴</span>
<div><span class="text-gray-200 font-semibold">Staging area compartida</span><br><span class="text-gray-400 text-sm">Humano y agente compiten por el índice.<br> Un <code>git add</code> pisotea al otro.</span></div>
</div>
<div class="flex items-start gap-2">
<span class="text-red-500 font-bold text-sm mt-1">🔴</span>
<div><span class="text-gray-200 font-semibold">Worktrees frágiles</span><br><span class="text-gray-400 text-sm">Git worktrees necesitan branches nombradas.<br> Colisionan. No escalan a N agentes.</span></div>
</div>
<div class="flex items-start gap-2">
<span class="text-orange-400 font-bold text-sm mt-1">🟠</span>
<div><span class="text-gray-200 font-semibold">Centralización invisible</span><br><span class="text-gray-400 text-sm">GitHub/GitLab son el single point of failure.<br> Tu código, sus reglas.</span></div>
</div>
</div>
</div>
::right::
<div class="mt-24 ml-4">
<div class="flex items-start gap-2">
<span class="text-orange-400 font-bold text-sm mt-1">🟠</span>
<div><span class="text-gray-200 font-semibold">Push-to-review es un cuello de botella</span><br><span class="text-gray-400 text-sm">El agente necesita push + PR + aprobación.<br> ¿Le das acceso de escritura al remoto?</span></div>
</div>
<div class="flex items-start gap-2 mt-3">
<span class="text-yellow-400 font-bold text-sm mt-1">🟡</span>
<div><span class="text-gray-200 font-semibold">Merge hell con N actores</span><br><span class="text-gray-400 text-sm">3 agentes + 1 humano = conflictos serializados.<br> Alguien siempre pierde.</span></div>
</div>
<v-click>
<div class="mt-6 border-l-4 border-orange-500 pl-4 text-sm text-gray-300 leading-6">
Git fue diseñado para <em>humanos</em> que trabajan en <em>branches</em>.<br><br>
Hoy tenemos <strong>N actores concurrentes</strong> (humanos + agentes) que necesitan <strong>aislamiento real</strong>, no convenciones.
</div>
</v-click>
<v-click>
<div class="mt-1 font-mono text-xs text-gray-500 bg-gray-900 p-3 rounded">
```text
Agente-1: git stash → trabaja → git stash pop
Agente-2: git stash → trabaja → CONFLICTO
Humano: "¿quién tocó mi working tree?"
```
</div>
</v-click>
</div>
<Footer />
<!--
No es que Git sea malo. Es que fue diseñado para un mundo con un actor por working copy.
Hacer la pregunta: "¿cuántos de vosotros habéis tenido conflictos con el copiloto o un agente?"
-->
---
layout: default
---
# Jujutsu (jj) — el modelo mental
<div class="grid grid-cols-2 gap-8 mt-4">
<div>
**Cambio fundamental: no hay staging area**
<div class="text-sm text-gray-300 space-y-3 mt-3">
<div class="border-l-2 border-blue-500 pl-3">
<span class="text-blue-300 font-semibold">Working copy IS a commit</span><br>
<span class="text-gray-400">En jj, <code>@</code> ya es un commit. Cada save es una snapshot automática. No hay <code>git add</code>, no hay índice.</span>
</div>
<div class="border-l-2 border-blue-500 pl-3">
<span class="text-blue-300 font-semibold">Changes, no commits</span><br>
<span class="text-gray-400">jj trabaja con <em>change IDs</em> estables. Reescribir historia no cambia el ID del cambio — solo su commit ID muta.</span>
</div>
<div class="border-l-2 border-blue-500 pl-3">
<span class="text-blue-300 font-semibold">Conflictos son first-class</span><br>
<span class="text-gray-400">Un conflicto no bloquea. Se materializa en el árbol de trabajo. Puedes commitear conflictos, resolverlos después.</span>
</div>
<div class="border-l-2 border-blue-500 pl-3">
<span class="text-blue-300 font-semibold">Undo nativo</span><br>
<span class="text-gray-400"><code>jj undo</code> deshace <em>cualquier</em> operación. El operation log es persistente e inmutable.</span>
</div>
</div>
</div>
<div>
<v-click>
<div class="font-mono text-xs bg-gray-900 px-4 rounded space-y-2">
**Git mental model**
```text
working tree → staging → commit → branch → push
↓ ↓ ↓ ↓
(mutable) (mutable) (fixed) (named)
```
**jj mental model**
```text
working copy (@) = commit automático
describe → bookmark → push
(siempre snapshot, siempre revertible)
```
</div>
</v-click>
<v-click>
<div class="mt-1 font-mono text-xs bg-gray-900 p-3 rounded">
```bash
# Git: 4 pasos para guardar trabajo
git add -A && git commit -m "wip"
# jj: 0 pasos — ya está guardado
jj describe -m "feat: add parser"
# El working copy YA era un commit
```
</div>
</v-click>
</div>
</div>
<Footer />
<!--
El cambio mental más importante: no hay staging.
El working copy ya ES un commit. Esto cambia todo el flujo.
"describe" no es "commit" — es poner nombre a algo que ya existe.
-->
---
layout: default
---
# jj Workspaces — aislamiento real para agentes
<div class="grid grid-cols-2 gap-8 mt-2">
<div>
**Problema resuelto: N actores, N working copies**
<div class="text-sm text-gray-300 space-y-2 mt-3">
- Cada `jj workspace add` crea un **working copy independiente**
- Sin branches de por medio — cada workspace tiene su propio `@`
- Los cambios son **visibles entre workspaces** (mismo repo)
- Merge entre workspaces: `jj new workspace_change main`
</div>
<v-click>
<div class="mt-3 font-mono text-xs bg-gray-900 p-3 rounded">
```bash
# Crear workspace para un agente
jj workspace add .agents/architect-01
# Cada workspace tiene su propio @
# Sin conflictos con el working copy principal
# Cuando termina:
jj new agent_change main # merge
jj bookmark set main -r @ # avanzar bookmark
```
</div>
</v-click>
</div>
<div>
<v-click>
**Comparación directa**
<div class="mt-3 text-xs">
| | Git worktree | jj workspace |
|---|---|---|
| **Requiere branch** | Sí (named) | No |
| **Staging area** | Compartida | No existe |
| **Visibilidad** | Aislada | Repo-wide |
| **Merge** | Checkout + merge | `jj new A B` |
| **Cleanup** | Manual | `jj workspace forget` |
| **N agentes** | N branches | N workspaces |
</div>
</v-click>
<v-click>
<div class="-mt-1 border-l-4 border-orange-500 pl-4 text-sm text-gray-300">
Un agente IA puede tener <strong>su propio working copy</strong> sin afectar al humano.<br>
Sin <code>stash</code>. Sin <code>branch switch</code>. Sin locks.
</div>
</v-click>
</div>
</div>
<Footer />
<!--
Aquí es donde jj brilla para AI agents. Cada agente tiene su propia copia de trabajo,
literalmente un directorio separado, con su propio @. No hay colisión posible.
-->
---
layout: default
---
# Radicle — code hosting soberano
<div class="grid grid-cols-2 gap-8 mt-4">
<div>
**Git, pero peer-to-peer**
<div class="text-sm text-gray-300 space-y-3 mt-3">
<div class="border-l-2 border-green-500 pl-3">
<span class="text-green-300 font-semibold">Protocolo, no plataforma</span><br>
<span class="text-gray-400">Sin servidor central. Cada nodo tiene una copia completa. Las colaboraciones son <em>patches</em>, no PRs.</span>
</div>
<div class="border-l-2 border-green-500 pl-3">
<span class="text-green-300 font-semibold">Identidad criptográfica</span><br>
<span class="text-gray-400">Cada desarrollador tiene un <code>DID</code> (ed25519). No hay cuentas, no hay contraseñas. Tu clave <em>es</em> tu identidad.</span>
</div>
<div class="border-l-2 border-green-500 pl-3">
<span class="text-green-300 font-semibold">Patches > Pull Requests</span><br>
<span class="text-gray-400">Un patch es un objeto nativo del protocolo. Vive en el gossip network. No depende de ningún servicio web.</span>
</div>
<div class="border-l-2 border-green-500 pl-3">
<span class="text-green-300 font-semibold">Seed nodes = espejos voluntarios</span><br>
<span class="text-gray-400">Cualquiera puede ser un seed. Web UI opcional (<code>radicle-explorer</code>). Sin vendor lock-in.</span>
</div>
</div>
</div>
<div>
<v-click>
<div class="font-mono text-xs bg-gray-900 p-4 rounded space-y-2">
```bash
# Inicializar repo en Radicle
rad init
# Tu identidad
rad self
# did:key:z6Mk... (ed25519)
# Publicar un patch (≈ PR)
rad patch open --title "feat: parser"
# Listar patches
rad patch list
# Clonar desde la red
rad clone rad:z42...
```
</div>
</v-click>
<v-click>
</v-click>
</div>
</div>
<Footer />
<!--
Radicle no es "otro GitLab self-hosted". Es un protocolo P2P.
Si GitHub cae mañana, tu código sigue existiendo en todos los nodos que lo replican.
Los patches son objetos del protocolo, no features de un servicio web.
-->
---
layout: center
---
# Radicle — code hosting soberano
<div class="mt-11 text-xs">
| | GitHub/GitLab | Radicle |
|---|---|---|
| **Hosting** | Centralizado | P2P (gossip) |
| **Identidad** | Username+password | ed25519 DID |
| **Review** | Pull Request | Patch (nativo) |
| **CI** | Actions/Pipelines | ci-broker (adaptable) |
| **Disponibilidad** | SPOF | Seed mesh |
| **Coste** | SaaS / self-host | Gratis (tu nodo) |
</div>
---
layout: center
---
# ¿Por qué juntos? jj + Radicle
<div class="mt-4 space-y-3">
<div class="border-l-4 border-blue-400 pl-4 py-2 bg-gray-900 rounded-r">
<div class="text-blue-300 font-mono font-semibold text-sm">COLOCATED MODE &nbsp;·&nbsp; .jj/ + .git/</div>
<div class="text-gray-400 text-sm mt-1">jj opera sobre el repo. Radicle ve los git refs. Ambos coexisten sin conflicto.</div>
<div class="text-gray-500 text-xs mt-1"><code>jj git init --colocate</code> — crea ambos directorios. Radicle no sabe que jj existe.</div>
</div>
<div class="border-l-4 border-orange-400 pl-4 py-2 bg-gray-900 rounded-r">
<div class="text-orange-300 font-mono font-semibold text-sm">WORKFLOW &nbsp;·&nbsp; workspace → describe → publish → patch</div>
<div class="text-gray-400 text-sm mt-1">El agente trabaja en un jj workspace. Cuando termina, publica un Radicle patch. Sin push a ningún server.</div>
<div class="text-gray-500 text-xs mt-1">La revisión ocurre en el gossip network. El merge ocurre localmente con <code>jj new</code>.</div>
</div>
<div class="border-l-4 border-green-400 pl-4 py-2 bg-gray-900 rounded-r">
<div class="text-green-300 font-mono font-semibold text-sm">SEGURIDAD &nbsp;·&nbsp; El agente nunca necesita push access</div>
<div class="text-gray-400 text-sm mt-1">Patches firmados criptográficamente. El humano decide si mergea. El agente propone, nunca impone.</div>
<div class="text-gray-500 text-xs mt-1">Sin tokens de GitHub. Sin claves SSH al servidor. La identidad es local.</div>
</div>
<div class="border-l-4 border-purple-400 pl-4 py-2 bg-gray-900 rounded-r">
<div class="text-purple-300 font-mono font-semibold text-sm">SOBERANÍA &nbsp;·&nbsp; Tu infraestructura, tus reglas</div>
<div class="text-gray-400 text-sm mt-1">Sin dependencia de servicios cloud para colaborar. Seed nodes privados o públicos. Tú decides.</div>
<div class="text-gray-500 text-xs mt-1">Combinado con NATS para eventos → ecosistema completo sin vendor lock-in.</div>
</div>
</div>
<Footer />
<!--
El colocated mode es la pieza clave. jj y Radicle no se conocen entre sí — solo comparten .git/.
Esto no es un hack: es diseño intencionado de jj para interoperabilidad.
-->
---
layout: default
---
# Implementación real — vapora-worktree
<div class="grid grid-cols-2 gap-8 -mt-4">
<div>
**Abstracción VCS en Rust**
<div class="text-sm text-gray-300 space-y-2 mt-1">
- Trait `WorkspaceBackend` con implementaciones jj y Git
- Detección automática: ¿hay `.jj/`? → `JjBackend`. ¿Solo `.git/`? → `GitBackend`
- `VcsCapabilities` declarativo — no hard-coded blocking
- Runtime dispatch via `Box<dyn WorkspaceBackend>`
</div>
<div class="font-mono text-xs bg-gray-900 p-3 rounded -mt-5">
```rust
#[async_trait]
pub trait WorkspaceBackend: Send + Sync {
async fn create_workspace(
&self, agent_id: &str, ws_id: &str,
) -> Result<WorkspaceInfo>;
async fn check_merge(
&self, ws_id: &str, target: &str,
) -> Result<MergeStatus>;
async fn merge_workspace(
&self, ws_id: &str, target: &str,
) -> Result<()>;
fn capabilities(&self) -> VcsCapabilities;
}
```
</div>
</div>
<div>
<v-click>
**¿Por qué un trait, no un enum?**
<div class="text-sm text-gray-300 mt-2 space-y-2">
<div class="border-l-2 border-orange-500 pl-3">
<span class="text-orange-300">Extensibilidad</span><br>
<span class="text-gray-400">Añadir Pijul, Sapling, etc. = implementar el trait. Sin tocar el manager.</span>
</div>
<div class="border-l-2 border-orange-500 pl-3">
<span class="text-orange-300">Capabilities declarativas</span><br>
<span class="text-gray-400">Cada backend declara qué puede hacer. El orquestador no asume.</span>
</div>
</div>
</v-click>
<v-click>
<div class="font-mono text-xs bg-gray-900 p-3 rounded mt-4">
```rust
pub struct VcsCapabilities {
pub supports_workspaces: bool,
pub supports_patch_open: bool,
pub merge_is_atomic: bool,
pub has_operation_log: bool,
}
// JjBackend: all true
// GitBackend: workspaces partial, no patch,
// no atomic merge, no op log
```
</div>
</v-click>
</div>
</div>
<Footer />
<!--
Esto no es abstracción teórica. Es el crate vapora-worktree, ya implementado y con tests.
El trait permite que el orchestrator no sepa si está hablando con jj o git.
Nota: VcsCapabilities es el pattern correcto. No boolean flags en el manager.
-->
---
layout: default
---
# Implementación real — ontoref vcs.nu
<div class="grid grid-cols-2 gap-8 -mt-4">
<div>
**Abstracción VCS en Nushell**
<div class="text-sm text-gray-300 space-y-2 -mt-2">
- Módulo `vcs.nu` — único punto de contacto con el VCS
- Detección por filesystem: `(.jj/ exists?)` → jj, else → git
- Todos los módulos de ontoref importan `vcs.nu`
- Ningún `^git` directo fuera de vcs.nu
</div>
<div class="font-mono text-xs bg-gray-900 p-3 rounded -mt-3">
```nushell
# vcs.nu — API uniforme
export def detect [] -> string {
if (".jj" | path exists) { "jj" }
else if (".git" | path exists) { "git" }
else { "none" }
}
export def current-ref [] -> string {
match (detect) {
"jj" => {
# @ es el working copy, @- es el parent
jj log -r "@-" --no-graph -T 'change_id'
}
"git" => { git rev-parse --short HEAD }
}
}
```
</div>
</div>
<div>
<v-click>
**Decisión arquitectónica: ADR-013**
<div class="text-sm text-gray-300 -mt-4 space-y-2">
<div class="border-l-2 border-blue-500 pl-3">
<span class="text-blue-300">Todo VCS pasa por vcs.nu</span><br>
<span class="text-gray-400">Hard constraint. Sin excepciones.</span>
</div>
<div class="border-l-2 border-blue-500 pl-3">
<span class="text-blue-300">jj y rad nunca son required</span><br>
<span class="text-gray-400">Un proyecto consumer usa git puro. Sin configuración adicional.</span>
</div>
<div class="border-l-2 border-blue-500 pl-3">
<span class="text-blue-300">Filesystem detection = la verdad</span><br>
<span class="text-gray-400">No env vars. No config files. El filesystem es autoritativo.</span>
</div>
</div>
</v-click>
<v-click>
<div class="mt-4 text-xs text-gray-500 font-mono bg-gray-900 p-3 rounded">
**Alternativas rechazadas (ADR-013):**
```text
❌ Env var ONTOREF_VCS → estado mutable, desync
❌ Detección inline por módulo → duplicación
❌ Shim de traducción → frágil, no isomórfico
```
</div>
</v-click>
</div>
</div>
<Footer />
<!--
Dos implementaciones de la misma idea: Rust para vapora (crate), Nushell para ontoref (módulo).
La detección por filesystem es clave: no hay configuración que pueda quedar desincronizada.
ADR-013 formaliza esto como una hard constraint.
-->
---
layout: default
---
# jjw — el lifecycle wrapper para agentes
<div class="-mt-4">
<div class="font-mono text-xs bg-gray-900 p-3 rounded">
```text
jjw agent create → workspace aislado + ontoref run start + metadata .ontoref-run
jjw agent step → report de paso (pass/fail/skip) al protocolo ontoref
jjw agent publish → push + rad patch open (o git push fallback)
jjw agent merge → merge al main + bookmark advance + cleanup
jjw agent discard → abandon workspace + cancel run
jjw agent status → lista workspaces activos con estado de run
```
</div>
</div>
<v-click>
<div class="grid grid-cols-2 gap-8 -mt-7">
<div>
**Flujo completo de un agente**
<div class="font-mono text-xs bg-gray-900 px-3 rounded -mt-4">
```bash
# 1. Agente recibe tarea
jjw agent create --agent architect-01 \
--mode develop --task "add parser module"
# 2. Trabaja en .agents/architect-01/
# (su propio working copy)
cd .agents/architect-01/
# ... edita archivos ...
# 3. Reporta progreso
jjw agent step develop parse-impl --status pass
# 4. Publica resultado
jjw agent publish
# → rad patch open (si hay remote rad)
# → git push (fallback)
# 5. Humano revisa y mergea
jjw agent merge
```
</div>
</div>
<div>
**¿Qué ocurre por debajo?**
<div class="text-xs text-gray-300 space-y-2 mt-2">
- `create`<small>`jj workspace add` + `ontoref run start` + JSON metadata</small>
- `step`<small>`ontoref step report` (DAG de modo)</small>
- `publish`<small>detecta remote `rad` via `jj git remote list`;<br> si existe → `rad patch open`; si no → `git push`</small>
- `merge`<small>`jj new agent_change main` + `jj bookmark set main -r @`</small>
- `discard`<small>`jj abandon` + `ontoref mode cancel`</small>
</div>
<div class="mt-4 border-l-4 border-green-500 pl-3 text-sm text-gray-400">
El agente nunca toca el working copy del humano.<br>
El humano nunca necesita saber que jj existe.
</div>
</div>
</div>
</v-click>
<Footer />
<!--
jjw es el pegamento. Conecta jj workspaces con el protocolo ontoref (runs, steps, modes)
y con Radicle (patches). Es un script Nushell de ~320 líneas.
Lo importante: el agente tiene un lifecycle formal — no es "haz lo que quieras en un branch".
-->
---
layout: default
---
# NCL merge tool — resolver conflictos en .ontology/
<div class="grid grid-cols-2 gap-8 mt-4">
<div>
**El problema**
<div class="text-sm text-gray-300 space-y-2 mt-2">
- Dos agentes editan `.ontology/core.ncl` en paralelo
- jj materializa el conflicto (no bloquea)
- Pero NCL no es texto plano — es un lenguaje tipado
- Un merge textual puede romper la semántica
</div>
**La solución: merge en dos capas**
<div class="text-sm text-gray-300 space-y-2 mt-3">
<div class="border-l-2 border-blue-500 pl-3">
<span class="text-blue-300">Capa 1: git merge-file</span><br>
<span class="text-gray-400">3-way text merge. Resuelve adiciones disjuntas (la mayoría de casos).</span>
</div>
<div class="border-l-2 border-orange-500 pl-3">
<span class="text-orange-300">Capa 2: nickel export</span><br>
<span class="text-gray-400">Valida que el resultado sea NCL válido. Si falla → conflicto manual.</span>
</div>
</div>
</div>
<div>
<v-click>
<div class="font-mono text-xs bg-gray-900 p-3 rounded">
```bash
# Registrar el merge tool en jj config
# ~/.config/jj/config.toml
[merge-tools.ncl]
program = "nu"
args = [
"jjw-ncl-merge.nu",
"$left", "$right", "$base", "$output"
]
# Usar al resolver
jj resolve --tool ncl -- .ontology/core.ncl
```
</div>
</v-click>
<v-click>
<div class="mt-4 font-mono text-xs bg-gray-900 p-3 rounded">
```text
Solo mergea archivos ontológicos:
✓ core.ncl (nodos, edges, tensiones)
✓ state.ncl (FSM dimensiones)
✓ gate.ncl (quality gates)
✓ manifest.ncl (capabilities)
✗ Cualquier otro .ncl → merge manual
```
</div>
</v-click>
</div>
</div>
<Footer />
<!--
Los conflictos en NCL son especiales. Un merge textual exitoso puede producir NCL inválido.
Por eso la segunda capa: nickel export como validador semántico.
Esto solo aplica a los archivos de ontología — el resto se resuelve normalmente.
-->
---
layout: default
---
# NATS como pegamento — radicle-nats-adapter
<div class="grid grid-cols-2 gap-8 mt-2">
<div>
**Tres mundos conectados sin acoplamiento**
<div class="text-sm text-gray-300 space-y-2 mt-3">
<div class="border-l-2 border-purple-500 pl-3">
<span class="text-purple-300 font-semibold">vapora → ontoref</span><br>
<span class="text-gray-400"><code>vapora.tasks.completed</code> filtra paths <code>.ontology/</code><br>→ publica <code>ecosystem.reflection.request</code></span>
</div>
<div class="border-l-2 border-green-500 pl-3">
<span class="text-green-300 font-semibold">radicle → ontoref</span><br>
<span class="text-gray-400"><code>radicle.patch.opened</code> / <code>updated</code><br>→ publica <code>ecosystem.reflection.request</code></span>
</div>
<div class="border-l-2 border-orange-500 pl-3">
<span class="text-orange-300 font-semibold">ontoref → vapora</span><br>
<span class="text-gray-400"><code>ecosystem.reflection.step.completed</code><br>→ publica <code>vapora.tracking.ontoref</code></span>
</div>
</div>
<v-click>
<div class="mt-3 border-l-4 border-yellow-500 pl-3 text-sm text-gray-400">
<strong>Invariante crítico:</strong> subscribe-before-publish.<br>
El adapter se suscribe al resultado <em>antes</em> de publicar la solicitud. Previene race conditions.
</div>
</v-click>
</div>
<div>
<v-click>
<div class="font-mono text-xs bg-gray-900 p-3 rounded">
```text
┌─────────┐ NATS ┌──────────┐
│ Radicle │ ────────→ │ Adapter │
│ node │ patches │ (bridge) │
└─────────┘ └────┬─────┘
┌─────────────┼──────────┐
↓ ↓ ↓
┌──────────┐ ┌─────────┐ ┌────────┐
│ ontoref │ │ vapora │ │ CI │
│ daemon │ │ tracker │ │(broker)│
└──────────┘ └─────────┘ └────────┘
```
</div>
</v-click>
<v-click>
<div class="mt-3 font-mono text-xs bg-gray-900 p-3 rounded">
```bash
# Dos modos de ejecución
radicle-nats-adapter run
# → efímero: 1 evento CI, stdin/stdout
radicle-nats-adapter bridge all
# → long-running: 3 bridges concurrentes
# → tokio::select! + explicit .abort()
```
</div>
</v-click>
</div>
</div>
<Footer />
<!--
El adapter es un binario Rust standalone. No depende de ontoref ni de vapora.
Es puro glue: traduce eventos entre sistemas usando NATS subjects.
Las convenciones de subject (radicle.*, vapora.*, ecosystem.*) son el contrato.
-->
---
layout: default
---
<h1 class="-mt-4"> Flujo completo — del agente al patch</h1>
<div class="-mt-8">
<div class="font-mono text-xs bg-gray-900 p-4 rounded leading-6">
```text
Agente IA jj Radicle Humano
───────── ──── ─────── ──────
│ jjw agent create │ │ │
│────────────────────→ │ │ │
│ workspace aislado │ │ │
│ .agents/agent-01/ │ │ │
│ │ │ │
│ ... trabaja ... │ │ │
│ jjw agent step ✓ │ │ │
│ │ │ │
│ jjw agent publish │ │ │
│────────────────────→ │ │ │
│ jj git push │ │ │
│ │──rad patch open──→ │ │
│ │ │──── notifica ────→ │
│ │ │ revisa patch │
│ │ │ ←── rad patch │
│ │ │ merge ─────────│
│ jjw agent merge │ │ │
│────────────────────→ │ │ │
│ jj new A main │ │ │
│ bookmark set main │ │ │
│ workspace forget │ │ │
│ │ │ │
▼ ▼ ▼ ▼
```
</div>
</div>
<v-click>
<div class="mt-3 text-sm text-gray-300 grid grid-cols-3 gap-4">
<div class="border-l-2 border-blue-500 pl-3">
<span class="text-blue-300">Aislamiento</span><br>
<span class="text-gray-400">El agente nunca toca el working copy del humano</span>
</div>
<div class="border-l-2 border-green-500 pl-3">
<span class="text-green-300">Revisión</span><br>
<span class="text-gray-400">El patch es un objeto firmado en la red P2P</span>
</div>
<div class="border-l-2 border-orange-500 pl-3">
<span class="text-orange-300">Control</span><br>
<span class="text-gray-400">El humano decide cuándo y qué se mergea</span>
</div>
</div>
</v-click>
<Footer />
<!--
Este es el flujo end-to-end. Nótese que el agente nunca necesita acceso de escritura al remoto.
Propone via patch. El humano mantiene el control. jj provee el aislamiento. Radicle provee la distribución.
-->
---
layout: default
---
# Graceful degradation — jj es opcional
<div class="grid grid-cols-2 gap-8 mt-4">
<div>
**Principio: opt-in, never required**
<div class="text-sm text-gray-300 space-y-3 mt-3">
<div class="border-l-2 border-green-500 pl-3">
<span class="text-green-300">Si hay .jj/ → usa jj</span><br>
<span class="text-gray-400">Workspaces nativos, operation log, snapshots automáticas.</span>
</div>
<div class="border-l-2 border-blue-500 pl-3">
<span class="text-blue-300">Si solo hay .git/ → usa git</span><br>
<span class="text-gray-400">Todo funciona. Sin worktree isolation, pero funciona.</span>
</div>
<div class="border-l-2 border-gray-500 pl-3">
<span class="text-gray-300">Radicle: igual</span><br>
<span class="text-gray-400">Si hay remote <code>rad</code> → patches. Si no → <code>git push</code> clásico.</span>
</div>
</div>
<v-click>
<div class="mt-4 font-mono text-xs bg-gray-900 p-3 rounded">
```nushell
# init-repo.nu — decisión de inicialización
if (".jj" | path exists) {
# Ya inicializado con jj
} else if (".git" | path exists) {
# Git puro — funciona sin cambios
} else {
# Nuevo proyecto → preferir colocated
^jj git init --colocate
# Crea .jj/ + .git/ → compatible con todo
}
```
</div>
</v-click>
</div>
<div>
<v-click>
<div class="text-sm text-gray-300">
**Lo que esto significa para adopción**
<div class="mt-3 space-y-4">
<div class="border border-gray-700 rounded p-3 bg-gray-900">
<div class="text-green-300 font-semibold text-xs">PROYECTO EXISTENTE (git)</div>
<div class="text-gray-400 text-xs mt-1">No cambias nada. Todo funciona. vcs.nu detecta git y usa git.</div>
</div>
<div class="border border-gray-700 rounded p-3 bg-gray-900">
<div class="text-blue-300 font-semibold text-xs">QUIERES PROBAR JJ</div>
<div class="text-gray-400 text-xs mt-1"><code>jj git init --colocate</code> en un repo existente. Listo. Git sigue funcionando.</div>
</div>
<div class="border border-gray-700 rounded p-3 bg-gray-900">
<div class="text-orange-300 font-semibold text-xs">QUIERES RADICLE</div>
<div class="text-gray-400 text-xs mt-1"><code>rad init</code> sobre el repo. Añade un remote. Los patches funcionan.</div>
</div>
<div class="border border-gray-700 rounded p-3 bg-gray-900">
<div class="text-purple-300 font-semibold text-xs">QUIERES TODO</div>
<div class="text-gray-400 text-xs mt-1">jj colocated + rad init. Agentes con jjw. Patches soberanos. 0 vendor lock-in.</div>
</div>
</div>
</div>
</v-click>
</div>
</div>
<Footer />
<!--
Esto es crítico para la adopción. Nadie tiene que migrar de golpe.
Un proyecto git puro sigue siendo un proyecto git puro.
jj y Radicle se añaden incrementalmente, sin romper nada.
-->
---
layout: center
---
# Resumen — qué aporta cada pieza
<div class="mt-4 space-y-3">
<div class="grid grid-cols-4 gap-4 text-center">
<div class="border border-gray-700 rounded p-4 bg-gray-900">
<div class="text-blue-300 font-mono font-semibold text-lg">jj</div>
<div class="text-gray-400 text-xs mt-2">Aislamiento real<br>Sin staging area<br>Undo nativo<br>Workspaces nativos</div>
</div>
<div class="border border-gray-700 rounded p-4 bg-gray-900">
<div class="text-green-300 font-mono font-semibold text-lg">Radicle</div>
<div class="text-gray-400 text-xs mt-2">Soberanía<br>Patches firmados<br>P2P gossip<br>Sin vendor lock-in</div>
</div>
<div class="border border-gray-700 rounded p-4 bg-gray-900">
<div class="text-orange-300 font-mono font-semibold text-lg">jjw</div>
<div class="text-gray-400 text-xs mt-2">Lifecycle formal<br>Agent workspaces<br>Protocol bridge<br>NCL merge tool</div>
</div>
<div class="border border-gray-700 rounded p-4 bg-gray-900">
<div class="text-purple-300 font-mono font-semibold text-lg">NATS</div>
<div class="text-gray-400 text-xs mt-2">Event mesh<br>Desacoplamiento<br>Bridge adapter<br>CI integration</div>
</div>
</div>
</div>
<v-click>
<div class="mt-6 border border-gray-700 rounded p-4 bg-gray-900 text-sm text-gray-300 font-mono leading-7 text-center">
```text
Git solo → funciona, pero no escala a N agentes concurrentes
+ jj → aislamiento real por workspace, undo, sin staging
+ Radicle → soberanía, patches firmados, sin plataforma central
+ jjw + NATS → lifecycle formal, eventos entre sistemas, CI adaptable
```
</div>
</v-click>
<Footer />
<!--
Cada pieza añade una capa. Ninguna es obligatoria.
Puedes quedarte en cualquier nivel. El valor es aditivo, no todo-o-nada.
-->
---
layout: center
---
<h1 class="font-medium">Gracias</h1>
<div class="flex justify-center mt-4 text-gray-400 text-lg italic">
Tu código, tus reglas, tu infraestructura.
</div>
<div class="mt-8 text-sm text-gray-500 space-y-1 text-center">
`jj` — https://martinvonz.github.io/jj/
`Radicle` — https://radicle.xyz/
`ontoref` — protocolo de auto-conocimiento para proyectos
`vapora` — orquestador de workspaces para agentes
</div>
<div class="flex justify-center">
<img class="mt-8 w-40" src="/jesusperez_w.svg">
</div>
<Footer />
<!--
Cerrar con calma. El mensaje es soberanía + aislamiento + formalismo.
No es anti-GitHub. Es pro-independencia.
-->