Codeflow
Live-stream coding agent sessions to Discord or Telegram. Zero AI tokens burned.
Breaking note:
- Command name:
/codeflow - Env var prefix:
CODEFLOW_*
Setup
First-time setup: see references/setup.md for webhook creation, unbuffer install, bot token, and smoke test.
For hard thread-scoped enforcement (skill-owned /codeflow + plugin tool blocking), install the bundled enforcer plugin once on the host:
bash {baseDir}/scripts/codeflow enforcer install --restart
If the plugin is missing, /codeflow still works in soft mode. The button/no-button behavior must be owned by the deterministic router script, not by free-form assistant prose.
Developer Checks (optional)
Run a local sanity check bundle (Python syntax compile + unit tests + bash -n):
bash {baseDir}/scripts/codeflow check
/codeflow command contract (session-scoped)
When user invokes /codeflow, treat it as a control command first, then as a session-scoped declaration:
- For the current OpenClaw session, all coding, development, or review tasks (code/architecture/security/product review) — including any direct Codex or Claude Code invocations — must be executed via Codeflow relay + local Codex/Claude Code (not direct edits unless explicitly requested).
- Follow Codeflow output conventions (including compact Telegram behavior when applicable).
- If user asks for direct non-code tasks, handle normally; this contract applies to coding, development, and review tasks.
- The contract lasts for the current session context. If user resets/new session, require re-invocation.
Important command-order rule:
- A bare control message such as
/codeflow,/codeflow on,/codeflow status,/codeflow off, orcallback_data: cfe:installis not a request for a coding task. - For these control messages, do not send a generic acknowledgement like “ready, what should I do next?”.
- First execute the deterministic control router below.
- If the router succeeds, end with
NO_REPLYbecause the router already sent the user-facing control reply. - Only after that should later coding tasks in the same session be executed under the Codeflow contract.
- If the same inbound message includes both the
/codeflowcommand and an actual coding task, run the control router first, then continue with the remaining task text.
Soft fallback contract:
/codeflowis always owned by the skill. That is the public entrypoint and the soft fallback path.- The bundled enforcer plugin is optional. When installed, it adds hard
before_prompt_build/before_tool_callblocking on top of the skill flow; it does not own/codeflow. - Step 0) Call
session_statusand read the currentsessionKeyfor the active OpenClaw conversation. - Step 1) Always run the deterministic control script:
bash {baseDir}/scripts/codeflow control \
--session-key "<sessionKey>" \
--text "<raw user message text>"
- The script owns these cases:
/codeflowor/codeflow on|enable|activate/codeflow status/codeflow off|disable|deactivatecallback_data: cfe:install(or rawcfe:install)
- For a bare
/codeflowmessage, always route it here first. Do not improvise a textual reply. - The script performs the real work itself:
- runs
codeflow guard activate|deactivatewhen needed - runs
codeflow enforcer status --json --session-key <sessionKey> - sends the final message itself via Gateway
message.send - includes
recommendation.buttonswhen Telegram routing is available - falls back to plain
Install:/Restart:command text when buttons cannot be sent
- runs
- Normal path: the script already sent the user-facing reply, so end the turn with
NO_REPLY. - Fallback path: if the script exits with code
3and stdout starts withNEED_LLM_ROUTE, parse the JSON payload on the next line ({message, buttons}) and send that yourself. If your current channel/tooling cannot attach buttons, keep the plain text exactly as provided. - Installer note: when handling
cfe:install, be explicit that gateway restart may interrupt or reset the current conversation/thread.
Guard enforcement (hard constraint in script):
- All execution modes that can post/run work (
run,resume,review,parallel) are guard-protected by default.reviewandparallelmust precheck the guard before they clone repos, create worktrees, or post start summaries. - Guard management commands (
codeflow guard activate|deactivate|status|current) bypass the precheck. - Guard is bound to chat/topic context (and session key when available).
- Every allow/deny decision is appended to
${XDG_STATE_HOME:-$HOME/.local/state}/codeflow/guard-audit.jsonl(storescommandHintonly — redacted + truncated; never the full command). - If blocked, instruct user to re-run
/codeflowin the same chat/topic.
Default inference rules (do not ask unless ambiguous):
- Task content: ask user for this when missing.
- Workdir: infer from recent chat/context/task history (not blindly current workspace). If unclear, ask user to re-declare workdir.
- Platform: infer from current channel (Telegram/Discord).
- Target chat/thread: infer from inbound metadata (current chat/thread).
Codex session policy under /codeflow:
- Reuse the prior Codex session associated with the same
-w <workdir>when available (keyed byrealpath(workdir)). - If no prior session is found, create a new session when the task is dispatched.
- Only force new session when user explicitly requests it.
Under /codeflow, avoid asking for workdir/platform/chat if derivable from context.
Invocation
Launch with exec background:true. Background exec sessions survive agent turns. Exit notifications (e.g. notifyOnExit) are provided by the host runtime (OpenClaw), not by Codeflow itself.
exec background:true command:"cat <<'PROMPT' | {baseDir}/scripts/codeflow run -w ~/projects/myapp -- claude -p --dangerously-skip-permissions --output-format stream-json --verbose
Your task here
PROMPT"
Prompt-quoting tip (avoid shell escaping footguns):
- For Codex,
codex exec(andcodex exec resume) reads the prompt from stdin when PROMPT is-(or omitted inexec). For multi-line prompts or prompts containing shell metacharacters (e.g. backticks), prefer stdin + a quoted heredoc. - For Claude Code,
claude -palso supports reading the prompt from stdin; prefer stdin for the same reasons.
Note the session ID from the response — use it to monitor via process.
CLI (stable)
Public entrypoint (do not call _internal/ scripts directly):
bash {baseDir}/scripts/codeflow <command> [...]
Commands:
codeflow run [run-flags] -- <agent command>— start a relay sessioncodeflow guard activate|deactivate|status|current [run-flags]— manage/query the session-scoped guardcodeflow control --session-key <key> --text <raw_text>— deterministic/codeflowsoft-mode control routercodeflow resume [run-flags] <relay_dir>— replay a previous session fromstream.jsonlcodeflow review [...] <pr_url>— PR review modecodeflow parallel [...] <tasks_file>— parallel tasks modecodeflow bridge [...]— Discord gateway bridgecodeflow enforcer install|update|uninstall|status [--json]— manage/query the bundled OpenClaw enforcer plugincodeflow check— local checks (syntax + unit tests)codeflow smoke— config/prereq smoke test
See bash {baseDir}/scripts/codeflow --help for the canonical CLI.
Run flags (codeflow run)
| Flag | Description | Default |
|---|---|---|
-w <dir> | Working directory | Current dir |
-t <sec> | Timeout | 1800 |
-h <sec> | Hang threshold | 120 |
-n <name> | Agent display name | Auto-detected |
-P <platform> | discord, telegram, auto (inferred) | discord |
--thread | Post into a Discord thread | Off |
--tg-chat <id> | Telegram chat id (when -P telegram) | — |
--tg-thread <id> | Telegram thread/topic id (optional) | — |
--skip-reads | Hide Read tool events | Off |
--new-session | For Codex exec: force a new Codex session | auto policy |
--reuse-session | For Codex exec: require and reuse previous session | auto policy |
--prompt-stdin | Enforce prompt via stdin for supported headless agents (Codex exec / Claude -p) | Auto (OpenClaw → on) |
--prompt-argv | Allow legacy argv prompt for supported headless agents | Auto (non-OpenClaw → on) |
Prompt mode can also be set via env: CODEFLOW_PROMPT_MODE=auto|argv|stdin (default: auto).
Rate limiting note (Telegram hardening): parse-stream now routes all delivery through an in-process delivery governor with strict 429 handling:
- Telegram 429 backoff:
next_allowed_at = now + retry_after + 1s(strictly follows Telegramretry_after; adds +1s to avoid immediately hitting 429 again) - do not send any request before
next_allowed_at - queue priority:
final>event>state - compact
statecards (thinking/cmd) are strict snapshot overwrite (latest-wins); during 429 windows updates are merged in memory and only the latest snapshot is applied once the window opens
If you still need to reduce output volume, use CODEFLOW_OUTPUT_MODE (see below) plus existing knobs (--skip-reads, CODEFLOW_SAFE_MODE=true, CODEFLOW_COMPACT).
For PR review, parallel tasks, Discord bridge, and Codex structured output: see references/advanced-modes.md.
Agent Launch Checklist
- Start background session → note session ID from the
execresponse codeflow runposts the session header and streams events to the target channel automatically- Monitor via
process log/process poll; stop viaprocess kill
Completion Detection
Completion notifications are runtime-dependent (OpenClaw). Codeflow itself simply exits when the inner agent command exits.
Backup: Append this to the inner agent's prompt for an additional signal:
When completely finished, run: openclaw system event --text "Done: <brief summary>" --mode now
Monitoring
process poll sessionId:<id> # Check status
process log sessionId:<id> # View recent output
process kill sessionId:<id> # Stop session
Safe mode (optional)
If you stream relay output into a shared channel, enable:
CODEFLOW_SAFE_MODE=true
Effects:
- Suppress file-content previews (Claude
Write) - Suppress command output bodies (Claude Bash output, Codex
command_executionoutput, raw mode output) - Apply stricter redaction to high-risk fields
Output mode
Control how verbose channel posts are via env:
CODEFLOW_OUTPUT_MODE=minimal|balanced|verbose(default:balanced)minimal: onlywarning/error/finalbalanced: key progress +warning/error/finalverbose: near-full (debug; Telegram is more likely to hit 429)
Telegram compact state cards (strict snapshot overwrite):
- thinking/cmd cards always
editthe same anchor (no extra posts) - each
editreplaces the full snapshot (no accumulated log); during 429 windows updates are merged in memory (latest-wins) and only the latest snapshot is applied once the window opens - only when the message exceeds platform limits do we truncate/compress and mark
…(truncated); otherwise we do not fold/hide content
Telegram anti-spam mode (default)
When -P telegram is used, Codex sessions run in compact mode by default:
- per turn, one rolling "thinking" message (edited in place)
- per turn, one rolling "commands/results" message (edited in place)
- one separate turn-complete output message (full text; paginated if oversized)
Next turn starts a fresh pair of rolling messages.
Override with env:
CODEFLOW_COMPACT=true|false(defaultauto, where Telegram => true)
Telegram 429 / anchor stability (compact mode):
- edit failures do not "post a new anchor immediately" (no anchor explosion); state cards preserve single-anchor edit semantics
- on 429, Codeflow sleeps for
retry_after + 1s(not a fixed 10s); when the window opens, it flushes by priority (final > event > state)
Telegram adapter memory guard (oversized message edit groups):
CODEFLOW_TELEGRAM_EDIT_GROUPS_MAX=<n>: max tracked groups (LRU, default64; set0to disable tracking)CODEFLOW_TELEGRAM_TRACK_EDIT_GROUPS=true|false: enable/disable tracking (defaulttrue)
Notes:
- This only affects multi-message editing via
platforms/telegram.pyedit(); compact mode usesedit_singleand is unaffected. - Disabling tracking means
edit()cannot delete/overwrite prior tail messages for already-split posts.
Codex session reuse policy (hard workflow constraint)
For codex exec ..., Codeflow enforces a session policy in code (not just docs):
- default
auto: reuse previous Codex session for the same workdir when available - if prompt contains
/newunderauto: force a new session for that run --new-session: force new session--reuse-session: require previous session and force resume (error if missing)
Optional env overrides:
CODEFLOW_CODEX_SESSION_MODE=auto|new|reuse(defaultauto)CODEFLOW_CODEX_SESSION_MAP=/tmp/dev-relay-codex-sessions.json(session map path)
Agent Support
| Agent | Output Mode | Status |
|---|---|---|
| Claude Code | stream-json | Full support |
| Codex | --json JSONL | Full support |
| Any CLI | Raw ANSI | Basic support |
Session Tracking
- Active sessions:
/tmp/dev-relay-sessions/<PID>.json(auto-removed on end) - Event logs:
/tmp/dev-relay.XXXXXX/stream.jsonl(7-day auto-cleanup) - Stream log policy:
CODEFLOW_STREAM_LOG=full|redacted|off(default:full; ifCODEFLOW_SAFE_MODE=trueandCODEFLOW_STREAM_LOGis unset, default becomesredacted).offwrites minimal metadata only (resume/debug is limited). - Delivery anomaly events: appended into
stream.jsonlascodeflow.delivery.*(exceptions only; no message bodies/tokens/URLs). - Delivery stats (local):
/tmp/dev-relay.XXXXXX/delivery-summary.json(single-file summary; includes rate-limit counts, drops, etc). - Guard state:
${XDG_STATE_HOME:-$HOME/.local/state}/codeflow/guard.json(storescommandHintonly — redacted + truncated) - Guard audit log:
${XDG_STATE_HOME:-$HOME/.local/state}/codeflow/guard-audit.jsonl(JSONL) - Interactive input:
process submit sessionId:<id> data:"message"- If the XDG state dir is not writable, guard/state/audit fall back to dotfiles under
{baseDir}/scripts/(seecodeflow/scripts/_internal/bin/lib.sh).
- If the XDG state dir is not writable, guard/state/audit fall back to dotfiles under
- Telegram/Discord delivery no longer spawns
curl; tokens/webhooks do not appear in child process argv (avoidspsleakage). /tmp/dev-relay-sessions/<PID>.jsonis written atomically (tmp + rename, best-effort fsync) and minimized (does not persist full command/context).
Reference Docs
- Setup guide — first-time install, webhook, bot token
- Advanced modes — PR review, parallel tasks, Discord bridge, Codex
- Discord output — message formats, architecture, env vars, troubleshooting