| name | phantom-ai-coworker |
| description | AI co-worker agent with its own computer, persistent memory, self-evolution, MCP server, and Slack/email identity built on Claude Agent SDK |
| triggers | ["set up phantom ai agent","configure phantom co-worker","phantom self-evolving agent","phantom mcp server setup","phantom slack bot agent","build phantom ai coworker","phantom persistent memory agent","deploy phantom on docker"] |
Phantom AI Co-worker
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.
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│
│ └──────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────┘
Installation
Docker (Recommended)
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
nano .env
docker compose up -d
curl http://localhost:3100/health
docker compose logs -f phantom
From Source (Bun)
git clone https://github.com/ghostwright/phantom.git
cd phantom
bun install
cp .env.example .env
docker run -d -p 6333:6333 qdrant/qdrant
bun run start
bun run dev
Configuration (.env)
ANTHROPIC_API_KEY=
SLACK_BOT_TOKEN=xoxb-
SLACK_APP_TOKEN=xapp-
SLACK_SIGNING_SECRET=
OWNER_SLACK_USER_ID=U0XXXXXXXXX
QDRANT_URL=http://localhost:6333
QDRANT_API_KEY=
OLLAMA_URL=http://localhost:11434
RESEND_API_KEY=
PHANTOM_EMAIL=phantom@yourdomain
TELEGRAM_BOT_TOKEN=
PHANTOM_VM_DOMAIN=
PHANTOM_PORT=3100
EVOLUTION_VALIDATION_MODEL=claude-3-5-sonnet-20241022
EVOLUTION_ENABLED=true
CREDENTIAL_ENCRYPTION_KEY=
Key Commands
docker compose up -d
docker compose down
docker compose logs -f phantom
docker compose pull
bun run start
bun run dev
bun run test
bun run build
curl http://localhost:3100/health
curl http://localhost:3100/status
curl http://localhost:3100/mcp
Core Concepts & Code Examples
1. Memory System (Qdrant + Embeddings)
Phantom stores memories as vector embeddings for semantic recall across sessions.
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);
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.(, {
: queryEmbedding,
limit,
: ,
});
results.( r.?.);
}
2. Dynamic MCP Tool Registration
Phantom creates MCP tools at runtime that persist across restarts.
interface PhantomTool {
name: string;
description: string;
inputSchema: Record<string, unknown>;
handler: string;
}
async function registerDynamicTool(tool: PhantomTool) {
await storeMemory(JSON.stringify(tool), {
type: 'mcp_tool',
toolName: tool.name,
});
mcpServer.tool(tool.name, tool.description, tool.inputSchema, async (args) => {
return await executeToolHandler(tool.handler, args);
});
}
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
const mcpServer = new McpServer({
: ,
: ,
});
3. Slack Channel Integration
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,
});
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);
const response = await runPhantomAgent({
message: userMessage,
userId,
memories,
channel: (event as any).channel,
});
await ({ : response, : (event ). });
});
() {
slack...({
: process..!,
: ,
});
}
4. Claude Agent SDK Integration
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);
const response = await anthropic.messages.create({
model: 'claude-opus-4-5',
max_tokens: 8096,
system: systemPrompt,
messages: [{ role: 'user', content: message }],
tools: await getAvailableTools(),
});
if (response.stop_reason === 'tool_use') {
return await handleToolCalls(response, message, userId);
}
await storeMemory(`User ${userId} asked: . I responded: `, {
: ,
userId,
channel,
});
(response.);
}
(): {
;
}
5. Secure Credential Collection
Phantom collects credentials via encrypted forms, never plain text.
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.();
iv = .(ivHex, );
authTag = .(authTagHex, );
encrypted = .(encryptedHex, );
decipher = (, , iv);
decipher.(authTag);
decipher.(encrypted) + decipher.();
}
() {
token = ().();
(token, { service, fields, : .() + });
;
}
6. Self-Evolution Engine
Phantom observes its own behavior, proposes improvements, validates with a separate model, and evolves.
interface EvolutionProposal {
observation: string;
currentBehavior: string;
proposedChange: string;
rationale: string;
version: string;
}
async function runEvolutionCycle() {
if (process.env.EVOLUTION_ENABLED !== 'true') return;
const recentMemories = await recallMemories('recent interactions', 50);
const proposals = await generateEvolutionProposals(recentMemories);
for (const proposal of proposals) {
const isValid = await validateProposal(proposal);
if (isValid) {
await applyEvolution(proposal);
await versionEvolution(proposal);
await notifySlack(
);
}
}
}
(): <> {
validationResponse = anthropic..({
: process..!,
: ,
: [{
: ,
: ,
}],
});
result = .((validationResponse.));
result.;
}
7. Infrastructure Building (VM Operations)
import { exec } from 'child_process';
import { promisify } from 'util';
const execAsync = promisify(exec);
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));
await registerDynamicTool({
: ,
: ,
: { : { : } },
: ,
});
{ connectionString, port };
}
() {
filePath = ;
.(filePath, htmlContent);
;
}
8. Webhook Channel
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();
Connecting Claude Code to Phantom's MCP Server
Once Phantom is running, connect Claude Code to use all of Phantom's registered tools:
{
"mcpServers": {
"phantom": {
"url": "http://your-phantom-vm:3100/mcp"
}
}
}
Or via CLI:
claude mcp add phantom --url http://your-phantom-vm:3100/mcp
claude mcp list
Docker Compose Structure
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
depends_on:
- qdrant
- ollama
restart: unless-stopped
qdrant:
image: qdrant/qdrant:latest
volumes:
- qdrant_data:/qdrant/storage
restart: unless-stopped
ollama:
Slack App Setup
- Go to api.slack.com/apps → Create New App → From manifest
- Use this manifest:
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 (
xoxb-) to SLACK_BOT_TOKEN
- Generate App-Level Token with
connections:write → copy to SLACK_APP_TOKEN
- Copy Signing Secret →
SLACK_SIGNING_SECRET
- Get your user ID: In Slack, click your profile → copy Member ID →
OWNER_SLACK_USER_ID
Common Patterns
Asking Phantom to Build a Tool
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.
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
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.
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
Troubleshooting
Phantom not starting
docker compose ps
docker compose logs qdrant
curl http://localhost:6333/health
docker compose logs ollama
Memory not persisting
curl http://localhost:6333/collections
docker compose exec phantom curl http://qdrant:6333/health
Slack not receiving messages
- 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)
MCP tools not appearing in Claude Code
curl http://localhost:3100/mcp
curl http://localhost:3100/mcp/tools
Evolution not triggering
echo $EVOLUTION_ENABLED
echo $EVOLUTION_VALIDATION_MODEL
docker compose logs phantom | grep -i evolv
Docker socket permission denied
sudo docker compose up -d
API Reference
| Endpoint | Method | Description |
|---|
/health | GET | Health check |
/status | GET | Agent status + uptime |
/mcp | GET/POST | MCP server endpoint |
/mcp/tools | GET | List registered tools |
/webhook/message | POST | Send message to agent |
/credentials/:token | GET/POST | Secure credential form |
/public/:slug | GET | Served static assets |
Version History & Rollback
docker compose logs phantom | grep -i "evolved"
docker compose down
docker compose up -d