phantom-ai-coworker
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChinesePhantom 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
undefinedbash
undefinedDownload compose file and env template
下载compose文件和环境变量模板
curl -fsSL https://raw.githubusercontent.com/ghostwright/phantom/main/docker-compose.user.yaml -o docker-compose.yaml
curl -fsSL https://raw.githubusercontent.com/ghostwright/phantom/main/.env.example -o .env
curl -fsSL https://raw.githubusercontent.com/ghostwright/phantom/main/docker-compose.user.yaml -o docker-compose.yaml
curl -fsSL https://raw.githubusercontent.com/ghostwright/phantom/main/.env.example -o .env
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
undefineddocker compose logs -f phantom
undefinedFrom Source (Bun)
源码安装(Bun)
bash
git clone https://github.com/ghostwright/phantom.git
cd phantombash
git clone https://github.com/ghostwright/phantom.git
cd phantomInstall 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
undefinedbun run dev
undefinedConfiguration (.env)
配置(.env)
bash
undefinedbash
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)
undefinedCREDENTIAL_ENCRYPTION_KEY= # AES-256-GCM密钥(留空则自动生成)
undefinedKey Commands
常用命令
bash
undefinedbash
undefinedDocker 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服务器端点
undefinedundefinedCore 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
undefinedPhantom运行后,你可以将Claude Code连接到它,使用所有Phantom注册的工具:
json
// ~/.claude/claude_desktop_config.json 或者 .cursor/mcp.json
{
"mcpServers": {
"phantom": {
"url": "http://your-phantom-vm:3100/mcp"
}
}
}或者通过CLI操作:
bash
undefinedClaude 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
undefinedclaude mcp list
undefinedDocker Compose Structure
Docker Compose结构
yaml
undefinedyaml
undefineddocker-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:
undefinedservices:
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:
undefinedSlack App Setup
Slack App设置
- Go to api.slack.com/apps → Create New App → From manifest
- 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- Install to workspace → copy Bot Token () to
xoxb-SLACK_BOT_TOKEN - Generate App-Level Token with → copy to
connections:writeSLACK_APP_TOKEN - Copy Signing Secret →
SLACK_SIGNING_SECRET - Get your user ID: In Slack, click your profile → copy Member ID →
OWNER_SLACK_USER_ID
- 访问api.slack.com/apps → 创建新App → 从清单导入
- 使用以下清单:
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- 安装到工作区 → 复制Bot Token(开头)到
xoxb-SLACK_BOT_TOKEN - 生成带权限的应用级令牌 → 复制到
connections:writeSLACK_APP_TOKEN - 复制签名密钥 → 填入
SLACK_SIGNING_SECRET - 获取你的用户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 , and sends you the link.
https://your-phantom-domain/dashboards/deploy-freq@phantom 构建一个展示过去30天部署频率的看板,支持团队共享。Phantom会完成搭建,将看板部署在,并发送链接给你。
https://your-phantom-domain/dashboards/deploy-freqMemory 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
undefinedbash
undefinedCheck 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
undefineddocker compose logs ollama
undefinedMemory not persisting
内存不持久化
bash
undefinedbash
undefinedVerify Qdrant collections exist
验证Qdrant集合存在
Check Phantom can reach Qdrant
检查Phantom能否访问Qdrant
docker compose exec phantom curl http://qdrant:6333/health
undefineddocker compose exec phantom curl http://qdrant:6333/health
undefinedSlack not receiving messages
Slack收不到消息
- Verify starts with
SLACK_APP_TOKEN(notxapp-)xoxb- - Socket mode must be enabled in Slack App settings
- Check bot is invited to channels:
/invite @Phantom - Verify is correct (not display name, actual ID)
OWNER_SLACK_USER_ID
- 确认以
SLACK_APP_TOKEN开头(不是xapp-)xoxb- - Slack App设置中必须开启Socket模式
- 确认机器人已被邀请到频道:
/invite @Phantom - 确认正确(不是显示名称,是实际ID)
OWNER_SLACK_USER_ID
MCP tools not appearing in Claude Code
MCP工具未出现在Claude Code中
bash
undefinedbash
undefinedVerify MCP server is running
验证MCP服务器正在运行
Check tool registration
检查工具注册情况
Restart Claude Code after adding MCP config
添加MCP配置后重启Claude Code
undefinedundefinedEvolution not triggering
进化未触发
bash
undefinedbash
undefinedCheck 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
undefineddocker compose logs phantom | grep -i evolv
undefinedDocker socket permission denied
Docker socket权限被拒绝
bash
undefinedbash
undefinedAdd 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
undefinedundefinedAPI Reference
API参考
| Endpoint | Method | Description |
|---|---|---|
| GET | Health check |
| GET | Agent status + uptime |
| GET/POST | MCP server endpoint |
| GET | List registered tools |
| POST | Send message to agent |
| GET/POST | Secure credential form |
| GET | Served static assets |
| 端点 | 请求方法 | 描述 |
|---|---|---|
| GET | 健康检查 |
| GET | Agent状态+运行时长 |
| GET/POST | MCP服务器端点 |
| GET | 列出已注册的工具 |
| POST | 向Agent发送消息 |
| GET/POST | 安全凭证表单 |
| GET | 对外提供的静态资源 |
Version History & Rollback
版本历史与回滚
bash
undefinedbash
undefinedPhantom 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
undefineddocker compose up -d
undefined