Repository files navigation

Anycode

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.

QuickstartHow It WorksCommandsConfigurationArchitectureContributing


Why?

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 or acpx
  • 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

Quickstart

Prerequisites

  • 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_KEY for Claude Code)

1. Clone and build

git clone https://github.com/manthan787/anycode.git
cd anycode
cargo build --release

2. Interactive setup (recommended)

The setup wizard walks you through configuring messaging platforms, sandbox providers, agent credentials, and builds everything automatically:

cargo run --bin anycode-setup

The 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.toml and run cargo build --release + docker build for you

2b. Manual setup (alternative)

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.toml

Edit 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-..." }

3. Run

./target/release/anycode --config config.toml

4. Use it

Open your bot in Telegram and send:

/task claude-code fix the login bug in org/repo

How It Works

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.


Commands

CommandDescription
/task [agent] <prompt>Start a coding task. Agent defaults to config if omitted.
/statusList active sessions with agent, status, and start time.
/cancel [id]Cancel a session. Omit ID to cancel the most recent.
/agentsList available agents and which is the default.
/helpShow 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.


Configuration

Tip: Run cargo run --bin anycode-setup to generate config.toml interactively instead of editing by hand.

# At least one messaging platform must be configured.
[telegram]
bot_token = "YOUR_BOT_TOKEN"# Requiredallowed_users = [] # Telegram user IDs (empty = allow all)
[slack]
app_token = "xapp-..."# App-level token with connections:write scopebot_token = "xoxb-..."# Bot token with chat:write, files:write scopesallowed_users = [] # Slack user IDs (empty = allow all)
[sandbox]
provider = "docker"# "docker" or "ecs"protocol = "opencode"# "opencode" (REST/SSE) or "acpx" (headless ACP)
[docker]
image = "anycode-sandbox:latest"# Sandbox container imageport_range_start = 12000# Host port range for containersport_range_end = 12100network = "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 = truecontainer_port = 2468startup_timeout_secs = 120poll_interval_ms = 1000region = "us-west-2"platform_version = "LATEST"container_name = "anycode-sandbox"# Optional; inferred from task def if emptylog_group = "/ecs/anycode"# Optional, for get_logslog_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 chattimeout_minutes = 30# Auto-cancel after this durationdebounce_ms = 500# Streaming output flush interval

Agent 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.

GitHub Authentication

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.

ECS Fargate Notes

  • Anycode launches one Fargate task per /task via RunTask.
  • It waits for task state RUNNING, resolves the ENI IP, then connects to the sandbox agent on ecs.container_port.
  • ANYCODE_AGENT, ANYCODE_REPO, and agent credentials are passed as ECS container environment overrides.
  • ecs.container_name is optional. If omitted, Anycode infers it from the ECS task definition.
  • get_logs uses CloudWatch when ecs.log_group is configured.

Architecture

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/ AgentClient trait + OpenCode/acpx implementations
│ │ ├── 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

Trait abstractions

The three core extension points are traits, making it straightforward to add new messaging platforms, infrastructure backends, or agent protocols:

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.

AgentClient — abstract agent communication (wait, create session, send message, subscribe events). Currently implemented for OpenCode (REST/SSE) and acpx (NDJSON via docker exec). Selected via sandbox.protocol.

Concurrency model

  • 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

Streaming output

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.

Resilience

  • 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

Sandbox Image

The default sandbox image (docker/Dockerfile.agent) is Ubuntu 24.04 with:

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.


Development

# 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 check

Tests

Unit 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.


Roadmap

  • Slack messaging provider
  • Interactive TUI setup wizard
  • Discord messaging provider
  • Kubernetes sandbox provider
  • ACP protocol support via acpx 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

License

MIT


Built with Rust, tokio, teloxide, bollard, and ratatui.

About

Spawn coding agents in the cloud directly from any messaging platform

Topics

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

Anycode

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.

QuickstartHow It WorksCommandsConfigurationArchitectureContributing


Why?

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 or acpx
  • 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

Quickstart

Prerequisites

  • 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_KEY for Claude Code)

1. Clone and build

git clone https://github.com/manthan787/anycode.git
cd anycode
cargo build --release

2. Interactive setup (recommended)

The setup wizard walks you through configuring messaging platforms, sandbox providers, agent credentials, and builds everything automatically:

cargo run --bin anycode-setup

The 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.toml and run cargo build --release + docker build for you

2b. Manual setup (alternative)

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.toml

Edit 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-..." }

3. Run

./target/release/anycode --config config.toml

4. Use it

Open your bot in Telegram and send:

/task claude-code fix the login bug in org/repo

How It Works

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.


Commands

CommandDescription
/task [agent] <prompt>Start a coding task. Agent defaults to config if omitted.
/statusList active sessions with agent, status, and start time.
/cancel [id]Cancel a session. Omit ID to cancel the most recent.
/agentsList available agents and which is the default.
/helpShow 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.


Configuration

Tip: Run cargo run --bin anycode-setup to generate config.toml interactively instead of editing by hand.

# At least one messaging platform must be configured.
[telegram]
bot_token = "YOUR_BOT_TOKEN"# Requiredallowed_users = [] # Telegram user IDs (empty = allow all)
[slack]
app_token = "xapp-..."# App-level token with connections:write scopebot_token = "xoxb-..."# Bot token with chat:write, files:write scopesallowed_users = [] # Slack user IDs (empty = allow all)
[sandbox]
provider = "docker"# "docker" or "ecs"protocol = "opencode"# "opencode" (REST/SSE) or "acpx" (headless ACP)
[docker]
image = "anycode-sandbox:latest"# Sandbox container imageport_range_start = 12000# Host port range for containersport_range_end = 12100network = "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 = truecontainer_port = 2468startup_timeout_secs = 120poll_interval_ms = 1000region = "us-west-2"platform_version = "LATEST"container_name = "anycode-sandbox"# Optional; inferred from task def if emptylog_group = "/ecs/anycode"# Optional, for get_logslog_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 chattimeout_minutes = 30# Auto-cancel after this durationdebounce_ms = 500# Streaming output flush interval

Agent 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.

GitHub Authentication

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.

ECS Fargate Notes

  • Anycode launches one Fargate task per /task via RunTask.
  • It waits for task state RUNNING, resolves the ENI IP, then connects to the sandbox agent on ecs.container_port.
  • ANYCODE_AGENT, ANYCODE_REPO, and agent credentials are passed as ECS container environment overrides.
  • ecs.container_name is optional. If omitted, Anycode infers it from the ECS task definition.
  • get_logs uses CloudWatch when ecs.log_group is configured.

Architecture

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/ AgentClient trait + OpenCode/acpx implementations
│ │ ├── 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

Trait abstractions

The three core extension points are traits, making it straightforward to add new messaging platforms, infrastructure backends, or agent protocols:

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.

AgentClient — abstract agent communication (wait, create session, send message, subscribe events). Currently implemented for OpenCode (REST/SSE) and acpx (NDJSON via docker exec). Selected via sandbox.protocol.

Concurrency model

  • 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

Streaming output

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.

Resilience

  • 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

Sandbox Image

The default sandbox image (docker/Dockerfile.agent) is Ubuntu 24.04 with:

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.


Development

# 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 check

Tests

Unit 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.


Roadmap

  • Slack messaging provider
  • Interactive TUI setup wizard
  • Discord messaging provider
  • Kubernetes sandbox provider
  • ACP protocol support via acpx 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

License

MIT


Built with Rust, tokio, teloxide, bollard, and ratatui.

About

Spawn coding agents in the cloud directly from any messaging platform

Topics

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Anycode

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.

QuickstartHow It WorksCommandsConfigurationArchitectureContributing


Why?

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 or acpx
  • 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

Quickstart

Prerequisites

  • 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_KEY for Claude Code)

1. Clone and build

git clone https://github.com/manthan787/anycode.git
cd anycode
cargo build --release

2. Interactive setup (recommended)

The setup wizard walks you through configuring messaging platforms, sandbox providers, agent credentials, and builds everything automatically:

cargo run --bin anycode-setup

The 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.toml and run cargo build --release + docker build for you

2b. Manual setup (alternative)

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.toml

Edit 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-..." }

3. Run

./target/release/anycode --config config.toml

4. Use it

Open your bot in Telegram and send:

/task claude-code fix the login bug in org/repo

How It Works

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.


Commands

CommandDescription
/task [agent] <prompt>Start a coding task. Agent defaults to config if omitted.
/statusList active sessions with agent, status, and start time.
/cancel [id]Cancel a session. Omit ID to cancel the most recent.
/agentsList available agents and which is the default.
/helpShow 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.


Configuration

Tip: Run cargo run --bin anycode-setup to generate config.toml interactively instead of editing by hand.

# At least one messaging platform must be configured.
[telegram]
bot_token = "YOUR_BOT_TOKEN"# Requiredallowed_users = [] # Telegram user IDs (empty = allow all)
[slack]
app_token = "xapp-..."# App-level token with connections:write scopebot_token = "xoxb-..."# Bot token with chat:write, files:write scopesallowed_users = [] # Slack user IDs (empty = allow all)
[sandbox]
provider = "docker"# "docker" or "ecs"protocol = "opencode"# "opencode" (REST/SSE) or "acpx" (headless ACP)
[docker]
image = "anycode-sandbox:latest"# Sandbox container imageport_range_start = 12000# Host port range for containersport_range_end = 12100network = "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 = truecontainer_port = 2468startup_timeout_secs = 120poll_interval_ms = 1000region = "us-west-2"platform_version = "LATEST"container_name = "anycode-sandbox"# Optional; inferred from task def if emptylog_group = "/ecs/anycode"# Optional, for get_logslog_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 chattimeout_minutes = 30# Auto-cancel after this durationdebounce_ms = 500# Streaming output flush interval

Agent 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.

GitHub Authentication

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.

ECS Fargate Notes

  • Anycode launches one Fargate task per /task via RunTask.
  • It waits for task state RUNNING, resolves the ENI IP, then connects to the sandbox agent on ecs.container_port.
  • ANYCODE_AGENT, ANYCODE_REPO, and agent credentials are passed as ECS container environment overrides.
  • ecs.container_name is optional. If omitted, Anycode infers it from the ECS task definition.
  • get_logs uses CloudWatch when ecs.log_group is configured.

Architecture

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/ AgentClient trait + OpenCode/acpx implementations
│ │ ├── 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

Trait abstractions

The three core extension points are traits, making it straightforward to add new messaging platforms, infrastructure backends, or agent protocols:

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.

AgentClient — abstract agent communication (wait, create session, send message, subscribe events). Currently implemented for OpenCode (REST/SSE) and acpx (NDJSON via docker exec). Selected via sandbox.protocol.

Concurrency model

  • 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

Streaming output

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.

Resilience

  • 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

Sandbox Image

The default sandbox image (docker/Dockerfile.agent) is Ubuntu 24.04 with:

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.


Development

# 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 check

Tests

Unit 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.


Roadmap

  • Slack messaging provider
  • Interactive TUI setup wizard
  • Discord messaging provider
  • Kubernetes sandbox provider
  • ACP protocol support via acpx 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

License

MIT


Built with Rust, tokio, teloxide, bollard, and ratatui.

About

Spawn coding agents in the cloud directly from any messaging platform

Topics

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Anycode

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.

QuickstartHow It WorksCommandsConfigurationArchitectureContributing


Why?

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 or acpx
  • 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

Quickstart

Prerequisites

  • 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_KEY for Claude Code)

1. Clone and build

git clone https://github.com/manthan787/anycode.git
cd anycode
cargo build --release

2. Interactive setup (recommended)

The setup wizard walks you through configuring messaging platforms, sandbox providers, agent credentials, and builds everything automatically:

cargo run --bin anycode-setup

The 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.toml and run cargo build --release + docker build for you

2b. Manual setup (alternative)

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.toml

Edit 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-..." }

3. Run

./target/release/anycode --config config.toml

4. Use it

Open your bot in Telegram and send:

/task claude-code fix the login bug in org/repo

How It Works

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.


Commands

CommandDescription
/task [agent] <prompt>Start a coding task. Agent defaults to config if omitted.
/statusList active sessions with agent, status, and start time.
/cancel [id]Cancel a session. Omit ID to cancel the most recent.
/agentsList available agents and which is the default.
/helpShow 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.


Configuration

Tip: Run cargo run --bin anycode-setup to generate config.toml interactively instead of editing by hand.

# At least one messaging platform must be configured.
[telegram]
bot_token = "YOUR_BOT_TOKEN"# Requiredallowed_users = [] # Telegram user IDs (empty = allow all)
[slack]
app_token = "xapp-..."# App-level token with connections:write scopebot_token = "xoxb-..."# Bot token with chat:write, files:write scopesallowed_users = [] # Slack user IDs (empty = allow all)
[sandbox]
provider = "docker"# "docker" or "ecs"protocol = "opencode"# "opencode" (REST/SSE) or "acpx" (headless ACP)
[docker]
image = "anycode-sandbox:latest"# Sandbox container imageport_range_start = 12000# Host port range for containersport_range_end = 12100network = "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 = truecontainer_port = 2468startup_timeout_secs = 120poll_interval_ms = 1000region = "us-west-2"platform_version = "LATEST"container_name = "anycode-sandbox"# Optional; inferred from task def if emptylog_group = "/ecs/anycode"# Optional, for get_logslog_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 chattimeout_minutes = 30# Auto-cancel after this durationdebounce_ms = 500# Streaming output flush interval

Agent 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.

GitHub Authentication

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.

ECS Fargate Notes

  • Anycode launches one Fargate task per /task via RunTask.
  • It waits for task state RUNNING, resolves the ENI IP, then connects to the sandbox agent on ecs.container_port.
  • ANYCODE_AGENT, ANYCODE_REPO, and agent credentials are passed as ECS container environment overrides.
  • ecs.container_name is optional. If omitted, Anycode infers it from the ECS task definition.
  • get_logs uses CloudWatch when ecs.log_group is configured.

Architecture

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/ AgentClient trait + OpenCode/acpx implementations
│ │ ├── 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

Trait abstractions

The three core extension points are traits, making it straightforward to add new messaging platforms, infrastructure backends, or agent protocols:

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.

AgentClient — abstract agent communication (wait, create session, send message, subscribe events). Currently implemented for OpenCode (REST/SSE) and acpx (NDJSON via docker exec). Selected via sandbox.protocol.

Concurrency model

  • 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

Streaming output

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.

Resilience

  • 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

Sandbox Image

The default sandbox image (docker/Dockerfile.agent) is Ubuntu 24.04 with:

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.


Development

# 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 check

Tests

Unit 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.


Roadmap

  • Slack messaging provider
  • Interactive TUI setup wizard
  • Discord messaging provider
  • Kubernetes sandbox provider
  • ACP protocol support via acpx 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

License

MIT


Built with Rust, tokio, teloxide, bollard, and ratatui.

About

Spawn coding agents in the cloud directly from any messaging platform

Topics

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

Anycode

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.

QuickstartHow It WorksCommandsConfigurationArchitectureContributing


Why?

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 or acpx
  • 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

Quickstart

Prerequisites

  • 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_KEY for Claude Code)

1. Clone and build

git clone https://github.com/manthan787/anycode.git
cd anycode
cargo build --release

2. Interactive setup (recommended)

The setup wizard walks you through configuring messaging platforms, sandbox providers, agent credentials, and builds everything automatically:

cargo run --bin anycode-setup

The 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.toml and run cargo build --release + docker build for you

2b. Manual setup (alternative)

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.toml

Edit 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-..." }

3. Run

./target/release/anycode --config config.toml

4. Use it

Open your bot in Telegram and send:

/task claude-code fix the login bug in org/repo

How It Works

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.


Commands

CommandDescription
/task [agent] <prompt>Start a coding task. Agent defaults to config if omitted.
/statusList active sessions with agent, status, and start time.
/cancel [id]Cancel a session. Omit ID to cancel the most recent.
/agentsList available agents and which is the default.
/helpShow 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.


Configuration

Tip: Run cargo run --bin anycode-setup to generate config.toml interactively instead of editing by hand.

# At least one messaging platform must be configured.
[telegram]
bot_token = "YOUR_BOT_TOKEN"# Requiredallowed_users = [] # Telegram user IDs (empty = allow all)
[slack]
app_token = "xapp-..."# App-level token with connections:write scopebot_token = "xoxb-..."# Bot token with chat:write, files:write scopesallowed_users = [] # Slack user IDs (empty = allow all)
[sandbox]
provider = "docker"# "docker" or "ecs"protocol = "opencode"# "opencode" (REST/SSE) or "acpx" (headless ACP)
[docker]
image = "anycode-sandbox:latest"# Sandbox container imageport_range_start = 12000# Host port range for containersport_range_end = 12100network = "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 = truecontainer_port = 2468startup_timeout_secs = 120poll_interval_ms = 1000region = "us-west-2"platform_version = "LATEST"container_name = "anycode-sandbox"# Optional; inferred from task def if emptylog_group = "/ecs/anycode"# Optional, for get_logslog_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 chattimeout_minutes = 30# Auto-cancel after this durationdebounce_ms = 500# Streaming output flush interval

Agent 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.

GitHub Authentication

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.

ECS Fargate Notes

  • Anycode launches one Fargate task per /task via RunTask.
  • It waits for task state RUNNING, resolves the ENI IP, then connects to the sandbox agent on ecs.container_port.
  • ANYCODE_AGENT, ANYCODE_REPO, and agent credentials are passed as ECS container environment overrides.
  • ecs.container_name is optional. If omitted, Anycode infers it from the ECS task definition.
  • get_logs uses CloudWatch when ecs.log_group is configured.

Architecture

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/ AgentClient trait + OpenCode/acpx implementations
│ │ ├── 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

Trait abstractions

The three core extension points are traits, making it straightforward to add new messaging platforms, infrastructure backends, or agent protocols:

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.

AgentClient — abstract agent communication (wait, create session, send message, subscribe events). Currently implemented for OpenCode (REST/SSE) and acpx (NDJSON via docker exec). Selected via sandbox.protocol.

Concurrency model

  • 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

Streaming output

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.

Resilience

  • 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

Sandbox Image

The default sandbox image (docker/Dockerfile.agent) is Ubuntu 24.04 with:

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.


Development

# 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 check

Tests

Unit 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.


Roadmap

  • Slack messaging provider
  • Interactive TUI setup wizard
  • Discord messaging provider
  • Kubernetes sandbox provider
  • ACP protocol support via acpx 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

License

MIT


Built with Rust, tokio, teloxide, bollard, and ratatui.

About

Spawn coding agents in the cloud directly from any messaging platform

Topics

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Anycode

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.

QuickstartHow It WorksCommandsConfigurationArchitectureContributing


Why?

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 or acpx
  • 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

Quickstart

Prerequisites

  • 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_KEY for Claude Code)

1. Clone and build

git clone https://github.com/manthan787/anycode.git
cd anycode
cargo build --release

2. Interactive setup (recommended)

The setup wizard walks you through configuring messaging platforms, sandbox providers, agent credentials, and builds everything automatically:

cargo run --bin anycode-setup

The 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.toml and run cargo build --release + docker build for you

2b. Manual setup (alternative)

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.toml

Edit 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-..." }

3. Run

./target/release/anycode --config config.toml

4. Use it

Open your bot in Telegram and send:

/task claude-code fix the login bug in org/repo

How It Works

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.


Commands

CommandDescription
/task [agent] <prompt>Start a coding task. Agent defaults to config if omitted.
/statusList active sessions with agent, status, and start time.
/cancel [id]Cancel a session. Omit ID to cancel the most recent.
/agentsList available agents and which is the default.
/helpShow 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.


Configuration

Tip: Run cargo run --bin anycode-setup to generate config.toml interactively instead of editing by hand.

# At least one messaging platform must be configured.
[telegram]
bot_token = "YOUR_BOT_TOKEN"# Requiredallowed_users = [] # Telegram user IDs (empty = allow all)
[slack]
app_token = "xapp-..."# App-level token with connections:write scopebot_token = "xoxb-..."# Bot token with chat:write, files:write scopesallowed_users = [] # Slack user IDs (empty = allow all)
[sandbox]
provider = "docker"# "docker" or "ecs"protocol = "opencode"# "opencode" (REST/SSE) or "acpx" (headless ACP)
[docker]
image = "anycode-sandbox:latest"# Sandbox container imageport_range_start = 12000# Host port range for containersport_range_end = 12100network = "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 = truecontainer_port = 2468startup_timeout_secs = 120poll_interval_ms = 1000region = "us-west-2"platform_version = "LATEST"container_name = "anycode-sandbox"# Optional; inferred from task def if emptylog_group = "/ecs/anycode"# Optional, for get_logslog_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 chattimeout_minutes = 30# Auto-cancel after this durationdebounce_ms = 500# Streaming output flush interval

Agent 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.

GitHub Authentication

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.

ECS Fargate Notes

  • Anycode launches one Fargate task per /task via RunTask.
  • It waits for task state RUNNING, resolves the ENI IP, then connects to the sandbox agent on ecs.container_port.
  • ANYCODE_AGENT, ANYCODE_REPO, and agent credentials are passed as ECS container environment overrides.
  • ecs.container_name is optional. If omitted, Anycode infers it from the ECS task definition.
  • get_logs uses CloudWatch when ecs.log_group is configured.

Architecture

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/ AgentClient trait + OpenCode/acpx implementations
│ │ ├── 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

Trait abstractions

The three core extension points are traits, making it straightforward to add new messaging platforms, infrastructure backends, or agent protocols:

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.

AgentClient — abstract agent communication (wait, create session, send message, subscribe events). Currently implemented for OpenCode (REST/SSE) and acpx (NDJSON via docker exec). Selected via sandbox.protocol.

Concurrency model

  • 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

Streaming output

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.

Resilience

  • 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

Sandbox Image

The default sandbox image (docker/Dockerfile.agent) is Ubuntu 24.04 with:

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.


Development

# 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 check

Tests

Unit 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.


Roadmap

  • Slack messaging provider
  • Interactive TUI setup wizard
  • Discord messaging provider
  • Kubernetes sandbox provider
  • ACP protocol support via acpx 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

License

MIT


Built with Rust, tokio, teloxide, bollard, and ratatui.

About

Spawn coding agents in the cloud directly from any messaging platform

Topics

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Anycode

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.

QuickstartHow It WorksCommandsConfigurationArchitectureContributing


Why?

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 or acpx
  • 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

Quickstart

Prerequisites

  • 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_KEY for Claude Code)

1. Clone and build

git clone https://github.com/manthan787/anycode.git
cd anycode
cargo build --release

2. Interactive setup (recommended)

The setup wizard walks you through configuring messaging platforms, sandbox providers, agent credentials, and builds everything automatically:

cargo run --bin anycode-setup

The 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.toml and run cargo build --release + docker build for you

2b. Manual setup (alternative)

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.toml

Edit 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-..." }

3. Run

./target/release/anycode --config config.toml

4. Use it

Open your bot in Telegram and send:

/task claude-code fix the login bug in org/repo

How It Works

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.


Commands

CommandDescription
/task [agent] <prompt>Start a coding task. Agent defaults to config if omitted.
/statusList active sessions with agent, status, and start time.
/cancel [id]Cancel a session. Omit ID to cancel the most recent.
/agentsList available agents and which is the default.
/helpShow 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.


Configuration

Tip: Run cargo run --bin anycode-setup to generate config.toml interactively instead of editing by hand.

# At least one messaging platform must be configured.
[telegram]
bot_token = "YOUR_BOT_TOKEN"# Requiredallowed_users = [] # Telegram user IDs (empty = allow all)
[slack]
app_token = "xapp-..."# App-level token with connections:write scopebot_token = "xoxb-..."# Bot token with chat:write, files:write scopesallowed_users = [] # Slack user IDs (empty = allow all)
[sandbox]
provider = "docker"# "docker" or "ecs"protocol = "opencode"# "opencode" (REST/SSE) or "acpx" (headless ACP)
[docker]
image = "anycode-sandbox:latest"# Sandbox container imageport_range_start = 12000# Host port range for containersport_range_end = 12100network = "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 = truecontainer_port = 2468startup_timeout_secs = 120poll_interval_ms = 1000region = "us-west-2"platform_version = "LATEST"container_name = "anycode-sandbox"# Optional; inferred from task def if emptylog_group = "/ecs/anycode"# Optional, for get_logslog_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 chattimeout_minutes = 30# Auto-cancel after this durationdebounce_ms = 500# Streaming output flush interval

Agent 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.

GitHub Authentication

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.

ECS Fargate Notes

  • Anycode launches one Fargate task per /task via RunTask.
  • It waits for task state RUNNING, resolves the ENI IP, then connects to the sandbox agent on ecs.container_port.
  • ANYCODE_AGENT, ANYCODE_REPO, and agent credentials are passed as ECS container environment overrides.
  • ecs.container_name is optional. If omitted, Anycode infers it from the ECS task definition.
  • get_logs uses CloudWatch when ecs.log_group is configured.

Architecture

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/ AgentClient trait + OpenCode/acpx implementations
│ │ ├── 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

Trait abstractions

The three core extension points are traits, making it straightforward to add new messaging platforms, infrastructure backends, or agent protocols:

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.

AgentClient — abstract agent communication (wait, create session, send message, subscribe events). Currently implemented for OpenCode (REST/SSE) and acpx (NDJSON via docker exec). Selected via sandbox.protocol.

Concurrency model

  • 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

Streaming output

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.

Resilience

  • 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

Sandbox Image

The default sandbox image (docker/Dockerfile.agent) is Ubuntu 24.04 with:

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.


Development

# 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 check

Tests

Unit 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.


Roadmap

  • Slack messaging provider
  • Interactive TUI setup wizard
  • Discord messaging provider
  • Kubernetes sandbox provider
  • ACP protocol support via acpx 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

License

MIT


Built with Rust, tokio, teloxide, bollard, and ratatui.

About

Spawn coding agents in the cloud directly from any messaging platform

Topics

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

Anycode

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.

QuickstartHow It WorksCommandsConfigurationArchitectureContributing


Why?

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 or acpx
  • 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

Quickstart

Prerequisites

  • 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_KEY for Claude Code)

1. Clone and build

git clone https://github.com/manthan787/anycode.git
cd anycode
cargo build --release

2. Interactive setup (recommended)

The setup wizard walks you through configuring messaging platforms, sandbox providers, agent credentials, and builds everything automatically:

cargo run --bin anycode-setup

The 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.toml and run cargo build --release + docker build for you

2b. Manual setup (alternative)

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.toml

Edit 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-..." }

3. Run

./target/release/anycode --config config.toml

4. Use it

Open your bot in Telegram and send:

/task claude-code fix the login bug in org/repo

How It Works

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.


Commands

CommandDescription
/task [agent] <prompt>Start a coding task. Agent defaults to config if omitted.
/statusList active sessions with agent, status, and start time.
/cancel [id]Cancel a session. Omit ID to cancel the most recent.
/agentsList available agents and which is the default.
/helpShow 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.


Configuration

Tip: Run cargo run --bin anycode-setup to generate config.toml interactively instead of editing by hand.

# At least one messaging platform must be configured.
[telegram]
bot_token = "YOUR_BOT_TOKEN"# Requiredallowed_users = [] # Telegram user IDs (empty = allow all)
[slack]
app_token = "xapp-..."# App-level token with connections:write scopebot_token = "xoxb-..."# Bot token with chat:write, files:write scopesallowed_users = [] # Slack user IDs (empty = allow all)
[sandbox]
provider = "docker"# "docker" or "ecs"protocol = "opencode"# "opencode" (REST/SSE) or "acpx" (headless ACP)
[docker]
image = "anycode-sandbox:latest"# Sandbox container imageport_range_start = 12000# Host port range for containersport_range_end = 12100network = "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 = truecontainer_port = 2468startup_timeout_secs = 120poll_interval_ms = 1000region = "us-west-2"platform_version = "LATEST"container_name = "anycode-sandbox"# Optional; inferred from task def if emptylog_group = "/ecs/anycode"# Optional, for get_logslog_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 chattimeout_minutes = 30# Auto-cancel after this durationdebounce_ms = 500# Streaming output flush interval

Agent 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.

GitHub Authentication

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.

ECS Fargate Notes

  • Anycode launches one Fargate task per /task via RunTask.
  • It waits for task state RUNNING, resolves the ENI IP, then connects to the sandbox agent on ecs.container_port.
  • ANYCODE_AGENT, ANYCODE_REPO, and agent credentials are passed as ECS container environment overrides.
  • ecs.container_name is optional. If omitted, Anycode infers it from the ECS task definition.
  • get_logs uses CloudWatch when ecs.log_group is configured.

Architecture

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/ AgentClient trait + OpenCode/acpx implementations
│ │ ├── 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

Trait abstractions

The three core extension points are traits, making it straightforward to add new messaging platforms, infrastructure backends, or agent protocols:

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.

AgentClient — abstract agent communication (wait, create session, send message, subscribe events). Currently implemented for OpenCode (REST/SSE) and acpx (NDJSON via docker exec). Selected via sandbox.protocol.

Concurrency model

  • 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

Streaming output

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.

Resilience

  • 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

Sandbox Image

The default sandbox image (docker/Dockerfile.agent) is Ubuntu 24.04 with:

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.


Development

# 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 check

Tests

Unit 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.


Roadmap

  • Slack messaging provider
  • Interactive TUI setup wizard
  • Discord messaging provider
  • Kubernetes sandbox provider
  • ACP protocol support via acpx 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

License

MIT


Built with Rust, tokio, teloxide, bollard, and ratatui.

About

Spawn coding agents in the cloud directly from any messaging platform

Topics

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages