Present as standalone project. Keep origin attribution in header and license. Add key features summary.
8.4 KiB
Dual-agent AI assistant running Claude Code + Codex as parallel services over Discord.
Based on qwibitai/nanoclaw
Overview
Two AI agents running as parallel systemd services, communicating over Discord:
- Claude Code — powered by Claude Agent SDK, trigger
@claude - Codex — powered by Codex app-server (JSON-RPC), trigger
@codex
Each agent has its own store, data, and groups directories. Discord channels can be registered with either agent, or both (both agent type for shared channels).
Key Features
- Direct host processes — no container overhead, agents run natively
- Bidirectional image support — receive images as multimodal input, send as Discord attachments
- Skill sync — single source of truth (
~/.claude/skills/), auto-synced to all sessions - OAuth auto-refresh — token lifecycle managed automatically for headless environments
- Priority queue — per-group serialization, global concurrency limit, idle preemption
- Auto-continue — Codex text-only turns automatically retried to enforce task execution
Architecture
Discord ──► SQLite ──► GroupQueue ──┬──► Claude Agent SDK (host process)
└──► Codex App-Server (JSON-RPC stdio)
├── thread/start, thread/resume
├── turn/start (streaming, multimodal)
├── turn/steer (mid-turn injection)
├── Auto-approval (bypass sandbox)
└── Auto-continue (text-only turn retry)
Directory Layout
nanoclaw/
├── src/
│ ├── index.ts # Orchestrator: state, message loop, agent invocation
│ ├── agent-runner.ts # Spawns agent processes, manages env/sessions/skills
│ ├── group-queue.ts # Per-group concurrency, priority queue, idle preemption
│ ├── group-folder.ts # Group directory resolution and management
│ ├── router.ts # Outbound message formatting and routing
│ ├── sender-allowlist.ts # Security: sender-based access control
│ ├── session-commands.ts # Session commands (/compact)
│ ├── token-refresh.ts # OAuth auto-refresh + session directory sync
│ ├── task-scheduler.ts # Scheduled tasks (cron/interval/once)
│ ├── ipc.ts # IPC watcher and task processing
│ ├── db.ts # SQLite operations
│ ├── config.ts # Paths, intervals, trigger patterns
│ └── channels/
│ ├── registry.ts # Channel self-registration system
│ └── discord.ts # Discord: mentions, images, typing, file attachments
├── container/
│ ├── agent-runner/ # Claude Code runner (Agent SDK, multimodal input)
│ ├── codex-runner/ # Codex runner (app-server JSON-RPC, auto-continue)
│ └── skills/ # Shared agent skills (browser, etc.)
├── store/ # Claude Code service DB
├── store-codex/ # Codex service DB
├── data/
│ ├── sessions/ # Per-group Claude sessions (.claude/)
│ └── attachments/ # Downloaded Discord image attachments
├── data-codex/sessions/ # Per-group Codex sessions (.codex/)
├── groups/ # Per-group memory and workspace (Claude Code)
├── groups-codex/ # Per-group memory and workspace (Codex)
└── logs/ # Service logs
Codex App-Server Integration
The Codex runner (container/codex-runner/) communicates with codex app-server via JSON-RPC over stdio:
- Session persistence: Thread IDs stored in DB, sessions saved as JSONL on disk
- Streaming:
item/agentMessage/deltanotifications for real-time text - Mid-turn steering: IPC messages injected via
turn/steerduring execution - Auto-approval:
approvalPolicy: "never"+sandbox: "danger-full-access" - Auto-continue: Detects text-only turns (no tool execution) and automatically retries up to 5 times to nudge the agent into actually executing tasks
- Multimodal input: Image attachments converted to
localImageinput blocks inturn/start - Per-group config: Model, effort, MCP servers configured per channel
Image Handling
Bidirectional image support through Discord:
- Receiving (user → agent): Discord image attachments are downloaded to
data/attachments/, then passed as base64ImageBlockParamcontent blocks (Claude Code) orlocalImageinput blocks (Codex) - Sending (agent → user): Markdown image links
[name.png](/path)in agent responses are automatically parsed and sent as Discord file attachments. Non-image file links are converted to readable filenames (BuildPanel.tsx:320)
Skill Sync
Skills are managed from a single source of truth (~/.claude/skills/ on the server) and automatically synced to all agent session directories at process start:
- Claude Code sessions:
~/.claude/skills/+ projectcontainer/skills/ - Codex sessions: Same sources, synced to per-group
.codex/directories - Skills auto-register as slash commands (
/name) in Claude Code and$namein Codex
OAuth Token Auto-Refresh
src/token-refresh.ts handles Claude Code OAuth token lifecycle:
- Checks every 5 minutes, refreshes 30 minutes before expiry
- Tries
platform.claude.comthen falls back toapi.anthropic.com - Syncs refreshed credentials to all per-group session directories
- Solves the known headless environment token expiry issue
GroupQueue
src/group-queue.ts manages agent execution with:
- Per-group serialization: Only one agent process per group at a time
- Global concurrency limit: Configurable max concurrent agents across all groups
- Task priority: Scheduled tasks drain before message processing
- Idle preemption: Idle agents are terminated when higher-priority tasks arrive
- Exponential backoff: Retries with backoff on processing failure
Setup
Prerequisites
- Linux (Ubuntu 22.04+) or macOS
- Node.js 20+
- Claude Code CLI
- Codex CLI (
npm install -g @openai/codex)
Environment Variables
# .env
DISCORD_BOT_TOKEN= # Claude Code bot token
DISCORD_CODEX_BOT_TOKEN= # Codex bot token (optional, for dual-bot)
ANTHROPIC_API_KEY= # Or use OAuth (CLAUDE_CODE_OAUTH_TOKEN)
OPENAI_API_KEY= # For Codex
CODEX_MODEL= # Default codex model
CODEX_EFFORT= # Default reasoning effort (low/medium/high)
Service Management (Linux)
systemctl --user start nanoclaw # Claude Code service
systemctl --user start nanoclaw-codex # Codex service
systemctl --user restart nanoclaw nanoclaw-codex # Restart both
journalctl --user -u nanoclaw -f # Follow logs
Service Management (macOS)
launchctl load ~/Library/LaunchAgents/com.nanoclaw.plist
launchctl load ~/Library/LaunchAgents/com.nanoclaw-codex.plist
launchctl kickstart -k gui/$(id -u)/com.nanoclaw
Channel Registration
Channels are registered in each service's SQLite database (registered_groups table). Each entry specifies:
- jid: Discord channel ID (
dc:<channel_id>) - folder: Group folder name (matches Discord channel name)
- trigger_pattern: Regex for bot activation (
@claudeor@codex) - agent_type:
claude-code,codex, orboth(shared channel) - work_dir: Working directory for the agent
- container_config: JSON config (e.g.,
{"codexEffort":"high"})
Development
npm run build # Build main project
cd container/agent-runner && npm run build # Build Claude runner
cd container/codex-runner && npm run build # Build Codex runner
npm run dev # Dev mode with hot reload
npm test # Run tests
License
MIT — Based on qwibitai/nanoclaw
