@polydeukes/adapter-codex
The Codex install unit — lifecycle events become adapter-owned session evidence, and
PreToolUsepayloads become covenant input IR with one element per file the patch touches.Beta. Install it next to
polydeukes, which it names as apeerDependency.
What this package owns
Section titled “What this package owns”The boundary where Codex’s vocabulary is translated away. Agent and tool literals live here by design, so that they never reach the core.
| Unit | What it does |
|---|---|
pdks-codex bin |
One subcommand, pdks-codex init, which registers the session surface in a project |
runHook |
Records lifecycle evidence, or translates one PreToolUse payload into the input IR and spawns the judge |
| Session evidence | Stores timestamped human messages and completed tool calls under a SHA-256 session filename, then removes it at SessionEnd |
| Payload validation | Demands every key the host’s generated schema marks required, in the one spelling that host sends |
| Patch parsing | Turns the raw patch text of an apply_patch call into one file change per file it touches |
| Path rebasing | Resolves a patch path against the call’s working directory and carries it relative to the project root |
runHook({ repoRoot }) is what the generated hook delegator imports. UserPromptSubmit and
PostToolUse append strict JSONL records under .polydeukes/codex-sessions/; SessionEnd
removes only that session’s file. PreToolUse builds the IR with the tools roster and a
session sourced from those records, but no synthesized actor or channels, then spawns
pdks covenant check --enforce block in repoRoot and returns the child’s exit code. The
judging happens in that child process; this package carries no judgment logic.
pdks writes the telemetry. The adapter sends failures that occur before spawning to
pdks on stdin, so those failures are recorded through the same path.
Why this adapter parses text where its siblings read arguments
Section titled “Why this adapter parses text where its siblings read arguments”Codex normalises every file edit that reaches the hook into a single tool name, apply_patch,
and puts the patch itself in tool_input.command — the same field a shell call uses for its
command line. There is no path argument to read. Edit and Write exist as matcher aliases
you may write in .codex/hooks.json, but the payload always names the tool apply_patch, so
a roster or a branch keyed on the aliases matches nothing that ever arrives.
One patch can create, update, delete and rename files in one call. The adapter carries each as its own element of one IR, on one spawn: every file is judged, and any one of them blocking blocks the whole call. A rename contributes two elements, because it changes the path it leaves as well as the one it takes.
Where the consumer touches it
Section titled “Where the consumer touches it”Two lines install the Codex session surface, run from the project root:
npm install --save-dev polydeukes @polydeukes/core @polydeukes/adapter-codexnpx pdks-codex initpdks-codex init resolves polydeukes from the project, spawns pdks init for the
agent-neutral scaffold, then writes the delegator and merges its entries for PreToolUse,
UserPromptSubmit, PostToolUse, and SessionEnd into
.codex/hooks.json non-destructively — other events, other matchers and keys it does not know
are left alone. A re-run reports each existing artifact as skipped and overwrites nothing. The
full artifact list is in pdks init.
Writing the registration is not the end of the install. Codex records trust against the
hash of a hook’s definition, so the generated hook is listed for review and skipped until you
approve it with /hooks. This is why init writes a byte-identical command string on every
run: a changed one needs approving again, and until someone does, nothing is judged.
Installing this adapter beside @polydeukes/adapter-claude-code or
@polydeukes/adapter-grok in one project can run the judge twice per call.
- The generated hook imports
runHookfrom this package. Upgrading the package upgrades what runs; the hook file itself never changes.
No configuration namespace of its own. The scaffold pdks init writes protects .codex/hooks
by default, so the registration this installer creates is covered by the config it creates.
Declared limits
Section titled “Declared limits”The first four are the host’s own, and no adapter can narrow them. The first is measured rather than documented; the next three are stated in the host’s hook documentation.
- A Code Mode
execdispatch is not observed. In codex-cli 0.154 the host does not emitPreToolUsefor a Code Modeexeccall, nor for thetools.apply_patchandtools.exec_commandcalls nested in its JavaScript (openai/codex#23411, #38850). A hook that/hookslists as Active still sees nothing on that surface, and a protected path edited there leaves no telemetry row.pdks-codex initprints this as anote:line. - The roster, not the matcher, decides what is judged. The adapter translates
apply_patchandBash; every other name — a Code Mode name, an MCP tool,write_stdin— is refused with exit 2 and ablockedrunner row before the judge runs. Widening the matcher in.codex/hooks.jsonto such a name blocks every call under it rather than judging it, and the edit changes the trust hash, so the hook is skipped until/hooksapproves it again. write_stdinis not judged again. It delivers input to a unified-exec session that already passedPreToolUse. A shell left open for input is judged once, at the call that opened it.- Hosted tools do not take this path. Web search and its kind do not run through the local function-tool hooks, so they reach no covenant.
- The host calls its tool hooks a guardrail rather than a complete enforcement boundary. Some specialized tool paths can opt out of the default hook path.
- The unstable transcript is not a channel. The payload names a transcript path, but the
host documents that format as unstable, so no judgment reads it. The adapter instead builds
sessionfromUserPromptSubmitandPostToolUse. A fresh configured token, alone on the first line of a human message, can release the retried protected call. If no human evidence was recorded, or evidence storage failed, the recovery message directs the repair to the user terminal.actor,channels, and tool success are never synthesized. - A patch resolving outside the project is refused rather than judged. Such a path has no project-relative form, and an element carrying one would land in no scope — judged over nothing, and passed.
- An unresolvable
polydeukesleaves no row. When the umbrella cannot be resolved from the project there is no process to spawn and no log path to write to, so the hook exits2with one line on stderr and the telemetry log gains nothing. Every other pre-spawn failure does reachpdksand does leave a row.