Run any coding agent from Telegram or Slack.
Dispatch tasks to Claude Code, Codex, or Goose from a chat message.
Anycode spins up an isolated sandbox (Docker locally or ECS Fargate in cloud), streams output back, handles Q&A via buttons, and cleans up when done.
Quickstart • How It Works • Commands • Configuration • Architecture • Contributing
No single tool lets you dispatch coding tasks from a messaging app, spin up a sandboxed agent, get streaming output, answer the agent's questions interactively, and tear everything down automatically. Anycode does.
- Message-driven — start tasks from Telegram or Slack
- Agent-agnostic — Claude Code, Codex, Goose, or any agent behind Rivet's Sandbox Agent SDK
- Fully sandboxed — each task runs in an ephemeral Docker container or ECS Fargate task
- Bidirectional — questions and permission requests appear as inline buttons; your replies go back to the agent
- Streaming — see the agent's output as it types, debounced to avoid rate limits
- Rust 1.75+ (for building)
- Docker running locally (if
sandbox.provider = "docker") - AWS account + IAM credentials (if
sandbox.provider = "ecs") - A Telegram Bot Token (from @BotFather) and/or Slack App + Bot Tokens (at least one platform required)
- API keys for at least one agent (e.g.
ANTHROPIC_API_KEYfor Claude Code)
git clone https://github.com/manthan787/anycode.git
cd anycode
cargo build --releaseThe setup wizard walks you through configuring messaging platforms, sandbox providers, agent credentials, and builds everything automatically:
cargo run --bin anycode-setupThe wizard will:
- Check that Rust, Cargo, and Docker are available
- Guide you through Telegram and/or Slack configuration
- Configure Docker or ECS Fargate sandbox settings
- Set up agent selection and API keys
- Write
config.tomland runcargo build --release+docker buildfor you
If you prefer to configure manually:
# Build the sandbox image
docker build -f docker/Dockerfile.agent -t anycode-sandbox:latest .
# Copy and edit the config
cp config.example.toml config.tomlEdit config.toml with your bot token and agent credentials:
[telegram]
bot_token = "123456:ABC-DEF..."
[agents.credentials.claude-code]
env = { ANTHROPIC_API_KEY = "sk-ant-..." }./target/release/anycode --config config.tomlOpen your bot in Telegram and send:
/task claude-code fix the login bug in org/repo
You (Telegram) Anycode Daemon Sandbox Backend
━━━━━━━━━━━━━━ ━━━━━━━━━━━━━━ ━━━━━━━━━━━━━━━
/task claude-code Parse command
fix the auth bug ──────────────▶ Check limits
Create sandbox ──────────────▶ 🐳 Docker or ☁️ ECS
Wait for healthy ◀────────────── + claude-code
Create session
Send prompt ──────────────▶ Agent starts working
◀── SSE stream ────────────
Streaming output ◀──────────── Debounced edits
"Which file?" ◀──────────── Inline keyboard
│
[Press button] ──────────────▶ Reply to agent ──────────────▶ Agent continues
◀── SSE stream ────────────
"Done! Here's ◀──────────── Final message
the fix." Destroy sandbox ──────────────▶ 🗑️ cleaned up
Each session is fully isolated: its own container/task, its own API endpoint, and its own event stream. Sandboxes are automatically destroyed on completion, failure, timeout, or cancellation.
| Command | Description |
|---|---|
/task [agent] <prompt> |
Start a coding task. Agent defaults to config if omitted. |
/status |
List active sessions with agent, status, and start time. |
/cancel [id] |
Cancel a session. Omit ID to cancel the most recent. |
/agents |
List available agents and which is the default. |
/help |
Show available commands. |
Agent selection: If the first word after /task matches a known agent name, it's used as the agent. Otherwise the default agent is used and the full text is the prompt.
/task fix the bug → default agent, prompt = "fix the bug"
/task codex fix the bug → agent = codex, prompt = "fix the bug"
Repo detection: GitHub/GitLab URLs (or org/repo shorthand) in the prompt are automatically detected and passed to the sandbox.
Follow-up messages: Plain text sent while a session is active gets routed to the most recent session in that chat.
Interactive Q&A: When the agent asks a question or requests permission, inline buttons appear. Press a button to respond.
Tip: Run
cargo run --bin anycode-setupto generateconfig.tomlinteractively instead of editing by hand.
# At least one messaging platform must be configured.
[telegram]
bot_token = "YOUR_BOT_TOKEN" # Required
allowed_users = [] # Telegram user IDs (empty = allow all)
[slack]
app_token = "xapp-..." # App-level token with connections:write scope
bot_token = "xoxb-..." # Bot token with chat:write, files:write scopes
allowed_users = [] # Slack user IDs (empty = allow all)
[sandbox]
provider = "docker" # "docker" or "ecs"
[docker]
image = "anycode-sandbox:latest" # Sandbox container image
port_range_start = 12000 # Host port range for containers
port_range_end = 12100
network = "bridge"
[ecs]
cluster = "anycode-cluster" # Required when provider = "ecs"
task_definition = "anycode-task:1" # Required when provider = "ecs"
subnets = ["subnet-abc123"] # Required when provider = "ecs"
security_groups = ["sg-abc123"]
assign_public_ip = true
container_port = 2468
startup_timeout_secs = 120
poll_interval_ms = 1000
region = "us-west-2"
platform_version = "LATEST"
container_name = "anycode-sandbox" # Optional; inferred from task def if empty
log_group = "/ecs/anycode" # Optional, for get_logs
log_stream_prefix = "anycode" # Optional
[database]
path = "anycode.db" # SQLite database file
[agents]
default_agent = "claude-code" # Default when /task has no agent name
[agents.credentials.claude-code]
env = { ANTHROPIC_API_KEY = "sk-ant-..." }
[agents.credentials.codex]
env = { OPENAI_API_KEY = "sk-..." }
[agents.credentials.goose]
env = { OPENAI_API_KEY = "sk-..." }
[github]
token = "ghp_..." # Optional; enables gh CLI + git auth in sandbox
[session]
max_concurrent = 5 # Max active sessions per chat
timeout_minutes = 30 # Auto-cancel after this duration
debounce_ms = 500 # Streaming output flush intervalAgent credentials are injected as environment variables into the sandbox at creation time. They are never baked into images. Keep config.toml out of version control.
Setting [github] token enables authenticated git clone and gh CLI access inside sandbox containers. The token is passed as an environment variable and configured by the container entrypoint—never written to disk in the image.
- Anycode launches one Fargate task per
/taskviaRunTask. - It waits for task state
RUNNING, resolves the ENI IP, then connects to the sandbox agent onecs.container_port. ANYCODE_AGENT,ANYCODE_REPO, and agent credentials are passed as ECS container environment overrides.ecs.container_nameis optional. If omitted, Anycode infers it from the ECS task definition.get_logsuses CloudWatch whenecs.log_groupis configured.
anycode/
├── crates/
│ ├── anycode-core/ Library: all business logic
│ │ ├── config.rs TOML config parsing + validation
│ │ ├── error.rs Unified error types (thiserror)
│ │ ├── db/ SQLite persistence (tokio-rusqlite)
│ │ ├── messaging/ MessagingProvider trait + Telegram/Slack impls
│ │ ├── infra/ SandboxProvider trait + Docker/ECS impls
│ │ ├── sandbox/ HTTP client + SSE stream for sandbox agent
│ │ ├── control/ Messaging ↔ Sandbox bridge orchestration
│ │ └── session/ Timeout watchdog + orphan cleanup
│ ├── anycode-bin/ CLI entrypoint (clap + tracing)
│ └── anycode-setup/ Interactive TUI setup wizard (ratatui)
├── migrations/ SQLite schema
├── docker/ Sandbox container image
└── config.example.toml
The two core extension points are traits, making it straightforward to add new messaging platforms or infrastructure backends:
MessagingProvider — send/edit messages, handle callbacks, subscribe to events, upload files.
Currently implemented for Telegram and Slack. Extensible to Discord, Matrix, and others.
SandboxProvider — create/destroy sandboxes, health check, fetch logs.
Currently implemented for Docker and AWS ECS Fargate. Extensible to Kubernetes and other backends.
- tokio async runtime with spawned tasks per event, per session, per SSE stream
- DashMap for lock-free concurrent session routing
- Async Mutex for per-session delta buffers
- Watch channel for graceful shutdown broadcast
Agent output arrives as many small SSE item.delta events. Sending each one as a separate Telegram message would hit rate limits and be unreadable. Instead, a DeltaBuffer accumulates text and flushes it as a Telegram message edit every 500ms (configurable). When a message approaches Telegram's 4096-char limit, a new message is started automatically.
- SSE reconnection: Exponential backoff (1s → 30s cap), max 5 retries
- Session timeouts: Background watchdog every 60s
- Orphan cleanup: Dead containers detected and failed on startup
- Graceful shutdown: SIGTERM destroys all active containers
The default sandbox image (docker/Dockerfile.agent) is Ubuntu 24.04 with:
- Rivet Sandbox Agent SDK
- Claude Code CLI
- Codex CLI
- GitHub CLI (
gh) for authenticated git operations - Node.js 22, Python 3, git, build-essential
Build it with:
docker build -f docker/Dockerfile.agent -t anycode-sandbox:latest .The image exposes port 2468 (sandbox agent HTTP API). Each container gets a unique host port from the configured range, mapped to 127.0.0.1 only.
# Run tests
cargo test
# Run with debug logging
RUST_LOG=debug cargo run -- --config config.toml
# Launch the setup wizard
cargo run --bin anycode-setup
# Check compilation
cargo checkUnit tests covering config validation (including ECS), database CRUD, bridge behavior, message splitting, URL extraction, delta buffering, infra helpers, and config generation. All tests use in-memory SQLite and are fully isolated.
- Slack messaging provider
- Interactive TUI setup wizard
- Discord messaging provider
- Kubernetes sandbox provider
- ACP (JSON-RPC) protocol support alongside OpenCode REST
- Git repo cloning into sandbox (private repos via GitHub token)
- File output as Telegram document uploads
- Per-user rate limiting
- Web dashboard for session monitoring
MIT
Built with Rust, tokio, teloxide, bollard, and ratatui.