Skip to content

Instantly share code, notes, and snippets.

@vitorcalvi
Last active January 15, 2026 13:15
Show Gist options
  • Select an option

  • Save vitorcalvi/33a3a60faca19bbcf18704584a67b4e6 to your computer and use it in GitHub Desktop.

Select an option

Save vitorcalvi/33a3a60faca19bbcf18704584a67b4e6 to your computer and use it in GitHub Desktop.
Complete guide for inter-agent communication between Charm Crush and OpenCode using the Agent Communication Protocol (ACP)

Full Inter-Agent Communication: Charm Crush ↔ OpenCode via ACP

There are two different ACP protocols - be careful not to confuse them:

Protocol Purpose Transport
OpenCode ACP (Agent Client Protocol) Editor-to-agent integration JSON-RPC via stdio
ACP (Agent Communication Protocol) Agent-to-agent interoperability REST/HTTP

For Charm Crush ↔ OpenCode communication, use the Agent Communication Protocol (ACP) developed by IBM/BeeAI under Linux Foundation.


Architecture Overview

┌─────────────────┐                    ┌─────────────────┐
│   Charm Crush   │ ◄───── HTTP ─────► │    OpenCode     │
│   (Agent A)     │                    │   (Agent B)     │
│   :8000         │                    │   :8001         │
└─────────────────┘                    └─────────────────┘
        │                                      │
        │           ┌─────────────┐            │
        └──────────►│  Task Queue │◄───────────┘
                    │  (Redis)    │
                    └─────────────┘

Core Concepts

Concept Description
Agent Service with registered capabilities
Run Single agent execution with inputs/outputs
Message Multimodal communication unit with parts
Session Conversation continuity across interactions
Await Pause/resume mechanism for interactive tasks

API Endpoints

Health Check

GET /ping

Response: {} (200 OK)

Agent Discovery

GET /agents
GET /agents?limit=10&offset=0

Response:

{
  "agents": [
    {
      "name": "chat",
      "description": "Conversational agent",
      "input_content_types": ["text/plain", "image/*"],
      "output_content_types": ["text/plain", "application/json"],
      "metadata": { "tags": ["Chat"], "capabilities": [...] },
      "status": { "avg_run_tokens": 1500, "success_rate": 98.5 }
    }
  ]
}

Agent Manifest

GET /agents/{agent_name}

Start Run

POST /runs
Content-Type: application/json

Request Body:

{
  "agent_name": "chat",
  "session_id": "123e4567-e89b-12d3-a456-426614174000",
  "input": [
    {
      "role": "user",
      "parts": [
        {
          "content_type": "text/plain",
          "content": "Help me fix failing tests"
        }
      ]
    }
  ],
  "mode": "sync"
}

Response:

{
  "agent_name": "chat",
  "session_id": "123e4567-e89b-12d3-a456-426614174000",
  "run_id": "456e7890-e89b-12d3-a456-426614174000",
  "status": "completed",
  "output": [
    {
      "role": "agent/chat",
      "parts": [
        {
          "content_type": "text/plain",
          "content": "I can help you with that..."
        }
      ]
    }
  ],
  "created_at": "2024-01-15T10:30:00Z",
  "finished_at": "2024-01-15T10:30:10Z"
}

Check Run Status

GET /runs/{run_id}

Resume Awaiting Run

POST /runs/{run_id}
Content-Type: application/json

{
  "run_id": "456e7890-e89b-12d3-a456-426614174000",
  "await_resume": { "response": "Yes, proceed" },
  "mode": "sync"
}

Cancel Run

POST /runs/{run_id}/cancel

Get Run Events (Streaming)

GET /runs/{run_id}/events

Event Types:

  • run.created, run.in-progress, run.completed
  • run.awaiting, run.cancelled, run.failed
  • message.created, message.part, message.completed
  • error

Message Format

interface Message {
  role: "user" | "agent" | "agent/{agent_name}";
  parts: MessagePart[];
  created_at?: string;
  completed_at?: string;
}

interface MessagePart {
  name?: string;
  content_type: "text/plain" | "text/markdown" | "application/json" | "image/*";
  content: string;
  content_encoding?: "plain" | "base64";
  metadata?: {
    kind: "citation" | "trajectory";
    title?: string;
    url?: string;
  };
}

Run Status Lifecycle

created → in-progress → completed
    ↓         ↓            ↓
cancelled ← cancelling   failed
    ↑         ↓
    ↑     awaiting → (resume) → in-progress

Implementation: Charm Crush as ACP Server

Create acp-server.mjs:

// acp-server.mjs - Charm Crush running as ACP server
import { Server } from './acp-sdk/server.js';

const server = new Server({ 
  name: 'crush',
  description: 'Charm Crush - CLI AI Assistant'
});

@server.agent()
async function crush_agent(input, context) {
  const userMessage = input[0]?.parts?.[0]?.content;
  
  // Process with Crush logic
  const result = await processWithCrush(userMessage);
  
  return {
    thought: "Processing user request...",
    result
  };
}

server.run(port=8000);

Implementation: OpenCode as ACP Client

Configure OpenCode to connect to Crush:

// opencode.json
{
  "agent": {
    "crush-remote": {
      "mode": "subagent",
      "description": "Charm Crush remote agent",
      "endpoint": "http://localhost:8000",
      "tools": {
        "write": true,
        "edit": true,
        "bash": true
      }
    }
  }
}

Or use the OpenCode Task tool to invoke Crush:

@crush-remote Fix the failing Jest tests in __tests__/

Implementation: Full Client Example

# acp_client.py - Generic ACP client
import asyncio
from acp_sdk.client import Client
from acp_sdk.models import Message, MessagePart

async def run_crush_agent(prompt: str) -> str:
    async with Client(base_url="http://localhost:8000") as client:
        run = await client.run_sync(
            agent="crush_agent",
            input=[
                Message(
                    role="user",
                    parts=[
                        MessagePart(
                            content=prompt,
                            content_type="text/plain"
                        )
                    ]
                )
            ]
        )
        return run.output[0].parts[0].content

# Usage
result = asyncio.run(run_crush_agent("Fix the failing tests"))
print(result)

Communication Pattern: Bidirectional

Charm Crush calling OpenCode

// In Crush, call OpenCode via HTTP
async function callOpenCode(task) {
  const response = await fetch('http://localhost:8001/runs', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      agent_name: 'build',  // OpenCode's Build agent
      input: [{ role: 'user', parts: [{ content: task, content_type: 'text/plain' }] }],
      mode: 'async'
    })
  });
  return response.json();
}

OpenCode calling Charm Crush

// In OpenCode prompt or task
{
  "agent": "orchestrator",
  "tasks": [
    { "agent": "crush-remote", "input": "Analyze the codebase structure" },
    { "agent": "build", "input": "Implement the feature" }
  ]
}

Setup Commands

1. Install ACP SDK

# Python SDK (recommended)
pip install acp-sdk

# Or TypeScript SDK
npm install acp-sdk

2. Start Charm Crush ACP Server

# Option A: If Crush supports native ACP
crush --protocol acp --port 8000

# Option B: Wrap Crush in ACP server
python acp-server.py &

3. Start OpenCode ACP Server

opencode acp --port 8001

4. Verify Connectivity

# Check Crush agent availability
curl http://localhost:8000/agents

# Check OpenCode agent availability  
curl http://localhost:8001/agents

Advanced: Message Queue for Scale

For production multi-agent workflows:

# Using Redis as message broker
import redis
import json

r = redis.Redis(host='localhost', port=6379)

def submit_task(agent, task):
    r.lpush('acp_tasks', json.dumps({
        'agent': agent,
        'task': task,
        'timestamp': time.time()
    }))

def get_result(task_id):
    return r.get(f'result:{task_id}')

Content Types Supported

Type Description
text/plain Plain text
text/markdown Markdown formatted
application/json Structured data
image/* Any image format
*/_ Wildcard (any type)

Error Response Format

{
  "code": "server_error | invalid_input | not_found",
  "message": "Human-readable error description",
  "data": {
    "additional": "error context"
  }
}

Summary

  1. Install SDK: pip install acp-sdk
  2. Run servers: crush --protocol acp --port 8000 and opencode acp --port 8001
  3. Discover agents: GET /agents
  4. Execute tasks: POST /runs with agent_name and input
  5. Check status: GET /runs/{run_id}
  6. Stream events: GET /runs/{run_id}/events

Both agents communicate via standard HTTP/REST with JSON payloads, enabling flexible inter-agent workflows.


References

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment