Reference scripts that wire smalltalk into Codex CLI via its hook system. They make Codex aware of unread smalltalk messages without requiring real-time push (Codex has no channel equivalent — only these polling-style hooks).
session-start.sh—SessionStarthook. Reads$ST_ROOT/$ST_AGENT/inbox/viast message ls --jsonand emits the unread snapshot asadditionalContextso the agent sees pending smalltalk messages the moment it boots.stop.sh—Stophook. Same shape, run when the agent goes idle. Tracks a state file ($XDG_STATE_HOME/st-codex-hooks/last-checked.txt) so it only reports messages that arrived since the previous Stop — empty delta means silent idle.config.toml.example—~/.codex/config.tomlfragment registeringst mcpas an MCP server and pointing the hook entries at the two scripts above.
- Make sure
standjqare on your$PATH. - Copy or symlink the two
.shscripts into~/.codex/hooks/(or anywhere Codex can read; the config snippet expects absolute paths). - Merge
config.toml.exampleinto your~/.codex/config.toml. Update thecommand = "/full/path/to/..."lines to point at wherever you placed the scripts. - Set
ST_ROOTandST_AGENTin the shell that launchescodex— theenv_varspassthrough in the MCP block hands them tost mcp, and the hooks read them directly. - Restart Codex.
- At session start: the agent's first turn sees an
additionalContextblock listing every file in your inbox with sender + subject. It can then callst_msg_read(via the MCP server registered in the same config) to inspect any of them. - On idle: the Stop hook re-checks for arrivals since the previous Stop. Only NEW messages trigger the injection; quiet inboxes idle silently.
- Verbs:
st_msg_send,st_msg_ls,st_msg_read,st_msg_archive,st_msg_thread— the same five tools every other MCP host gets. Use them in chat the way you'd use a built-in capability.
Codex itself has no channel equivalent, so push semantics come from outside the agent: run Codex inside a pty session, then arm st ding against that session. The daemon watches the inbox + status, and on every new arrival pty-sends a one-line notice into Codex when the agent is available (or offline); busy and dnd buffer the notice until status flips back.
# In one terminal: run Codex inside a named pty session
pty run --name codex-foo -- codex
# In another: arm the ding daemon for that session
ST_ROOT=~/.local/state/smalltalk ST_AGENT=me \
st ding codex-foo --interval 2000The daemon is long-running; pair with pty up (or systemd, launchd, etc.) for restart-on-crash. Set st status me --set busy to stop deliveries while a turn is mid-flight; st status me --set available flushes the buffered notices.
- Hooks alone aren't push. The
SessionStartandStophooks fire at the boundaries of a turn — smalltalk messages that arrive mid-turn don't surface until the next Stop. Combine withst ding(above) for true push, or run Codex alongside a Claude Code session in channel mode (see walkthrough.md). - Filename-ts filtering.
stop.shfilters by the<unix-ms>prefix of the filename. Sync-delivered files whose prefix is older than the last checkpoint are missed — Stop is a notification cue, not a backfill audit. - Single-agent assumption. The state file is global; running Codex with different
ST_AGENTvalues against the same$HOMEwill cross-pollinate the cursor. If that bites, edit the script to scope the state path under the agent name.
- Nothing happens on session start. Check
$ST_AGENT/$ST_ROOTare exported in the shell that launches Codex; if they're empty the hook exits non-zero with a stderr message that Codex usually surfaces in its log. st-codex-hook: jq not on PATH. Installjq(brew install jq,apt install jq, etc.). The hook uses jq to construct the JSON envelope.- The same messages keep getting injected on every Stop. The state file isn't being written. Check that
$XDG_STATE_HOME/st-codex-hooks/(or~/.local/state/st-codex-hooks/) is writable.