White-label news curation agent for Leviathan News — a decentralized crypto/DeFi news platform where contributors earn $SQUID tokens. Fork it, set AGENT_NAME, and run your own instance.
The agent monitors Telegram news channels, evaluates newsworthiness via a provider chain (Codex primary, Claude fallback by default — swappable via PROVIDER_ORDER), crafts headlines, submits articles, votes, comments, and replies — all autonomously in a continuous loop.
Phase 1 Phase 2 Phase 3 Phase 4 Phase 5
─────── ─────── ─────── ─────── ───────
Telegram → Evaluate & → Post to → Vote & → Reply to
Channels Deduplicate LN API Comment Responses
│ │ │ │ │
├─ Fetch new ├─ Batch LLM ├─ Resolve ├─ Eval quality ├─ Scan old
│ messages │ scoring │ primary URL ├─ Upvote/down │ comments
├─ Cursor- ├─ Story-level ├─ Check dupes ├─ Write ├─ Detect
│ based │ dedup │ (DB+Bot HQ) │ analysis │ unreplied
│ pagination ├─ Twitter/X ├─ Craft ├─ Vote on ├─ Craft
└─ Cache IDs │ verification │ headline │ others' yaps │ replies
└─ JSON output ├─ Submit └─ Self-upvote └─ Sentinel
├─ Self-upvote verify
└─ TL;DR comment
Two additions run alongside the classic 5-phase cycle: a flag-gated Phase 6
market match (ENABLE_MARKET_MATCH, default off) that attaches/proposes
prediction markets for approved articles, and a live-news WebSocket listener
(ENABLE_WS_EVENTS, default on) that queues news.approved events so a
throttled between-cycle mini-pass votes/comments minutes after approval
instead of up to an hour later.
- Provider-agnostic LLM dispatch (
providers.py): Codex / Claude / OpenCode interchangeably.PROVIDER_ORDERenv picks the primary; the rest are fallbacks. Each provider has its own circuit breaker — failures in one don't penalize the others. - Semantic tier abstraction: caller code says
tier="classification"for cheap/fast calls; each provider maps that to its own model+effort (Claude→sonnet/low, Codex→same model at low effort). No Claude-specific model names leak into provider-neutral call sites. - 6-layer prompt injection defense: input sanitization, XML boundary tags, output injection detection, NFKD Unicode normalization, URL validation, independent sentinel verification on replies
- Wallet-based auth: EIP-191 signature flow with thread-safe session management (30-min refresh, RLock)
- Headline validation: 10 automated checks (character count, sentence case, passive voice, articles, etc.) via bundled bash validator
- Story deduplication: four layers — local DB URL check, self-dedup word-overlap against own last-24h posts, provenance-API short-circuit (positive matches trusted, absence falls through), deterministic Bot HQ fetch + classification-tier semantic match
- Anti-AI-detection: banned phrase filtering to avoid patterns that get content deprioritized
- Cursor-based Telegram pagination: with numeric ID caching to avoid flood waits
- Evidence-grounded replies (
reply_grounding.py): the chat bot composes public replies from typed evidence bundles (focal URLs, bounded research, media observations), verifies every claim against evidence, repairs once, and re-verifies — no failure path may substitute stale context for missing evidence - Runtime-mediated sandbox: chat models emit a
[SANDBOX]code block; trusted bot Python intent-gates and executes it in a locked-down Docker container (bounded output, credential-free env), then a tools-disabled pass synthesizes the answer - Breaking-news reactions: the bot drains the WS event queue and pings the group about genuinely notable stories (hard rate/freshness gates + classification notability gate before any creative call)
- Codex lockdown policy (
codex-policy/): permission profiles + execpolicy rules that deny every~/.claudesecret inside the Codex sandbox while allowlisted wrapper scripts run outside it - Optional async build daemon (
benthic-builder.py): operators queue project briefs; a goal-driven loop overcodex app-server(exec fallback) builds, review-gates, and pushes them as public GitHub repos underBUILD_GITHUB_ORG. Hard-capped, isolated per-task workdirs. Operator CLI:bin/benthic-build.
- Twitter/X script not included: The agent uses a Twitter/X fetch script for research context, but the bundled implementation uses cookie-based access to X's internal API which raises ToS concerns. You must provide your own
scripts/twitter_fetch.pyor setTWITTER_FETCH_SCRIPTto an alternative. See Twitter/X Integration below. - No dotenv auto-loading: The agent reads
os.environdirectly. You must export environment variables manually or configure them via your process manager (see .env.example).
Installed via pip install -r requirements.txt:
- telethon — Telegram client
- requests — HTTP client
- eth-account — Ethereum wallet signing
- Pillow — image processing (media analysis in chat bot)
- websockets — live-news WS listener
You need at least one LLM provider. Configure the priority via PROVIDER_ORDER:
- Codex CLI (
codex) — primary by default. Auto-detected or setCODEX_BIN. Default model:gpt-5.6-solatxhighreasoning effort. - Claude CLI (
claude) — fallback by default. Must be on$PATHor setCLAUDE_BIN. - OpenCode CLI (optional) — additional fallback. Set
OPENCODE_BINandOPENCODE_MODELto enable. - Telegram API credentials —
api_idandapi_hashfrom my.telegram.org - Ethereum wallet — private key for LN API authentication (EIP-191 signing)
- Leviathan News account — wallet must be registered at leviathannews.xyz
- Twitter/X access (optional) — your own script for Twitter research context (see Twitter/X Integration)
# 1. Clone
git clone https://github.com/leviathan-news/be-benthic.git
cd be-benthic
# 2. Create virtual environment
python3 -m venv .venv
source .venv/bin/activate
# 3. Install dependencies
pip install -r requirements.txt
# 4. Set up Telegram credentials
# Create a JSON file with your Telegram API credentials:
cat > ~/.claude/telegram-creds.json << 'EOF'
{
"api_id": 12345678,
"api_hash": "your_api_hash_here"
}
EOF
# 5. Set up wallet key
echo "your_private_key_hex" > ~/.claude/.ln-wallet-key
# 6. Export required environment variables
export BOT_HQ_GROUP_ID="-100..." # Your Bot HQ Telegram group ID
export CHANNELS='["@examplechannel", "@anotherchannel"]' # Your Telegram news channels
# 7. Run (single cycle for testing)
python3 ln-agent.py
# Or run via PM2 for continuous operation (see Deployment section)On first run, Telethon will prompt for your Telegram phone number and verification code to create a session file.
The agent reads all config from os.environ. There is no dotenv auto-loading — export variables before running or configure them in your process manager.
See .env.example for a complete template with export lines you can source directly.
| Variable | Default | Description |
|---|---|---|
LN_API |
https://api.leviathannews.xyz/api/v1 |
Leviathan News API base URL |
WALLET_PRIVATE_KEY |
— | ETH wallet private key (takes priority over file) |
WALLET_KEY_FILE |
~/.claude/.ln-wallet-key |
Fallback: read wallet key from this file |
BOT_HQ_GROUP_ID |
— (skip HQ dedup) | Telegram group ID used as ground truth for duplicate detection |
CLAUDE_BIN |
auto-detected | Path to Claude CLI binary |
CODEX_BIN |
auto-detected | Path to Codex CLI binary |
CODEX_MODEL |
gpt-5.6-sol |
Model for Codex |
CODEX_EFFORT |
xhigh |
Codex reasoning effort: low / medium / high / xhigh |
OPENCODE_BIN |
auto-detected | Path to OpenCode CLI binary |
OPENCODE_MODEL |
— (disabled) | OpenCode model (e.g. anthropic/claude-sonnet-4-5). Required to enable. |
PROVIDER_ORDER |
codex,claude |
Comma-separated provider priority (first available is primary). Add opencode explicitly (with OPENCODE_MODEL) to enable OpenCode. |
CLAUDE_LIMIT_COOLDOWN |
21600 (6h) |
Seconds to skip Claude after quota/rate-limit error |
CHANNELS |
[] |
JSON array of Telegram channels, e.g. '["@chan1","@chan2"]' |
PRIVATE_CHANNELS |
[] |
JSON array of private channel display names |
HEADLINE_BOT_USER_ID |
0 (any) |
Optional Telegram user ID of the headline bot. When set, Bot HQ dedup filters to messages from that user only. |
CYCLE_INTERVAL |
3600 (1h) |
Seconds between cycles |
INITIAL_LOOKBACK_HOURS |
1 |
Hours to look back on first run |
TELEGRAM_CLIENT_SCRIPT |
skills/telegram-explorer/scripts/telegram_client.py |
Path to Telegram CLI wrapper |
TELEGRAM_CLIENT_PYTHON |
.venv/bin/python3 |
Python interpreter for Telegram script |
TWITTER_FETCH_SCRIPT |
scripts/twitter_fetch.py |
Path to Twitter/X script (not bundled) |
HQ_DEDUP_HOURS / HQ_DEDUP_FETCH_LIMIT |
168 / 300 |
Bot HQ duplicate-check window and fetch size |
ENABLE_PROVENANCE_DEDUP |
1 |
Provenance-API dedup short-circuit before the HQ check |
BLOCKED_SOURCE_DOMAINS / EXTRA_BLOCKED_SOURCE_DOMAINS |
built-in list | Replace / append the primary-source domain blocklist |
AUTO_UPVOTE_USERS / AUTO_DOWNVOTE_USERS |
— | Comma-separated LN usernames auto-voted without LLM evaluation |
ENABLE_WS_EVENTS / ENABLE_WS_MINI_PASS |
1 / 1 |
Live-news WS listener + between-cycle mini-pass |
ENABLE_MARKET_MATCH |
0 |
Flag-gated market-match phase |
CYCLE_DEADLINE_SECONDS |
3300 |
Watchdog deadline for one full cycle |
Telegram user-session credentials live at fixed paths: ~/.claude/telegram-creds.json and ~/.claude/agent_session.session. Advanced tuning knobs (WS backoff, mini-pass caps, market-match confidence, grounding limits) are documented in .env.example.
The skills/leviathan-headlines/ directory is a Claude Code plugin for manual headline crafting and review. It includes:
- SKILL.md — 11 non-negotiable editorial rules, writing workflow, tone guide
- validate-headline.sh — automated 10-check validator
- Reference docs — style guide, examples, terminology
# Validate a headline
./skills/leviathan-headlines/scripts/validate-headline.sh "Uniswap v4 hooks unlock custom pool logic, opening AMM design to third-party developers"A Telethon-based CLI wrapper is bundled at skills/telegram-explorer/scripts/telegram_client.py. It provides JSON-output commands for all Telegram operations the agent needs:
# List recent messages from a channel
.venv/bin/python3 skills/telegram-explorer/scripts/telegram_client.py messages -1001234567890 --limit 5
# Search messages
.venv/bin/python3 skills/telegram-explorer/scripts/telegram_client.py messages -1001234567890 --search "Ethereum"
# Send a message
.venv/bin/python3 skills/telegram-explorer/scripts/telegram_client.py send -1001234567890 --text "Hello"Available commands: messages, send, reply, forward, edit, delete, react, info, participants, dialogs, topics, search-global, buttons, click, pinned, download, upload.
Requires Telegram API credentials in ~/.claude/telegram-creds.json (see Quick Start).
The agent uses a Twitter/X script during Phases 2-3 to search for context, verify stories, and find primary source tweets. This script is not bundled because common approaches (cookie-based access to X's internal GraphQL API) raise Terms of Service concerns.
To enable Twitter research, provide your own scripts/twitter_fetch.py or set TWITTER_FETCH_SCRIPT to point to an alternative. Your script should support:
# Search for recent tweets
your_script.py search --query "Ethereum ETF" --limit 5
# Fetch a user's recent tweets
your_script.py user --username "VitalikButerin" --limit 10Output should be JSON with at minimum: tweet text, author handle, tweet URL, and timestamp.
Options for implementation:
- X API v2 (official) — requires a developer account and API key. Most compliant approach.
- Nitter instances — open-source Twitter frontend with RSS feeds. Availability varies.
- Skip entirely — a no-op stub is bundled at
scripts/twitter_fetch.pythat returns[]. The agent works without Twitter; it just won't have X context for headline crafting and story verification.
The agent processes untrusted input (Telegram messages, user comments) and produces public-facing output. Six defense layers prevent prompt injection:
sanitize_untrusted()— strips control characters, replaces<>with fullwidth equivalents (prevents XML boundary injection), collapses separator patterns, truncates<user_content>tags — all untrusted text wrapped with explicit "treat as DATA" warnings in every LLM promptcheck_output_for_injection()— validates output for signs of successful injection (secret leakage, "ignore previous instructions", AI self-identification)- NFKD Unicode normalization — defeats homoglyph bypass attempts in injection detection
validate_url()— rejects control characters, non-HTTP schemes, oversized URLs in LLM-returned content- Sentinel verification — independent Sonnet-based semantic check on reply output before posting (different model for cross-verification)
Fail-safe defaults: freshness checks fail open (article already passed evaluation), duplicate checks fail closed (unknown = reject).
Agent state lives in agent.db (SQLite, WAL mode). Tables: channel_cursors, channel_ids, evaluated_messages, posted_articles, commented_articles, voted_articles, voted_yaps, replied_yaps, runs, chat_history, notes, own_actions, knowledge, market_decisions, ws_events, build_tasks, reply_grounding_traces, seen_photos, seen_documents.
# Inspect counts
.venv/bin/python3 -c "
import sqlite3
conn = sqlite3.connect('agent.db')
for t in ['evaluated_messages', 'posted_articles', 'commented_articles',
'voted_articles', 'voted_yaps', 'replied_yaps', 'runs', 'channel_ids',
'chat_history', 'notes', 'own_actions']:
c = conn.execute(f'SELECT COUNT(*) FROM {t}').fetchone()[0]
print(f'{t}: {c}')
conn.close()
"For continuous operation, use PM2 with the included config:
# Start
pm2 start ecosystem.config.js
# Monitor
pm2 logs ln-agent
pm2 monit
# Restart after config changes
pm2 restart ln-agentThe agent runs in a continuous loop — PM2's autorestart handles crashes, and the internal loop sleeps CYCLE_INTERVAL seconds between cycles. No cron_restart is used to avoid killing long-running cycles mid-work.
be-benthic/
├── ln-agent.py # Automated news agent (phase loop + WS listener)
├── benthic-bot.py # Telegram chat bot (shared brain + memory)
├── benthic-builder.py # Optional Codex-powered build daemon
├── reply_grounding.py # Evidence-grounding contracts for bot replies
├── appserver_client.py # JSON-RPC client for `codex app-server`
├── providers.py # Provider-agnostic LLM dispatch (Claude/Codex/OpenCode + chain)
├── prompt_loader.py # Shared prompt template loader
├── github_client.sh # Write-only GitHub client wrapper
├── bin/
│ └── benthic-build # Operator CLI for the build queue
├── codex-policy/ # Codex permission profiles + execpolicy rules
├── sandbox/ # Docker sandbox (image, wrapper, network allowlist)
├── prompts/ # External prompt templates with {placeholders}
│ ├── agent/ # News agent prompts
│ ├── bot/ # Chat bot prompts + knowledge topics
│ └── _shared/ # Shared blocks (anti-slop ruleset)
├── scripts/
│ ├── twitter_fetch.py # No-op stub (replace with your own)
│ └── eval_reply_grounding.py # Grounding-pipeline eval harness
├── skills/
│ ├── telegram-explorer/
│ │ ├── SKILL.md # Telegram skill definition
│ │ ├── scripts/
│ │ │ └── telegram_client.py # Telethon CLI wrapper (JSON output)
│ │ └── references/
│ │ └── api-reference.md # Full subcommand reference
│ └── leviathan-headlines/
│ ├── SKILL.md # Headline crafting skill definition
│ ├── references/
│ │ ├── style-guide.md
│ │ ├── examples.md
│ │ └── ethereum-terminology.md
│ └── scripts/
│ └── validate-headline.sh
├── tests/ # pytest test suite (includes provider chain unit tests)
├── ecosystem.config.js # PM2 deployment config
├── requirements.txt # Python dependencies
├── requirements-dev.txt # Dev dependencies (pytest)
├── .env.example # Environment variable template
├── CLAUDE.md # Claude Code context (architecture, conventions)
├── AGENTS.md # Agent operational state notes
├── SOUL.md # Psychological-character document loaded into prompts
└── .claude-plugin/
└── plugin.json # Claude plugin metadata
pip install -r requirements-dev.txt
python -m pytest tests/ -v1,100+ tests covering the provider chain, evidence-grounding pipeline, sandbox directives, build queue, breaking news, market matching, dedup layers, and the security-critical functions (sanitize_untrusted(), check_output_for_injection(), validate_url(), validate-headline.sh, AgentDB, prompt templates, GitHub client enforcement).
benthic-bot.py is a Telegram chat bot that shares brain and memory with ln-agent.py. It uses Telegram's bot-to-bot communication to participate in group chats with other bots and humans.
- Per-chat memory isolation — each group gets its own conversation context; no cross-group leaking
- Operator tiered auth — operators (by Telegram user ID) get expanded tool access for diagnostics; everyone else gets read-only research tools
- [GROUP] directive — operators can route messages from DMs to the group chat, with multi-command support (each line sent as a separate message)
- Agent-chat API polling — catches messages Telegram doesn't deliver via the LN agent-chat API (60s interval, content-based dedup)
- Message debouncing — merges consecutive rapid messages from same sender before LLM processing
- Media analysis — photos re-encoded via PIL (strips EXIF/metadata); PDFs blocked for security; text files sanitized
- Persistent memory —
[REMEMBER:category]/[FORGET:id]directives for notes that survive restarts (operator-only writes, auto-prunes at 200) - Self-awareness — tracks own actions (bets, messages, replies) so it knows what IT did
- Evidence-grounded replies — compose → verify → repair-once → re-verify against typed evidence; ambient replies publish only when factually supported
- Runtime-mediated sandbox —
[SANDBOX]directive executed by trusted Python in a locked-down Docker container, then synthesized tools-disabled - Breaking-news pings — rate-gated reactions to live-news WS events
- Two-pass pre-screen + notification gate — cheap classification screen (and zero-LLM regex gate for mechanical headline-bot notifications) before any expensive call
- Knowledge base — platform reference topics keyword-loaded into prompts on demand
- Configurable providers — same
PROVIDER_ORDERsupport as ln-agent.py
- Create a bot via @BotFather on Telegram
- Enable bot-to-bot communication in BotFather → Bot Settings → Bot-to-Bot Communication
- Disable privacy mode in BotFather → Bot Settings → Group Privacy → Turn OFF. This is critical — with privacy mode on, your bot will be invisible to other bots
- Add the bot to your Telegram group
- Store the token securely:
echo "YOUR_BOT_TOKEN" > ~/.claude/.ln-bot-token chmod 600 ~/.claude/.ln-bot-token
| Variable | Required | Description |
|---|---|---|
BOT_TOKEN |
Yes (or BOT_TOKEN_FILE) |
Telegram bot token |
BOT_TOKEN_FILE |
No | Path to file containing bot token (default: ~/.claude/.ln-bot-token) |
BOT_USERNAME |
Yes | Bot username in lowercase, without @ (e.g. my_bot) |
AGENTS_GROUP_ID |
Yes | Telegram group ID where the bot operates (prefix channels with -100) |
ALLOWED_GROUPS |
No | JSON array of additional group IDs: '[-100123456789]' |
OPERATOR_IDS |
No | JSON array of Telegram user IDs: '[12345678]' |
AGENT_DIR |
No | Agent directory for operator Bash tool restrictions (default: script directory) |
PROVIDER_ORDER |
No | Comma-separated provider priority (default: codex,claude; add opencode explicitly to enable it) |
CODEX_MODEL / CODEX_EFFORT |
No | Codex model + reasoning effort (defaults: gpt-5.6-sol / xhigh) |
CODEX_CLASSIFY_MODEL |
No | Codex classification-tier model (default gpt-5.6-terra) |
BENTHIC_DB / BENTHIC_LOG_FILE |
No | Override DB / log paths (also used by the test suite) |
ENABLE_REPLY_GROUNDING |
No | Evidence-grounded reply pipeline (default on; 0 = emergency rollback) |
OPENCODE_MODEL |
No | OpenCode model to enable it (e.g. anthropic/claude-sonnet-4-5) |
# Export required variables
export BOT_USERNAME="my_bot"
export AGENTS_GROUP_ID="-100XXXXXXXXXX"
# Optional: set operator and provider
export OPERATOR_IDS='[YOUR_TELEGRAM_USER_ID]'
export PROVIDER_ORDER=opencode,codex # if you don't have Claude CLI
# Run directly
python3 benthic-bot.py
# Or via PM2 for continuous operation
pm2 start ecosystem.config.js- Direct mentions (
@my_bot what do you think?) or replies to the bot's messages → always responds - Ambient messages in the group → the LLM decides if it has something useful to add (otherwise silently skips)
- Operator DMs → operators get full cross-group context with
[GroupName]headers; can use[GROUP]to send messages to the group - Media messages → photos analyzed via PIL (re-encoded for safety); documents noted in context; PDFs blocked
- Shared memory — reads from the same
agent.dbasln-agent.py - Loop prevention — rate limit (5s per sender), max thread depth (5), message dedup, content-based cross-path dedup
Set AGENT_NAME to brand your instance, then customize prompts/bot/identity.md for your agent's personality. The identity prompt uses {agent_name} placeholders filled at runtime.
benthic-builder.py is an opt-in background daemon that consumes a build_tasks
queue from the shared SQLite. Operators (via the chat bot's persistent-memory
directives) can hand the daemon a project brief; the daemon spawns Codex CLI in
an isolated workdir and drives a goal-driven loop over codex app-server
(codex exec fallback) with the brief plus a delivery rubric (working code,
README, .env.example, dependency manifest, hard security acceptance criteria).
An acceptance gate (review + independent VERIFY) must pass before the result is
pushed as a public repo under your configured GitHub org.
Each task is isolated (per-task workdir under BUILD_ROOT, never touches
the agent's own files), time-capped (6h hard wall, real target 30-90 min),
and logged to BUILD_LOG_ROOT/<id>.log. On startup the daemon sweeps
orphaned running rows whose PIDs are gone so a crashed task doesn't deadlock
the queue.
| Variable | Required | Description |
|---|---|---|
BUILD_GITHUB_ORG |
Yes | GitHub org/user the daemon publishes built repos under |
BUILD_GIT_USER_NAME |
No | git author name on the initial commit (default Agent Builder) |
BUILD_GIT_USER_EMAIL |
No | git author email on the initial commit (default agent@example.com) |
BUILD_ROOT |
No | Per-task workdir root (default /tmp/agent-builds) |
BUILD_LOG_ROOT |
No | Build log directory (default /tmp/agent-build-logs) |
BUILD_TIMEOUT |
No | Hard cap per task in seconds (default 21600 = 6h) |
BUILD_POLL_INTERVAL |
No | Seconds between queue polls (default 10) |
BUILD_USE_APPSERVER |
No | 0 forces the codex exec fallback (default app-server mode) |
BUILD_MAX_TURNS |
No | Goal-loop turn cap in app-server mode (default 40) |
BENTHIC_BASE |
No | Agent base dir for DB + github_client.sh (default: script directory) |
CODEX_BIN / CODEX_MODEL / CODEX_EFFORT |
No | Same vars as the news agent; daemon shares Codex config |
export BUILD_GITHUB_ORG=YourGithubOrg
export BUILD_GIT_USER_NAME="Agent Builder"
export BUILD_GIT_USER_EMAIL="agent@example.com"
python3 benthic-builder.py
# or alongside the agent + bot via PM2:
pm2 start ecosystem.config.jsThe bot's identity prompt documents the operator-facing CLI (bin/benthic-build start <repo_name> --notes "..." <<'BRIEF' ... BRIEF); see prompts/bot/identity.md for the full flow.
For AI-assisted development, see CLAUDE.md — it contains detailed architecture documentation, class references, and conventions optimized for Claude Code.