---
title: |-
  Playbook *UC5*
  Workspace E2E
description: "Verificación completa: workspace container remoto con gateway-server + a2a-mcp + channels + spawn-plane. Victoria final del ecosistema a2a."
kicker: Playbook · UC5 · Workspace E2E
subtitle: |-
  El use case definitivo: **todo en el workspace container remoto**. Sesión via
  gateway-web, channels activos, spawn de segunda sesión desde el workspace,
  mensajería bidireccional realtime entre peers remotos y Mac. Si esto pasa,
  el ecosistema a2a está completo.
strip:
  - { k: PLAYBOOK, v: UC5-E2E }
  - { k: REV, v: "1.0" }
  - { k: FECHA, v: 21 JUN 2026 }
  - { k: DEPENDE, v: P0 + UC3 + UC4 }
meta:
  - { k: Esfuerzo, v: 30 min }
  - { k: Depende de, v: "P0 + UC1 + UC2 + UC3 + UC4" }
  - { k: Bloquea, v: nada (es el hito final)" }
  - { k: Criticidad, v: "[[hot:VICTORIA]] — si esto pasa, 0.18 completo" }
stamps:
  - { text: Workspace container configurado, tone: ok }
  - { text: a2a-mcp en workspace, tone: ok }
  - { text: Runner daemon en Mac pendiente, tone: warn }
toc_legend:
  - { text: verificado, tone: ok }
  - { text: requiere fix, tone: warn }
  - { text: roto, tone: bad }
footer:
  left: |-
    **UC5-E2E · REV 1.0**
    Playbooks de verificación E2E para mks-agentics
  right: |-
    **WORKSPACE** remoto · **CHANNELS** · **SPAWN** · **VICTORIA**
---

## Objetivo *final* {#objetivo}

El playbook de victoria. Todo el ecosistema a2a funcionando en el workspace container remoto: gateway-web conectado al workspace, sesión con channels, spawn de una segunda sesión desde el workspace, mensajería bidireccional realtime entre peers remotos y el Mac de waxin. Si este playbook pasa, la fase 0.18 está completa. {.lead}

```dossier:diagram
engine: d2
source: |
  direction: right

  # ── Mac de waxin ──
  mac: "Mac (waxin)\ncl minimax\n--proxy --channels" {
    style: {
      fill: "#1a1a2e"
      stroke: "#e94560"
    }
    macMCP: "a2a-mcp" {
      style: {
        fill: "#16213e"
        stroke: "#e94560"
      }
    }
    browser: "gateway-web" {
      style: {
        fill: "#16213e"
        stroke: "#e94560"
      }
    }
  }

  # ── Nube ──
  cloud: {
    broker: "Broker público\nbroker.gateway.mks2508.systems" {
      style: {
        fill: "#0f3460"
        stroke: "#e94560"
      }
    }

    proxy: "gateway-proxy\nproxy.gateway.mks2508.systems" {
      style: {
        fill: "#1a1a2e"
        stroke: "#e94560"
      }
    }

    workspace: "Workspace Container\nlab1-helsinki.api.mks2508.systems" {
      style: {
        fill: "#0f3460"
        stroke: "#533483"
      }

      gateway: "gateway-server\n:3456" {
        style: {
          fill: "#16213e"
          stroke: "#533483"
        }
      }

      sessionWS: "Sesión A (workspace)\nclaude --sdk-url\nchannels=true" {
        style: {
          fill: "#1a1a2e"
          stroke: "#533483"
        }
        a2aWS: "a2a-mcp\n(embedded)" {
          style: {
            fill: "#16213e"
            stroke: "#533483"
          }
        }
      }

      sessionWS2: "Sesión B (spawneada)\nclaude --sdk-url\nchannels=true" {
        style: {
          fill: "#1a1a2e"
          stroke: "#0f3460"
        }
        a2aWS2: "a2a-mcp\n(embedded)" {
          style: {
            fill: "#16213e"
            stroke: "#0f3460"
          }
        }
      }
    }
  }

  # ── Conexiones ──
  # Mac
  mac.browser -> workspace.gateway: "POST /api/sessions\n+ WS bridge" {
    style.stroke-dash: 3
  }
  mac.macMCP -> broker: "MCP tools\n+ channel-push WS" {
    style.stroke-dash: 3
  }

  # Workspace → Broker
  workspace.gateway -> workspace.sessionWS: "cliLauncher.launch()\nchannels=true"
  workspace.sessionWS.a2aWS -> broker: "peer register\n+ channel-push WS" {
    style.stroke-dash: 3
  }

  # Workspace spawn
  workspace.gateway -> workspace.sessionWS2: "cliLauncher.launch()\n(spawn interno)" {
    style.stroke-dash: 3
  }
  workspace.sessionWS2.a2aWS2 -> broker: "peer register\n+ channel-push WS" {
    style.stroke-dash: 3
  }

  # Mensajería E2E
  mac.macMCP -> broker: "SendPeerMessage\nto workspace peer" {
    style.stroke: "#e94560"
  }
  broker -> workspace.sessionWS: "CHANNEL PUSH\nmessage from Mac" {
    style.stroke: "#e94560"
  }
  workspace.sessionWS.a2aWS -> broker: "SendPeerMessage\nto Mac peer" {
    style.stroke: "#533483"
  }
  broker -> mac: "CHANNEL PUSH\nresponse from workspace" {
    style.stroke: "#533483"
  }
```

## El *flow* completo {#flow-completo}

```dossier:cards
items:
  - no: "1"
    title: Mac → gateway-web
    chip: { text: browser, tone: ok }
    bullets:
      - Abrir gateway-web en el navegador
      - Conectar al workspace remoto
      - Seleccionar provider + channels ON
  - no: "2"
    title: Workspace → Sesión A
    chip: { text: cli-launcher, tone: ok }
    bullets:
      - POST /api/sessions {channels:true}
      - cliLauncher.launch() → Bun.spawn(claude)
      - a2a-mcp config → peer registrado en broker
  - no: "3"
    title: Sesión A → Channels
    chip: { text: channel-push, tone: ok }
    bullets:
      - dangerouslyLoadDevelopmentChannels
      - WS /ws/broker/peer/:id
      - Listo para recibir mensajes realtime
  - no: "4"
    title: Mac → ListPeers
    chip: { text: discovery, tone: ok }
    bullets:
      - Sesión Claude en Mac con channels
      - ListPeers muestra peer del workspace
      - "Verificación: el workspace es visible"
  - no: "5"
    title: Mac ↔ Workspace
    chip: { text: bidireccional, tone: hot }
    bullets:
      - SendPeerMessage Mac → workspace
      - Channel block en workspace (realtime)
      - Respuesta workspace → Mac
      - Channel block en Mac (realtime)
  - no: "6"
    title: Workspace spawn
    chip: { text: spawn-plane, tone: hot }
    bullets:
      - Sesión A spawnea Sesión B
      - Runner integrado en gateway-server
      - B aparece en ListPeers
      - Comunicación A↔B↔Mac
```

## F1 — *Preparación* del workspace {#f1-prep}

```dossier:phases
items:
  - dur: "5 min"
    title: Verificar que el workspace está listo
    checks:
      - text: "`curl -s https://lab1-helsinki.api.mks2508.systems/health` → 200 OK"
        sub: Gateway-server del workspace responde
      - text: "`curl -s https://broker.gateway.mks2508.systems/healthz` → 200 OK"
        sub: Broker público responde
      - text: "SSH al workspace: `which a2a-mcp` → `/usr/local/bin/a2a-mcp`"
        sub: a2a-mcp wrapper existe
      - text: "SSH al workspace: `env | grep GATEWAY_BROKER_URL` → broker público"
        sub: Env var configurada
      - text: "SSH al workspace: `env | grep OIDC_CLIENT_ID` → valor presente"
        sub: Credenciales OIDC inyectadas
      - text: "SSH al workspace: `supervisorctl status gateway` → RUNNING"
        sub: Servicio gateway corriendo
```

## F2 — *Crear* sesión A en workspace {#f2-sessionA}

```dossier:phases
items:
  - dur: "5 min"
    title: Crear sesión con channels desde gateway-web
    checks:
      - text: "Abrir gateway-web → Remote slot → `https://lab1-helsinki.api.mks2508.systems`"
        sub: Conectado al workspace remoto
      - text: "Provider: MiniMax, Channels: ON"
        sub: La sesión se crea con channels activos
      - text: "Click 'New Session' → sesión creada con estado 'running'"
        sub: POST /api/sessions 200 + WS bridge activo
      - text: "Enviar 'di hola' en el chat → respuesta del modelo"
        sub: La sesión funciona normalmente
      - text: "SSH al workspace: `ps aux | grep claude` → proceso corriendo"
        sub: El CLI de Claude está vivo dentro del container
```

### POST /api/sessions con channels

```dossier:term
title: Crear sesión con channels en el workspace
content: |
  # Desde gateway-web o vía curl:
  $ curl -s -X POST https://lab1-helsinki.api.mks2508.systems/api/sessions \
      -H "Content-Type: application/json" \
      -d '{
        "providerMode": "minimax",
        "cwd": "/workspace",
        "channels": true
      }' | jq .
  → {
  →   "session": {
  →     "sessionId": "ws-a-abc123...",
  →     "state": "running",
  →     "model": "claude-sonnet-4-20250514",
  →     "channels": true
  →   }
  → }

  # Verificar en el workspace:
  $ ssh developer@lab1-helsinki.api.mks2508.systems
  $ cat ~/.mks-harness/sessions/ws-a-abc123/.claude.json | jq '.mcpServers'
  → {
  →   "agent2agent": {
  →     "command": "a2a-mcp",
  →     "env": {
  →       "GATEWAY_BASE_URL": "https://broker.gateway.mks2508.systems",
  →       "A2A_PEER_ID": "ws-a-abc123...",
  →       "A2A_CHANNEL_PUSH": "1"
  →     }
  →   }
  → }
```

## F3 — *Conectar* Mac al ecosistema {#f3-mac}

```dossier:phases
items:
  - dur: "5 min"
    title: Arrancar sesión Claude en Mac con channels
    checks:
      - text: "En Mac: `cl minimax --proxy --channels`"
        sub: Claude Code en Mac con channels apuntando al broker público
      - text: "`ListPeers` → muestra peer del Mac + peer del workspace (sesión A)"
        sub: Ambos peers visibles desde el Mac
      - text: "`GetPeer ws-a-abc123...` → card de la sesión A en el workspace"
        sub: El Mac puede ver el peer del workspace
```

## F4 — *Mensajería* Mac ↔ Workspace {#f4-messaging}

```dossier:phases
items:
  - dur: "5 min"
    title: Mensaje bidireccional realtime
    checks:
      - text: "En Mac: `SendPeerMessage to=ws-a-abc123... content='hola desde Mac'`"
        sub: Mensaje enviado al peer en el workspace
      - text: "En gateway-web (sesión A): aparece `<channel>` block con 'hola desde Mac'"
        sub: El mensaje llegó en tiempo real al workspace
      - text: "En gateway-web: responder 'hola desde workspace, recibido!'"
        sub: La sesión A responde al Mac
      - text: "En Mac: aparece `<channel>` block con la respuesta del workspace"
        sub: Bidireccional confirmado — Mac ↔ Workspace realtime
```

### Verificación del channel block en gateway-web

```dossier:term
title: gateway-web — salida esperada
content: |
  # En el chat de gateway-web, después de que Mac envía un mensaje:

  <channel source="agent2agent" from_id="mac-peer-xyz..."
           kind="request" priority="normal">
  hola desde Mac
  </channel>

  # La sesión A en el workspace puede responder:
  > SendPeerMessage to=mac-peer-xyz... content="hola desde workspace, recibido!"

  # Y en el Mac, Claude Code muestra:
  <channel source="agent2agent" from_id="ws-a-abc123..."
           kind="request" priority="normal">
  hola desde workspace, recibido!
  </channel>
```

## F5 — *Spawn* en workspace {#f5-spawn}

```dossier:phases
items:
  - dur: "5 min"
    title: Spawn de segunda sesión desde el workspace
    checks:
      - text: "En sesión A (workspace): `SpawnPeer name='ws-session-B' provider='minimax' host='lab1-helsinki'`"
        sub: Spawn de una segunda sesión gestionada por el gateway-server del workspace
      - text: "`ListPeers` → 4+ peers (Mac, sesión A, sesión B, gateway-server como runner)"
        sub: La sesión B aparece registrada en el broker
      - text: "En Mac: `SendPeerMessage to=<peerId-B> content='hola a la spawneada'`"
        sub: El Mac puede enviar mensajes a la sesión spawneada
      - text: "En sesión A (workspace): `SendPeerMessage to=<peerId-B> content='hola desde A'`"
        sub: La sesión A también puede comunicarse con la spawneada
```

```dossier:note
tone: accent
tag: Spawn en workspace vs spawn en Mac
body: |
  En el workspace, el **gateway-server en modo server** (no runner) actúa como
  runner implícito para spawns internos. Cuando el broker recibe un `POST /api/spawn`
  con `host='lab1-helsinki'`, enruta el `spawn_order` al gateway-server del workspace
  (que está registrado como runner en el broker).

  Esto es diferente a UC4 (runner independiente en Mac). Aquí el gateway-server
  del workspace es tanto servidor de sesiones como runner de spawns — unifica
  ambos roles en el mismo proceso.

  **Si el gateway-server del workspace NO está registrado como runner en el broker**,
  el spawn fallará con `RUNNER_OFFLINE`. Hay que verificar que el gateway-server
  del workspace se registra como runner al iniciar (el código en `runner.ts` vs
  `server.ts` — verificar cuál entrypoint se usa en el workspace).
```

## F6 — *Verificación* final {#f6-final}

```dossier:phases
items:
  - dur: "5 min"
    title: E2E completo — todos los caminos
    checks:
      - text: "Mac ↔ Workspace A: mensajes bidireccionales realtime"
        sub: Channel-push entre Mac y workspace
      - text: "Workspace A ↔ Workspace B: mensajes bidireccionales"
        sub: Channel-push entre sesiones en el mismo workspace
      - text: "Mac ↔ Workspace B: mensajes bidireccionales"
        sub: Channel-push entre Mac y sesión spawneada
      - text: "`ListPeers` desde cualquier peer muestra todos los peers (4+)"
        sub: Peer discovery completo
      - text: "`CheckInbox` de cada peer → mensajes también en inbox (durable)"
        sub: Los mensajes se persisten en el inbox del broker (SQLite)
      - text: "`StopPeer` de la sesión B → peer removido"
        sub: "Lifecycle completo: spawn → communicate → stop"
```

### Verificación de logs en el workspace

```dossier:term
title: SSH al workspace — verificar logs
content: |
  $ ssh developer@lab1-helsinki.api.mks2508.systems

  # Log del gateway-server (cli-launcher):
  $ tail -50 /var/log/supervisor/gateway.log
  → Spawning CLI for session ws-a-abc123...
  → Peer registered with remote broker: ws-a-abc123 (depth=1)
  → Spawning CLI for session ws-b-def456...
  → Peer registered with remote broker: ws-b-def456 (depth=1)

  # Procesos Claude Code corriendo:
  $ ps aux | grep claude
  → developer  1234  ...  claude --sdk-url http://127.0.0.1:4105/ws/cli/ws-a-abc123 ...
  → developer  5678  ...  claude --sdk-url http://127.0.0.1:4105/ws/cli/ws-b-def456 ...

  # Directorios de sesión:
  $ ls ~/.mks-harness/sessions/
  → ws-a-abc123/  ws-b-def456/
```

## *Veredicto* {#veredicto}

```dossier:matrix
groups:
  - tone: v
    title: Condiciones de victoria
    badge: "6 checks"
    items:
      - text: Sesión en workspace con channels creada desde gateway-web
        sub: UI → workspace → sesión → output
      - text: Mac ve el peer del workspace vía ListPeers
        sub: Peer discovery cross-machine
      - text: "Mac ↔ Workspace: mensajes realtime vía channel-push"
        sub: Channel-push cross-machine funciona
      - text: "Workspace A ↔ Workspace B: mensajes realtime"
        sub: Channel-push intra-workspace funciona
      - text: Spawn de segunda sesión desde el workspace
        sub: Spawn-plane en workspace remoto funciona
      - text: Ciclo completo Mac ↔ A ↔ B con mensajería realtime
        sub: Ecosistema a2a completo y verificado
  - tone: r
    title: Puede fallar (esperable)
    badge: "3 checks"
    items:
      - text: Channel-push cross-machine tiene latencia >5s
        sub: Posible reconnect del WS en el workspace
      - text: El workspace no aparece como runner en /api/runners
        sub: El gateway-server del workspace no está en modo runner
      - text: Los mensajes solo se entregan por inbox (no channel-push)
        sub: WS channel-push caído en algún peer
  - tone: p
    title: Si falla → regresión
    badge: "2 checks"
    items:
      - text: El workspace no registra peers en el broker
        sub: a2a-mcp no funcional, GATEWAY_BROKER_URL mal configurada
      - text: Mac no ve ningún peer del workspace
        sub: Broker no accesible desde ambos, o credenciales OIDC incorrectas
```

```dossier:questions
items:
  - no: Q1
    title: "¿Aparece el peer del workspace en ListPeers del Mac?"
    chip: { text: discovery, tone: hot }
    body: Desde el Mac, ejecutar ListPeers después de crear la sesión en el workspace
    branches:
      - tone: "yes"
        label: "Sí → peer del workspace visible"
        text: Continuar a F4 (mensajería). Peer discovery cross-machine funciona.
      - tone: "no"
        label: "No → no se ve"
        text: "Revisar: (1) ¿está a2a-mcp funcionando en el workspace? (2) ¿GATEWAY_BROKER_URL apunta al mismo broker que el Mac? (3) ¿el peer se registró? Ver logs del gateway-server."
  - no: Q2
    title: "¿Llega el mensaje del Mac al workspace en tiempo real?"
    chip: { text: channel-push, tone: hot }
    body: SendPeerMessage de Mac a workspace → verificar gateway-web
    branches:
      - tone: "yes"
        label: "Sí → <channel> block en gateway-web"
        text: Channel-push cross-machine funciona. Continuar a F5 (spawn).
      - tone: "no"
        label: "No → no aparece"
        text: "Primero verificar que el mensaje está en el inbox (CheckInbox). Si está en inbox pero no en channel, el WS channel-push del workspace está caído. Si no está ni en inbox, el broker no enrutó el mensaje."
  - no: Q3
    title: "¿Funciona el spawn de segunda sesión en el workspace?"
    chip: { text: spawn-plane, tone: hot }
    body: SpawnPeer desde sesión A del workspace → ¿peer B aparece?
    branches:
      - tone: "yes"
        label: "Sí → peer B visible + responde"
        text: "**VICTORIA. El ecosistema a2a está completo.** UC5 VERDE. Fase 0.18 cerrada."
      - tone: "no"
        label: "No → spawn falla o no se ve"
        text: "Revisar si el gateway-server del workspace está registrado como runner en el broker. Puede que necesite modo runner explícito o que el spawn interno no esté implementado aún."
```

```dossier:note
tone: accent
tag: Si UC5 pasa → 0.18 DONE
body: |
  UC5 es el criterio de cierre definitivo para la fase 0.18 (broker reliability + flip público + spawn-plane).

  Con UC5 verde:
  - **0.18.E** (MCP OIDC + settings) → ✅ verificado
  - **0.18.F** (workspace a2a E2E) → ✅ verificado
  - **0.18.D** (broker flip público) → ✅ verificado (el broker público es el bus central)
  - **0.18.C** (proxy UI dashboard) → pendiente (no bloquea)

  Siguiente fase: **0.19** — polish + test harness automatizado basado en estos playbooks.
```

```dossier:sources
groups:
  - title: Playbooks relacionados
    items:
      - label: P0 — Prerrequisitos
        url: ./PREREQUISITES.dossier.md
        chip: { text: prereq }
      - label: UC1 — Proxy session
        url: ./UC1-proxy-provider.dossier.md
        chip: { text: prereq }
      - label: UC2 — Gateway-web session
        url: ./UC2-gateway-web-workspace.dossier.md
        chip: { text: prereq }
      - label: UC3 — Mac peers channels
        url: ./UC3-mac-peers-channels.dossier.md
        chip: { text: prereq }
      - label: UC4 — Spawn peer local
        url: ./UC4-mac-spawn-peer.dossier.md
        chip: { text: prereq }
  - title: Código y diseño
    items:
      - label: cli-launcher.service.ts
        url: apps/gateway-server/src/services/cli-launcher.service.ts
        chip: { text: source }
      - label: spawn-plane-design-2026-06-20.md
        url: docs/jarvis/spawn-plane-design-2026-06-20.md
        chip: { text: spec }
      - label: broker-prod-sprint-state-2026-06-20.md
        url: docs/jarvis/broker-prod-sprint-state-2026-06-20.md
        chip: { text: state }
      - label: entrypoint.sh
        url: ../mks-workspaces/docker/devenv-full/entrypoint.sh
        chip: { text: source }
```
