# ๐ Setting Up Codex Agent: Self-Hosted AI Coding Agent (App-Server + Telegram Bot)
**Date:** July 16, 2026
**Tags:** AI, Codex, Coding Agent, Self-Hosted, Telegram, 9Router, MCP, AI Agents
---
## ๐ฏ Overview
[Codex](https://github.com/openai/codex) (the OpenAI **Codex CLI**) is a terminal-native **AI coding agent** with a strong non-interactive mode (`codex exec`), a built-in **app-server** (WebSocket) for remote/UI clients, and a companion **Telegram bot**. On this VM it's the *exec-and-automate* coder โ great for one-shot `exec` runs, code reviews, and chat-driven coding via Telegram.
Like the rest of the stack, Codex rides **9Router** (primary) with an **OpenRouter** fallback for free inference, and speaks **MCP** so we wire in **GBrain** (shared memory).
### Why Codex?
| Feature | Benefit |
|---------|---------|
| **โก `exec` mode** | Non-interactive one-shot coding/patches |
| **๐ `review`** | Automated code review passes |
| **๐ฐ๏ธ app-server** | WebSocket server for remote/UI clients |
| **๐ก Telegram Bot** | Code from chat |
| **๐ MCP Ready** | GBrain + any MCP server |
| **๐ก๏ธ Sandbox** | Scoped `writable_roots` + network policy |
---
## ๐๏ธ Architecture Overview
```
Telegram โโโบ codex-telegram-bot (Python) โโโ
โ โ
UI / CLI โโโบ codex (interactive/TUI) โโค โผ
โ โโโโบ codex app-server (ws://127.0.0.1:8765)
โ โ โ
โ โ โผ
โ โ 9Router / OpenRouter (free models)
โ โ โ
โโโ MCP โโโโโโโโโโโดโโโบ GBrain (shared memory / Postgres)
```
---
## ๐ Prerequisites
- Linux VM with Node toolchain (Codex ships as a Node package).
- A model backend โ **9Router** (see the [9Router guide](/blog/9router-setup/)) and/or OpenRouter.
- (Optional) GBrain MCP server for shared memory.
- A Telegram bot token from [@BotFather](https://t.me/BotFather) (placeholder below).
- Non-root user (`ryan` in examples).
> โ ๏ธ **Security note:** Every secret below is a ``. Never commit real keys.
---
## ๐ฆ Step 1 โ Install Codex
```bash
# Install Codex globally
npm install -g @openai/codex
# On this VM the binary resolves here:
# /home/ryan/.hermes/node/bin/codex ->
# ../lib/node_modules/@openai/codex/bin/codex.js
# A convenience symlink also lives at /home/ryan/.local/bin/codex
# Verify
which codex
codex --help
# Diagnose install/config/auth health
codex doctor
```
---
## โ๏ธ Step 2 โ Model Backend (9Router + OpenRouter)
Codex reads config from `~/.codex/config.toml`. The model key is fetched at runtime by a small helper that reads from the env file:
```python
# ~/.codex/get-9router-key.py (sanitized concept)
# Reads NINE_ROUTER_API_KEY from ~/.hermes/.env and prints it.
from pathlib import Path
for line in Path('/home/ryan/.hermes/.env').read_text().splitlines():
if line.startswith('NINE_ROUTER_API_KEY='):
print(line.split('=', 1)[1].strip()) # use as
raise SystemExit(0)
raise SystemExit('NINE_ROUTER_API_KEY not found')
```
Point Codex's provider at `http://192.168.51.115:20128/v1` (9Router) with `api_key: ""`, and keep an OpenRouter key (``) as fallback. The `config.toml` also carries sandbox + MCP settings:
```toml
# ~/.codex/config.toml (sanitized)
[mcp_servers.gbrain]
command = "/home/ryan/.bun/bin/gbrain"
args = ["serve"]
[mcp_servers.gbrain.env]
GBRAIN_DATABASE_URL = ""
GBRAIN_DIRECT_DATABASE_URL = ""
PATH = "/home/ryan/.bun/bin:/home/ryan/.local/bin:/usr/local/bin:/usr/bin:/bin"
[projects."/home/ryan/.codex"]
trust_level = "trusted"
[tui.model_availability_nux]
"gpt-5.6-sol" = 1
[sandbox_workspace_write]
writable_roots = ["/home/ryan"]
network_access = true
```
---
## ๐งฉ Step 3 โ Wire GBrain via MCP
Codex supports MCP servers (same GBrain server as the other agents):
```toml
[mcp_servers.gbrain]
command = "/home/ryan/.bun/bin/gbrain"
args = ["serve"]
[mcp_servers.gbrain.env]
GBRAIN_DATABASE_URL = ""
GBRAIN_DIRECT_DATABASE_URL = ""
```
Verify before relying on it:
```bash
/home/ryan/.bun/bin/gbrain get_health
# -> healthy
```
You can also manage servers at runtime:
```bash
codex mcp list
codex mcp add gbrain -- /home/ryan/.bun/bin/gbrain serve
```
---
## ๐ Step 4 โ App-Server Mode (WebSocket)
Codex exposes an **app-server** over WebSocket so remote/UI clients can drive it:
```bash
# Start the app-server on a local WebSocket port
codex app-server --listen ws://127.0.0.1:8765
# On this VM this runs persistently (seen via):
pgrep -af 'codex app-server'
```
> ๐ก Bind to `127.0.0.1` only; put it behind a reverse proxy if external access is needed.
---
## ๐ค Step 5 โ Telegram Bot
Codex has a companion **Telegram bot** (Python) that bridges chat โ coding sessions. It runs from a dedicated venv.
```ini
# ~/.config/systemd/user/codex-telegram-bot.service
[Unit]
Description=Codex Telegram Bot
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
WorkingDirectory=/home/ryan/telegram-codex
ExecStart=/home/ryan/telegram-codex/venv/bin/python /home/ryan/telegram-codex/bot.py
Restart=always
RestartSec=5
StandardOutput=journal
StandardError=journal
Environment=PATH=/home/ryan/telegram-codex/venv/bin:/usr/local/bin:/usr/bin:/bin
Environment=TELEGRAM_BOT_TOKEN=
Environment=NINEROUTER_API_KEY=
[Install]
WantedBy=default.target
```
```bash
systemctl --user daemon-reload
systemctl --user enable --now codex-telegram-bot.service
systemctl --user is-active codex-telegram-bot.service
# -> active
```
> ๐ The bot token is a `` โ supply it via env, never commit.
---
## ๐งช Step 6 โ Usage Examples
**One-shot exec (non-interactive):**
```bash
codex exec -- "Add input validation to src/validate.py and run the tests"
```
**Code review:**
```bash
codex review
```
**Resume a session:**
```bash
codex resume --last
```
**Apply the last diff:**
```bash
codex apply
```
---
## ๐ฉบ Step 7 โ Smoke Test
```bash
# 1. Codex binary resolves
which codex
# 2. app-server listening
pgrep -af 'codex app-server'
# 3. 9Router reachable with key (placeholder)
curl -s --max-time 8 -H "Authorization: Bearer " \
http://192.168.51.115:20128/v1/models | head -c 200
# 4. Telegram bot service active
systemctl --user is-active codex-telegram-bot.service
# 5. GBrain MCP healthy
/home/ryan/.bun/bin/gbrain get_health
# 6. Doctor check
codex doctor
```
---
## ๐ฉบ Troubleshooting
| Symptom | Likely cause | Fix |
|---------|-------------|-----|
| `codex: command not found` | Not on PATH | Use `/home/ryan/.hermes/node/bin/codex` or `npm link` |
| Model calls 401 | Wrong/expired key | Re-export `` / `` |
| Bot silent | Bad `TELEGRAM_BOT_TOKEN` | Re-paste placeholder, restart service |
| MCP tools missing | gbrain down / bad env | `gbrain get_health`, fix `` |
| Sandbox write denied | `writable_roots` too narrow | Widen `[sandbox_workspace_write]` in `config.toml` |
---
## ๐ Security Checklist
- [ ] `TELEGRAM_BOT_TOKEN`, `NINEROUTER_API_KEY`, `OPENROUTER_API_KEY` are ``s, env-injected, never committed.
- [ ] GBrain Postgres URLs are placeholders; DB user is least-privilege.
- [ ] `codex app-server` binds `127.0.0.1` (or behind a proxy), not `0.0.0.0` unprotected.
- [ ] `[sandbox_workspace_write].writable_roots` scoped to `/home/ryan`.
- [ ] `network_access` deliberately set (here `true` for package installs during coding).
---
## โ
Summary
You now have the **Codex Agent** wired into your AI stack:
- Installed as a global Node CLI (`~/.hermes/node/bin/codex`).
- Runs an **app-server** over WebSocket (`ws://127.0.0.1:8765`) for remote/UI clients.
- Bridges to **Telegram** through `codex-telegram-bot` (managed systemd service, Python venv).
- Routes models through **9Router** (primary) with **OpenRouter** fallback โ both free.
- Connected to **GBrain** via MCP for shared memory across all agents.
- Sandboxed with scoped `writable_roots` + network policy.
Codex joins [Pi](/blog/pi-coding-agent-setup/) and [OpenCode](/blog/opencode-agent-setup/) as a coding agent โ all behind [OpenClaw](/blog/openclaw-setup/) (gateway), orchestrated with [Hermes](/blog/hermes-setup/), leaning on [9Router](/blog/9router-setup/) for inference. ๐
---
*No API keys, tokens, or secrets are included in this guide โ every value is a `` you supply locally. Built with Astro, GitHub, and Cloudflare Pages.*