Files
EJClaw/README.md
Eyejoker 1897b56760 docs: clean up README, remove upstream comparison table
Present as standalone project. Keep origin attribution in header
and license. Add key features summary.
2026-03-13 19:59:06 +09:00

8.4 KiB

NanoClaw

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/delta notifications for real-time text
  • Mid-turn steering: IPC messages injected via turn/steer during 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 localImage input blocks in turn/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 base64 ImageBlockParam content blocks (Claude Code) or localImage input 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/ + project container/skills/
  • Codex sessions: Same sources, synced to per-group .codex/ directories
  • Skills auto-register as slash commands (/name) in Claude Code and $name in 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.com then falls back to api.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

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 (@claude or @codex)
  • agent_type: claude-code, codex, or both (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