Agent 集成方案
Agent 集成 Cider Claude 自动化
Claude/CodeBuddy CLI 作为 Cider Agent 与 Firecracker 沙箱的集成方案设计
本章详细设计如何将 Claude 或 CodeBuddy CLI 作为 Cider Agent 集成到 Firecracker 本地开发沙箱中,实现自动化开发工作流。
架构设计
系统架构总览
flowchart TB
subgraph "Cider Platform"
A[Claude/CodeBuddy CLI] -->|Agent Protocol| B[Cider Agent Runtime]
end
subgraph "macOS Host"
B -->|API Calls| C[Agent Controller]
C -->|gRPC/HTTP| D[Lima VM]
end
subgraph "Lima VM 内部"
D --> E[Orchestrator Service]
E --> F[Firecracker API]
end
subgraph "Firecracker MicroVMs"
F --> G[MicroVM 1<br/>Node.js App]
F --> H[MicroVM 2<br/>Go App]
F --> I[MicroVM N<br/>Python App]
G --> J[Agent Sidecar]
H --> J
I --> J
end
subgraph "服务暴露"
J -->|HTTP/gRPC| K[API Gateway]
K -->|Port Forward| L[macOS Host Ports]
end
style A fill:#e1f5e1
style C fill:#fff3cd
style E fill:#f8d7da
style J fill:#d1ecf1
style K fill:#d4edda
核心组件说明
| 组件 | 职责 | 技术栈 | 部署位置 |
|---|---|---|---|
| Agent Controller | 接收 Cider Agent 指令,转换为 Firecracker 操作 | Node.js/Go | macOS Host |
| Orchestrator Service | 管理 MicroVM 生命周期、资源调度 | Go/Rust | Lima VM |
| Firecracker API | 与 Firecracker 进程通信 | REST API | Lima VM |
| Agent Sidecar | 在 MicroVM 内执行 Agent 指令 | Node.js | MicroVM |
| API Gateway | 统一服务暴露和路由 | Nginx/Envoy | Lima VM |
Claude/CodeBuddy CLI 集成
Cider Agent 协议适配
Cider Agent 通常通过标准化协议与外部环境交互。我们需要在 Firecracker 沙箱中实现适配层。
协议转换层设计
sequenceDiagram
participant Agent as Cider Agent<br/>(Claude/CodeBuddy)
participant Adapter as Protocol Adapter
participant Controller as Agent Controller
participant Orchestrator as Orchestrator
participant MicroVM as Firecracker MicroVM
Agent->>Adapter: 1. Agent Action<br/>(如: run_tests)
Adapter->>Adapter: 2. 解析并验证
Adapter->>Controller: 3. gRPC Call<br/>(ExecuteAction)
Controller->>Controller: 4. 权限检查
Controller->>Orchestrator: 5. HTTP API<br/>(POST /vms/action)
Orchestrator->>MicroVM: 6. vsock/HTTP<br/>(执行命令)
MicroVM-->>Orchestrator: 7. 执行结果
Orchestrator-->>Controller: 8. 结果聚合
Controller-->>Adapter: 9. gRPC Response
Adapter-->>Agent: 10. Agent Result<br/>(标准化格式)
适配器实现方案
方案 A:Sidecar 模式(推荐)
在 MicroVM 内运行轻量级 Agent Sidecar:
# MicroVM 启动时自动启动 Sidecar
#!/bin/sh
# /etc/init.d/agent-sidecar
# 启动 Sidecar 服务
/usr/local/bin/agent-sidecar \
--vsock-cid 3 \
--vsock-port 8080 \
--workspace /app \
--runtime nodejs
Sidecar 代码示例(Node.js):
// agent-sidecar.js
const http = require('http');
const { exec } = require('child_process');
const net = require('net');
class AgentSidecar {
constructor(config) {
this.workspace = config.workspace;
this.runtime = config.runtime;
this.port = config.port || 8080;
}
async start() {
// 创建 HTTP 服务器
const server = http.createServer((req, res) => {
this.handleRequest(req, res);
});
// 监听 vsock(如果在 Firecracker 内)
// 或标准 TCP
server.listen(this.port, () => {
console.log(`Agent Sidecar listening on port ${this.port}`);
});
}
async handleRequest(req, res) {
const { method, url } = req;
if (method === 'POST' && url === '/execute') {
let body = '';
req.on('data', chunk => body += chunk);
req.on('end', async () => {
const action = JSON.parse(body);
const result = await this.executeAction(action);
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(result));
});
}
}
async executeAction(action) {
switch (action.type) {
case 'run_command':
return this.runCommand(action.command, action.cwd);
case 'read_file':
return this.readFile(action.path);
case 'write_file':
return this.writeFile(action.path, action.content);
case 'list_files':
return this.listFiles(action.path);
case 'run_tests':
return this.runTests(action.testCommand);
default:
throw new Error(`Unknown action: ${action.type}`);
}
}
runCommand(command, cwd = this.workspace) {
return new Promise((resolve, reject) => {
exec(command, { cwd }, (error, stdout, stderr) => {
resolve({
success: !error,
exitCode: error ? error.code : 0,
stdout: stdout.toString(),
stderr: stderr.toString()
});
});
});
}
// 其他方法实现...
}
// 启动 Sidecar
const sidecar = new AgentSidecar({
workspace: process.env.WORKSPACE || '/app',
runtime: process.env.RUNTIME || 'nodejs',
port: process.env.PORT || 8080
});
sidecar.start().catch(console.error);
方案 B:API Gateway 模式
通过统一的 API Gateway 暴露所有 MicroVM 服务:
# nginx-agent-gateway.conf
upstream microvms {
server 172.20.0.2:8080; # MicroVM 1
server 172.20.0.3:8080; # MicroVM 2
# 动态添加更多
}
server {
listen 9000;
location /agent/ {
proxy_pass http://microvms/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
# 超时设置
proxy_connect_timeout 30s;
proxy_send_timeout 30s;
proxy_read_timeout 300s;
}
location /agent/stream {
proxy_pass http://microvms/stream;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
与 Claude CLI 集成
Claude CLI 配置
Claude CLI 可以通过 --tool 参数扩展能力。我们需要创建自定义工具:
// claude-firecracker-tool.json
{
"name": "firecracker-sandbox",
"description": "Manage Firecracker MicroVMs for development",
"tools": [
{
"name": "create_vm",
"description": "Create a new Firecracker MicroVM",
"parameters": {
"type": "object",
"properties": {
"runtime": {
"type": "string",
"enum": ["nodejs", "go", "python", "rust"]
},
"workspace": {
"type": "string",
"description": "Path to workspace"
},
"resources": {
"type": "object",
"properties": {
"cpu": { "type": "number", "default": 2 },
"memory": { "type": "string", "default": "512MiB" }
}
}
},
"required": ["runtime", "workspace"]
}
},
{
"name": "execute_command",
"description": "Execute a command in the MicroVM",
"parameters": {
"type": "object",
"properties": {
"vm_id": { "type": "string" },
"command": { "type": "string" },
"cwd": { "type": "string" }
},
"required": ["vm_id", "command"]
}
},
{
"name": "read_file",
"description": "Read a file from the MicroVM",
"parameters": {
"type": "object",
"properties": {
"vm_id": { "type": "string" },
"path": { "type": "string" }
},
"required": ["vm_id", "path"]
}
},
{
"name": "run_tests",
"description": "Run tests in the MicroVM",
"parameters": {
"type": "object",
"properties": {
"vm_id": { "type": "string" },
"test_command": { "type": "string", "default": "npm test" }
},
"required": ["vm_id"]
}
}
]
}
Claude CLI 使用示例
# 启动 Claude CLI 并加载 Firecracker 工具
claude --tool ./claude-firecracker-tool.json
# 在 Claude 会话中使用
[Claude] > Create a Node.js MicroVM for my project at ~/workspace/my-app
[Tool Call] create_vm
{
"runtime": "nodejs",
"workspace": "~/workspace/my-app",
"resources": {
"cpu": 2,
"memory": "1GiB"
}
}
[Tool Result]
✓ MicroVM created: vm-nodejs-abc123
✓ Runtime: Node.js 20.x
✓ Resources: 2 vCPU, 1GiB RAM
✓ API Endpoint: http://localhost:9000/agent/vm-nodejs-abc123
[Claude] > Run npm install in the VM
[Tool Call] execute_command
{
"vm_id": "vm-nodejs-abc123",
"command": "npm install",
"cwd": "/app"
}
[Tool Result]
✓ Command executed successfully
Exit Code: 0
Duration: 45s
Output: added 234 packages in 45s
与 CodeBuddy CLI 集成
CodeBuddy 配置文件
# .codebuddy/firecracker.yaml
version: "1.0"
sandbox:
provider: firecracker
config:
lima_instance: firecracker
network_bridge: br0
tap_prefix: tap
runtimes:
nodejs:
image: alpine-nodejs-dev:latest
kernel: vmlinux-5.10.209
default_resources:
vcpu: 2
memory: 512MiB
ports:
- 3000:3000
- 9229:9229 # Debug
go:
image: alpine-go-dev:latest
kernel: vmlinux-5.10.209
default_resources:
vcpu: 2
memory: 512MiB
ports:
- 8080:8080
- 2345:2345 # Delve debug
agent:
sidecar:
enabled: true
port: 8080
commands:
test: "npm test"
build: "npm run build"
lint: "npm run lint"
permissions:
- read: "/app"
- write: "/app/src"
- execute: "/app/scripts"
hooks:
pre_start: "./scripts/setup-vm.sh"
post_start: "./scripts/health-check.sh"
on_change: "./scripts/sync-changes.sh"
CodeBuddy 命令集成
# 初始化 Firecracker 沙箱
codebuddy sandbox init --provider firecracker
# 创建新的开发环境
codebuddy sandbox create --runtime nodejs --workspace ./my-app
# 在沙箱中执行命令
codebuddy sandbox exec -- npm install
codebuddy sandbox exec -- npm run dev
# 运行测试
codebuddy sandbox test
# 查看沙箱状态
codebuddy sandbox status
# 进入沙箱 shell
codebuddy sandbox shell
# 销毁沙箱
codebuddy sandbox destroy
准备工作清单
Agent Controller 部署
步骤 1:安装 Controller
# 在 macOS Host 上安装 Agent Controller
npm install -g @cider/firecracker-controller
# 或从源码构建
git clone https://github.com/cider/firecracker-controller.git
cd firecracker-controller
npm install
npm run build
npm link
步骤 2:配置 Controller
# ~/.cider/firecracker-controller.yaml
server:
host: localhost
port: 9090
grpc_port: 9091
lima:
instance: firecracker
socket_path: /Users/$USER/.lima/firecracker/sock/lima.sock
firecracker:
binary_path: /usr/local/bin/firecracker
kernel_path: /Users/$USER/firecracker-images/vmlinux-5.10.209
images_path: /Users/$USER/firecracker-images
vms_path: /Users/$USER/firecracker-vms
network:
bridge: br0
subnet: 172.20.0.0/24
gateway: 172.20.0.1
tap_prefix: tap
agent:
sidecar_image: cider/agent-sidecar:latest
default_port: 8080
logging:
level: info
file: ~/.cider/logs/controller.log
步骤 3:启动 Controller
# 前台启动(调试)
firecracker-controller --config ~/.cider/firecracker-controller.yaml
# 后台启动
firecracker-controller --daemon
# 验证状态
curl http://localhost:9090/health
MicroVM 模板准备
创建 Agent Sidecar 镜像
# Dockerfile.agent-sidecar
FROM alpine:3.19
# 安装 Node.js
RUN apk add --no-cache nodejs npm curl
# 安装 Sidecar
WORKDIR /opt/sidecar
COPY agent-sidecar.js ./
COPY package.json ./
RUN npm install --production
# 安装常用工具
RUN apk add --no-cache \
git \
openssh-client \
rsync \
htop
# 配置环境
ENV WORKSPACE=/app
ENV RUNTIME=nodejs
ENV PORT=8080
EXPOSE 8080
CMD ["node", "agent-sidecar.js"]
构建并转换:
# 构建 Docker 镜像
docker build -t agent-sidecar:latest -f Dockerfile.agent-sidecar .
# 转换为 ext4 根文件系统
docker create --name sidecar-export agent-sidecar:latest
docker export sidecar-export | tar -C /tmp/sidecar-rootfs -xvf -
docker rm sidecar-export
# 创建 ext4 镜像
dd if=/dev/zero of=agent-sidecar-rootfs.ext4 bs=1M count=1024
mkfs.ext4 agent-sidecar-rootfs.ext4
mkdir -p /mnt/sidecar
mount -o loop agent-sidecar-rootfs.ext4 /mnt/sidecar
cp -a /tmp/sidecar-rootfs/* /mnt/sidecar/
umount /mnt/sidecar
通信协议设计
协议栈架构
flowchart TB
subgraph "应用层"
A[Agent Actions]
end
subgraph "协议层"
B[JSON-RPC 2.0]
C[gRPC]
D[HTTP/REST]
end
subgraph "传输层"
E[vsock]
F[TCP/IP]
G[Unix Socket]
end
subgraph "适配层"
H[Host Transport]
I[VM Transport]
end
A --> B
A --> C
A --> D
B --> E
C --> F
D --> G
E --> H
F --> H
G --> I
推荐协议:JSON-RPC over vsock
优势:
- 简单:易于实现和调试
- 高效:vsock bypass 网络栈,性能接近本地通信
- 安全:基于 CID(Context ID)隔离,天然多租户
协议定义:
// agent-protocol.ts
interface AgentRequest {
jsonrpc: "2.0";
id: string;
method: string;
params: unknown;
}
interface AgentResponse {
jsonrpc: "2.0";
id: string;
result?: unknown;
error?: {
code: number;
message: string;
data?: unknown;
};
}
// 支持的 Method
enum AgentMethods {
// 文件操作
FILE_READ = "file/read",
FILE_WRITE = "file/write",
FILE_DELETE = "file/delete",
FILE_LIST = "file/list",
FILE_STAT = "file/stat",
// 命令执行
EXEC_RUN = "exec/run",
EXEC_START = "exec/start",
EXEC_STOP = "exec/stop",
EXEC_STATUS = "exec/status",
// 进程管理
PROCESS_LIST = "process/list",
PROCESS_KILL = "process/kill",
// 测试和构建
TEST_RUN = "test/run",
BUILD_START = "build/start",
BUILD_STATUS = "build/status",
// 环境信息
ENV_GET = "env/get",
ENV_SET = "env/set",
INFO_SYSTEM = "info/system",
INFO_RUNTIME = "info/runtime"
}
// 示例请求
const exampleRequest: AgentRequest = {
jsonrpc: "2.0",
id: "req-123",
method: "exec/run",
params: {
command: "npm test",
cwd: "/app",
timeout: 120000,
env: {
NODE_ENV: "test"
}
}
};
vsock 通信实现
宿主机端(Lima VM):
// vsock-server.go
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"net"
"github.com/mdlayher/vsock"
)
type AgentServer struct {
microVMs map[string]*MicroVM
}
type MicroVM struct {
CID uint32
Port uint32
}
func (s *AgentServer) handleConnection(conn net.Conn) {
defer conn.Close()
decoder := json.NewDecoder(conn)
encoder := json.NewEncoder(conn)
var req AgentRequest
if err := decoder.Decode(&req); err != nil {
log.Printf("Decode error: %v", err)
return
}
resp := s.processRequest(&req)
if err := encoder.Encode(resp); err != nil {
log.Printf("Encode error: %v", err)
}
}
func (s *AgentServer) processRequest(req *AgentRequest) *AgentResponse {
// 路由到对应的 MicroVM
vm, ok := s.microVMs[req.Params.VMID]
if !ok {
return &AgentResponse{
JSONRPC: "2.0",
ID: req.ID,
Error: &AgentError{
Code: -32602,
Message: "MicroVM not found",
},
}
}
// 转发到 MicroVM
return s.forwardToVM(vm, req)
}
func (s *AgentServer) forwardToVM(vm *MicroVM, req *AgentRequest) *AgentResponse {
ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
defer cancel()
// 建立 vsock 连接
conn, err := vsock.DialContext(ctx, vm.CID, vm.Port, nil)
if err != nil {
return &AgentResponse{
JSONRPC: "2.0",
ID: req.ID,
Error: &AgentError{
Code: -32000,
Message: fmt.Sprintf("Failed to connect to VM: %v", err),
},
}
}
defer conn.Close()
// 发送请求
encoder := json.NewEncoder(conn)
if err := encoder.Encode(req); err != nil {
return &AgentResponse{
JSONRPC: "2.0",
ID: req.ID,
Error: &AgentError{
Code: -32001,
Message: fmt.Sprintf("Failed to send request: %v", err),
},
}
}
// 接收响应
decoder := json.NewDecoder(conn)
var resp AgentResponse
if err := decoder.Decode(&resp); err != nil {
return &AgentResponse{
JSONRPC: "2.0",
ID: req.ID,
Error: &AgentError{
Code: -32002,
Message: fmt.Sprintf("Failed to receive response: %v", err),
},
}
}
return &resp
}
func main() {
server := &AgentServer{
microVMs: make(map[string]*MicroVM),
}
// 监听 vsock
listener, err := vsock.Listen(8080, nil)
if err != nil {
log.Fatalf("Failed to listen: %v", err)
}
defer listener.Close()
log.Println("Agent Server listening on vsock:8080")
for {
conn, err := listener.Accept()
if err != nil {
log.Printf("Accept error: %v", err)
continue
}
go server.handleConnection(conn)
}
}
MicroVM 端(Sidecar):
// vsock-client.js
const net = require('net');
const { exec } = require('child_process');
class VsockServer {
constructor(port = 8080) {
this.port = port;
this.handlers = new Map();
this.setupHandlers();
}
setupHandlers() {
this.handlers.set('file/read', this.handleFileRead.bind(this));
this.handlers.set('file/write', this.handleFileWrite.bind(this));
this.handlers.set('exec/run', this.handleExecRun.bind(this));
// 更多 handler...
}
start() {
// 在 Firecracker MicroVM 内,vsock 表现为特殊的 socket
const server = net.createServer((socket) => {
this.handleConnection(socket);
});
// Firecracker vsock 在 guest 内表现为 /dev/vsock
server.listen({
port: this.port,
host: '0.0.0.0'
}, () => {
console.log(`Vsock server listening on port ${this.port}`);
});
}
handleConnection(socket) {
let buffer = '';
socket.on('data', (data) => {
buffer += data.toString();
// 尝试解析 JSON-RPC
try {
const lines = buffer.split('\n');
for (let i = 0; i < lines.length - 1; i++) {
const line = lines[i].trim();
if (line) {
const request = JSON.parse(line);
this.processRequest(socket, request);
}
}
buffer = lines[lines.length - 1];
} catch (err) {
// 等待更多数据
}
});
}
async processRequest(socket, request) {
const handler = this.handlers.get(request.method);
let response;
if (handler) {
try {
const result = await handler(request.params);
response = {
jsonrpc: '2.0',
id: request.id,
result
};
} catch (err) {
response = {
jsonrpc: '2.0',
id: request.id,
error: {
code: -32603,
message: err.message
}
};
}
} else {
response = {
jsonrpc: '2.0',
id: request.id,
error: {
code: -32601,
message: `Method not found: ${request.method}`
}
};
}
socket.write(JSON.stringify(response) + '\n');
}
async handleFileRead(params) {
const fs = require('fs').promises;
const content = await fs.readFile(params.path, 'utf8');
return { content };
}
async handleExecRun(params) {
return new Promise((resolve, reject) => {
const options = {
cwd: params.cwd,
env: { ...process.env, ...params.env },
timeout: params.timeout || 60000
};
exec(params.command, options, (error, stdout, stderr) => {
resolve({
success: !error,
exitCode: error ? error.code : 0,
stdout: stdout.toString(),
stderr: stderr.toString()
});
});
});
}
}
// 启动 vsock 服务器
const server = new VsockServer(8080);
server.start();
自动化集成流程
CI/CD 集成
# .github/workflows/firecracker-ci.yaml
name: Firecracker CI
on: [push, pull_request]
jobs:
test:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- name: Setup Firecracker
uses: cider/setup-firecracker@v1
with:
lima-version: '1.0.0'
firecracker-version: '1.7.0'
- name: Start Development Environment
run: |
codebuddy sandbox create \
--runtime nodejs \
--workspace . \
--name ci-test
- name: Install Dependencies
run: codebuddy sandbox exec -- npm ci
- name: Run Linter
run: codebuddy sandbox exec -- npm run lint
- name: Run Tests
run: codebuddy sandbox test --coverage
- name: Build
run: codebuddy sandbox exec -- npm run build
- name: Cleanup
if: always()
run: codebuddy sandbox destroy --name ci-test
本地开发自动化
#!/bin/bash
# dev-automation.sh
# 项目根目录钩子脚本
start_dev_env() {
echo "🚀 启动 Firecracker 开发环境..."
# 检查是否存在现有环境
if codebuddy sandbox status | grep -q "Running"; then
echo "✓ 开发环境已在运行"
return 0
fi
# 创建新环境
codebuddy sandbox create \
--runtime nodejs \
--workspace . \
--name "$(basename $PWD)-dev"
# 安装依赖
echo "📦 安装依赖..."
codebuddy sandbox exec -- npm install
# 启动开发服务器
echo "🌐 启动开发服务器..."
codebuddy sandbox exec -- npm run dev &
echo "✓ 开发环境准备完成"
echo "访问: http://localhost:3000"
}
sync_changes() {
echo "🔄 同步变更到沙箱..."
# 使用 rsync 快速同步
rsync -avz --delete \
--exclude node_modules \
--exclude .git \
./ lima-firecracker:/app/
# 触发沙箱内重载(如需要)
codebuddy sandbox exec -- kill -HUP 1 # 发送 SIGHUP 触发重载
}
run_tests() {
echo "🧪 在沙箱中运行测试..."
codebuddy sandbox test
}
# 根据参数执行
case "$1" in
start)
start_dev_env
;;
sync)
sync_changes
;;
test)
run_tests
;;
*)
echo "Usage: $0 {start|sync|test}"
exit 1
;;
esac
总结
集成架构关键点
- 多层抽象:从 Cider Agent 到 MicroVM 需要多层协议转换,但每层职责清晰
- Sidecar 模式:在 MicroVM 内运行轻量级 Agent 是最灵活的方案
- vsock 通信:利用 Firecracker 的 vsock 特性实现高效宿主机-客户机通信
- 标准化协议:JSON-RPC 2.0 提供简单、通用的交互方式
准备工作要点
| 组件 | 优先级 | 预计工作量 | 依赖 |
|---|---|---|---|
| Agent Controller | 高 | 1-2 周 | Lima VM |
| Orchestrator Service | 高 | 1 周 | Firecracker |
| Agent Sidecar | 高 | 1 周 | Node.js 运行时 |
| vsock 通信层 | 中 | 3-5 天 | vsock 内核模块 |
| Claude CLI 工具 | 中 | 3-5 天 | Agent Controller |
| CodeBuddy 插件 | 中 | 1 周 | CodeBuddy SDK |
下一步
完成 Agent 集成设计后,需要关注:
- 05-多语言扩展性设计 - 设计语言无关的运行时架构
- 06-实施建议 - 制定详细实施计划
本章参考资料: