A local ambient hook + read-only MCP that makes Codex thread timing predictable and model-usable.
Codex can record timing in local session logs and runtime state, but that timing is not always available to the model as clean thread context. Quiet Clock adds a predictable model-facing layer: a tiny ambient hook for current timing context and a read-only lookup surface for exact chronology when needed. Without a layer like this, agents may infer timing from message order, current environment dates, or file/tool artifacts, but that is not the same as reliable per-message chat chronology. It is intentionally small: no daemon, no network service, no memory system, no background watcher.
Personal project note: this is a personal project by Zackary Skelly. It is not affiliated with, sponsored by, or endorsed by Dragonfly or any employer.
Quiet Clock has two pieces:
- A Codex
UserPromptSubmithook that injects compact current timing context on each user prompt. - A local stdio MCP server named
quiet_clockthat reads local Codex session state for timeline/history tools when exact chronology matters.
The hook gives Codex a small model-visible note with current UTC/local time, timezone, local date, session/thread id, turn id, elapsed time since the previous user prompt, and stale/date-boundary hints. It is not a full transcript timeline.
The MCP provides read-only tools:
nowfor current UTC/local time.thread_timelinefor compact chronology.elapsed_sincefor elapsed time since a message, turn, or query match.find_messagefor prior message lookup.staleness_reportfor long pauses, day changes, stale-context risk, and exact-date hints.
Hook context looks like this in model-visible context:
Quiet Clock is active.
Now UTC: 2026-05-03T06:18:13Z.
Now local: 2026-05-02T23:18:13-07:00 (America/Los_Angeles); local date 2026-05-02.
Session/thread id: 019...
Current turn id: 019...
Elapsed since previous user prompt: 20m 45s.
Use the quiet_clock MCP instead of guessing for timing/history questions.
A tool result from quiet_clock.now looks like:
{
"ok": true,
"time": {
"utc": "2026-05-03T06:18:13Z",
"local": "2026-05-02T23:18:13-07:00",
"timezone": "America/Los_Angeles",
"local_date": "2026-05-02"
}
}- macOS or another Unix-like environment.
- Python 3.11+.
- Codex CLI/app with hooks and MCP support.
- No Python package dependencies.
This is the fully validated path. It installs both the hook and the MCP server directly into local Codex config.
git clone https://github.com/zorkary/codex-quiet-clock.git quiet-clock
cd quiet-clock
python3 -m unittest discover -s tests -v
scripts/install_hook.sh
scripts/install_mcp.shThe hook installer writes a marked Quiet Clock block to ~/.codex/config.toml. The MCP installer registers the server equivalent to:
codex mcp add quiet_clock -- python3 /path/to/quiet-clock/quiet_clock/mcp_server.pyIf you move the checkout, rerun both installers.
codex plugin marketplace add zorkary/codex-quiet-clockThis registers the Quiet Clock plugin marketplace with Codex. The plugin package is self-contained and includes bundled lifecycle config (./hooks/hooks.json) plus a bundled stdio MCP server config (./.mcp.json).
As of Codex 0.130.0-alpha.5, plugin-bundled hook discovery exists behind the under-development plugin_hooks feature flag, and plugin detail APIs can surface bundled hooks. That is a real improvement over earlier builds, but the stable path for complete Quiet Clock behavior is still the direct hook + MCP install above. Use plugin packaging only if you are comfortable testing under-development Codex plugin-hook behavior.
Observed plugin-runtime caveats:
codex features listreportshooksandpluginsas stable, butplugin_hooksas under development.- Plugin-bundled hooks may require
--enable plugin_hooksor equivalent config while this feature is still guarded. codex plugin marketplace addregisters a marketplace; depending on Codex version and marketplace source, that is not the same as proving the hook fires in every runtime path.- The same hook logic works through direct Codex hook configuration.
For now, treat plugin packaging as a compatibility experiment and use the direct installer scripts for the working product.
python3 -m unittest discover -s tests -v
scripts/smoke_hook.sh
scripts/smoke_mcp.sh
codex mcp listExpected MCP list entry:
quiet_clock python3 /path/to/quiet-clock/quiet_clock/mcp_server.py enabled
Live hook probe:
codex exec --ephemeral --sandbox read-only "If Quiet Clock hook context is visible, answer exactly QUIET_CLOCK_VISIBLE. Otherwise answer NO."Live MCP probe:
codex exec --ephemeral --sandbox read-only "Use the quiet_clock.now tool once. Then answer TOOL_USED if it succeeded, otherwise TOOL_FAILED."If quiet_clock does not appear in an already-open Codex chat, restart Codex or start a fresh chat.
Usually you do not ask for Quiet Clock directly. The hook gives Codex minimal current timing context automatically.
Ask explicit timing questions when needed:
how long ago did I say that?
find when I first mentioned the MCP cancellation issue
did this thread go stale overnight?
Codex should use the MCP tools instead of guessing or spelunking through local files when exact timing or chronology matters.
Timeline tools should receive the session/thread id from the hook context. They do not silently fall back to the latest local thread unless allow_latest=true is passed explicitly. They also refuse arbitrary transcript paths; transcript reads are limited to Codex rollout JSONL files under the configured Codex sessions root.
Timeline outputs support:
include_snippets=falseto suppress message snippets.snippet_charsto cap snippet length.debug_paths=trueto include raw transcript paths only when debugging.
See PRIVACY.md for the full privacy model.
Remove the hook block:
scripts/uninstall_hook.shRemove the MCP registration:
codex mcp remove quiet_clockpython3 -m unittest discover -s tests -v
scripts/smoke_hook.sh
scripts/smoke_mcp.sh
scripts/public_release_check.shMain files:
quiet_clock/hook.py: Codex hook entrypoint.quiet_clock/mcp_server.py: stdio MCP server.plugins/quiet-clock/: self-contained Codex plugin package.quiet_clock/transcript.py: local Codex transcript/session readers.quiet_clock/time_context.py: timing context and stale/date-boundary logic.
Normal Quiet Clock operation is read-only.
It may read Codex hook input, local Codex transcript/session files, and the local Codex session index in read-only SQLite mode.
It does not call the network, run a daemon, create cron jobs, create launch agents, run watchers, write memory, mutate repo files during normal use, push to GitHub, or contact third parties.
The only intentional writes are install/uninstall/configuration operations.
Quiet Clock uses hooks and MCP because those extension points are available today. Codex 0.130 also exposes more app-server thread and turn timing primitives, which may become a cleaner timeline backend later.
The current hook + read-only MCP interface remains the supported path because it works in the Codex app today and does not require running a separate app-server or remote-control session. See docs/runtime-notes.md.
Make sure tools include read-only MCP annotations:
{
"readOnlyHint": true,
"destructiveHint": false,
"idempotentHint": true,
"openWorldHint": false
}Without those annotations, Codex may list tools but cancel calls before sending tools/call to the server.
Run scripts/smoke_hook.sh, then confirm the marked hook block exists in ~/.codex/config.toml.
Run codex mcp list. If quiet_clock appears there, restart Codex or start a fresh chat. Existing sessions may not pick up newly registered MCP namespaces immediately.
Quiet Clock does not add hidden per-message envelope metadata to Codex internals, and it does not depend on proving what timing metadata Codex may already have internally. It uses Codex hook additionalContext, which is model-visible context but not normal chat text.
The MCP reconstructs timeline information from local Codex session state and JSONL transcript files. If those files are unavailable or the runtime layout changes, Quiet Clock returns the best available timing context rather than blocking the turn.