# Protocolo de crecimiento
Cómo decide el enjambre qué hacer con un nodo que se acaba de unir, sin reiniciar
nada y sin apagar ningún pipeline.
## 1. El problema
Un clúster doméstico no es estático: un portátil se enciende, un mini-PC se añade, un
móvil deja de estar disponible. Reiniciar el enjambre cada vez que cambia el hardware
convierte el sistema en un juguete.
El protocolo de crecimiento responde a una sola pregunta: **dado un nodo nuevo y el
plan actual, ¿cuál es el mejor uso para ese hardware?**
## 2. Puntuación de extensión (FSENNL)
El marco de referencia es **FSENNL** (*Federated Self-Expanding Neural Network
Learning*): decide *cuándo* y *dónde* expandir combinando la información de Fisher de los
parámetros con el perfil del dispositivo.
El plano de control solo puede observar la parte del dispositivo, así que implementa esa
mitad:
```text
score = w_vram · vram_gb
+ w_ram · ram_gb
+ w_npu · npu_tops
- w_lat · latency_ms
```
| Peso | Valor por defecto | Motivo |
| --- | --- | --- |
| `w_vram` | 1.0 | Es el recurso escaso: los expertos y el contexto compiten por ella. |
| `w_ram` | 0.25 | Abundante en equipos domésticos; sirve para expertos, no para latencia. |
| `w_npu` | 1.5 | Un nodo con NPU ejecuta un fragmento completo sin competir por el bus de la GPU. |
| `w_lat` | 0.05 | 20 ms de latencia cuestan una capa de capacidad: la red es el límite físico. |
La puntuación **se recalcula en el plano de control**, no se acepta la que anuncia el
nodo. Un nodo puede estar desactualizado o medir mal su propia latencia, y el crecimiento
no debe depender de una cifra que no se puede auditar. El campo `extension_score` del
contrato Protobuf es informativo.
### Umbral de extensión
`ExtensionWeights::extend_threshold` (6.0 por defecto) separa a los nodos que pueden
sostener un tramo nuevo de los que solo pueden replicar. Un nodo por debajo del umbral no
es inútil: sumando réplicas mejora el throughput y la tolerancia a fallos.
## 3. La decisión
`growth_decision(new_node, current_plan)` es una cascada explícita, en orden de prioridad:
```mermaid
flowchart TD
A[Nodo nuevo] --> B{¿Faltan pipelines
para lo pedido?}
B -->|Sí| R1[Replicate
copia completa]
B -->|No| C{¿Réplicas medias
< objetivo 2.0?}
C -->|Sí| R2[Replicate
reduce riesgo de failover]
C -->|No| D{¿Telomerasa + modelo local
y score ≥ umbral?}
D -->|Sí| E[AddExpert
aporta conocimiento propio]
D -->|No| F{¿score ≥ umbral?}
F -->|Sí| G[ExtendDepth
capas nuevas al final]
F -->|No| R3[Replicate
nodo débil, solo redundancia]
```
### Por qué este orden
1. **Faltan pipelines** — es la ganancia más directa: una copia entera más del modelo.
Nada de lo demás compite con eso.
2. **Faltan réplicas** — sin dos copias por capa, un failover no tiene a quién recurrir.
La redundancia es requisito del Pilar A, no un lujo.
3. **Aporta inteligencia** — si el nodo ya trae una IA local con telomerasa y además tiene
capacidad, su valor no es más cómputo del mismo modelo, sino **conocimiento distinto**.
Ese es el caso de *Composition of Experts*.
4. **Puede alargar** — con capacidad suficiente, aporta capas nuevas al final del pipeline
(*Growing Transformers*: composición modular sobre un sustrato congelado).
5. **Nodo débil** — replicar es lo único honesto que se puede ofrecer.
## 4. Las tres acciones
### `Replicate`
```json
{
"action": "replicate",
"pipeline_id": "ncm-base#0",
"layers": [{ "first": 0, "last": 7 }, { "first": 8, "last": 11 }],
"reason": "réplicas medias 1.00 < objetivo 2.00; laptop reduce el riesgo de failover"
}
```
El orquestador re-ejecuta `plan_swarm` con el nodo añadido y `target_pipelines + 1`. El
plan nuevo se valida antes de desplegarse; si no encaja, no se toca nada.
### `ExtendDepth`
```json
{
"action": "extend_depth",
"pipeline_id": "ncm-base#0",
"new_layers": 8,
"reason": "puntuación 20.90 >= 6.00; beast extiende el modelo 8 capas"
}
```
Las capas nuevas son proporcionales a la VRAM declarada (`layers_per_vram_gb`, una capa
por GB por defecto) y se acotan a **la mitad del modelo**: crecer más de golpe convertiría
la expansión en una migración y el sustrato dejaría de estar congelado.
### `AddExpert`
```json
{
"action": "add_expert",
"expert_id": "ncm-vision@seer",
"model_id": "ncm-vision",
"reason": "seer tiene IA local con telomerasa y puntuación 49.85 >= 6.00; entra como experto"
}
```
El experto se planifica con `plan_swarm_named` sobre su propio modelo y luego se fusiona
con `merge_expert`, que mantiene los pipelines base vivos y añade los del experto. El
enrutador distingue unos de otros por el campo `model_id` de la petición.
## 5. Aplicación atómica
`apply_growth(plan, nodes, new_node, action)` es una función **pura**: recalcula el plan
completo y lo valida. No muta el plan anterior ni habla con la red.
```rust
let action = growth_decision(&newcomer, &plan)?;
let grown = apply_growth(&plan, &nodes, &newcomer, &action)?;
grown.validate()?; // invariantes del enjambre
cluster.deploy(&grown).await?; // aquí sí se toca la red
```
Si `validate()` falla, el orquestador descarta el plan nuevo y sigue con el viejo. Esa es
la propiedad que hace que el crecimiento no pueda degradar el servicio: **el plan nuevo
existe completo antes de que nadie dependa de él**.
## 6. Invariantes que se verifican
| Invariante | Qué rompe si se viola |
| --- | --- |
| Cada pipeline cubre `0..model_layers` de forma contigua | Un salto se quedaría sin capas que ejecutar. |
| Cada capa tiene al menos un nodo | Petición imposible de servir. |
| `deployments` de un nodo = unión de sus etapas | Un nodo cargaría fragmentos que nadie le pide, o le faltarían. |
| El reparto suma exactamente `model_layers` | Capas huérfanas o duplicadas dentro de una copia. |
| El plan es determinista para la misma entrada | Dos orquestadores calcularían planes distintos. |
## 7. Límites honestos
- **El crecimiento es de hardware, no de conocimiento.** Añadir un nodo añade copias o
profundidad; la inteligencia nueva solo llega por la telomerasa.
- **`ExtendDepth` no reentrena el sustrato.** Las capas nuevas se inicializan y hay que
ajustarlas; el planificador decide *dónde* crecer, no *cómo* entrenar.
- **La decisión es codiciosa.** No planifica a cinco nodos vista: reacciona a cada llegada.
Con hardware que llega en ráfaga conviene llamar a `plan_swarm` una vez con todos.