phantom-ai-coworker

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Phantom AI Co-worker

Phantom AI同事

Skill by ara.so — Daily 2026 Skills collection.
Phantom is an AI co-worker that runs on its own dedicated machine. Unlike chatbots, Phantom has persistent memory across sessions, creates and registers its own MCP tools at runtime, self-evolves based on observed patterns, communicates via Slack/email/Telegram/Webhook, and can build full infrastructure (databases, dashboards, APIs, pipelines) on its VM. Built on the Claude Agent SDK with TypeScript/Bun.
ara.so开发的Skill — 2026年度技能合集系列。
Phantom是一款运行在专属独立机器上的AI同事。与聊天机器人不同,Phantom拥有跨会话的持久化内存,可在运行时创建并注册自己的MCP工具,基于观测到的模式自我进化,支持通过Slack/邮件/Telegram/Webhook进行通信,还能在自身VM上搭建完整的基础设施(数据库、看板、API、数据pipeline)。它基于Claude Agent SDK开发,使用TypeScript/Bun编写。

Architecture Overview

架构概览

┌─────────────────────────────────────────────────────┐
│                   Phantom Agent                     │
│  ┌──────────┐  ┌──────────┐  ┌───────────────────┐ │
│  │  Claude  │  │ Qdrant   │  │   MCP Server      │ │
│  │  Agent   │  │ (memory) │  │ (dynamic tools)   │ │
│  │   SDK    │  │          │  │                   │ │
│  └──────────┘  └──────────┘  └───────────────────┘ │
│  ┌──────────────────────────────────────────────┐   │
│  │         Channels                             │   │
│  │  Slack │ Email │ Telegram │ Webhook │ Discord │   │
│  └──────────────────────────────────────────────┘   │
│  ┌──────────────────────────────────────────────┐   │
│  │         Self-Evolution Engine                │   │
│  │  observe → reflect → propose → validate → evolve│
│  └──────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│                   Phantom Agent                     │
│  ┌──────────┐  ┌──────────┐  ┌───────────────────┐ │
│  │  Claude  │  │ Qdrant   │  │   MCP Server      │ │
│  │  Agent   │  │ (memory) │  │ (dynamic tools)   │ │
│  │   SDK    │  │          │  │                   │ │
│  └──────────┘  └──────────┘  └───────────────────┘ │
│  ┌──────────────────────────────────────────────┐   │
│  │         Channels                             │   │
│  │  Slack │ Email │ Telegram │ Webhook │ Discord │   │
│  └──────────────────────────────────────────────┘   │
│  ┌──────────────────────────────────────────────┐   │
│  │         Self-Evolution Engine                │   │
│  │  observe → reflect → propose → validate → evolve│
│  └──────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────┘

Installation

安装

Docker (Recommended)

Docker(推荐)

bash
undefined
bash
undefined

Download compose file and env template

下载compose文件和环境变量模板

Edit .env with your credentials (see Configuration section)

编辑.env填入你的凭证(参考配置章节)

nano .env
nano .env

Start Phantom (includes Qdrant + Ollama)

启动Phantom(包含Qdrant + Ollama)

docker compose up -d
docker compose up -d

Check health

检查健康状态

View logs

查看日志

docker compose logs -f phantom
undefined
docker compose logs -f phantom
undefined

From Source (Bun)

源码安装(Bun)

bash
git clone https://github.com/ghostwright/phantom.git
cd phantom
bash
git clone https://github.com/ghostwright/phantom.git
cd phantom

Install dependencies

安装依赖

bun install
bun install

Copy env

复制环境变量文件

cp .env.example .env
cp .env.example .env

Edit .env

编辑.env文件

Start Qdrant (required for memory)

启动Qdrant(内存功能必需)

docker run -d -p 6333:6333 qdrant/qdrant
docker run -d -p 6333:6333 qdrant/qdrant

Start Phantom

启动Phantom

bun run start
bun run start

Development mode with hot reload

带热重载的开发模式

bun run dev
undefined
bun run dev
undefined

Configuration (.env)

配置(.env)

bash
undefined
bash
undefined

=== Required ===

=== 必选配置 ===

ANTHROPIC_API_KEY= # Your Anthropic API key
ANTHROPIC_API_KEY= # 你的Anthropic API密钥

=== Slack (required for Slack channel) ===

=== Slack(Slack渠道必需) ===

SLACK_BOT_TOKEN=xoxb- # Bot OAuth token SLACK_APP_TOKEN=xapp- # App-level token (socket mode) SLACK_SIGNING_SECRET= # Signing secret OWNER_SLACK_USER_ID=U0XXXXXXXXX # Your Slack user ID
SLACK_BOT_TOKEN=xoxb- # Bot OAuth令牌 SLACK_APP_TOKEN=xapp- # 应用级令牌(Socket模式) SLACK_SIGNING_SECRET= # 签名密钥 OWNER_SLACK_USER_ID=U0XXXXXXXXX # 你的Slack用户ID

=== Memory (Qdrant) ===

=== 内存(Qdrant) ===

QDRANT_URL=http://localhost:6333 # Qdrant vector DB URL QDRANT_API_KEY= # Optional, for cloud Qdrant OLLAMA_URL=http://localhost:11434 # Ollama for embeddings
QDRANT_URL=http://localhost:6333 # Qdrant向量数据库URL QDRANT_API_KEY= # 可选,云版Qdrant使用 OLLAMA_URL=http://localhost:11434 # 生成嵌入向量用的Ollama地址

=== Email (optional) ===

=== 邮件(可选) ===

RESEND_API_KEY= # For email sending via Resend PHANTOM_EMAIL=phantom@yourdomain # Phantom's email address
RESEND_API_KEY= # 通过Resend发送邮件用 PHANTOM_EMAIL=phantom@yourdomain # Phantom的邮箱地址

=== Telegram (optional) ===

=== Telegram(可选) ===

TELEGRAM_BOT_TOKEN= # BotFather token
TELEGRAM_BOT_TOKEN= # BotFather生成的令牌

=== Infrastructure ===

=== 基础设施 ===

PHANTOM_VM_DOMAIN= # Public domain for served assets PHANTOM_PORT=3100 # HTTP port (default 3100)
PHANTOM_VM_DOMAIN= # 对外提供服务的公网域名 PHANTOM_PORT=3100 # HTTP端口(默认3100)

=== Self-Evolution ===

=== 自我进化 ===

EVOLUTION_VALIDATION_MODEL=claude-3-5-sonnet-20241022 # Separate model for validation EVOLUTION_ENABLED=true
EVOLUTION_VALIDATION_MODEL=claude-3-5-sonnet-20241022 # 用于进化校验的独立模型 EVOLUTION_ENABLED=true

=== Credentials Vault ===

=== 凭证保险箱 ===

CREDENTIAL_ENCRYPTION_KEY= # AES-256-GCM key (auto-generated if empty)
undefined
CREDENTIAL_ENCRYPTION_KEY= # AES-256-GCM密钥(留空则自动生成)
undefined

Key Commands

常用命令

bash
undefined
bash
undefined

Docker operations

Docker操作

docker compose up -d # Start all services docker compose down # Stop all services docker compose logs -f phantom # Stream logs docker compose pull # Update to latest image
docker compose up -d # 启动所有服务 docker compose down # 停止所有服务 docker compose logs -f phantom # 流式查看日志 docker compose pull # 更新到最新镜像

Bun development

Bun开发相关

bun run start # Production start bun run dev # Dev mode with watch bun run test # Run test suite bun run build # Build TypeScript
bun run start # 生产环境启动 bun run dev # 带监听的开发模式 bun run test # 运行测试套件 bun run build # 构建TypeScript代码

Health checks

健康检查

MCP server endpoint

MCP服务器端点

Core Concepts & Code Examples

核心概念与代码示例

1. Memory System (Qdrant + Embeddings)

1. 内存系统(Qdrant + 嵌入向量)

Phantom stores memories as vector embeddings for semantic recall across sessions.
typescript
// src/memory/memory-manager.ts pattern
import { QdrantClient } from '@qdrant/js-client-rest';

const client = new QdrantClient({ url: process.env.QDRANT_URL });

// Storing a memory
async function storeMemory(content: string, metadata: Record<string, unknown>) {
  const embedding = await generateEmbedding(content); // via Ollama
  await client.upsert('phantom_memory', {
    points: [{
      id: crypto.randomUUID(),
      vector: embedding,
      payload: {
        content,
        timestamp: Date.now(),
        ...metadata,
      },
    }],
  });
}

// Recalling relevant memories
async function recallMemories(query: string, limit = 5) {
  const queryEmbedding = await generateEmbedding(query);
  const results = await client.search('phantom_memory', {
    vector: queryEmbedding,
    limit,
    with_payload: true,
  });
  return results.map(r => r.payload?.content);
}
Phantom将记忆存储为向量嵌入,实现跨会话的语义召回。
typescript
// src/memory/memory-manager.ts 实现模式
import { QdrantClient } from '@qdrant/js-client-rest';

const client = new QdrantClient({ url: process.env.QDRANT_URL });

// 存储记忆
async function storeMemory(content: string, metadata: Record<string, unknown>) {
  const embedding = await generateEmbedding(content); // 通过Ollama生成
  await client.upsert('phantom_memory', {
    points: [{
      id: crypto.randomUUID(),
      vector: embedding,
      payload: {
        content,
        timestamp: Date.now(),
        ...metadata,
      },
    }],
  });
}

// 召回相关记忆
async function recallMemories(query: string, limit = 5) {
  const queryEmbedding = await generateEmbedding(query);
  const results = await client.search('phantom_memory', {
    vector: queryEmbedding,
    limit,
    with_payload: true,
  });
  return results.map(r => r.payload?.content);
}

2. Dynamic MCP Tool Registration

2. 动态MCP工具注册

Phantom creates MCP tools at runtime that persist across restarts.
typescript
// Pattern: registering a dynamically created tool
interface PhantomTool {
  name: string;
  description: string;
  inputSchema: Record<string, unknown>;
  handler: string; // serialized or endpoint URL
}

// Phantom internally registers tools like this
async function registerDynamicTool(tool: PhantomTool) {
  // Store tool definition in persistent storage
  await storeMemory(JSON.stringify(tool), {
    type: 'mcp_tool',
    toolName: tool.name,
  });

  // Register with MCP server at runtime
  mcpServer.tool(tool.name, tool.description, tool.inputSchema, async (args) => {
    return await executeToolHandler(tool.handler, args);
  });
}

// MCP server setup (how Phantom exposes tools to Claude Code)
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';

const mcpServer = new McpServer({
  name: 'phantom',
  version: '0.18.1',
});

// Connect Claude Code to Phantom's MCP server:
// In claude_desktop_config.json or .cursor/mcp.json:
// {
//   "mcpServers": {
//     "phantom": {
//       "url": "http://your-phantom-vm:3100/mcp"
//     }
//   }
// }
Phantom可在运行时创建MCP工具,且工具会在重启后持久化保留。
typescript
// 模式:注册动态创建的工具
interface PhantomTool {
  name: string;
  description: string;
  inputSchema: Record<string, unknown>;
  handler: string; // 序列化内容或端点URL
}

// Phantom内部按此逻辑注册工具
async function registerDynamicTool(tool: PhantomTool) {
  // 将工具定义存储到持久化存储
  await storeMemory(JSON.stringify(tool), {
    type: 'mcp_tool',
    toolName: tool.name,
  });

  // 运行时注册到MCP服务器
  mcpServer.tool(tool.name, tool.description, tool.inputSchema, async (args) => {
    return await executeToolHandler(tool.handler, args);
  });
}

// MCP服务器搭建(Phantom向Claude Code暴露工具的方式)
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';

const mcpServer = new McpServer({
  name: 'phantom',
  version: '0.18.1',
});

// 将Claude Code连接到Phantom的MCP服务器:
// 在claude_desktop_config.json 或 .cursor/mcp.json中添加:
// {
//   "mcpServers": {
//     "phantom": {
//       "url": "http://your-phantom-vm:3100/mcp"
//     }
//   }
// }

3. Slack Channel Integration

3. Slack渠道集成

typescript
// How Phantom handles Slack messages
import { App } from '@slack/bolt';

const slack = new App({
  token: process.env.SLACK_BOT_TOKEN,
  appToken: process.env.SLACK_APP_TOKEN,
  socketMode: true,
  signingSecret: process.env.SLACK_SIGNING_SECRET,
});

// Phantom listens for direct messages and mentions
slack.event('message', async ({ event, say }) => {
  if (event.subtype) return; // Skip bot messages, edits

  const userMessage = (event as any).text;
  const userId = (event as any).user;

  // Recall relevant context from memory
  const memories = await recallMemories(userMessage);

  // Run Claude agent with memory context
  const response = await runPhantomAgent({
    message: userMessage,
    userId,
    memories,
    channel: (event as any).channel,
  });

  await say({ text: response, thread_ts: (event as any).ts });
});

// Phantom DMs you when ready
async function notifyOwnerReady() {
  await slack.client.chat.postMessage({
    channel: process.env.OWNER_SLACK_USER_ID!,
    text: "👻 Phantom is online and ready.",
  });
}
typescript
// Phantom处理Slack消息的逻辑
import { App } from '@slack/bolt';

const slack = new App({
  token: process.env.SLACK_BOT_TOKEN,
  appToken: process.env.SLACK_APP_TOKEN,
  socketMode: true,
  signingSecret: process.env.SLACK_SIGNING_SECRET,
});

// Phantom监听私信和@提及消息
slack.event('message', async ({ event, say }) => {
  if (event.subtype) return; // 跳过机器人消息、编辑消息

  const userMessage = (event as any).text;
  const userId = (event as any).user;

  // 从内存召回相关上下文
  const memories = await recallMemories(userMessage);

  // 传入内存上下文运行Claude agent
  const response = await runPhantomAgent({
    message: userMessage,
    userId,
    memories,
    channel: (event as any).channel,
  });

  await say({ text: response, thread_ts: (event as any).ts });
});

// 就绪后Phantom会给所有者发私信
async function notifyOwnerReady() {
  await slack.client.chat.postMessage({
    channel: process.env.OWNER_SLACK_USER_ID!,
    text: "👻 Phantom已上线,准备就绪。",
  });
}

4. Claude Agent SDK Integration

4. Claude Agent SDK集成

typescript
// Core agent loop using Anthropic Agent SDK
import Anthropic from '@anthropic-ai/sdk';

const anthropic = new Anthropic({
  apiKey: process.env.ANTHROPIC_API_KEY,
});

async function runPhantomAgent({
  message,
  userId,
  memories,
  channel,
}: PhantomAgentInput) {
  const systemPrompt = buildSystemPrompt(memories);

  // Agentic loop with tool use
  const response = await anthropic.messages.create({
    model: 'claude-opus-4-5',
    max_tokens: 8096,
    system: systemPrompt,
    messages: [{ role: 'user', content: message }],
    tools: await getAvailableTools(), // includes dynamic MCP tools
  });

  // Handle tool calls in loop
  if (response.stop_reason === 'tool_use') {
    return await handleToolCalls(response, message, userId);
  }

  // Store this interaction as memory
  await storeMemory(`User ${userId} asked: ${message}. I responded: ${response.content}`, {
    type: 'interaction',
    userId,
    channel,
  });

  return extractTextContent(response.content);
}

function buildSystemPrompt(memories: string[]): string {
  return `You are Phantom, an AI co-worker with your own computer.
You have persistent memory and can build infrastructure.

Relevant memories from past sessions:
${memories.map((m, i) => `${i + 1}. ${m}`).join('\n')}

You have access to your VM, can install software, build tools,
serve web pages on ${process.env.PHANTOM_VM_DOMAIN}, and register
new capabilities for yourself.`;
}
typescript
// 使用Anthropic Agent SDK的核心Agent循环
import Anthropic from '@anthropic-ai/sdk';

const anthropic = new Anthropic({
  apiKey: process.env.ANTHROPIC_API_KEY,
});

async function runPhantomAgent({
  message,
  userId,
  memories,
  channel,
}: PhantomAgentInput) {
  const systemPrompt = buildSystemPrompt(memories);

  // 支持工具调用的Agent循环
  const response = await anthropic.messages.create({
    model: 'claude-opus-4-5',
    max_tokens: 8096,
    system: systemPrompt,
    messages: [{ role: 'user', content: message }],
    tools: await getAvailableTools(), // 包含动态MCP工具
  });

  // 循环处理工具调用
  if (response.stop_reason === 'tool_use') {
    return await handleToolCalls(response, message, userId);
  }

  // 将本次交互存储为记忆
  await storeMemory(`用户${userId}提问:${message}。我回复:${response.content}`, {
    type: 'interaction',
    userId,
    channel,
  });

  return extractTextContent(response.content);
}

function buildSystemPrompt(memories: string[]): string {
  return `你是Phantom,拥有独立计算机的AI同事。
你有持久化内存,可以搭建基础设施。

过往会话的相关记忆:
${memories.map((m, i) => `${i + 1}. ${m}`).join('\n')}

你可以访问自己的VM,安装软件,构建工具,
${process.env.PHANTOM_VM_DOMAIN}上提供网页服务,还可以为自己注册新的能力。`;
}

5. Secure Credential Collection

5. 安全凭证收集

Phantom collects credentials via encrypted forms, never plain text.
typescript
// Credential vault pattern
import { createCipheriv, createDecipheriv, randomBytes } from 'crypto';

const ALGORITHM = 'aes-256-gcm';
const KEY = Buffer.from(process.env.CREDENTIAL_ENCRYPTION_KEY!, 'hex');

function encryptCredential(plaintext: string): string {
  const iv = randomBytes(16);
  const cipher = createCipheriv(ALGORITHM, KEY, iv);
  const encrypted = Buffer.concat([
    cipher.update(plaintext, 'utf8'),
    cipher.final(),
  ]);
  const authTag = cipher.getAuthTag();
  return `${iv.toString('hex')}:${authTag.toString('hex')}:${encrypted.toString('hex')}`;
}

function decryptCredential(ciphertext: string): string {
  const [ivHex, authTagHex, encryptedHex] = ciphertext.split(':');
  const iv = Buffer.from(ivHex, 'hex');
  const authTag = Buffer.from(authTagHex, 'hex');
  const encrypted = Buffer.from(encryptedHex, 'hex');
  const decipher = createDecipheriv(ALGORITHM, KEY, iv);
  decipher.setAuthTag(authTag);
  return decipher.update(encrypted) + decipher.final('utf8');
}

// Phantom generates a one-time secure form URL for credential collection
async function generateCredentialForm(service: string, fields: string[]) {
  const token = randomBytes(32).toString('hex');
  // Store token with expiry
  await storeCredentialRequest(token, { service, fields, expires: Date.now() + 3600000 });
  return `${process.env.PHANTOM_VM_DOMAIN}/credentials/${token}`;
}
Phantom通过加密表单收集凭证,绝不使用明文传输。
typescript
// 凭证保险箱实现模式
import { createCipheriv, createDecipheriv, randomBytes } from 'crypto';

const ALGORITHM = 'aes-256-gcm';
const KEY = Buffer.from(process.env.CREDENTIAL_ENCRYPTION_KEY!, 'hex');

function encryptCredential(plaintext: string): string {
  const iv = randomBytes(16);
  const cipher = createCipheriv(ALGORITHM, KEY, iv);
  const encrypted = Buffer.concat([
    cipher.update(plaintext, 'utf8'),
    cipher.final(),
  ]);
  const authTag = cipher.getAuthTag();
  return `${iv.toString('hex')}:${authTag.toString('hex')}:${encrypted.toString('hex')}`;
}

function decryptCredential(ciphertext: string): string {
  const [ivHex, authTagHex, encryptedHex] = ciphertext.split(':');
  const iv = Buffer.from(ivHex, 'hex');
  const authTag = Buffer.from(authTagHex, 'hex');
  const encrypted = Buffer.from(encryptedHex, 'hex');
  const decipher = createDecipheriv(ALGORITHM, KEY, iv);
  decipher.setAuthTag(authTag);
  return decipher.update(encrypted) + decipher.final('utf8');
}

// Phantom生成一次性安全表单URL用于收集凭证
async function generateCredentialForm(service: string, fields: string[]) {
  const token = randomBytes(32).toString('hex');
  // 存储带有效期的token
  await storeCredentialRequest(token, { service, fields, expires: Date.now() + 3600000 });
  return `${process.env.PHANTOM_VM_DOMAIN}/credentials/${token}`;
}

6. Self-Evolution Engine

6. 自我进化引擎

Phantom observes its own behavior, proposes improvements, validates with a separate model, and evolves.
typescript
// Evolution cycle pattern
interface EvolutionProposal {
  observation: string;
  currentBehavior: string;
  proposedChange: string;
  rationale: string;
  version: string;
}

async function runEvolutionCycle() {
  if (process.env.EVOLUTION_ENABLED !== 'true') return;

  // 1. Observe recent interactions
  const recentMemories = await recallMemories('recent interactions', 50);

  // 2. Generate proposals with primary model
  const proposals = await generateEvolutionProposals(recentMemories);

  for (const proposal of proposals) {
    // 3. Validate with DIFFERENT model to avoid self-enhancement bias
    const isValid = await validateProposal(proposal);

    if (isValid) {
      // 4. Apply evolution and version it
      await applyEvolution(proposal);
      await versionEvolution(proposal);

      // Notify owner of evolution
      await notifySlack(
        `🧬 I've evolved: ${proposal.observation}\n→ ${proposal.proposedChange}`
      );
    }
  }
}

async function validateProposal(proposal: EvolutionProposal): Promise<boolean> {
  // Uses a separate model instance to validate
  const validationResponse = await anthropic.messages.create({
    model: process.env.EVOLUTION_VALIDATION_MODEL!,
    max_tokens: 1024,
    messages: [{
      role: 'user',
      content: `Evaluate this AI self-improvement proposal for safety and benefit:
${JSON.stringify(proposal, null, 2)}

Respond with JSON: { "approved": boolean, "reason": string }`,
    }],
  });
  // Parse and return approval
  const result = JSON.parse(extractTextContent(validationResponse.content));
  return result.approved;
}
Phantom观测自身行为,提出改进方案,通过独立模型验证后完成进化。
typescript
// 进化循环实现模式
interface EvolutionProposal {
  observation: string;
  currentBehavior: string;
  proposedChange: string;
  rationale: string;
  version: string;
}

async function runEvolutionCycle() {
  if (process.env.EVOLUTION_ENABLED !== 'true') return;

  // 1. 观测近期交互
  const recentMemories = await recallMemories('recent interactions', 50);

  // 2. 用主模型生成进化提案
  const proposals = await generateEvolutionProposals(recentMemories);

  for (const proposal of proposals) {
    // 3. 用独立模型验证,避免自我增强偏差
    const isValid = await validateProposal(proposal);

    if (isValid) {
      // 4. 应用进化并版本化
      await applyEvolution(proposal);
      await versionEvolution(proposal);

      // 通知所有者进化完成
      await notifySlack(
        `🧬 我完成了自我进化:${proposal.observation}\n→ ${proposal.proposedChange}`
      );
    }
  }
}

async function validateProposal(proposal: EvolutionProposal): Promise<boolean> {
  // 使用独立的模型实例进行验证
  const validationResponse = await anthropic.messages.create({
    model: process.env.EVOLUTION_VALIDATION_MODEL!,
    max_tokens: 1024,
    messages: [{
      role: 'user',
      content: `评估这个AI自我改进提案的安全性和收益:
${JSON.stringify(proposal, null, 2)}

返回JSON格式:{ "approved": boolean, "reason": string }`,
    }],
  });
  // 解析并返回审批结果
  const result = JSON.parse(extractTextContent(validationResponse.content));
  return result.approved;
}

7. Infrastructure Building (VM Operations)

7. 基础设施搭建(VM操作)

typescript
// Phantom can run shell commands and Docker on its VM
import { exec } from 'child_process';
import { promisify } from 'util';

const execAsync = promisify(exec);

// Example: Phantom spinning up a Postgres container
async function provisionDatabase(projectName: string) {
  const port = await findAvailablePort(5432);
  const password = randomBytes(16).toString('hex');

  const { stdout } = await execAsync(`
    docker run -d \
      --name phantom-pg-${projectName} \
      -e POSTGRES_PASSWORD=${password} \
      -e POSTGRES_DB=${projectName} \
      -p ${port}:5432 \
      postgres:16-alpine
  `);

  const connectionString = `postgresql://postgres:${password}@localhost:${port}/${projectName}`;

  // Store connection string securely
  await storeCredential(`${projectName}_postgres`, encryptCredential(connectionString));

  // Register as MCP tool for future use
  await registerDynamicTool({
    name: `query_${projectName}_db`,
    description: `Query the ${projectName} PostgreSQL database`,
    inputSchema: { sql: { type: 'string' } },
    handler: `postgres:${connectionString}`,
  });

  return { connectionString, port };
}

// Serving a web page on Phantom's domain
async function serveWebPage(slug: string, htmlContent: string) {
  const filePath = `/var/phantom/public/${slug}/index.html`;
  await Bun.write(filePath, htmlContent);
  return `${process.env.PHANTOM_VM_DOMAIN}/${slug}`;
}
typescript
// Phantom可以在自身VM上运行Shell命令和Docker
import { exec } from 'child_process';
import { promisify } from 'util';

const execAsync = promisify(exec);

// 示例:Phantom启动Postgres容器
async function provisionDatabase(projectName: string) {
  const port = await findAvailablePort(5432);
  const password = randomBytes(16).toString('hex');

  const { stdout } = await execAsync(`
    docker run -d \
      --name phantom-pg-${projectName} \
      -e POSTGRES_PASSWORD=${password} \
      -e POSTGRES_DB=${projectName} \
      -p ${port}:5432 \
      postgres:16-alpine
  `);

  const connectionString = `postgresql://postgres:${password}@localhost:${port}/${projectName}`;

  // 安全存储连接字符串
  await storeCredential(`${projectName}_postgres`, encryptCredential(connectionString));

  // 注册为MCP工具供后续使用
  await registerDynamicTool({
    name: `query_${projectName}_db`,
    description: `查询${projectName}的PostgreSQL数据库`,
    inputSchema: { sql: { type: 'string' } },
    handler: `postgres:${connectionString}`,
  });

  return { connectionString, port };
}

// 在Phantom的域名上提供网页服务
async function serveWebPage(slug: string, htmlContent: string) {
  const filePath = `/var/phantom/public/${slug}/index.html`;
  await Bun.write(filePath, htmlContent);
  return `${process.env.PHANTOM_VM_DOMAIN}/${slug}`;
}

8. Webhook Channel

8. Webhook渠道

typescript
// Send messages to Phantom via webhook
const response = await fetch('http://your-phantom:3100/webhook/message', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${process.env.PHANTOM_WEBHOOK_SECRET}`,
  },
  body: JSON.stringify({
    message: 'Analyze our GitHub issues and create a priority matrix',
    userId: 'automation-system',
    context: { source: 'ci-pipeline', repo: 'myorg/myrepo' },
  }),
});

const { response: agentResponse, taskId } = await response.json();
typescript
// 通过webhook向Phantom发送消息
const response = await fetch('http://your-phantom:3100/webhook/message', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${process.env.PHANTOM_WEBHOOK_SECRET}`,
  },
  body: JSON.stringify({
    message: '分析我们的GitHub issues并生成优先级矩阵',
    userId: 'automation-system',
    context: { source: 'ci-pipeline', repo: 'myorg/myrepo' },
  }),
});

const { response: agentResponse, taskId } = await response.json();

Connecting Claude Code to Phantom's MCP Server

将Claude Code连接到Phantom的MCP服务器

Once Phantom is running, connect Claude Code to use all of Phantom's registered tools:
json
// ~/.claude/claude_desktop_config.json  or  .cursor/mcp.json
{
  "mcpServers": {
    "phantom": {
      "url": "http://your-phantom-vm:3100/mcp"
    }
  }
}
Or via CLI:
bash
undefined
Phantom运行后,你可以将Claude Code连接到它,使用所有Phantom注册的工具:
json
// ~/.claude/claude_desktop_config.json  或者  .cursor/mcp.json
{
  "mcpServers": {
    "phantom": {
      "url": "http://your-phantom-vm:3100/mcp"
    }
  }
}
或者通过CLI操作:
bash
undefined

Claude Code CLI

Claude Code CLI

claude mcp add phantom --url http://your-phantom-vm:3100/mcp
claude mcp add phantom --url http://your-phantom-vm:3100/mcp

Verify connection

验证连接

claude mcp list
undefined
claude mcp list
undefined

Docker Compose Structure

Docker Compose结构

yaml
undefined
yaml
undefined

docker-compose.yaml (production user config)

docker-compose.yaml(生产环境用户配置)

services: phantom: image: ghostwright/phantom:latest ports: - "3100:3100" environment: - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY} - SLACK_BOT_TOKEN=${SLACK_BOT_TOKEN} - SLACK_APP_TOKEN=${SLACK_APP_TOKEN} - SLACK_SIGNING_SECRET=${SLACK_SIGNING_SECRET} - OWNER_SLACK_USER_ID=${OWNER_SLACK_USER_ID} - QDRANT_URL=http://qdrant:6333 - OLLAMA_URL=http://ollama:11434 - PHANTOM_VM_DOMAIN=${PHANTOM_VM_DOMAIN} - RESEND_API_KEY=${RESEND_API_KEY} volumes: - phantom_data:/var/phantom - /var/run/docker.sock:/var/run/docker.sock # For Docker-in-Docker depends_on: - qdrant - ollama restart: unless-stopped
qdrant: image: qdrant/qdrant:latest volumes: - qdrant_data:/qdrant/storage restart: unless-stopped
ollama: image: ollama/ollama:latest volumes: - ollama_data:/root/.ollama restart: unless-stopped
volumes: phantom_data: qdrant_data: ollama_data:
undefined
services: phantom: image: ghostwright/phantom:latest ports: - "3100:3100" environment: - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY} - SLACK_BOT_TOKEN=${SLACK_BOT_TOKEN} - SLACK_APP_TOKEN=${SLACK_APP_TOKEN} - SLACK_SIGNING_SECRET=${SLACK_SIGNING_SECRET} - OWNER_SLACK_USER_ID=${OWNER_SLACK_USER_ID} - QDRANT_URL=http://qdrant:6333 - OLLAMA_URL=http://ollama:11434 - PHANTOM_VM_DOMAIN=${PHANTOM_VM_DOMAIN} - RESEND_API_KEY=${RESEND_API_KEY} volumes: - phantom_data:/var/phantom - /var/run/docker.sock:/var/run/docker.sock # 用于Docker-in-Docker depends_on: - qdrant - ollama restart: unless-stopped
qdrant: image: qdrant/qdrant:latest volumes: - qdrant_data:/qdrant/storage restart: unless-stopped
ollama: image: ollama/ollama:latest volumes: - ollama_data:/root/.ollama restart: unless-stopped
volumes: phantom_data: qdrant_data: ollama_data:
undefined

Slack App Setup

Slack App设置

  1. Go to api.slack.com/apps → Create New App → From manifest
  2. Use this manifest:
yaml
display_information:
  name: Phantom
features:
  bot_user:
    display_name: Phantom
    always_online: true
  app_home:
    messages_tab_enabled: true
oauth_config:
  scopes:
    bot:
      - channels:history
      - channels:read
      - chat:write
      - chat:write.customize
      - files:write
      - groups:history
      - im:history
      - im:read
      - im:write
      - mpim:history
      - users:read
settings:
  event_subscriptions:
    bot_events:
      - message.channels
      - message.groups
      - message.im
      - message.mpim
  interactivity:
    is_enabled: true
  socket_mode_enabled: true
  1. Install to workspace → copy Bot Token (
    xoxb-
    ) to
    SLACK_BOT_TOKEN
  2. Generate App-Level Token with
    connections:write
    → copy to
    SLACK_APP_TOKEN
  3. Copy Signing Secret →
    SLACK_SIGNING_SECRET
  4. Get your user ID: In Slack, click your profile → copy Member ID →
    OWNER_SLACK_USER_ID
  1. 访问api.slack.com/apps → 创建新App → 从清单导入
  2. 使用以下清单:
yaml
display_information:
  name: Phantom
features:
  bot_user:
    display_name: Phantom
    always_online: true
  app_home:
    messages_tab_enabled: true
oauth_config:
  scopes:
    bot:
      - channels:history
      - channels:read
      - chat:write
      - chat:write.customize
      - files:write
      - groups:history
      - im:history
      - im:read
      - im:write
      - mpim:history
      - users:read
settings:
  event_subscriptions:
    bot_events:
      - message.channels
      - message.groups
      - message.im
      - message.mpim
  interactivity:
    is_enabled: true
  socket_mode_enabled: true
  1. 安装到工作区 → 复制Bot Token(
    xoxb-
    开头)到
    SLACK_BOT_TOKEN
  2. 生成带
    connections:write
    权限的应用级令牌 → 复制到
    SLACK_APP_TOKEN
  3. 复制签名密钥 → 填入
    SLACK_SIGNING_SECRET
  4. 获取你的用户ID:在Slack中点击你的头像 → 复制成员ID → 填入
    OWNER_SLACK_USER_ID

Common Patterns

常见使用场景

Asking Phantom to Build a Tool

要求Phantom构建工具

In Slack:
@phantom Create an MCP tool that queries our internal metrics API at 
https://metrics.internal/api/v2. It should accept a metric_name and 
time_range parameter and return JSON.
Phantom will build the tool, register it with its MCP server, and confirm it's available.
在Slack中发送:
@phantom 构建一个MCP工具,用于查询我们的内部指标API https://metrics.internal/api/v2。需要支持传入metric_name和time_range参数,返回JSON格式结果。
Phantom会完成工具构建,注册到自身MCP服务器,并告知你工具已可用。

Scheduling Recurring Tasks

调度周期性任务

@phantom Every weekday at 9am, check our GitHub repo myorg/myrepo for 
open PRs older than 3 days and post a summary to #engineering
@phantom 每个工作日上午9点,检查我们的GitHub仓库myorg/myrepo中超过3天未处理的开放PR,将摘要发送到#engineering频道

Requesting a Dashboard

请求构建看板

@phantom Build a dashboard showing our deployment frequency over the 
last 30 days. Make it shareable with the team.
Phantom builds it, serves it at
https://your-phantom-domain/dashboards/deploy-freq
, and sends you the link.
@phantom 构建一个展示过去30天部署频率的看板,支持团队共享。
Phantom会完成搭建,将看板部署在
https://your-phantom-domain/dashboards/deploy-freq
,并发送链接给你。

Memory Queries

内存查询

@phantom What did I tell you about our database architecture last week?
@phantom What tools have you built for me so far?
@phantom Summarize everything you know about Project X
@phantom 我上周和你说的数据库架构是什么样的?
@phantom 你目前为我构建了哪些工具?
@phantom 总结你知道的所有关于X项目的信息

Troubleshooting

故障排查

Phantom not starting

Phantom无法启动

bash
undefined
bash
undefined

Check all services are healthy

检查所有服务是否健康

docker compose ps
docker compose ps

Qdrant must be ready before Phantom

Qdrant必须在Phantom启动前就绪

docker compose logs qdrant curl http://localhost:6333/health
docker compose logs qdrant curl http://localhost:6333/health

Ollama must pull embedding model

Ollama必须拉取嵌入模型

docker compose logs ollama
undefined
docker compose logs ollama
undefined

Memory not persisting

内存不持久化

bash
undefined
bash
undefined

Verify Qdrant collections exist

验证Qdrant集合存在

Check Phantom can reach Qdrant

检查Phantom能否访问Qdrant

docker compose exec phantom curl http://qdrant:6333/health
undefined
docker compose exec phantom curl http://qdrant:6333/health
undefined

Slack not receiving messages

Slack收不到消息

  • Verify
    SLACK_APP_TOKEN
    starts with
    xapp-
    (not
    xoxb-
    )
  • Socket mode must be enabled in Slack App settings
  • Check bot is invited to channels:
    /invite @Phantom
  • Verify
    OWNER_SLACK_USER_ID
    is correct (not display name, actual ID)
  • 确认
    SLACK_APP_TOKEN
    xapp-
    开头(不是
    xoxb-
  • Slack App设置中必须开启Socket模式
  • 确认机器人已被邀请到频道:
    /invite @Phantom
  • 确认
    OWNER_SLACK_USER_ID
    正确(不是显示名称,是实际ID)

MCP tools not appearing in Claude Code

MCP工具未出现在Claude Code中

bash
undefined
bash
undefined

Verify MCP server is running

验证MCP服务器正在运行

Check tool registration

检查工具注册情况

Restart Claude Code after adding MCP config

添加MCP配置后重启Claude Code

undefined
undefined

Evolution not triggering

进化未触发

bash
undefined
bash
undefined

Check env var

检查环境变量

echo $EVOLUTION_ENABLED # should be "true"
echo $EVOLUTION_ENABLED # 应为"true"

Verify validation model is set

验证校验模型已配置

echo $EVOLUTION_VALIDATION_MODEL
echo $EVOLUTION_VALIDATION_MODEL

Check logs for evolution cycle

查看进化循环日志

docker compose logs phantom | grep -i evolv
undefined
docker compose logs phantom | grep -i evolv
undefined

Docker socket permission denied

Docker socket权限被拒绝

bash
undefined
bash
undefined

Add phantom user to docker group, or run with:

将phantom用户添加到docker组,或者使用以下命令启动:

sudo docker compose up -d
sudo docker compose up -d

Or add to docker-compose.yaml:

或者在docker-compose.yaml中添加:

user: root

user: root

undefined
undefined

API Reference

API参考

EndpointMethodDescription
/health
GETHealth check
/status
GETAgent status + uptime
/mcp
GET/POSTMCP server endpoint
/mcp/tools
GETList registered tools
/webhook/message
POSTSend message to agent
/credentials/:token
GET/POSTSecure credential form
/public/:slug
GETServed static assets
端点请求方法描述
/health
GET健康检查
/status
GETAgent状态+运行时长
/mcp
GET/POSTMCP服务器端点
/mcp/tools
GET列出已注册的工具
/webhook/message
POST向Agent发送消息
/credentials/:token
GET/POST安全凭证表单
/public/:slug
GET对外提供的静态资源

Version History & Rollback

版本历史与回滚

bash
undefined
bash
undefined

Phantom versions its own evolution

Phantom会对自身的进化进行版本管理

View evolution history in logs

查看日志中的进化历史

docker compose logs phantom | grep -i "evolved"
docker compose logs phantom | grep -i "evolved"

Pin to specific version

固定到指定版本

Edit docker-compose.yaml:

编辑docker-compose.yaml:

image: ghostwright/phantom:0.18.1

image: ghostwright/phantom:0.18.1

Roll back

回滚版本

docker compose down
docker compose down

Change image tag in compose file

修改compose文件中的镜像标签

docker compose up -d
undefined
docker compose up -d
undefined