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

32 KiB

theme title titleTemplate layout keywords download exportFilename monaco remoteAssets selectable record colorSchema lineNumbers themeConfig fonts drawings scripts class
default jj + Radicle — VCS para la era de los agentes %s cover jj,Jujutsu,Radicle,VCS,Git,AI,Agents,ontoref,vapora true jj-radicle-agents true true true true dark false
primary
#f74c00
mono
Victor Mono
enabled persist presenterOnly syncAll
true false false true
setup/image-overlay.ts
justify-center flex flex-cols

jj + Radicle

VCS para la era de los agentes

Workspaces aislados. Parches soberanos. Sin staging area.

layout: two-cols

El problema con Git + agentes IA

Fricción acumulada

🔴
Staging area compartida
Humano y agente compiten por el índice.
Un git add pisotea al otro.
🔴
Worktrees frágiles
Git worktrees necesitan branches nombradas.
Colisionan. No escalan a N agentes.
🟠
Centralización invisible
GitHub/GitLab son el single point of failure.
Tu código, sus reglas.

::right::

🟠
Push-to-review es un cuello de botella
El agente necesita push + PR + aprobación.
¿Le das acceso de escritura al remoto?
🟡
Merge hell con N actores
3 agentes + 1 humano = conflictos serializados.
Alguien siempre pierde.
Git fue diseñado para humanos que trabajan en branches.

Hoy tenemos N actores concurrentes (humanos + agentes) que necesitan aislamiento real, no convenciones.
Agente-1: git stash → trabaja → git stash pop
Agente-2: git stash → trabaja → CONFLICTO
Humano:   "¿quién tocó mi working tree?"

layout: default

Jujutsu (jj) — el modelo mental

Cambio fundamental: no hay staging area

Working copy IS a commit
En jj, @ ya es un commit. Cada save es una snapshot automática. No hay git add, no hay índice.
Changes, no commits
jj trabaja con change IDs estables. Reescribir historia no cambia el ID del cambio — solo su commit ID muta.
Conflictos son first-class
Un conflicto no bloquea. Se materializa en el árbol de trabajo. Puedes commitear conflictos, resolverlos después.
Undo nativo
jj undo deshace cualquier operación. El operation log es persistente e inmutable.

Git mental model

working tree → staging → commit → branch → push
     ↓            ↓         ↓        ↓
  (mutable)   (mutable)  (fixed)  (named)

jj mental model

working copy (@) = commit automático
     ↓
  describe → bookmark → push
     ↓
  (siempre snapshot, siempre revertible)
# 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

layout: default

jj Workspaces — aislamiento real para agentes

Problema resuelto: N actores, N working copies

  • 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
# 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

Comparación directa

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
Un agente IA puede tener su propio working copy sin afectar al humano.
Sin stash. Sin branch switch. Sin locks.

layout: default

Radicle — code hosting soberano

Git, pero peer-to-peer

Protocolo, no plataforma
Sin servidor central. Cada nodo tiene una copia completa. Las colaboraciones son patches, no PRs.
Identidad criptográfica
Cada desarrollador tiene un DID (ed25519). No hay cuentas, no hay contraseñas. Tu clave es tu identidad.
Patches > Pull Requests
Un patch es un objeto nativo del protocolo. Vive en el gossip network. No depende de ningún servicio web.
Seed nodes = espejos voluntarios
Cualquiera puede ser un seed. Web UI opcional (radicle-explorer). Sin vendor lock-in.
# 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...

layout: center

Radicle — code hosting soberano

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)

layout: center

¿Por qué juntos? jj + Radicle

COLOCATED MODE  ·  .jj/ + .git/
jj opera sobre el repo. Radicle ve los git refs. Ambos coexisten sin conflicto.
jj git init --colocate — crea ambos directorios. Radicle no sabe que jj existe.
WORKFLOW  ·  workspace → describe → publish → patch
El agente trabaja en un jj workspace. Cuando termina, publica un Radicle patch. Sin push a ningún server.
La revisión ocurre en el gossip network. El merge ocurre localmente con jj new.
SEGURIDAD  ·  El agente nunca necesita push access
Patches firmados criptográficamente. El humano decide si mergea. El agente propone, nunca impone.
Sin tokens de GitHub. Sin claves SSH al servidor. La identidad es local.
SOBERANÍA  ·  Tu infraestructura, tus reglas
Sin dependencia de servicios cloud para colaborar. Seed nodes privados o públicos. Tú decides.
Combinado con NATS para eventos → ecosistema completo sin vendor lock-in.

layout: default

Implementación real — vapora-worktree

Abstracción VCS en Rust

  • 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>
#[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;
}

¿Por qué un trait, no un enum?

Extensibilidad
Añadir Pijul, Sapling, etc. = implementar el trait. Sin tocar el manager.
Capabilities declarativas
Cada backend declara qué puede hacer. El orquestador no asume.
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

layout: default

Implementación real — ontoref vcs.nu

Abstracción VCS en Nushell

  • 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
# 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 }
  }
}

Decisión arquitectónica: ADR-013

Todo VCS pasa por vcs.nu
Hard constraint. Sin excepciones.
jj y rad nunca son required
Un proyecto consumer usa git puro. Sin configuración adicional.
Filesystem detection = la verdad
No env vars. No config files. El filesystem es autoritativo.

Alternativas rechazadas (ADR-013):

❌ Env var ONTOREF_VCS → estado mutable, desync
❌ Detección inline por módulo → duplicación
❌ Shim de traducción → frágil, no isomórfico

layout: default

jjw — el lifecycle wrapper para agentes

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

Flujo completo de un agente

# 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

¿Qué ocurre por debajo?

  • createjj workspace add + ontoref run start + JSON metadata
  • stepontoref step report (DAG de modo)
  • publishdetecta remote rad via jj git remote list;
    si existe → rad patch open; si no → git push
  • mergejj new agent_change main + jj bookmark set main -r @
  • discardjj abandon + ontoref mode cancel
El agente nunca toca el working copy del humano.
El humano nunca necesita saber que jj existe.

layout: default

NCL merge tool — resolver conflictos en .ontology/

El problema

  • 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

La solución: merge en dos capas

Capa 1: git merge-file
3-way text merge. Resuelve adiciones disjuntas (la mayoría de casos).
Capa 2: nickel export
Valida que el resultado sea NCL válido. Si falla → conflicto manual.
# 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
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

layout: default

NATS como pegamento — radicle-nats-adapter

Tres mundos conectados sin acoplamiento

vapora → ontoref
vapora.tasks.completed filtra paths .ontology/
→ publica ecosystem.reflection.request
radicle → ontoref
radicle.patch.opened / updated
→ publica ecosystem.reflection.request
ontoref → vapora
ecosystem.reflection.step.completed
→ publica vapora.tracking.ontoref
Invariante crítico: subscribe-before-publish.
El adapter se suscribe al resultado antes de publicar la solicitud. Previene race conditions.
┌─────────┐   NATS     ┌──────────┐
│ Radicle │ ────────→  │ Adapter  │
│  node   │  patches   │ (bridge) │
└─────────┘            └────┬─────┘
                            │
              ┌─────────────┼──────────┐
              ↓             ↓          ↓
        ┌──────────┐  ┌─────────┐ ┌────────┐
        │ ontoref  │  │ vapora  │ │  CI    │
        │ daemon   │  │ tracker │ │(broker)│
        └──────────┘  └─────────┘ └────────┘
# 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()

layout: default

Flujo completo — del agente al patch

```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 │ │ │ │ │ │ │ ▼ ▼ ▼ ▼ ```
Aislamiento
El agente nunca toca el working copy del humano
Revisión
El patch es un objeto firmado en la red P2P
Control
El humano decide cuándo y qué se mergea

layout: default

Graceful degradation — jj es opcional

Principio: opt-in, never required

Si hay .jj/ → usa jj
Workspaces nativos, operation log, snapshots automáticas.
Si solo hay .git/ → usa git
Todo funciona. Sin worktree isolation, pero funciona.
Radicle: igual
Si hay remote rad → patches. Si no → git push clásico.
# 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
}

Lo que esto significa para adopción

PROYECTO EXISTENTE (git)
No cambias nada. Todo funciona. vcs.nu detecta git y usa git.
QUIERES PROBAR JJ
jj git init --colocate en un repo existente. Listo. Git sigue funcionando.
QUIERES RADICLE
rad init sobre el repo. Añade un remote. Los patches funcionan.
QUIERES TODO
jj colocated + rad init. Agentes con jjw. Patches soberanos. 0 vendor lock-in.

layout: center

Resumen — qué aporta cada pieza

jj
Aislamiento real
Sin staging area
Undo nativo
Workspaces nativos
Radicle
Soberanía
Patches firmados
P2P gossip
Sin vendor lock-in
jjw
Lifecycle formal
Agent workspaces
Protocol bridge
NCL merge tool
NATS
Event mesh
Desacoplamiento
Bridge adapter
CI integration
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

layout: center

Gracias

Tu código, tus reglas, tu infraestructura.

jjhttps://martinvonz.github.io/jj/

Radiclehttps://radicle.xyz/

ontoref — protocolo de auto-conocimiento para proyectos

vapora — orquestador de workspaces para agentes