Add Memory to Codex¶
Codex sends hook_event_name on stdin and reads
hookSpecificOutput.additionalContext from stdout, exactly as Claude Code
does. The same popoto-memory hook command string serves both, so the
behavior is identical once it is wired up.
Wiring it up is not identical. Codex gates hooks twice, and both gates are worth understanding before you debug a silent failure.
Pre-release
popoto[mcp] is not on PyPI yet. Until a release ships, install from a
checkout: pip install -e '.[mcp]'.
Install¶
Write ~/.codex/hooks.json:
{
"hooks": {
"user_prompt_submit": [
{ "hooks": [{ "type": "command", "command": "popoto-memory hook" }] }
],
"stop": [
{ "hooks": [{ "type": "command", "command": "popoto-memory hook", "async": true }] }
]
}
}
Note the snake_case event names: Codex matches hooks on user_prompt_submit
and stop in configuration, while the payload it sends carries
hook_event_name: "UserPromptSubmit". The adapter accepts both spellings.
Turn hooks on in ~/.codex/config.toml:
Then, inside Codex, run /hooks and approve the entry.
The trust review is a feature, not a step to skip¶
Codex hashes every non-managed command hook and skips it until you have reviewed that exact definition. Editing the command re-triggers the review.
That is correct behavior. A hook is an arbitrary command that Codex runs on your behalf before and after every turn, with your environment. A memory integration that silently installed one would be indistinguishable from something worse. Read the command, then approve it.
There is a --dangerously-bypass-hook-trust flag for automation. The name
is accurate; do not use it interactively.
The failure mode that wastes an afternoon¶
A project-level .codex/hooks.json in an untrusted project is skipped
with no message at all. Codex does not warn, log, or fail. Memory simply
never happens.
This was reproduced first-hand against codex-cli 0.144.4: with a project
.codex/hooks.json and codex exec --enable hooks
--dangerously-bypass-hook-trust, no hook ran and nothing was reported.
Install at ~/.codex/hooks.json instead.
POPOTO_MEMORY_AGENT_ID tags writes and is honored as a read filter only on
the composite-score retrieval path — the shipped default lexical/BM25 path
does not filter by it yet (#576), so setting it per project does not
isolate one project's memories from another's on the same database:
If you need real per-project isolation today, point each project at its own
POPOTO_MEMORY_URL database instead.
Context limit¶
Codex caps injected context at 2500 tokens (additionalContextLimit). The
default POPOTO_MEMORY_MAX_TOKENS is 800, comfortably under it. If you
raise it, stay below the cap: Codex will truncate rather than tell you.
MCP tools¶
Or in ~/.codex/config.toml:
This registers memory_search, memory_save, memory_feedback, and
memory_status. It does not give you the subconscious loop: MCP tools
only run when the model elects to call them. If you configure MCP and skip
the hooks, you have instructed memory, not subconscious memory.
Verify¶
After a few turns, last assemble and last capture carry recent
timestamps and records climbs. If both stay at never, the hooks are not
firing: check [features] hooks = true, check that you approved them via
/hooks, and check that the file is at ~/.codex/hooks.json rather than in
a project.
Test the command directly, which is what Codex does:
echo '{"hook_event_name":"UserPromptSubmit","session_id":"t","prompt":"how do deploys work?"}' \
| popoto-memory hook
Verified against¶
codex-cli 0.144.4. The hook input contract was read from that binary's own
schema: session_id, transcript_path, hook_event_name,
permission_mode, turn_id, agent_transcript_path, agent_type,
last_assistant_message, prompt, cwd, stop_hook_active -- matching
Claude Code field for field, which is why one executable serves both.
The fixtures in tests/fixtures/harness_payloads/codex_*.json are derived
from that schema, not from a live turn: the live capture attempt hit the
silent project-level skip described above. A live acceptance run on a
trusted project is still outstanding. Each fixture records this in its
_provenance field.