Skip to content

PRD: Codex adapter #13

@omarelkhal

Description

@omarelkhal

PRD: Codex adapter

Published issue: #13

Problem Statement

Aftertone ships working post-reply speech for Cursor and Claude Code, but Codex is still listed as not shipped. Users who work in Codex cannot get the same local spoken summary after a Codex turn, even though Codex now exposes lifecycle hooks that can run deterministic command scripts at Stop and receive the final assistant message on stdin.

Solution

Ship a thin Codex adapter that reuses the existing Aftertone runtime. Codex should call an Aftertone Stop hook after each turn, pass the hook JSON through the shared hook_run --stdin pipeline, and let the existing prepare/extract/session/daemon path decide whether to speak.

The adapter should feel like the Claude adapter:

  • one global install wires hooks and model guidance;
  • daily usage is normal codex;
  • /aftertone-on and /aftertone-off semantics stay per chat through the shared session allowlist;
  • the daemon, config, voice, language, speed, and mode remain shared across adapters;
  • MCP remains an optional control plane, not the speech trigger.

The official Codex hooks docs are the contract for this work: https://developers.openai.com/codex/hooks

User Stories

  1. As a Codex user, I want Aftertone to speak after a Codex answer, so that I can keep momentum without staring at the screen.
  2. As a Codex user, I want the spoken text to come from <spoken_summary>, so that the audio is brief and intentional.
  3. As a Codex user, I want no spoken output when a reply omits <spoken_summary> in tag-only mode, so that noisy fallback speech does not surprise me.
  4. As a Codex user, I want /aftertone-on and /aftertone-off behavior to stay per chat, so that one session can speak while another stays silent.
  5. As a Codex user, I want global install to register the Codex hook, so that I do not hand-edit hook JSON.
  6. As a Codex user, I want the hook to start or find the daemon, so that speech still works after reboot.
  7. As a Codex user on Windows, I want a Windows command path or documented limitation, so that the adapter is not Linux-only by accident.
  8. As a maintainer, I want Codex to reuse the existing hook JSON preparation path, so that Cursor, Claude, and Codex do not drift.
  9. As a maintainer, I want unit tests around Codex hook payloads, so that future extract/session changes do not break Codex.
  10. As a maintainer, I want install and uninstall tests, so that user config files are merged and cleaned up predictably.
  11. As a contributor, I want docs/adapters/codex.md, so that the supported hook contract and troubleshooting path are explicit.
  12. As a contributor, I want README and site status to change only after the hook path is proven, so that the public adapter table stays honest.
  13. As a power user, I want an optional Codex MCP config snippet, so that CLI controls can be exposed where Codex supports MCP.
  14. As a maintainer, I want smoke-test instructions that do not require cloud TTS or API keys, so that local verification remains private.

Implementation Decisions

  • Treat Codex as a host adapter, not a new speech system.
  • Use Codex Stop as the required post-reply trigger. Codex docs say Stop runs at turn scope, receives hook JSON on stdin, includes last_assistant_message, and expects JSON or empty output on stdout.
  • Reuse the existing hook_run --stdin path. It already ensures the daemon, posts /hook, falls back to prepare plus /say, traces pipeline state, and returns {}.
  • Extend shared extraction only where needed. last_assistant_message, transcript_path, session_id, and hook_event_name already match the shape Aftertone needs.
  • Register user-level Codex hooks under ~/.codex/hooks.json for global install. Codex also supports repo-local hooks, config TOML hooks, and plugin-bundled hooks, but user-level hooks best match Aftertone's global Cursor and Claude installs.
  • Keep hook commands synchronous. Codex parses but does not support async command hooks yet.
  • Avoid plain text stdout from Codex hooks. Stop treats plain text stdout as invalid, so wrappers must print JSON or nothing.
  • Keep shared config under the install root .cursor/hooks/speak_summary.toml until the repo deliberately renames adapter-neutral state later.
  • Put Codex model guidance in an AGENTS-compatible place and document how it gets loaded. The guidance must preserve the current rule: the spoken tag language follows TOML lang, not the user's message language.
  • Keep MCP optional. It may expose Aftertone control commands, but it must not be required for post-reply speech.

Testing Decisions

  • Test external behavior with Codex-shaped hook JSON: Stop with last_assistant_message containing <spoken_summary> produces a /say payload.
  • Test no-speech cases: disabled config, disallowed session, quiet hours, unsupported event, and missing tag in tag-only mode.
  • Test that hook wrappers produce valid JSON-or-empty stdout for Codex Stop.
  • Test installer merge behavior with temporary ~/.codex/hooks.json: existing hooks remain, duplicate Aftertone entries are replaced, and Windows command overrides are preserved.
  • Test uninstall removes only Aftertone Codex entries.
  • Add smoke-test docs that use a local synthetic Codex hook payload before asking users to verify a real Codex turn.

Out of Scope

  • Replacing Supertonic ONNX or changing the daemon.
  • Moving shared config out of .cursor/hooks/.
  • Making MCP the speech trigger.
  • Building a Codex plugin marketplace package before the plain global hook path is proven.
  • Supporting every Codex surface if the official hook docs only prove CLI behavior.

Further Notes

Codex hook docs confirm the important integration details: hooks are enabled by default, ~/.codex/hooks.json is a supported location, non-managed hooks require review/trust, Stop receives last_assistant_message, and Stop expects JSON on stdout when exiting successfully.

Metadata

Metadata

Assignees

No one assigned

    Labels

    adapterIDE/CLI integrationenhancementNew feature or requestready-for-agentFully specified, ready for an AFK agent

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions