Typer-based CLI for tmux-backed Codex orchestration sessions
Project description
tmux-orche
tmux-backed Codex orchestration for OpenClaw and other fire-and-forget workflows.
Overview
tmux-orche is primarily designed to solve one practical problem: OpenClaw needs to hand work off to Codex without staying attached to the full conversation until Codex finishes.
The pattern is:
- OpenClaw receives a user request.
- OpenClaw calls
orcheto create or reuse a persistent tmux session that runs Codex. orchereturns immediately.- OpenClaw stops waiting, so it does not keep burning tokens while Codex works.
- Codex continues in the background inside tmux.
- When the task completes, a notify hook posts the result back to the same Discord channel.
This fire-and-forget model is the core value of tmux-orche: it lets OpenClaw hand off long-running Codex work while keeping OpenClaw's own token usage low.
Codex Config Limitation
Codex does not support selecting an arbitrary config file path with a CLI flag.
In practice:
codex -c ...overrides individual config values- it does not mean "load this config.toml file"
- Codex uses its standard config locations such as
~/.codex/config.tomlor project-local.codex/config.toml
For multi-session or multi-channel setups, the practical workaround is CODEX_HOME.
By default, tmux-orche manages this automatically:
- it creates
/tmp/orche-codex-<session>/ - it copies the contents of
~/.codex/into that directory - it rewrites the copied
config.tomlnotify entry for the current session and channel - it launches Codex with
CODEX_HOMEpointing at that temporary directory - it removes the temporary directory when the session is closed
--codex-home remains available as an advanced manual override, but it is no longer required for the normal workflow.
Primary Use Case
The intended production workflow looks like this:
- A user sends a task in a Discord server and mentions
@OpenClaw. - OpenClaw reads the message in the main chat channel.
- OpenClaw calls
orche session-newandorche send. orchereturns immediately, and OpenClaw ends the turn.- Codex keeps running in a persistent tmux session in the background.
- A notify hook sends a completion message back to the same Discord channel.
- The user sees the notification and can continue the conversation.
This is why tmux persistence matters: the Codex process survives after OpenClaw has already returned control to Discord.
Prerequisites
Runtime Requirements
orche depends on these tools:
tmuxtmux-bridgecodexCLI- Python
3.9+
Discord Environment
The core OpenClaw + Codex workflow assumes a Discord server with:
- one Discord Guild
- one channel such as
#coding, used for both:- OpenClaw receiving user messages
- Codex posting completion notifications back into the same channel
It also assumes two Discord bots:
OpenClaw Bot: receives user messages in that channel and callsorcheCodex Notify Bot: posts completion notifications back into that same channel after Codex finishes
OpenClaw Configuration
An OpenClaw deployment typically enables Discord in ~/.openclaw/openclaw.json.
Relevant fields include:
channels.discord.enabled: truechannels.discord.token: OpenClaw Bot Tokenchannels.discord.guilds: allowed guild and user configuration
Example:
{
"channels": {
"discord": {
"enabled": true,
"token": "YOUR_OPENCLAW_BOT_TOKEN",
"guilds": {
"123456789012345678": {
"enabled": true,
"allowed_users": ["234567890123456789"]
}
}
}
}
}
Architecture
Discord user
|
v
@OpenClaw in main channel (#coding)
|
v
OpenClaw Bot
|
+--> orche session-new
|
+--> orche send
|
v
OpenClaw returns immediately
|
v
Persistent tmux session
|
v
Codex runs in background
|
v
notify hook
|
v
Codex Notify Bot -> same Discord channel (#coding)
End-to-End Flow
- User mentions
@OpenClawwith a coding task. - OpenClaw validates the Discord message and allowed guild/user context.
- OpenClaw starts or reuses a Codex tmux session through
orche. - OpenClaw sends the task to Codex and exits immediately.
- Codex works asynchronously in tmux.
- A notify hook posts the result back to the same channel.
- The user receives the completion signal without OpenClaw holding the entire session open.
Installation
Agent-guided install guide: https://github.com/parkgogogo/tmux-orche/raw/main/install.md
Install with pip
From PyPI:
pip install tmux-orche
From a local checkout:
pip install .
Install with uv
As a tool:
uv tool install tmux-orche
From a local checkout:
uv tool install .
If you prefer a project environment instead of a global tool install:
uv pip install .
Install from source
git clone https://github.com/parkgogogo/orche
cd orche
python3 -m venv .venv
source .venv/bin/activate
pip install -U pip
pip install .
After installation, the orche command should be available on your PATH.
Quick Start
Create or reuse a persistent Codex session:
orche session-new --cwd /path/to/repo --agent codex
Create a named session and bind a Discord channel for notify hooks:
orche session-new \
--cwd /path/to/repo \
--agent codex \
--name repo-codex-main \
--discord-channel-id 123456789012345678
By default, orche will automatically create and manage a temporary CODEX_HOME under /tmp/orche-codex-<session>/.
Send a message into an existing session:
orche send --session repo-codex-main "analyze the test failures"
Check session status:
orche status --session repo-codex-main
Read the latest terminal output:
orche read --session repo-codex-main --lines 80
Show recent local action history:
orche history --session repo-codex-main --limit 20
Type text without pressing Enter:
orche type --session repo-codex-main --text "focus on database queries first"
Send keys:
orche keys --session repo-codex-main --key Enter
orche keys --session repo-codex-main --key Escape --key Enter
Get the latest turn summary:
orche turn-summary --session repo-codex-main
Interrupt the running task:
orche cancel --session repo-codex-main
Close the session window:
orche close --session repo-codex-main
Commands
| Command | Description | Key Options |
|---|---|---|
orche backend |
Print the active backend type. | None |
orche config get |
Read a supported runtime config value. | <key> |
orche config set |
Write a supported runtime config value. | <key>, <value> |
orche config list |
List supported runtime config values. | None |
orche session-new |
Create or reuse a persistent Codex session. | --cwd, --agent, --name, --codex-home, --discord-channel-id |
orche send |
Send a fire-and-forget message to an existing session. | --session, <message> |
orche status |
Show resolved pane, cwd, running state, and session metadata. | --session |
orche read |
Read recent pane output through tmux-bridge. |
--session, --lines |
orche history |
Show recent local action history for a session. | --session, --limit |
orche type |
Type text into the session without submitting it. | --session, --text |
orche keys |
Send one or more key presses to the session. | --session, --key |
orche cancel |
Send Ctrl-C to the active session. |
--session |
orche turn-summary |
Print the latest inferred turn summary for a session. | --session |
orche close |
Kill the tmux window and remove local session metadata. | --session |
For built-in help:
orche --help
orche session-new --help
orche send --help
Configuration
tmux-orche follows the XDG Base Directory convention.
Primary config file:
~/.config/orche/config.json
State directory:
~/.local/share/orche/
Typical state files include:
meta/<session>.jsonfor per-session metadatahistory/<session>.jsonlfor local action historylocks/<session>.lockfor session coordinationlogs/orche.logfor runtime event logging
The runtime config stores fields such as:
- active
session - resolved
discord_session codex_turn_complete_channel_idcodex_homecodex_home_managed- current
cwd,agent, andpane_id
Notification hooks and helper scripts should read ~/.config/orche/config.json or use orche config get ....
You can manage notification-related config directly from the CLI:
orche config set discord.bot-token "YOUR_DISCORD_BOT_TOKEN"
orche config set discord.channel-id "123456789012345678"
orche config set discord.mention-user-id "123456789012345678"
orche config set discord.webhook-url "https://discord.com/api/webhooks/..."
orche config set notify.enabled true
orche config list
Supported config keys:
discord.bot-tokendiscord.channel-iddiscord.mention-user-iddiscord.webhook-urlnotify.enabled
Auto-Managed CODEX_HOME
Each orche session gets its own Codex home automatically.
For a session named repo-codex-main, orche will use a path like:
/tmp/orche-codex-repo-codex-main/
On session-new, orche will:
- create the temporary directory if it does not already exist
- copy the contents of
~/.codex/into it - write a session-specific
hooks/discord-turn-notify.sh - rewrite the copied
config.tomlso the notify command targets the current session and channel - launch Codex with
CODEX_HOMEset to that directory
On close, orche removes the auto-managed temporary directory.
This gives you:
- one
CODEX_HOMEper session - one copied
config.tomlper session - isolated notify settings per session
- isolated Codex history and session state per session
If you need full manual control, --codex-home is still available as an advanced override.
Notification Workflow
tmux-orche uses the existing Codex native notify pipeline, but in the default workflow it manages a per-session copied hook automatically. This repository also includes the source hook variant at scripts/notify-discord.sh, which orche writes into each managed CODEX_HOME.
The shell hook is intentionally thin. It just forwards Codex notify events into the Python notify pipeline (orche _notify-discord), where payload parsing, message construction, registry lookup, and provider delivery are all testable in isolation.
What orche Provides
orche session-newwrites the active session context to~/.config/orche/config.jsonorche session-newcreates a per-session temporaryCODEX_HOMEand rewrites itsconfig.tomlorche turn-summary --session <name>exposes the current turn-summary logic in a CLI-friendly wayorche _turn-summary --session <name>is also available as a hidden compatibility aliasorche config get/set/listprovides a stable interface for notification secrets and channel settings
This keeps the responsibility split cleanly:
- Codex emits notify events
- the hook handles external delivery
orcheprovides session metadata and summary extraction
Automatic Notify Setup
The default flow is automatic.
When you run:
orche session-new \
--cwd /path/to/repo \
--agent codex \
--name repo-codex-main \
--discord-channel-id 123456789012345678
orche will:
- copy
~/.codex/into/tmp/orche-codex-repo-codex-main/ - write
hooks/discord-turn-notify.shinto that copied home - rewrite the copied
config.tomlso the notify command passes the current session and channel - start Codex with
CODEX_HOME=/tmp/orche-codex-repo-codex-main/
This means your global ~/.codex/config.toml acts as the base template, but each running session gets its own isolated notify configuration automatically.
What You Still Need To Configure
You still need:
- a valid base
~/.codex/directory for copying - Discord credentials via:
orche config set discord.bot-token ...- or
DISCORD_BOT_TOKEN
- a Discord channel ID via:
orche config set discord.channel-id ...- or
orche session-new --discord-channel-id ...
Example:
orche config set discord.bot-token "your-token"
orche session-new \
--cwd /path/to/repo \
--agent codex \
--name repo-codex-main \
--discord-channel-id 123456789012345678
Using orche with the Existing Hook
- Start or reuse a session:
orche session-new \
--cwd /path/to/repo \
--agent codex \
--name repo-codex-main \
--discord-channel-id 123456789012345678
- Send work:
orche send --session repo-codex-main "review the latest changes"
- When Codex emits a native notify event, the copied session-specific hook reads:
codex_turn_complete_channel_idsessioncwdagentpane_id
from the current session context and ~/.config/orche/config.json.
- If the hook needs a short completion summary, it should call:
orche turn-summary --session repo-codex-main
Hook Integration Note
If you are maintaining a custom hook and it still shells out to the old orch.py _turn-summary, update that call site to:
orche turn-summary --session "$session"
or, if you want minimal behavior change:
orche _turn-summary --session "$session"
The rest of the notification design can stay exactly as it is.
Testing
The notify stack is split into small layers so it can be tested without sending real Discord messages:
- payload parsing and message shaping live in
orche.notify.payload - provider delivery lives in
orche.notify.discord - registry and fan-out live in
orche.notify.registryandorche.notify.service - the shell hook is only a launcher for
orche _notify-discord
All automated tests mock HTTP delivery. No test sends a real Discord message.
Run the test suite locally:
python -m pip install -e .[test]
pytest
Coverage is enforced at 100% for the orche.notify package in CI.
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file tmux_orche-0.3.3.tar.gz.
File metadata
- Download URL: tmux_orche-0.3.3.tar.gz
- Upload date:
- Size: 47.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f625083664b8a7c75a719bc8c86da5e96adc6bb31aefcd7d731740b3dd5704e7
|
|
| MD5 |
b2cd3eafb2f0e706273266707a7dcd17
|
|
| BLAKE2b-256 |
6d64ce6595f8b72192d8b10493cbc475b8954aea06e9f841f8286001f33ec2ee
|
Provenance
The following attestation bundles were made for tmux_orche-0.3.3.tar.gz:
Publisher:
publish.yml on parkgogogo/tmux-orche
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
tmux_orche-0.3.3.tar.gz -
Subject digest:
f625083664b8a7c75a719bc8c86da5e96adc6bb31aefcd7d731740b3dd5704e7 - Sigstore transparency entry: 1206152679
- Sigstore integration time:
-
Permalink:
parkgogogo/tmux-orche@9db895635991be8a6216b98a1bb1fa1a3e8a0697 -
Branch / Tag:
refs/tags/v0.3.3 - Owner: https://github.com/parkgogogo
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@9db895635991be8a6216b98a1bb1fa1a3e8a0697 -
Trigger Event:
push
-
Statement type:
File details
Details for the file tmux_orche-0.3.3-py3-none-any.whl.
File metadata
- Download URL: tmux_orche-0.3.3-py3-none-any.whl
- Upload date:
- Size: 27.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
83c84663df837f97c02d10e70e9052c7f121e428e25f61116c1284aa822cf5df
|
|
| MD5 |
cde06d06b4221f2cbca31ae214cd79f9
|
|
| BLAKE2b-256 |
68c68280754dd08171f94fa1d7a59810690fe877d746d0744b0982c9cb80d034
|
Provenance
The following attestation bundles were made for tmux_orche-0.3.3-py3-none-any.whl:
Publisher:
publish.yml on parkgogogo/tmux-orche
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
tmux_orche-0.3.3-py3-none-any.whl -
Subject digest:
83c84663df837f97c02d10e70e9052c7f121e428e25f61116c1284aa822cf5df - Sigstore transparency entry: 1206152696
- Sigstore integration time:
-
Permalink:
parkgogogo/tmux-orche@9db895635991be8a6216b98a1bb1fa1a3e8a0697 -
Branch / Tag:
refs/tags/v0.3.3 - Owner: https://github.com/parkgogogo
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@9db895635991be8a6216b98a1bb1fa1a3e8a0697 -
Trigger Event:
push
-
Statement type: