ai-memory로 Claude Code 세션을 Codex에 넘길 때 건너가는 맥락
Claude Code로 하던 작업을 Codex로 옮겨야 할 때 정말 아까운 것은 코드가 아니라 앞 세션이 알아낸 원인, 그리고 “여기까지만 고치라”는 약속입니다. 코드는 git에 남지만 그 판단은 대화창과 함께 사라집니다. akitaonrails의 ai-memory는 바로 이 틈을 메우겠다는 도구입니다. 하네스 여러 개가 같은 기억 저장소를 쓰고, ai-memory run으로 띄운 세션끼리는 작업 흐름(workstream)을 이어 준다고 설명합니다.
설명만 읽고는 실제로 몇 바이트가 어떤 모양으로 건너가는지, 받는 쪽이 그걸 어떻게 해석하는지 알 수 없습니다. 그래서 작은 파이썬 저장소에 테스트 실패 두 개를 심어 두고, Claude Code가 그중 하나만 고친 상태에서 Codex가 나머지를 이어받게 했습니다. 이 글은 그 과정에서 Codex 프로세스에 도착한 인계 패킷과 임시 기록 파일을 열어 보고, 넘어간 것과 빠진 것, 그리고 이어받기가 기대대로 되지 않았던 장면과 그 처리 방법을 다룹니다.
테스트 저장소와 측정 환경
실험용 저장소는 제목을 URL 조각으로 바꾸는 slugify.py 하나와 테스트 네 개로 만들었습니다. 처음 상태에서는 연속 공백 테스트(test_multi_space)와 한글 테스트(test_korean) 두 개가 실패합니다. 일부러 두 개를 남긴 이유가 있습니다. 첫 세션에는 공백 문제만 고치고 한글 문제는 원인만 설명한 뒤 멈추라고 지시했습니다. 이렇게 해 두면 다음 에이전트가 “앞 세션이 무엇을 했고 무엇을 하지 말라고 들었는지”까지 받았는지를 결과로 가려낼 수 있습니다.
| 항목 | 값 |
|---|---|
| OS | Debian GNU/Linux 13 (trixie), x86_64 |
| ai-memory | 3.1.1 (2026-10-18 03:09 KST 릴리스, 설치 스크립트 체크섬 확인) |
| 넘겨주는 쪽 | Claude Code 2.1.302, --model haiku, 구독 로그인 |
| 받는 쪽 | codex-cli 0.162.1, gpt-6.1-sol, ChatGPT 로그인 |
| 테스트 | Python 3.13.5 unittest, 테스트 4개 |
| 확인일 | 2026-10-18 KST |
ai-memory는 처음 띄울 때 각 하네스의 훅과 MCP 설정을 자동으로 고쳐 씁니다. 같은 서버에서 다른 작업에 쓰는 ~/.claude와 ~/.codex를 건드리고 싶지 않았기 때문에, 실험 전용 HOME을 따로 두고 그 안에서만 설정이 바뀌게 했습니다. 로그인 정보는 원래 위치의 것을 그대로 쓰고, 훅 설정 파일만 실험 HOME 쪽을 읽도록 --settings로 지정했습니다. 격리 방법은 뒤에서 옵션과 함께 다시 정리합니다.
출발점의 실패는 아래와 같습니다. 공백 문제를 Claude가 고친 다음에도 남아 있던 한글 테스트 실패이고, 정규식이 ASCII만 허용해서 한글이 통째로 지워진 결과입니다.
$ python3 -m unittest
.F..
======================================================================
FAIL: test_korean (test_slugify.T.test_korean)
----------------------------------------------------------------------
Traceback (most recent call last):
File "~/demo/test_slugify.py", line 12, in test_korean
self.assertEqual(slugify("클로드 코드 2.1"), "클로드-코드-21")
AssertionError: '-21' != '클로드-코드-21'
Ran 4 tests in 0.001s
FAILED (failures=1)
설치하고 Claude Code 세션을 ai-memory run으로 시작하기
ai-memory는 Rust로 만든 단일 실행 파일입니다. 릴리스에 붙은 설치 스크립트를 받아 버전을 고정해서 실행하면, 아카이브를 받은 뒤 체크섬을 검증하고 ~/.local/bin에 링크를 겁니다. 설치 직후 안내는 ai-memory setup을 실행하라고 하는데, 이 명령은 서버를 systemd 사용자 서비스로 등록하려 합니다. 시험한 서버에는 사용자 세션 버스가 없어서 그 경로가 막혔고, 대신 ai-memory serve --transport http를 직접 띄웠습니다. 서버는 127.0.0.1:49374에만 바인드되고, 접속에는 데이터 디렉터리에 만들어지는 bearer 토큰을 씁니다.

캡처 맨 아래 줄이 이 환경에서 처음 부딪힌 지점입니다. systemctl --user가 세션 버스를 찾지 못하면 setup의 서비스 등록 단계가 진행되지 않습니다. 컨테이너나 SSH로만 들어가는 서버라면 흔히 겪는 상황이라, 저는 serve를 직접 띄우는 쪽을 택했습니다. 상주시켜야 한다면 systemd 시스템 서비스나 tmux 세션처럼 그 서버에서 이미 쓰는 방식에 맞추면 됩니다. status 요약에서 pages와 sessions가 모두 0으로 나오는 것은 기록이 하나도 없는 새 HOME에 막 설치한 상태에서 찍었기 때문입니다.
다음은 Claude Code를 ai-memory 아래에서 띄우는 단계입니다. ai-memory run claude는 처음 실행될 때 Claude Code의 훅과 MCP를 자동으로 연결하고, 세션이 끝나면 대화와 도구 호출을 workstream 이벤트로 저장합니다. 비대화형으로 돌리려고 -p와 허용 도구를 함께 넘겼습니다.
ai-memory run --executable <claude 래퍼> claude -p "<공백만 고치고 한글은 원인만 설명>" \
--model haiku --allowedTools "Read,Edit,Bash(python3 -m unittest*)"

09:51:00에 시작한 세션은 15초 뒤에 끝났고, ai-memory는 이벤트 17개를 저장했다고 알렸습니다. 본문에서 따로 밝히지 않은 숫자는 이 오전 실행의 값입니다. 같은 순서를 10:48~10:50에 한 번 더 실행해 보니 이벤트 25개, 패킷 7,374바이트, 임시 기록 5,717바이트, Codex 토큰 8,585개로 실행마다 값이 꽤 달랐고, 글에 실은 터미널 화면 네 장은 이 두 번째 실행에서 찍은 것입니다. Claude는 \s를 \s+로 바꿔 공백 테스트를 통과시켰고, 한글 테스트는 손대지 않은 채 5행의 문자 클래스가 원인이며 [^\w\s-]로 바꾸되 _는 따로 처리해야 한다고 정리하고 멈췄습니다. 여기까지가 Codex가 이어받아야 할 맥락입니다. 고친 것, 원인, 고칠 방향, 그리고 “한글은 고치지 말라”는 당시의 제약까지 포함됩니다.
Codex 쪽에 실제로 도착한 인계 패킷
ai-memory의 설명은 Codex가 두 가지를 받는다고 합니다. 하나는 서버가 만든 크기가 정해진 시작 패킷이고, 다른 하나는 직전 하네스의 화면에 보이던 기록을 옮긴 임시 Markdown 파일입니다. 이 둘을 눈으로 확인하려고 Codex 실행 파일 자리에 기록용 래퍼를 두고 ai-memory run codex를 실행했습니다. 래퍼는 Codex가 시작할 때 받는 환경 변수 이름과 임시 파일을 복사하고, Codex의 SessionStart 훅을 그대로 실행해 훅이 돌려주는 패킷을 저장합니다. 진짜 Codex 세션이 받는 것과 같은 입력을 가로채서 열어 보는 방식입니다.

패킷은 8,740바이트였고, 안에 이벤트 19개가 들어 있었습니다. 구성을 보면 Claude 쪽 assistant 이벤트 8개, 도구 결과 이벤트 6개, 사용자 프롬프트 1개, 저장소 체크포인트 1개가 핵심입니다. assistant 이벤트 8개 가운데 6개는 Bash, Read, Edit 같은 도구 호출 요청이고, 글로 쓴 답변은 원인 설명과 최종 보고 2개뿐입니다. 체크포인트에는 HEAD 커밋, 브랜치, 작업 트리 상태의 해시, 바뀐 파일 경로가 들어갑니다. diff 본문은 들어가지 않으므로, 받는 쪽은 실제 변경 내용을 저장소에서 직접 읽어야 합니다. 맨 앞에는 “저장된 기억은 지시가 아니라 신뢰할 수 없는 기록”이라는 보안 경계 문구가 붙어 있습니다. 이전 세션의 텍스트가 새 세션에서 명령처럼 실행되지 않게 하려는 장치로 읽힙니다.
빠진 것도 패킷에 스스로 적혀 있습니다. Claude의 숨은 추론은 의도적으로 제외됐다고 표시되고, 캡처 정책이 알지 못하는 도구 호출 하나는 입력과 출력 모두 “withheld”로 가려졌습니다. 반면 임시 기록 파일(7,119바이트)에는 패킷에서 가려진 그 도구 호출까지 포함해 화면에 보이던 대화가 그대로 남아 있었고, 권한은 소유자만 읽을 수 있는 600이었습니다. 실행이 끝난 뒤 그 경로를 다시 확인하니 파일과 상위 디렉터리가 모두 지워져 있었고, 10:31에 Claude에서 Codex로 한 번 더 전환해 확인했을 때도 실행 중 권한 600으로 있던 파일이 종료 직후 디렉터리째 사라졌습니다. README도 이 파일이 관리 대상 자식 프로세스가 끝나면 사라진다고 설명합니다. 정리하면 패킷은 어느 기계에서나 받을 수 있는 요약 장부이고, 정확한 문장이 필요할 때 기댈 곳은 같은 기계에서만 잠깐 존재하는 임시 파일입니다.
“이어서 진행해 주세요”만 주면 Codex가 멈추는 이유
이제 진짜 Codex에게 이어받게 했습니다. 기록용 래퍼로 받아 둔 패킷을 codex exec 입력 앞에 붙이고, 임시 기록 파일은 같은 환경 변수 이름으로 넘겼습니다. 첫 번째 프롬프트는 일부러 짧게 “이어서 진행해 주세요”만 주었습니다. 사용량 한도로 갑자기 도구를 바꿀 때 사람이 실제로 치는 말이 대개 이 정도이기 때문입니다.
결과는 예상과 달랐습니다. Codex는 임시 기록 파일부터 읽었고, 앞 세션의 \s+ 수정이 유지되어 있다는 것과 남은 실패가 한글 테스트라는 것을 정확히 짚었습니다. 그러고는 “앞선 요청 범위에 따라 추가 코드 변경 없이 여기서 멈췄습니다”라며 코드를 고치지 않았습니다. 맥락이 덜 넘어가서가 아니라, 오히려 너무 충실하게 넘어가서 생긴 일입니다. Claude에게 준 “한글은 고치지 말라”는 제약까지 이어받은 것입니다. 이 실행에 든 토큰은 8,335개였습니다.
두 번째 실행에서는 같은 패킷에 “이전 세션이 정리한 고칠 방향대로 이제 test_korean도 고쳐 주세요”라고 다음 작업을 이름으로 지정했습니다. Codex는 앞 세션이 제안한 방향 그대로 문자 클래스를 [^\w\s-]|_로 바꿨고, 테스트 네 개가 모두 통과했습니다. 토큰은 13,238개가 들었습니다. 앞 세션이 적어 둔 방향, 곧 [^\w\s-]로 바꾸고 _는 따로 처리한다는 내용이 그대로 반영됐다는 것은 아래 diff에서 확인할 수 있습니다. 처음 커밋과 비교한 diff라서 \s+ 줄은 앞 세션의 Claude가 고친 것이고, [^\w\s-]|_ 줄이 이번에 Codex가 바꾼 것입니다.
diff --git a/slugify.py b/slugify.py
index 65e3ed8..0b1ffc1 100644
--- a/slugify.py
+++ b/slugify.py
@@ -2,6 +2,6 @@ import re
def slugify(title: str) -> str:
s = title.strip().lower()
- s = re.sub(r"[^a-z0-9\s-]", "", s)
- s = re.sub(r"\s", "-", s)
+ s = re.sub(r"[^\w\s-]|_", "", s)
+ s = re.sub(r"\s+", "-", s)
return s
이 상태에서 테스트를 다시 돌린 결과입니다.
$ python3 -m unittest
....
----------------------------------------------------------------------
Ran 4 tests in 0.000s
OK

_ 제거) 없이 [^\w\s-]만 골랐고 토큰 8,585개, 테스트 4개 통과두 실행을 나란히 놓고 보니 규칙이 하나 생겼습니다. 인계가 잘될수록 앞 세션의 제약도 함께 따라온다는 것입니다. 그러니 넘겨받는 쪽 프롬프트에는 “이어서”가 아니라 이번에 풀어 줄 제약과 다음 작업 이름을 적어야 합니다. 이번에 실제로 통한 문장은 “이전 세션이 정리한 고칠 방향대로 이제 test_korean도 고쳐 주세요”였습니다. 앞 세션의 분석을 쓰라는 지시와, 앞 세션이 금지했던 작업을 이제 하라는 지시가 한 문장에 같이 들어 있습니다.
다만 같은 구체적 지시로 10:50에 한 번 더 실행했을 때는 위 화면처럼 Codex가 |_ 없이 [^\w\s-]만 넣었고 테스트는 그대로 통과했습니다. 인계가 앞 세션의 수정안을 글자 그대로 재현해 주는 것은 아니고, 받은 쪽이 테스트를 통과하는 다른 수정을 고를 수 있다는 뜻입니다. 테스트 파일의 네 가지 입력에는 밑줄이 하나도 없어서 |_가 있든 없든 결과가 같습니다. 예를 들어 “snake_case title”은 |_가 있으면 snakecase-title, 없으면 snake_case-title이 되는데, 이 차이를 잡는 테스트는 없습니다.
자동 배선, 훅 신뢰, 사용자 서비스에서 걸리는 지점
Codex 쪽 첫 실행에서 ai-memory는 ~/.codex/config.toml과 hooks.json을 새로 만들고, Codex가 새 훅을 쓰려면 명시적인 신뢰가 필요하다고 안내했습니다. 대화형 화면에서는 ‘Hooks need review’가 뜨고, 자동화된 설치라면 codex --dangerously-bypass-hook-trust로 건너뛸 수 있다고 합니다. 이름 그대로 위험한 옵션입니다. 저는 훅 스크립트를 먼저 읽고, 사람이 한 번 TUI에서 신뢰를 누르는 쪽을 권합니다. Codex 훅 문서를 보면 신뢰는 훅 정의의 해시에 묶이고, 내용이 바뀐 훅은 다시 검토 대상이 됩니다. 우회 옵션은 이 검토를 그 실행에서 통째로 건너뛰므로, CI처럼 사람이 없는 곳에서 기본으로 켜 두면 누가 훅 파일을 바꿔도 모른 채 실행됩니다.
첫 실행에는 경고가 한 줄 더 있었습니다. 원문은 아래와 같고, 같은 조건으로 한 번 더 실행했을 때는 나오지 않았습니다. 자동 배선이 처음 일어난 실행에서는 패킷 수신 확인이 돌아오지 않을 수 있다는 뜻으로 읽었고, 그래서 중요한 작업을 넘기기 전에는 빈 실행을 한 번 해서 배선부터 끝내 두는 편이 안전합니다.
ai-memory: this harness did not acknowledge its managed context packet; refresh its ai-memory hooks before the next run
Codex 실행을 모두 마친 뒤인 10:31에 ai-memory handoffs로 열린 인계 목록을 조회하면 “No open handoffs”가 나왔고, 같은 시각 ai-memory workstreams에는 workstream 하나에 codex와 claude-code가 함께 묶여 있었습니다. README 설명으로는 소유자가 정해지고 한 번만 가져가는 인계(handoff)는 이전 하네스가 직접 써서 남기는 기록이고, 한도 소진처럼 갑자기 끝나서 인계를 쓸 틈이 없는 경우를 임시 기록 파일이 메운다고 합니다. 이번 실험에서 실제로 맥락을 옮긴 것은 workstream 패킷과 임시 기록 파일이었습니다. 인계 목록이 비어 있다고 해서 넘어간 것이 없다고 판단하면 안 되는 이유입니다.
다른 사람과 같이 쓰는 개발 서버라면 격리도 챙겨야 합니다. ai-memory는 --executable로 실제 실행 파일을 바꿔 끼울 수 있고, 처음 실행할 때 자동 배선을 끄려면 --no-autowire를 주면 됩니다. 설정 파일을 덮어쓰는 도구를 들일 때는 이 두 옵션으로 먼저 격리된 HOME에서 무엇이 바뀌는지 본 뒤 본래 환경에 적용하는 것이 제 기준입니다.
README에는 20개가 넘는 하네스를 지원하고 쓰기 한도가 초당 약 700건이라고 적혀 있습니다. 이 글에서 직접 돌린 것은 Claude Code와 Codex 두 개이므로, 나머지는 프로젝트의 주장으로 읽으면 됩니다. setup 계획 출력에는 cursor, gemini-cli, open-code, grok 등 추가 가능한 에이전트 이름이 나열되어 있었습니다. 사용법 전체는 managed workstreams 문서와 v3.1.1 릴리스에 있습니다(확인일 2026-10-18).
넘겨받는 쪽 기준으로 정리한 제 사용법
| 항목 | 넘어간 것 | 넘어가지 않은 것 |
|---|---|---|
| 시작 패킷 (8,740바이트) | 사용자 프롬프트, assistant 이벤트 8개(도구 호출 6·답변 2), 도구 결과 6개, 체크포인트 | 숨은 추론, 정책이 모르는 도구 호출의 입력·출력 |
| 저장소 체크포인트 | HEAD, 브랜치, 작업 트리 해시, 바뀐 경로 | diff 본문 |
| 임시 기록 파일 (7,119바이트) | 화면에 보이던 대화 전체, 권한 600 | 실행이 끝나면 삭제, 다른 기계로는 안 감 |
| Codex의 해석 | 앞 세션의 원인 분석과 수정 방향 | 짧은 “이어서”만으로는 제약을 스스로 풀지 않음 |
시험해 본 결과만 놓고 보면, ai-memory의 이어받기는 “요약 한 단락”보다 훨씬 많은 것을 넘깁니다. 앞 세션이 실제로 실행한 명령과 출력, 그리고 사용자가 걸어 둔 제약까지 넘어가기 때문에, Codex가 원인을 다시 조사하지 않고 바로 앞 세션의 결론을 이어 쓸 수 있었습니다.
같은 기계에서 Claude Code와 Codex를 번갈아 쓰는 개인 개발 환경이라면 ai-memory run으로 두 하네스를 띄우는 방식을 시도해 볼 만합니다. 조건은 있습니다. 이번 실험에서 Codex 쪽은 기록 래퍼로 받은 패킷을 codex exec 입력에 직접 넣어 이어받게 했고, 그 래퍼를 처음 띄운 실행에서는 패킷 수신 확인이 없다는 경고가 나왔습니다. 그러니 처음에는 빈 실행으로 훅 배선과 Codex 훅 신뢰를 먼저 끝내 두고, 넘길 때마다 받는 쪽 프롬프트에 다음 작업 이름과 풀어 줄 제약을 적는 것을 규칙으로 삼기를 권합니다.
팀 공용 서버나 CI에서 쓰려는 경우에는 서두르지 않겠습니다. 훅 자동 배선이 사용자 설정 파일을 바꾸고, Codex 훅 신뢰를 건너뛰는 옵션은 감사 흔적 없이 스크립트를 실행하게 만들기 때문입니다. 격리된 HOME에서 바뀌는 파일 목록을 먼저 확인하고, 훅 스크립트를 리뷰한 다음에 들이는 순서를 택합니다.
다른 기계로 작업을 옮기는 경우라면 기대치를 낮춰 두는 편이 맞습니다. 임시 기록 파일은 처음 기계에만 있고 실행이 끝나면 사라지므로, 받는 쪽은 서버의 요약 장부만 받습니다. 정확한 오류 원문 같은 세부가 꼭 필요하다면 그 내용은 커밋 메시지나 이슈처럼 저장소 쪽에 남겨 두는 것이 여전히 가장 확실합니다.





