Skip to main content

Codex Dual Harness — AGENTS.md, Dual Agent Publication, Hook Adapter NEW

The shared and harness-local surfaces that let codex-cli use MoAI-ADK alongside Claude Code.

UPDATED 2026-09-28 7 min read EDIT ON GITHUB ↗

MoAI-ADK’s primary harness (the runtime that actually drives agents) is Claude Code, but as of v3.1.3 it carries a dual surface that codex-cli can read too. Shared rules and agent definitions are published in the locations and formats Codex expects, while personal instructions stay separated by harness. This page explains those surfaces and the problem each one solves.

The root AGENTS.md — a standing contract for any harness

The root AGENTS.md is a standing contract that binds a turn regardless of which agent harness drives it — not a Claude-specific file. It is one file because of how codex reads: it consumes project instructions under a byte cap and drops the overflow silently, with no warning and exit 0. A contract that does not fit reports as complete. Fitting under the ceiling is therefore itself a requirement, and a build guard (a check run at build time that the file stays under it) holds it there.

Making room took eleven always-loaded documents down to stubs (short summaries) pointing at eight lazy companions (detail documents read only on demand). What moved was the prose explaining each obligation, never the obligation itself — AGENTS.md is the canonical cross-harness contract, while .claude/rules/moai/** and CLAUDE.md expand Claude-only mechanisms.

Info
A personal ~/.codex/AGENTS.md joins the same merged chain and is consumed before this file, narrowing what the project’s contract can carry. Overflow is dropped from the tail silently — which is why the clauses in this file are ordered most-critical-first.

Dual agent publication — twelve TOMLs

The 12 retained MoAI-custom agents are published in two forms: .claude/agents/moai/*.md for Claude Code (the source) and .codex/agents/moai/*.toml for codex (the derivation). The TOML is not hand-written — internal/template/agentemit generates it deterministically (same input, same output, every time) from the markdown source, and the generated file’s header says “regenerate, do not edit”.

Three guards keep source and derivation from drifting apart: a golden-file comparison (against expected output), an embed check (against the templates compiled into the binary), and a deploy check (against what lands in user repositories). Edit the markdown and the TOML follows; edit only the TOML and the guards catch it.

.agents/skills — the skill mirror

codex-cli does not read Claude Code’s .claude/skills/, so skills are deployed as a mirror under .agents/skills. The mirror list is not hand-maintained — it is derived from the actual skill set at deploy time, so it cannot go stale as skills come and go. The directory is a deployment artifact aimed outside user repositories and is never committed; it prefers a symbolic link, falling back to a copy where links cannot be created (the moai init / moai update completion summaries say so — see the moai update page).

Harness-local personal instructions

AGENTS.local.md is the personal instruction file both harnesses read; CLAUDE.local.md is its former name and survives only as a fallback input. Every local moai codex route—bare, cli, app, --spawn, and -w—reads the non-empty regular files from the project root in that order, places a <!-- source: <filename> --> provenance header before each body, and sends the combined text as one developer_instructions override. For -w, the source stays the original project root even though Codex runs in the worktree. Neither shared AGENTS.md nor CLAUDE.md imports or links these local files. The launcher refuses links and non-regular inputs, reads from the same descriptor it checked, and fails before launch if the operator also supplies developer_instructions or if the direct/spawn argument is too large. Other harness-local settings and memory remain with their harness. Codex Web does not run the local launcher and receives none of this injection.

The CLAUDE.local.md fallback and the migration advisory

In a project that has CLAUDE.local.md but no AGENTS.local.md, moai codex still reads that file as a fallback and places it in developer_instructions. The provenance header carries the literal name of the file actually read — here, <!-- source: CLAUDE.local.md -->.

When the fallback is taken, the launcher prints a one-line migration advisory on standard error:

text
Advisory: CLAUDE.local.md is a legacy local instruction file; run `moai migrate local-instructions` to move it to AGENTS.local.md.

The advisory is addressed to the operator, so it never enters developer_instructions, which becomes model context. When both files exist, both are read and the advisory switches to asking for a manual merge. In the three-file structure — the shared contract in AGENTS.md, the Claude-only layer in CLAUDE.md, personal instructions in AGENTS.local.md — CLAUDE.local.md is only a former name waiting to be migrated.

internal/codexadapter — the hook adapter library

The two harnesses’ hook surfaces are nearly but not exactly the same. Measurement (against codex-cli 0.153.4) found three divergences: the event name the harness passes, three output keys codex declares but does not act on (systemMessage, continue, stopReason), and the PreToolUse decision contract — the codex parser rejects permissionDecision:allow without updatedInput and rejects permissionDecision:ask outright. internal/codexadapter is a thin translation layer that sits in front of the dispatcher (nothing under internal/hook is modified); refused decision shapes (allow, ask, defer) degrade to the no-opinion {}, handing the choice to codex’s own approval flow, each drop is announced through the discard sink, and a blank-reason deny gains a default reason.

The 12-event table

Codex eventMoAI dispatcher argAdapted this milestone?
PreToolUsepre-toolyes
PostToolUsepost-toolyes
SessionStartsession-startyes
SessionEndsession-endyes
Stopstopyes
UserPromptSubmituser-prompt-submityes
PreCompactcompactno — compaction never triggered non-interactively
PostCompactpost-compactno — compaction never triggered non-interactively
PermissionRequestpermission-requestno — approval request never raised non-interactively
SubagentStartsubagent-startyes
SubagentStopsubagent-stopyes
Interrupt— (no counterpart)no — fires on SIGINT; adaptation needs a new dispatcher subcommand (follow-up)

Twelve events are covered: eleven have a dispatcher counterpart, and Interrupt — the 12th officially documented event — is recognized and refused with a distinct no-counterpart message until a dispatcher subcommand exists. On codex-cli 0.153.4, SubagentStart and SubagentStop were measured to fire (reversing an earlier 0.147.0 observation that SubagentStop never fired) and are now adapted: RenderHooks installs moai hook subagent-start --harness codex and moai hook subagent-stop --harness codex into the user’s .codex/hooks.json. The compact and permission events are held back on honest evidence, not assumption: non-interactive codex exec runs never reached compaction (best effort 264,808 input tokens against a measured 1,048,576-character input cap) and never raised an approval request — recorded as trigger-not-achieved, never as “does not fire”.

Unadapted events are not silently ignored — they are refused. An unknown event (a typo) and a recognized-but-unadapted event (a scoping decision or a missing counterpart, as with Interrupt) return distinct errors, so an operator can tell a mistake from a decision. The config validator collects every unknown-key violation instead of stopping at the first.

What invokes it now

RenderHooks writes all eight adapted event commands into the user’s .codex/hooks.json; moai init --llm codex|both creates that wiring, and moai tool enable codex adds or refreshes it in an existing project.

Desktop apps and the v3.1.3 release gate

For a local Codex app session, open the project that contains the generated .codex/hooks.json and .codex/config.toml. Codex loads project hooks and configuration only after the project is trusted; use /hooks in the Codex CLI to review the hooks, and trust them again if their definitions change. Run moai doctor in the same project to inspect the MoAI wiring. The moai executable must be available to the app’s execution environment. See the Codex hooks documentation for the project trust rules.

Claude Code for Desktop’s Local Code session reads the same project CLAUDE.md, .mcp.json, hooks, skills, and settings as the CLI. Open the initialized project in its Code tab and make sure moai is available in that session’s environment. An SSH session runs on the remote machine, so that machine needs the project and moai installation. See Claude Code Desktop configuration.

These are setup paths, not a claim that the v3.1.3 desktop release gate has passed. Release remains on hold until the operator completes their own MoAI, Codex, and both desktop-app tests and declares the result.

Next steps