Manage communication between LLM agents through a persistent Node.js terminal channel on port 3009.

Install

mkdir -p .claude/skills/llm-b-comms && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/10534" && unzip -o skill.zip -d .claude/skills/llm-b-comms && rm skill.zip

Installs to .claude/skills/llm-b-comms

Activation

This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.

Protocolo oficial de comunicação de LLM-A com LLM-B via Terminal Permanente. Cobre como inicializar o terminal, verificar saúde, enviar mensagens, interpretar respostas, e lidar com todos os casos especiais (terminal offline, loop não pronto, ocupado, stall etc.). Use quando precisar se comunicar com LLM-B, verificar seu estado, ou quando as instruções de comunicação LLM-A ↔ LLM-B não estiverem claras.
405 charsno explicit “when” triggerlonger than Claude Code's old 250-char listing cap (fine on current versions)
Intermediate

Key capabilities

  • Check LLM-B terminal health and readiness
  • Start the LLM-B terminal via VS Code Task or command line
  • Wait for LLM-B dialog loop to become active
  • Send single messages to LLM-B with optional attachments
  • Send a pipeline of messages to LLM-B
  • Monitor LLM-B events in real-time

How it works

The skill interacts with LLM-B via a Permanent Terminal (Node.js process on port 3009) using HTTP, checking health, waiting for readiness, and injecting messages or pipelines through `inject.js`.

Inputs & outputs

You give it
Messages for LLM-B, optional attachments, and commands to check health or start terminal
You get back
LLM-B's reply, health status, or event notifications

When to use llm-b-comms

  • Communicating with LLM-B
  • Verifying agent health status
  • Managing inter-agent message loops

About this skill

LLM-B Comms — Protocolo Oficial de Comunicação LLM-A → LLM-B

Visão Geral

A LLM-B é um agente de raciocínio contínuo que opera em um Terminal Permanente (processo Node.js separado, porta 3009). A comunicação se dá exclusivamente via HTTP usando o arquivo src/copilot/channel/inject.js (ou seu equivalente de API pública em src/copilot/channel/index.js).

Regra fundamental: Você (LLM-A) NUNCA fala diretamente com a LLM-B — toda comunicação é intermediada pelo Terminal Permanente ativo na porta 3009.


Passo 1: Verificar se o Terminal está Ativo

Antes de qualquer comunicação, verifique se o terminal está rodando:

import { checkLlmBHealth } from '#copilot/channel';
// ou diretamente:
// import { checkLlmBHealth } from './src/copilot/channel/inject.js';

const health = await checkLlmBHealth();
// health = { ok: boolean, ready: boolean, busy: boolean, hubSessionId: string|null, agentStatus: string }

Interpretação:

  • ok: false → Terminal offline. Precisa ser iniciado antes de qualquer comunicação.
  • ok: true, ready: false → Terminal online mas dialog loop não inicializado ainda. Use waitForLlmBReady().
  • ok: true, ready: true, busy: false → Pronto para receber mensagem.
  • ok: true, ready: true, busy: true → Ocupado processando outra mensagem. injectToLlmB() tentará automaticamente (retry com backoff).

Passo 2: Iniciar o Terminal (se necessário)

CRÍTICO — Regra de inicialização: O terminal DEVE ser sempre iniciado via Task do VS Code (terminal:llm-b), não via run_in_terminal com isBackground=true. Apenas a task abre um painel visível para o usuário. Rodar em background oculto priva o usuário do terminal interativo.

No VSCode (método recomendado — integração nativa com workspace):

Usar a Task VSCode criada especificamente para isso:

Ctrl+Shift+P → "Tasks: Run Task" → "terminal:llm-b"

Ou pelo painel de tasks (Explorer → Run and Debug não — usar Terminal → Run Task).

O VSCode abrirá um painel dedicado chamado "Terminal LLM-B" e aguardará o pattern LLM-B pronta no output antes de marcar a task como concluída.

Via linha de comando direta:

npm run terminal:llm-b

Via PM2 (produção):

COPILOT_TERMINAL_ENABLED=true npx pm2 start ecosystem.config.cjs --only llm-b-terminal

Verificar se já está rodando:

curl -s http://127.0.0.1:3009/health | node -e "const d=require('fs').readFileSync('/dev/stdin','utf8');console.log(JSON.parse(d))"

Passo 3: Aguardar Prontidão (se acabou de iniciar)

import { waitForLlmBReady } from '#copilot/channel';
// Espera até 30s (padrão) pelo dialog loop ficar ativo
await waitForLlmBReady({ maxWaitMs: 30_000, pollIntervalMs: 2_000 });

Passo 4: Enviar Mensagem e Receber Resposta

Caso simples (mensagem única):

import { injectToLlmB } from '#copilot/channel';

const result = await injectToLlmB(
  'Analise o arquivo src/copilot/agent/always-alive.js e identifique possíveis race conditions.',
);
// result = { ok: true, reply: "...", durationMs: 18500, from: "llm-a" }

console.log(result.reply); // resposta completa da LLM-B
console.log(result.durationMs); // ex: 18500 (esperado: 15-25s por turno)

Com attachments (arquivos):

import { injectToLlmB } from '#copilot/channel';
import { readFileSync } from 'node:fs';

// Método 1: embed o conteúdo diretamente na mensagem (recomendado para arquivos pequenos)
const code = readFileSync('src/copilot/agent/always-alive.js', 'utf-8');
const result = await injectToLlmB(
  `Analise este arquivo e identifique todos os métodos que emitem eventos:\n\n\`\`\`js\n${code}\n\`\`\``,
);

// Método 2: usar o campo attachments (API do Copilot SDK — para arquivos via SDK)
// (somente quando executando dentro do contexto do Copilot SDK)

Pipeline (sequência de mensagens):

import { injectPipeline } from '#copilot/channel';

const { ok, results } = await injectPipeline([
  { prompt: 'Você está disponível para uma análise longa?' },
  {
    prompt:
      'Analise todas as classes em src/copilot/agent/ e liste os eventos emitidos por cada uma.',
    waitMs: 2000,
  },
  { prompt: 'Agora, identifique possíveis vazamentos de listeners (missing off() calls).' },
  { prompt: 'Gere um resumo executivo com os 3 problemas mais críticos encontrados.' },
]);

for (const r of results) {
  console.log(`Step ${r.step}: ${r.prompt.slice(0, 50)}...`);
  console.log(`Resposta (${r.durationMs}ms): ${r.reply.slice(0, 200)}...`);
}

Passo 5: Observar Eventos em Tempo Real (opcional)

Para monitorar respostas da LLM-B sem bloquear:

import { subscribeLlmB } from '#copilot/channel';

const sub = subscribeLlmB((evt) => {
  switch (evt.type) {
    case 'reply':
      console.log('[LLM-B]', evt.data.content);
      break;
    case 'ready':
      console.log('[LLM-B] Pronta para próxima mensagem');
      break;
    case 'stalled':
      console.warn('[LLM-B] Dialog loop travado — watchdog vai reiniciar');
      break;
    case 'busy':
      console.log('[LLM-B] Ocupada:', evt.data.busy);
      break;
  }
});

// Após uso:
sub.unsubscribe();

Para apenas eventos críticos (stall, fatal, system) sem overhead de replies:

import { subscribeLlmBCritical } from '#copilot/channel';
const sub = subscribeLlmBCritical((evt) => {
  /* ... */
});

Tratamento de Erros

Código de ErroCausaAção
LLM_B_TIMEOUTResposta demorou > timeoutMs (padrão: 130s)Aumentar timeoutMs ou dividir a pergunta
LLM_B_BUSYLLM-B ocupada após todos os retries (padrão: 3)Aguardar e tentar novamente; aumentar retries
LLM_B_UNAVAILABLETerminal não está rodando (503)Iniciar o terminal: npm run terminal:llm-b
LLM_B_NOT_READYDialog loop não ficou pronto em tempoVerificar logs do terminal; reiniciar se necessário
LLM_B_ERRORErro interno na execução do turnoVerificar logs: logs/llm-b-terminal-error.log
LLM_B_INVALID_RESPONSEResposta não é JSON válidoBug no terminal — verificar versão do código
import { injectToLlmB } from '#copilot/channel';

try {
  const result = await injectToLlmB('Sua pergunta aqui', {
    timeoutMs: 180_000, // 3 minutos para perguntas complexas
    retries: 5, // tentar até 5 vezes se ocupado
    retryDelayMs: 2_000, // 2s, 4s, 6s, 8s, 10s de espera
  });
  console.log(result.reply);
} catch (err) {
  if (err.code === 'LLM_B_UNAVAILABLE') {
    console.error('Terminal LLM-B offline. Execute: npm run terminal:llm-b');
  } else if (err.code === 'LLM_B_TIMEOUT') {
    console.error('LLM-B demorou para responder. Tente dividir a pergunta.');
  } else {
    console.error('Erro inesperado:', err.message, err.code);
  }
}

Referência Rápida de Endpoints HTTP (sem SDK)

Use curl para testes ou debug direto:

# Verificar saúde
curl -s http://127.0.0.1:3009/health | jq .

# Enviar mensagem
curl -s -X POST http://127.0.0.1:3009/inject \
  -H 'Content-Type: application/json' \
  -d '{"message": "Sua pergunta aqui", "from": "llm-a"}' | jq .

# Pipeline
curl -s -X POST http://127.0.0.1:3009/pipeline \
  -H 'Content-Type: application/json' \
  -d '{"steps": [{"prompt": "Pergunta 1"}, {"prompt": "Pergunta 2", "waitMs": 1000}], "from": "llm-a"}' | jq .

# Listar sessões do hub
curl -s 'http://127.0.0.1:3009/sessions?limit=5' | jq .

# Turnos de uma sessão
curl -s 'http://127.0.0.1:3009/sessions/<SESSION_ID>/turns?limit=20' | jq .

# Memórias semânticas
curl -s -X POST http://127.0.0.1:3009/memory \
  -H 'Content-Type: application/json' \
  -d '{"tag": "arquitetura", "content": "O AlwaysAliveAgent usa EventEmitter e tem dialog loop permanente."}' | jq .

curl -s 'http://127.0.0.1:3009/memory?tag=arquitetura' | jq .

# Configuração atual
curl -s http://127.0.0.1:3009/config | jq .

# Git status via terminal LLM-B
curl -s http://127.0.0.1:3009/git/status | jq .

# GitHub issues
curl -s 'http://127.0.0.1:3009/gh/issues?state=open&limit=10' | jq .

# Eventos SSE (streaming)
curl -N http://127.0.0.1:3009/events
curl -N 'http://127.0.0.1:3009/events?level=critical'

Arquitetura do Sistema

LLM-A (você, Copilot)
    │
    │  import { injectToLlmB } from '#copilot/channel'
    │  POST http://127.0.0.1:3009/inject
    ▼
Terminal Permanente LLM-B (processo separado: npm run terminal:llm-b)
    │
    │  src/copilot/terminal/server.js     ← servidor HTTP raw (porta 3009)
    │  src/copilot/terminal/http-handlers.js  ← lógica de handlers
    │  src/copilot/terminal/dialog.js     ← sendTurn() + ensureDialogLoop()
    │  src/copilot/channel/client.js      ← llmBridgeClient.dialogTurn()
    │  src/copilot/agent/always-alive.js  ← AlwaysAliveAgent + dialog loop
    │
    │  sendDialogTurn(message)
    ▼
AlwaysAliveAgent → answerPendingQuestion(message) → Copilot SDK ask_user()
    │
    ▼
LLM-B (modelo: gpt-5-mini, reasoning: high)
    │
    │  ask_user("REPLY: <resposta>")
    ▼
AlwaysAliveAgent → emit('dialog.reply', { reply }) → sendTurn() resolve
    │
    ▼
HTTP /inject response: { ok: true, reply: "...", durationMs: ... }
    │
    ▼
LLM-A recebe a resposta

Dialog Loop Protocol (interno):

O dialog loop é a "linguagem" que LLM-B fala internamente com o sistema:

  1. LLM-B chama ask_user("READY: aguardando próxima mensagem")dialog.ready emitido
  2. Sistema envia mensagem via answerPendingQuestion(message)
  3. LLM-B processa e chama ask_user("REPLY: <resposta>")dialog.reply emitido
  4. Sistema captura o reply → reso

Content truncated.

When not to use it

  • When LLM-A needs to communicate directly with LLM-B without the Permanent Terminal
  • When the task requires starting the terminal via `run_in_terminal` with `isBackground=true`
  • When the user wants to restart the terminal without a complete stall

Limitations

  • LLM-A never speaks directly with LLM-B
  • Terminal must be started via VS Code Task (`terminal:llm-b`) or `npm run terminal:llm-b`
  • Rate limit of 10 req/min for `/inject` endpoint

How it compares

This skill provides a standardized, indirect communication protocol with LLM-B through a dedicated terminal, ensuring stability and handling special cases like stalls, unlike direct or ad-hoc communication.

Compared to similar skills

llm-b-comms side by side with the closest alternatives in the catalog.

SkillInstallsUpdatedSafetyDifficulty
llm-b-comms (this skill)03moReviewIntermediate
agent-collective-intelligence-coordinator06moNo flagsAdvanced
agent-mesh-coordinator06moReviewAdvanced
clawhub252moReviewIntermediate

Try saying

Example prompts that trigger this skill in your AI assistant.

Search skills

Search the agent skills registry