---
name: agentes-dispositivos-mesh
id: 0009
status: pending
created: 2026-05-23
updated: 2026-05-23
priority: high
risk: high
related_issues: [0134, 0135, 0136, 0137, 0138, 0139, 0140, 0141, 0142, 0143]
apps: [agents_dashboard, agents_and_robots, wg_hub, device_agent]
projects: [element_agents]
vaults: []
capability_groups: [wireguard, device-agent, docker-agent]
trigger: manual
schedule: ""
expected_runtime_s: 300
tags: [mesh, wireguard, matrix, e2ee, agents, devices, docker, sandboxing]
---

## Goal

Hablar desde Element con dispositivos completos (PCs, moviles, raspberry, IoT) y con
contenedores Docker como si fueran agentes Matrix. Cada device/container ejecuta sus
capabilities declaradas (shell/fs/camera/docker/sensores) bajo:

1. **Mesh WireGuard** anclado en `organic-machine.com` — sin abrir puertos en los devices.
2. **Matrix E2EE** como bus de control y chat — un room por device/container.
3. **Capability manifest firmado** ed25519 — el device rechaza lo que no este firmado.

## Pre-requisitos

- VPS `organic-machine.com` con root SSH (alias `vps` en `~/.ssh/config`).
- `agents_and_robots` y `agents_dashboard` desplegados (ya OK).
- `pass` con clave operador ed25519 (`pass insert operator/ed25519` — crear si falta).
- `apt-get install wireguard wireguard-tools` permitido en el VPS.
- Devices Linux/WSL: sudo sin password para `wg`, `wg-quick`, `systemctl`.
- Devices Android: Termux + WireGuard app + `pkg install golang openssh-client`.

## Funciones del registry recomendadas

| Rol | Funcion candidata | Estado |
|---|---|---|
| WG install (host) | `wg_install_bash_infra` | FALTA: crear |
| WG keygen | `wg_keygen_go_infra` | FALTA: crear |
| WG hub setup | `wg_hub_setup_bash_infra` | FALTA: crear |
| WG peer add (hub) | `wg_peer_add_go_infra` | FALTA: crear |
| WG peer remove (hub) | `wg_peer_remove_go_infra` | FALTA: crear |
| WG peer revoke (kill switch) | `wg_peer_revoke_go_infra` | FALTA: crear |
| WG client config gen | `wg_client_config_go_infra` | FALTA: crear |
| WG client install (device) | `wg_client_install_bash_infra` | FALTA: crear |
| WG status (parse `wg show`) | `wg_status_bash_infra` | FALTA: crear |
| Docker list (host) | `docker_container_list_go_infra` | FALTA: crear |
| Docker exec capability | `docker_container_exec_go_infra` | FALTA: crear |
| Docker logs tail | `docker_container_logs_go_infra` | FALTA: crear |
| Docker container enroll | `docker_container_enroll_go_infra` | FALTA: crear |
| Capability sign | `capability_manifest_sign_go_infra` | FALTA: crear |
| Capability verify | `capability_manifest_verify_go_infra` | FALTA: crear |
| Enrollment token gen | `enrollment_token_create_go_infra` | FALTA: crear |
| Enrollment token verify | `enrollment_token_verify_go_infra` | FALTA: crear |
| Matrix room per device | `matrix_room_for_device_py_browser` (extender) | OK base, EXTENDER |
| Provision hub pipeline | `provision_wg_hub_bash_pipelines` | FALTA: crear |
| Enroll device pipeline | `enroll_device_bash_pipelines` | FALTA: crear |
| Sink audit log | `device_audit_append_go_infra` | FALTA: crear |
| Notify approval | `matrix_send_message_py_browser` (existente) | OK |

## Apps tocadas

- `agents_dashboard` (cockpit ImGui) — panel "Mesh" + "Devices" + "Containers" + approval queue.
- `agents_and_robots` (hub Matrix VPS) — listener Matrix por device/container.
- `wg_hub` (nuevo service Go en VPS) — enrollment endpoint, peer CRUD, SSE stream.
- `device_agent` (nuevo binario per-host) — capability dispatcher con sandbox.
- `container_agent_sidecar` (opcional, nuevo) — sidecar para containers que necesitan WG-peer propio.

## Projects relacionados

- `element_agents` (parent project — agents Matrix).

## Vaults / storage

- `apps/wg_hub/operations.db` — tabla `wg_peers`, `wg_enrollment_tokens`, `device_audit`.
- `apps/agents_dashboard/local_files/agents_dashboard.db` — cache devices + capabilities.
- `pass operator/ed25519` — clave maestra del operador (firma manifests).
- `pass wg/preshared/<device_id>` — PSK por peer.

## Capability groups consultados

- `wireguard` (nuevo, ver `docs/capabilities/wireguard.md`).
- `device-agent` (nuevo, capability dispatcher + sandbox + audit).
- `docker-agent` (nuevo, capabilities sobre containers locales).

## Flow

### Fase A — registry primero (delegar a fn-constructor en paralelo)

1. `function: wg_install_bash_infra` (delegada).
2. `function: wg_keygen_go_infra` (delegada).
3. `function: wg_hub_setup_bash_infra` (delegada).
4. `function: wg_peer_add_go_infra` (delegada).
5. `function: wg_peer_remove_go_infra` (delegada).
6. `function: wg_peer_revoke_go_infra` (delegada).
7. `function: wg_client_config_go_infra` (delegada).
8. `function: wg_client_install_bash_infra` (delegada).
9. `function: wg_status_bash_infra` (delegada).
10. `cmd: ./fn index` — registra las 9 nuevas.
11. `cmd: fn doctor unused | grep wg_` — confirma que estan listas y no huerfanas (se usan en pasos C).

### Fase C — POC manual end-to-end

12. `function: wg_install_bash_infra` (sobre `organic-machine.com` via SSH).
13. `function: wg_keygen_go_infra` → key par hub.
14. `function: wg_hub_setup_bash_infra` — wg0, 10.42.0.1/24, ufw 51820/udp, persistencia.
15. `function: wg_keygen_go_infra` → key par device `home-wsl`.
16. `function: wg_peer_add_go_infra` (en hub) → asigna 10.42.0.10.
17. `function: wg_client_config_go_infra` → genera client.conf.
18. `function: wg_client_install_bash_infra` (en `home-wsl`).
19. `cmd: ping -c3 10.42.0.1` desde `home-wsl` — verifica handshake.
20. `cmd: curl http://10.42.0.1:8080/healthz` — agents_and_robots accesible por IP privada.
21. Repetir 15-19 para `pc-aurgi`.

### Fase B — spec + capability manifest + bot Matrix

22. Issue 0134 spec protocol: envelope JSON `{request_id, capability, args, signature, nonce}`,
    error model, approval flow, audit chain hash.
23. `function: capability_manifest_sign_go_infra` (operator firma).
24. `function: capability_manifest_verify_go_infra` (device verifica antes de aceptar request).
25. `function: enrollment_token_create_go_infra` (token QR firmado, TTL 10min).
26. `function: enrollment_token_verify_go_infra` (hub valida en `/enroll`).
27. Implementar `apps/device_agent/` (Go cross-compile) — Matrix client + capability dispatcher + sandbox firejail.
28. Panel "Devices" en `agents_dashboard` — lista + capability matrix + approval queue + boton revoke.
29. Bot Matrix por device: cuando hablas en el room `#dev-aurgi:organic-machine.com`,
    `agents_and_robots` parsea, valida capability, despacha a device_agent, devuelve resultado al room.

### Fase D — agentes-contenedores docker

30. `function: docker_container_list_go_infra` — corre en host con docker socket access.
31. `function: docker_container_exec_go_infra` — exec en container con whitelist binarios.
32. `function: docker_container_logs_go_infra` — tail logs SSE.
33. Modo "light": container expuesto via host's `device_agent` capability `docker.*`.
    Element room: `#host-aurgi:organic-machine.com` con comando `!docker exec mycontainer ps`.
34. Modo "deep": container = peer WG propio. `container_agent_sidecar` corre WG dentro del container
    (privileged) o sidecar gluetun-wg. Manifest firmado mapea `agent_X` → container_id.
35. Sub-bot Matrix por container: `#cont-mycontainer:organic-machine.com` (opcional, modo deep).

## Acceptance

- [ ] 9 funciones `wg_*` creadas + indexadas + sin huerfanas.
- [ ] Hub WG corriendo en `organic-machine.com`, `wg show` muestra interface wg0.
- [ ] `home-wsl` y `pc-aurgi` con IP estable 10.42.0.10/11, `ping` OK.
- [ ] `agents_and_robots` accesible solo desde subnet 10.42.0.0/24 (publico = DROP en :8080).
- [ ] `agents_dashboard` panel "Mesh" muestra peers vivos via SSE.
- [ ] Chat en `#dev-aurgi` ejecuta capability (ej. `!ls /home/lucas`) y devuelve resultado.
- [ ] Capability fuera del manifest rechazada con error en room.
- [ ] Capability `requires_approval=true` espera confirmacion en `#operator-approvals` antes de ejecutar.
- [ ] `docker.container.list` invocado desde Element devuelve containers del host.
- [ ] `docker.container.exec` con binario fuera de whitelist rechazado.
- [ ] Revoke device desde `agents_dashboard` → device pierde acceso en <5s.
- [ ] Audit log append-only inviolable (hash chain) sobrevive reinicio.

## Definition of Done

Triada obligatoria (ver `.claude/rules/dod_quality.md`). Sin las 3 capas + 0 anti-criterios el flow NO se mueve a `completed/`.

### Mecanica (pre-requisito)

- [ ] **Build device_agent**: `cd apps/device_agent && CGO_ENABLED=0 GOOS=linux go build -o device_agent .` exit 0; cross-compile `GOOS=windows` + `GOOS=android GOARCH=arm64` tambien verdes.
- [ ] **Build agents_and_robots + agents_dashboard**: `./fn run redeploy_cpp_app_windows agents_dashboard apps/agents_dashboard --build` + Go build de `agents_and_robots` exit 0.
- [ ] **Tests unitarios funciones nuevas verdes**: `CGO_ENABLED=1 go test -tags fts5 -count=1 ./functions/infra/...` cubriendo wg_*, capability_*, enrollment_*, device_audit_*. Lista de IDs en `## Notas`.
- [ ] **`./fn index`** sin warnings nuevos tras anadir las ~20 funciones.
- [ ] **`./fn doctor unused --json | jq '.[]|select(.id|startswith("wg_"))'`** vacio (las wg_* tienen consumidores reales).
- [ ] **`./fn doctor uses-functions`** verde para `apps/device_agent/app.md`, `apps/wg_hub/app.md`, `apps/agents_dashboard/app.md`.
- [ ] **`./fn doctor services-spec`** verde para `wg_hub.service` y `device_agent.service` con bloque service: completo.

### Cobertura de comportamiento

Minimo: golden + 8 edge/error documentados aqui con assert ejecutable. Cada uno deja entry en `e2e_runs` de la app afectada (`apps/device_agent/operations.db`, `apps/wg_hub/operations.db`).

| Escenario | Tipo | Comando / evidencia | Resultado esperado |
|---|---|---|---|
| Golden: comando whitelist OK | e2e | Element `!exec ls /home/lucas` en `#dev-home-wsl` | output `ls` en <3s, entry en `device_audit` con hash valido |
| Edge: comando NO whitelist rechazado | e2e | Element `!exec rm -rf /` | reply `capability rejected: shell.exec.rm not in manifest`; entry `device_audit` status=`rejected_capability` |
| Edge: capability fuera de manifest | e2e | Element `!camera.snapshot` en device sin esa capability | reply `capability not in manifest`; alerta a `#operator-approvals` |
| Edge: replay nonce viejo | e2e | reenviar mismo envelope con nonce ya visto (cmd test: `device_agent --replay-test <envelope.json>`) | rechazo + log `nonce_replay`; entry `device_audit` status=`rejected_nonce` |
| Edge: ed25519 manifest invalido | e2e | servir manifest firmado por clave que no es operator; `device_agent` lo recibe en enrollment | `device_agent` rechaza + no instala wg_peer; hub log muestra `manifest_invalid_signature` |
| Edge: token enrollment expirado | e2e | `enrollment_token_create` con TTL=1s, esperar 5s, `POST /enroll` | hub responde 401 `token_expired`; cmd `curl ...` exit != 0 |
| Approval flow honrado | e2e | Element `!fs.write /tmp/x hello` (requires_approval=true); operador hace 👍 en `#operator-approvals` | exec ocurre SOLO tras approval; sin approval no escribe; entry `device_audit` con `approval_msg_id` |
| Approval flow no se salta | e2e | Forzar via API directa salto del approval queue (test negativo: cmd `curl --data ...` directo al device) | device rechaza + log; sin approval_msg_id en envelope = rechazo |
| Mesh-down handled | e2e | `wg-quick down wg0` en hub mientras device manda comando | device entra en `degraded`, comando encolado o respuesta `mesh_unreachable`; al volver hub: handshake reanuda, cola se vacia |
| Dos devices simultaneos sin interferencia | e2e | `home-wsl` y `pc-aurgi` ejecutan capabilities en paralelo (script python con 2 threads) | cada audit chain es independiente, sin cross-contamination; `device_audit` muestra 2 chains separadas, hash chain valido en cada una |
| Audit chain valida tras restart | e2e | matar `device_agent` mid-flight (`kill -9`) + relanzar; `cmd: device_audit_verify_chain --device home-wsl` | chain integra, hash anterior coincide, sin huecos |
| Revoke device <5s | e2e | desde `agents_dashboard` panel "Mesh" boton "Revoke home-wsl"; medir tiempo hasta `wg show` no liste peer | peer ausente en <5s; siguientes comandos a `#dev-home-wsl` -> `peer_revoked` |

**Regla**: cada fila genera `e2e_check` en `app.md` correspondiente (issue 0068). `fn-analizador` los corre periodicamente.

### Vida util validada

| Metrica | Umbral | Donde se observa | Ventana |
|---|---|---|---|
| Peers vivos en mesh | `>=2` constantes (home-wsl + pc-aurgi) | `agents_dashboard` panel "Mesh" (last_handshake < 3min) | 7 dias |
| Crashes `device_agent` | `0` | `journalctl --user -u device_agent.service` en cada device | 7 dias |
| Crashes `wg_hub` | `0` | `ssh vps journalctl -u wg_hub.service` | 7 dias |
| Huecos en audit chain | `0` | `cmd: device_audit_verify_chain --all` | continuo |
| Rollback de wg config | `0 ocurrencias` | hub: `git -C /etc/wireguard status` debe ser clean; sin restore manual | 7 dias |
| Handshake fail rate | `<5%` | `wg show all dump` parseado por `agents_dashboard` | 7 dias |
| Approval queue stuck | `0 pendientes >24h` | `agents_dashboard` panel "Approvals" | continuo |
| Comandos exec latencia p95 | `<3s` | `call_monitor.function_stats` para `capability.shell.exec` | 7 dias |
| Replay attacks bloqueados | `>=1 detectado y bloqueado` (pen-test real) | `device_audit` status=`rejected_nonce` count | 30 dias |

### User-facing (reforzado)

- [ ] **User-facing surface**: humano abre Element en movil/web (`element.organic-machine.com`), entra a `#dev-<nombre>` y escribe comandos. Output en el mismo room. NO en una BD, NO en un log.
- [ ] **User-facing usage real**: el operador (humano) usa Element con `home-wsl` Y `pc-aurgi` (>=2 maquinas reales), **>=1 sesion/dia durante >=7 dias consecutivos**, **>=20 comandos totales** repartidos entre devices.
- [ ] **User-facing variado**: cubre capabilities de **>=4 tipos**: read (`!fs.read`, `!ls`), write (`!fs.write`), exec (`!exec`), approval-required (`!fs.write` en path sensible), docker (`!docker exec`).
- [ ] **User-facing onboarding**: parrafo en `## Notas` con pasos numerados: abrir Element -> entrar a room -> `!help` -> ejemplo de comando. Sin leer el flow entero.
- [ ] **User-facing latencia**: tras enviar mensaje en Element, output visible en <3s (read/exec) o <5s (con approval) — medido y registrado en `## Notas`.

### Anti-criterios (invalidan DoD aunque checkboxes verdes)

- [ ] **Solo-en-home-wsl**: el flow funciona en mi WSL pero falla en `pc-aurgi` u otro device fisico.
- [ ] **device_agent muere cada noche**: cualquier crash recurrente del proceso device_agent en los 7 dias de validacion.
- [ ] **Approval flow se salta**: alguna entrada en `device_audit` con capability `requires_approval=true` ejecutada sin `approval_msg_id` valido.
- [ ] **Audit chain rota**: `device_audit_verify_chain` reporta huecos o hash mismatch en algun device.
- [ ] **wg config drift**: cambios manuales en `/etc/wireguard/wg0.conf` del hub sin pasar por `wg_peer_add/remove/revoke`. Git status muestra cambios sin trackear.
- [ ] **Dashboard fantasma**: `agents_dashboard` declarado pero el operador no lo abre durante la ventana de 7 dias. Telemetria muerta.
- [ ] **Pen-test no ejercitado**: replay attack / capability fuera de manifest / token expirado declarados pero sin entry real en `device_audit` con status `rejected_*` en los 7 dias.
- [ ] **Silent-fail**: peer cae >24h y nadie se entera (sin alerta a `#operator-approvals` ni badge rojo en dashboard).
- [ ] **Secrets en repo**: cualquier hit de `git grep -E 'PrivateKey|PSK|operator/ed25519' -- ':!*.md'` en cualquier rama.

### Custom (security-specific, deben tener evidencia en `device_audit`)

- [ ] _(custom)_ Pen-test capability fuera de manifest: entry `device_audit` status=`rejected_capability` ejercitado intencionalmente >=1 vez.
- [ ] _(custom)_ Pen-test replay: entry `device_audit` status=`rejected_nonce` ejercitado >=1 vez con cmd reproducible.
- [ ] _(custom)_ Stale device: forzar `home-wsl` offline >24h, verificar badge `stale` en `agents_dashboard` + mensaje en `#operator-approvals`.
- [ ] _(custom)_ Operator key rotation: ejecutar rollover de la clave ed25519 maestra + revoke-all + re-enroll, sin perder audit chain historica. Documentado en `## Notas`.

## Telemetria esperada

- `call_monitor.calls`: cada `wg_*`, `capability.*`, `docker.*` con duration_ms, success.
- `apps/wg_hub/operations.db`: tabla `wg_peers` + `device_audit` (hash-chained append-only).
- `apps/agents_and_robots/operations.db`: tabla `matrix_capability_dispatches`.
- `apps/agents_dashboard/local_files/agents_dashboard.db`: cache devices + approval queue.
- Dashboards visibles: `agents_dashboard` panel "Mesh" (peers vivos + last handshake + bytes rx/tx).
- Matrix room `#operator-approvals` recibe cada approval_request.
- Element en movil aprueba/rechaza con reacciones (👍/👎) o comando `!approve <id>`.

## Riesgos / gotchas

- **VPS UDP/51820**: firewall del proveedor del VPS puede bloquearlo. Verificar con `nc -u -v vps 51820`.
- **NAT carrier-grade (4G/5G)**: device tras NAT estricto → `PersistentKeepalive = 25` obligatorio.
- **Sleep laptop / android doze**: handshake muere. Auto-reconnect via `systemd-networkd-wait-online` + script.
- **Privilegio sudo**: `wg-quick` requiere root. Devices necesitan sudo-NOPASSWD para `wg-quick@wg0`.
- **Clock skew**: tokens enrollment + nonces dependen de NTP. Forzar `chrony` en VPS y devices.
- **Container privileged**: modo "deep" docker requiere `--cap-add NET_ADMIN`. Riesgo si container compromised.
  Mitigacion: solo modo "deep" para containers de tu propio control (ej. `agents_and_robots` self-hosted), no third-party.
- **Operator key compromise**: si tu ed25519 leaks → cualquiera firma manifests. Plan B: rotacion + revoke-all + re-enroll.
- **Matrix homeserver compromise**: chat E2EE protege contenido, pero metadata (quien habla con quien) leak.
  Aceptable porque homeserver es tuyo en `organic-machine.com`.

## Notas

(rellenar tras ejecutar fases A/C/B/D)

### Para hablar con un device desde Element (onboarding)

1. Abre Element en movil o web (`element.organic-machine.com`).
2. Entra al room `#dev-<nombre>` (un room por device).
3. Escribe `!help` → bot del room (`agents_and_robots`) responde con capability matrix del device.
4. Escribe comando, ej. `!exec ls /home/lucas` o `!fs.read /var/log/syslog`.
5. Si capability requiere approval, te llega notification a `#operator-approvals` → reaccionas 👍 → ejecuta.
6. Output aparece en el mismo room del device.

### Para hablar con un container docker

1. Si el host del container ya esta en la mesh: room `#dev-<host>` con `!docker exec <container> <cmd>`.
2. Modo deep: room dedicado `#cont-<container>` (solo containers enrolled).
