Skip to content

Claude Runners

Three runner containers, one per blog, each with its own agent configs and MCP connections.

Runner Inventory

Container Blog Agent Config Workspace
runner-cam4 Cam4 ./runners/cam4/claude-config/ ./runners/cam4/workspace/
runner-cam4models Cam4Models ./runners/cam4models/claude-config/ ./runners/cam4models/workspace/
runner-cam4pays Cam4Pays ./runners/cam4pays/claude-config/ ./runners/cam4pays/workspace/

Docker Image

Built from ./runners/Dockerfile: - Base: python:3.11-slim - Adds: Node.js 20, Claude Code CLI (@anthropic-ai/claude-code), Python (requests, anthropic) - Runs tail -f /dev/null to stay alive for docker exec

Volume Mounts

Each runner has 3 bind mounts:

volumes:
  - ./runners/<slug>/claude-config:/root/.claude      # Agent definitions + MCP config
  - ./runners/<slug>/.claude.json:/root/.claude.json   # Auth token (persists across recreate)
  - ./runners/<slug>/workspace:/workspace              # Working directory

Critical: The .claude.json bind mount ensures authentication survives container recreation. Without it, runners need re-authentication after every docker compose up -d.

Agent Definitions

Each runner has 6 agents in claude-config/agents/:

Agent File Purpose
Researcher wicked-researcher.md Finds trending topics via Reddit
Blogger wicked-blogger.md Writes the blog post
Photographer wicked-photographer.md Generates images via Runware
Optimizer wicked-optimizer.md SEO optimization
Humanizer wicked-humanizer.md Makes content sound natural
Publisher wicked-publisher.md Publishes to Ghost via MCP

MCP Connections

graph LR
    subgraph Runner["Claude Runner"]
        agents["6 Agents<br>Researcher, Blogger,<br>Photographer, Optimizer,<br>Humanizer, Publisher"]
    end
    Runner -->|"blog token"| ghost["ghost-mcp :3002"]
    Runner -->|"SSE"| reddit["reddit-mcp :8080"]
    Runner -->|"HTTP"| playwright["playwright-mcp :8931"]
    Runner -->|"HTTP"| runware["runware-mcp :8081"]
    Runner -->|"HTTP"| twitter["twitter-mcp :8081"]
    Runner -->|"HTTP"| stockimages["stockimages-mcp :8000"]
    Runner -->|"HTTP"| memes["memes-mcp :3000"]

Each runner connects to 7 MCP servers via claude-config/.mcp.json:

{
  "mcpServers": {
    "ghost": {
      "type": "http",
      "url": "http://ghost-mcp:3002/<blog-specific-token>"
    },
    "reddit": {
      "type": "sse",
      "url": "http://reddit-mcp:8080/sse"
    },
    "playwright": {
      "type": "http",
      "url": "http://playwright-mcp:8931/mcp"
    },
    "runware": {
      "type": "http",
      "url": "http://runware-mcp:8081/mcp"
    },
    "twitter": {
      "type": "http",
      "url": "http://twitter-mcp:8081/mcp"
    },
    "stockimages": {
      "type": "http",
      "url": "http://stockimages-mcp:8000/mcp"
    },
    "memes": {
      "type": "http",
      "url": "http://memes-mcp:3000/mcp"
    }
  }
}

Note: The Ghost token is unique per runner — it routes to the correct Ghost instance.

Brand Guidelines

Each runner has brand-specific configuration:

runners/<slug>/claude-config/config/brand-guidelines.json  # Blog voice, audience, topics
runners/<slug>/claude-config/config/runware-models.json     # Image generation model prefs

Authentication Procedure

After first build or if .claude.json is lost:

# Interactive login
docker exec -it runner-cam4 claude /login

# Follow the OAuth flow in your browser
# The token is saved to /root/.claude.json (bind-mounted for persistence)

# Verify
docker exec runner-cam4 claude --version

Common Operations

cd /opt/camlab

# Check if a runner is alive
docker exec runner-cam4 echo "alive"

# Check Claude Code version
docker exec runner-cam4 claude --version

# View runner logs
docker compose logs --tail 50 runner-cam4

# Restart a runner
docker compose restart runner-cam4

# Run a command in runner
docker exec runner-cam4 claude --agent wicked-researcher "Find trending topics"