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).
32 KiB
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 |
|
|
|
|
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
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.
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.
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?
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.
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.
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,
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.
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.
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 addcrea 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
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.
Sin servidor central. Cada nodo tiene una copia completa. Las colaboraciones son patches, no PRs.
Identidad criptográfica
Cada desarrollador tiene un
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.
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 (
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
WorkspaceBackendcon implementaciones jj y Git - Detección automática: ¿hay
.jj/? →JjBackend. ¿Solo.git/? →GitBackend VcsCapabilitiesdeclarativo — 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.
Añadir Pijul, Sapling, etc. = implementar el trait. Sin tocar el manager.
Capabilities declarativas
Cada backend declara qué puede hacer. El orquestador no asume.
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
^gitdirecto 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.
Hard constraint. Sin excepciones.
jj y rad nunca son required
Un proyecto consumer usa git puro. Sin configuración adicional.
Un proyecto consumer usa git puro. Sin configuración adicional.
Filesystem detection = la verdad
No env vars. No config files. El filesystem es autoritativo.
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?
create→jj workspace add+ontoref run start+ JSON metadatastep→ontoref step report(DAG de modo)publish→ detecta remoteradviajj git remote list;
si existe →rad patch open; si no →git pushmerge→jj new agent_change main+jj bookmark set main -r @discard→jj abandon+ontoref mode cancel
El agente nunca toca el working copy del humano.
El humano nunca necesita saber que jj existe.
El humano nunca necesita saber que jj existe.
layout: default
NCL merge tool — resolver conflictos en .ontology/
El problema
- Dos agentes editan
.ontology/core.nclen 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).
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.
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
→ publica
vapora.tasks.completed filtra paths .ontology/→ publica
ecosystem.reflection.request
radicle → ontoref
→ publica
radicle.patch.opened / updated→ publica
ecosystem.reflection.request
ontoref → vapora
→ publica
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.
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
El agente nunca toca el working copy del humano
Revisión
El patch es un objeto firmado en la red P2P
El patch es un objeto firmado en la red P2P
Control
El humano decide cuándo y qué se mergea
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.
Workspaces nativos, operation log, snapshots automáticas.
Si solo hay .git/ → usa git
Todo funciona. Sin worktree isolation, pero funciona.
Todo funciona. Sin worktree isolation, pero funciona.
Radicle: igual
Si hay remote
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
Sin staging area
Undo nativo
Workspaces nativos
Radicle
Soberanía
Patches firmados
P2P gossip
Sin vendor lock-in
Patches firmados
P2P gossip
Sin vendor lock-in
jjw
Lifecycle formal
Agent workspaces
Protocol bridge
NCL merge tool
Agent workspaces
Protocol bridge
NCL merge tool
NATS
Event mesh
Desacoplamiento
Bridge adapter
CI integration
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.
jj — https://martinvonz.github.io/jj/
Radicle — https://radicle.xyz/
ontoref — protocolo de auto-conocimiento para proyectos
vapora — orquestador de workspaces para agentes