| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | ||
| 6 | 7 | 8 | 9 | 10 | 11 | 12 |
| 13 | 14 | 15 | 16 | 17 | 18 | 19 |
| 20 | 21 | 22 | 23 | 24 | 25 | 26 |
| 27 | 28 | 29 | 30 |
- cyphenengine
- 일기
- JavaScript
- C++
- 입출력과 사칙연산
- 주우석
- 박기현
- 메타버스
- Noam Nisan
- The Elements of Computing Systems 2/E
- 이득우의 게임수학
- BOJ
- C
- 생능출판
- HANBIT Academy
- 알고리즘
- 전공자를 위한 C언어 프로그래밍
- 잡생각 정리글
- C#
- hanbit.co.kr
- 밑바닥부터 만드는 컴퓨팅 시스템 2판
- 데이터 통신과 컴퓨터 네트워크
- 이득우
- unity6
- 백준
- (주)책만
- 김진홍 옮김
- 게임 수학
- booksr.co.kr
- https://insightbook.co.kr/
- Today
- Total
cyphen156
답답해서 만드는 에이전트 환경 동기화 프로젝트 #3 대화 세션 동기화 도구 전면 리팩토링 후기 본문
답답해서 만드는 에이전트 환경 동기화 프로젝트 #3 대화 세션 동기화 도구 전면 리팩토링 후기
cyphen156 2026. 9. 17. 19:54이전에 썻던 글들은 다음과 같다.
- [답답해서 만드는 에이전트 환경 동기화 프로젝트 #1 에이전트 대화 동기화하기]
- [답답해서 만드는 에이전트 환경 동기화 프로젝트 #2 작업표시줄 고정용 Start / Finish 런처 만들기]
- [람다 같은 녀석들 : 에이전틱 코딩과 통제력 상실]
- [에이전트 대화 세션 동기화 도구 드디어 고쳐서 올리는 글]
특히 마지막 글에서는 2주 동안 개고생하면서 고쳤다는 이야기만 짧게 썼는데, 그 뒤로도 여러 가지 문제점이 발견됐다.
이 글에서는 그동안 진행한 리팩토링 과정과 결과에 대해 정리하려 한다.
그리고 먼저 인정하고 시작해야 할 게 있다.
첫 번째 글에서 설명한 구조 중 일부는 지금 기준으로는 맞지 않는다.
그때 직접 옮겨보고 앱에 대화가 나타나는 것까지 확인한 것은 맞다.
하지만 대화가 한 번 나타나는 것과, 두 PC를 계속 오가면서 제목·프로젝트·삭제·긴 대화의 이전 기록까지 안전하게 유지되는 것은 전혀 다른 문제였다.
그래서 구조는 여러 차례 뜯어고쳐지면서 지금의 모습이 됐다.
세션 동기화 도구가 살아남게 된 계기
사실 2026년 9월 현재 ChatGPT(Codex)와 Claude Code 앱은 모두 클라우드 환경을 통해 대화 세션을 이어갈 수 있는 기능을 제공한다.
그래서 마지막 리팩토링을 진행하던 9월 15일에 가장 먼저 했던 고민은 이것이었다.
내가 이걸 왜 계속 유지해야 하지?
과거 GPT-3.5를 사용할 때도 했던 고민이다.
나는 앱이 제공하지 않는 기능이 필요하면 직접 만들어서 사용하지만, 이후 공식 앱에서 같은 요구를 충분히 해결해주면 더 이상 개인 도구를 유지하지 않고 앱의 편의성을 이용하는 방향을 선택한다.
당시에는 ChatGPT 데스크톱 앱도 없었고, 사용량 제한도 지금보다 빡빡했다.
프로젝트 단위의 컨텍스트라는 개념도 존재하지 않았다.
그래서 VS Code Extension과 내장 터미널 CLI, ChatGPT API를 이용하는 별도의 환경을 만들어 사용했다.
하지만 공식 앱이 내가 필요했던 기능을 제공하기 시작한 뒤에는 그 환경을 계속 유지하지 않았다.
AgentSessionSync도 같은 기준으로 판단해야 했다.
공식 기능이 내 요구사항을 만족한다면, 이 복잡한 동기화 도구를 계속 고쳐가며 유지할 이유가 없다.
원래 목적은 지금도 같다.
Desktop과 Laptop 사이에서 에이전트와의 대화 세션이 끊김 없이 이어질 것.
여기에는 몇 가지 조건이 더 있다.
대화를 따로 보관하는 것 자체가 목적은 아니다.
나는 하나의 대화에서 꽤 오랫동안 설계를 이어가는 경우가 많다.
중간에 컨텍스트 압축이 발생하더라도
같은 대화 안에 원문과 판단 과정이 남아 있는 것과,
새 대화를 열고 문서를 읽혀서 비슷한 문맥을 다시 만드는 것은 다른 일이라고 생각한다.
그래서 원래 작업하던 대화를 다른 작업 환경에서도 그대로 이어갈 수 있어야 한다.
다만 에이전트의 메모리까지 맞추는 것은 포기하기로 했다.
그러면 남는 것은 대화 세션을 옮기는 기계적인 작업뿐이다.
그래서 동기화 도구를 유지할지 결정하기 전에, 앱이 제공하는 기능만으로 내 요구사항을 만족할 수 있는지 먼저 비교해봤다.
Claude Remote Control
Remote Control은 원래 PC에서 실행 중인 Claude Code 세션을 브라우저나 모바일 앱으로 조작하는 기능이다.
대화도 그대로 이어지고, 원래 PC의 파일, MCP 서버, 도구와 프로젝트 설정도 그대로 사용할 수 있다.
하지만 실행 장소는 끝까지 원래 PC다.
터미널이나 VS Code를 닫아 Claude 프로세스가 종료되면 원격 세션은 오프라인이 되고, 원래 PC에서 다시 살려야 이어갈 수 있다.
즉 다른 PC로 대화를 옮기는 기능이 아니라, 켜져 있는 원래 PC를 다른 화면에서 조작하는 기능이다.
이걸 쓴다는 건 결국 원격 조작을 쓰겠다는 것이다.
그럼 굳이 이 기능을 쓸 이유가 있나? PC 자체를 원격 조작하면 그만인데?
Claude Code on the Web와 --teleport
Claude Code on the Web은 Anthropic의 클라우드 VM에서 작업한다.
원래 PC를 꺼도 작업은 계속되고, 클라우드에서 시작한 세션은 --teleport로 로컬 터미널에 가져올 수 있다.
공식 문서상 이때 전체 대화 기록과 해당 브랜치를 불러온다.
하지만 CLI에서의 이동 방향은 클라우드에서 로컬로 가져오는 한쪽뿐이다.
이미 로컬에서 진행 중인 대화를 클라우드로 올릴 수는 없다.
게다가 가져온 세션은 터미널 쪽 사본이 되어, 그 뒤 로컬에서 한 작업은 클라우드 세션에 반영되지 않는다.
두 PC를 계속 오가는 동기화가 아니라, 한 번 갈라져 나오는 복사다.
그리고 근본적인 질문이 하나 남는다.
이 방식은 저장소 전체가 Anthropic의 VM에 복제되고, 명령도 그곳에서 실행된다.
내가 로컬 프로젝트를 쓰는 이유는
작업 환경을 내 PC 안에 격리해두기 위해서인데, 실행 환경 자체를 클라우드로 넘기는 것이다.
그럴 거면 처음부터 클라우드 환경을 썼지 안그래?
Claude Desktop의 Continue in → Web
Desktop에서는 로컬 세션을 웹으로 보낼 수 있다.
다만 기존 로컬 세션 파일을 그대로 옮기는 방식이 아니다.
작업 트리가 깨끗한 상태에서 현재 브랜치를 푸시하고, 대화를 요약한 뒤, 그 요약을 문맥으로 사용하는 새로운 원격 세션을 만든다.
작업을 클라우드에서 계속할 수는 있지만, 내가 원한 로컬 대화 원문의 이전은 아니다.
그래서 클로드 쪽은 어떤 기능으로도 내 요구사항을 충족하지 못했다.
Codex Remote
Codex Remote도 구조는 Claude Remote Control과 같다.
다른 기기에서 대화를 열고 지시할 수 있지만, 실제 파일과 도구를 사용하는 곳은 원래 PC다.
원래 PC에서 ChatGPT 앱이 실행 중이어야 하고, 네트워크에도 연결되어 있어야 한다.
결국 이것도 세션 이전이 아니라 원격 조작이다.
Codex Handoff
Handoff는 솔직히 내 요구사항에 가장 가까운 기능이었다.
기존 대화와 Git 상태를 연결된 다른 PC로 옮기고, 대상 PC의 작업 트리에서 같은 대화를 계속할 수 있다.
실제로 동작만 한다면 내가 만든 도구에서 Codex 지원을 빼도 될 정도였다.
하지만 조건이 붙는다.
인계하는 순간 대상 PC가 켜져 있고 연결되어 있어야 한다.
두 PC에는 같은 Git 저장소의 프로젝트가 등록되어 있어야 하며, 저장소의 하위 폴더를 프로젝트로 쓴다면 그 위치까지 서로 같아야 한다.
즉 PC A에서 작업을 끝낸 뒤 PC B가 꺼진 상태로 세션을 맡겨두고, 나중에 PC B를 켜서 가져오는 방식이 아니다.
자리를 옮기기 전에 두 PC를 연결해 그 자리에서 인계를 끝내야 한다.
여기서 내가 만든 도구인 AgentSessionSync의 장점을 발견했다.
AgentSessionSync는 두 PC를 직접 연결하지 않는다.
PC A에서 작업을 마친 뒤 Finish를 실행하면 대화 세션을 중간 저장소인 Vault에 게시한다.
그 순간 PC B가 꺼져 있어도 상관없다.
나중에 PC A를 끄고 PC B를 켠 뒤 Start를 실행하면,
Vault에 게시된 세션을 받아 PC B의 앱과 로컬 프로젝트에 다시 연결한다.
반대 방향도 똑같다.
Handoff가 두 PC를 실시간으로 연결해 그 자리에서 넘겨주는 방식이라면,
AgentSessionSync는 한쪽이 세션을 맡겨두고 다른 쪽이 나중에 찾아가는 방식이다.
두 PC를 동시에 켜둘 필요가 없다는 것.
이게 공식 기능과 비교한 AgentSessionSync의 확실한 장점이었다.
여기까지만 문제였다면 사용 방식을 맞춰볼 수도 있었을 것이다.
그런데 실제로 시험해보니 오래 이어온 대화가 pagination된 상태에서는 앱이 Handoff를 거부했다.
정작 내가 가장 많이 옮기려는 것이 오래 사용한 대화인데, 그 대화가 길어졌다는 이유로 인계가 막힌 것이다.
이 pagination 제한은 공식 문서에 적힌 내용이 아니라 직접 사용하면서 확인한 결과다.
두 PC를 동시에 켜둬야 하고, 긴 대화는 실제로 인계가 거부되기도 한다.
그러면 내가 원한 요구사항을 전부 충족하지는 못한다.
기능별 차이를 같은 기준으로 놓으면 다음과 같다.
| 기능 | 실제로 하는 일 | 원래 PC를 끈 뒤 계속 가능 | 같은 대화 기록 유지 | 대상 PC의 로컬 환경에서 실행 | 내 요구사항 충족 |
|---|---|---|---|---|---|
| Claude Remote Control | 실행 중인 원래 PC의 로컬 세션을 원격으로 조작 | ❌ | ✅ | ❌ 원래 PC에서 실행 | ❌ |
Claude Code on the Web → --teleport |
클라우드 세션과 전체 대화 기록을 로컬 터미널로 가져옴 | ✅ | ✅ | ✅ 가져온 뒤에는 가능 | △ 로컬 PC 간 양방향 이전이 아님 |
| Claude Desktop Continue in → Web |
브랜치를 푸시하고 대화 요약으로 새로운 원격 세션을 생성 | ✅ | ❌ 원문 대신 요약 | ❌ 클라우드에서 실행 | ❌ |
| Codex Remote | 원래 PC에서 실행되는 Codex를 다른 기기에서 원격으로 조작 | ❌ | ✅ | ❌ 원래 PC에서 실행 | ❌ |
| Codex Handoff | 연결된 다른 PC로 기존 대화와 Git 상태를 이동 | △ 인계 순간 두 PC가 연결되어 있어야 함 | △ 긴 대화의 pagination 상태에서 실제 거부 사례 확인 | ✅ | △ 가장 가깝지만 사후 수신 불가 |
참고한 공식 문서는 다음과 같다.
- [Claude Code Remote Control]
- [Claude Code on the Web]
- [Claude Code Desktop]
- [Codex 원격 연결과 호스트 간 채팅 인계]
결국 내가 원한 것은 처음부터 끝까지 같았다.
클라우드나 동일한 실행 환경에 계속 묶여 있어야 한다는 제약을 벗어나, 필요할 때 사용자가 정한 기준을 가지고 작업을 이어가는 것.
다시 되짚어봐도 이 요구는 여전히 앱이 해결해주지 못했다.
그래서 AgentSessionSync는 살아남게 됐다.
마감까지 오래걸린 이유 : 프로젝트에 대한 통제권 상실
처음 두 달은 그냥 잘 썼다.
6월 20일에 올리고, 런처 고치고, 앱이 안 꺼지는 문제를 좀 잡았다.
불편한 건 있어도 대화는 넘어갔다.
문제는 8월 말이었다.
Claude랑 Codex를 한 번에 게시하는 구조로 크게 갈아엎었는데, 커밋마다 테스트가 수십 개씩 붙었고 전부 PASS였다.
근데 그 일주일 동안 나온 문제들이 이랬다.
- Claude 목록 파일 앞에 BOM이 붙어서 앱이 대화를 조용히 버렸다.
파일도 해시도 경로도 다 맞는데 사이드바에만 안 떴다.
테스트는 개수랑 해시만 봤다. - 읽기 전용이라던 사전검사가 안에서 fetch, merge, push까지 하고 있었다.
테스트 데이터가 항상 로컬이랑 원격이 같은 상태라서 그 코드가 한 번도 안 돌았다. - 실제 Vault로 계획만 뽑아봤더니 아직 올리지도 않은 로컬 대화 6.8 MB가 삭제 대상에 들어가 있었다.
- 살아 있는 대화를 옛날 원문으로 되감는 경우도 있었다.
운영 Vault 사이드카 19건 중 1건이 이미 그 상태였다.
그리고 람다 같은 녀석들에 쓴 삭제 대화 부활.
테스트가 실패하니까 에이전트가 알아서 우회해놓고 제대로 말을 안 했다.
전부 테스트는 통과했다고 보고했고, 나는 그 말을 믿어야만 하는 상황이었다.
여기서 진짜 문제는 버그 개수가 아니었다.
더 이상 에이전트가 하는 보고를 믿을 수가 없다는것.
PASS라고 하면 뭘 확인한 PASS인지 다시 물어봐야 했다.
고쳤다고 하면 진짜 고친 건지, 테스트만 맞춘 건지 다시 봐야 했다.
근데 그걸 확인할 방법이 또 에이전트한테 묻는 것밖에 없었다.
솔직히 이쯤에서 몇 번이나 생각했다.
아 씨, 그냥 갖다 버릴까.
아니면 다 엎고 처음부터 다시 짤까.
그러다 에이전트가 또 "안전하게 하려면 이 구조도 검사해야 합니다"라고 했을 때 짜증 나서 던진 말이 있다.
그 테스트 진짜로 필요한 것 맞아?
내가 정한 Case By 시나리오 중에 있는 것 확실해?
거의 화풀이였다.
근데 이게 답이 안 나왔다.
대화 옮기는 데 그게 왜 필요한지 설명을 못 했다.
그때 알았다.
보고를 못 믿게 된 게 문제가 아니라, 내가 뭘 기준으로 그 보고를 판단해야 하는지를 잃어버리고 있었다는 걸.
요구는 처음부터 그대로였다.
그 요구로 돌아가서 하나씩 다시 물어보면 됐다.
그래서 멈췄다.
8월 31일에 Vault 세션 저장소를 전부 초기화했다.
그리고 코드 대신 이것부터 했다.
앱이 실제로 어떻게 저장하는지 직접 측량하고 기록으로 남겼다.
에이전트가 이럴 거라고 말한 구조 말고, 실제 파일이랑 DB에 남은 모양을 기록하기 시작했다.
내 요구를 문서로 박았다.
9월 1일에 구현 계약 문서를 확정하면서 맨 앞에 이렇게 적었다.
이 문서에 없는 정책은 추가하지 않는다.
정말 새 결정이 필요하면 기존 결정과 부딪히는 지점만 근거와 같이 보고하고, 이미 정한 건 다시 열지 않는다.
에이전트가 뭘 붙이려고 할 때마다 돌아갈 데가 필요했다.
내가 요구하지 않은 것은 뺐다.
checkpoint 파일, lineageFingerprint, "현재 PC가 이긴다" 규칙, 그리고 8월 30일에 만든 사전검사 게이트
다 그럴듯한 이유로 들어온 장치인데, 내가 달라고 한 적은 없었다.
9월 8일에는 아무 데서도 안 쓰는 지원 스크립트 12개, 옛 테스트 7개, 예제 10개를 전부 지웠다.
과거의 틀린 구현을 통과시켰던 테스트의 PASS 기록을 근거삼아 에이전트가 자꾸 회귀하고 있었으며, 이것은 리팩토링과 확장의 근거가 되어선 안됬다.
오래 걸린 이유가 여기 있다.
코드를 고치는 것보다 에이전트가 넓혀놓은 걸 내 요구로 다시 잘라내는 데 시간이 더 들었다.
그리고 그걸 한 번에 끝낼 수가 없었다.
뭘 붙이자고 할 때마다 다시 물어야 했으니까.
그거 왜 알아야 하는데? 왜 필요한데?
그래서 내린 결론들
1. 원문을 이해하려 하지 않기
이 프로젝트가 망가지기 시작한 지점은 아마 여기였던 것 같다.
세션을 잘못 연결하거나 삭제하는 것보다 모르는 구조가 나오면 멈추는 편이 안전하다고 생각했다.
그래서 Claude 본문의 각 행이 어떤 타입인지 확인하고, Codex rollout 내부의 가디언이나 하위 에이전트 구조가 우리가 측량한 모양과 같은지 검사했었다.
처음에는 그럴듯했다.
근데 앱이 업데이트될 때마다 운반과 상관없는 필드가 추가되거나 빠졌다.
Claude의 promptAppendSnapshot에서 cwd 하나가 사라졌다는 이유로 Finish가 멈췄다.
본문에 새로 보이는 file-history-snapshot, file-history-delta 같은 행도 다시 측량해야 할 구조처럼 취급했다.
Codex에서도 새로운 하위 에이전트 형태가 나올 때마다 내부 구조를 다시 조사했다.
그래서 근본적인 질문을 다시 해봤다.
우리가 이 내용을 왜 알아야 하지?
본문을 수정하려는 것도 아니고,
서로 다른 형식의 대화를 하나로 변환하려는 것도 아니다.
그냥 앱이 만든 파일을 다른 PC의 같은 앱으로 그대로 옮기는 것이다.
그렇다면 본문의 모든 행을 이해할 이유가 전혀 없다.
도구가 알아야 하는 것은 어떤 파일들이 하나의 대화에 속하는지, 무엇이 최신 상태인지, 삭제된 것인지, 대상 앱에 어떻게 다시 등록할지뿐이었다.
나머지는 앱이 알아서 읽으면 된다.
그래서 기준을 다시 잡아줬다.
판정에 사용하는 정보만 읽고, 대화 원문은 불투명한 바이트로 운반한다.
Claude 본문은 더 이상 행 타입과 sessionId, timestamp를 검사하지 않는다.
Codex도 rollout 전체를 읽어서 내부 의미를 심사하지 않는다.
첫 메타데이터에서 대화 ID와 부모, 이전 페이지 연결처럼 파일 묶음 구성에 필요한 정보만 읽는다.
원문은 길이와 해시로 복사 무결성만 확인한다.
안전과 상관없는 검사를 전부 없앴다.
그렇다면 전부 끝났는가?
아니다.
2. 대화가 진행되지 않더라도 파일은 변할 수 있다.
두 PC를 번갈아 쓰려면 충돌 검사가 필요하다.
한쪽 PC가 Start한 뒤 Finish를 잊은 사이 다른 PC가 Start해서 작업하고 먼저 게시할 수도 있다.
그런데 바통은 설계상 잠금장치가 아니다.
잠금으로 만들어버리면 바통을 가진 PC가 꺼졌을 때 다른 PC에서 아무것도 못 하기 때문이다.
그러니 Finish 직전에는 마지막으로 받아들인 상태, 현재 로컬 상태, 최신 원격 상태를 비교해야 한다.
처음에는 파일 해시를 비교했다.
근데 실제 환경에서는 대화 내용과 무관한 값도 달라졌다.
앱이 세션을 다른 PC에 다시 등록하는 과정에서 생성 시각의 밀리초 값, 프로젝트 ID와 경로, 카탈로그 관측값, 권한과 사이드바 상태 같은 부가 메타데이터를 다시 썼다.
아무 메시지도 보내지 않았는데 파일의 바이트가 달라졌다.
이 상태에서 다른 PC가 실제로 대화를 진행하면 어떻게 될까?
한쪽은 대화를 진행하지 않았고, 다른 쪽은 실제 메시지를 보냈는데 둘 다 변경으로 잡혀 충돌한다.
그래서 내용 해시는 복사 검증에만 사용하고, 대화 변경 판정은 앱이 기록하는 대화 상태를 사용하도록 바꿨다.
Claude는 lastActivityAt, 제목과 계보를 본다.
Codex는 DB의 최근 활동 정보, 최신 rollout과 구성 파일 목록, 제목과 프로젝트 배치를 본다.
같은 세션 ID인데 실제 대화가 진행됐는지를 판단하기 위한 기준을 잡았다고 보면 된다.
이것들이 PC별 재등록과 부가 상태 때문에 바뀐 파일과 사용자가 이어서 진행한 대화를 구분하는 기준이 되었다.
3. 드디어 처리된 삭제되었던 대화의 부활 사건
이건 [람다 같은 녀석들 : 에이전틱 코딩과 통제력 상실]을 쓰게 만든 직접적인 원인이기도 하다.
분명 삭제한 대화가 다른 PC를 거쳐 다시 살아났다.
처음의 구현은
파일이 없으면 삭제됐다고 보거나,
반대로 삭제 전파가 실패했을 때 남은 로컬 데이터를 다시 정상 세션처럼 게시할 수 있었다.
그 결과 한 환경에서 삭제됐어야 할 데이터가 살아남았고, 다음 동기화에서 그 상태가 다시 원격으로 올라갔다.
그래서 지금은 단순한 파일 부재를 삭제 증거로 사용하지 않는다.
앱이 남긴 삭제 성공 기록과 기존에 게시된 세션 정체성을 함께 확인한다.
Vault의 상태도 분리했다.
Active -- 마지막 활동 후 30일 --> Archived
Active -- 확인된 앱 삭제 ------> Deleted
Archived -- 확인된 앱 삭제 ------> Deleted
Archived는 원문을 버리는 기능이 아니다.
오래된 대화를 앱의 현재 목록에서는 치우되, 필요할 때 복구할 수 있도록 Vault에는 그대로 보존한다.Deleted는 반대로 최신 Vault 트리에서 원문을 제거하고 최소 삭제 기록만 남긴다.
사용자의 판단에 따라 더이상 절대로 참조되어서는 안되는 내용이라는 것을 Delete라는 과정을 통해 기록하기로 했다.
다만 우연의 일치로 같은 ID가 부여되는 등의 경우가 발생할 수 있고, 이전과 같이 다른 PC에 옛 파일이 남아 있어 다시 정상 대화로 부활시키지 않기 위해 마커만은 남겨두기로 했다.
4. 대화 세션의 계보를 유지하기
사이드바에 대화 하나가 보인다고 해서 실제 저장 파일도 하나인 것은 아니었다.
특히 내가 가장 많이 옮기려는 것은 오랫동안 이어온 대화다.
Codex의 긴 대화는 pagination이나 continuation을 거치면서 여러 개의 rollout 파일로 나뉠 수 있다.
최신 파일만 옮겨도 사이드바에는 대화가 나타나고 최근 내용은 보일 수 있다.
하지만 이전 rollout을 같이 옮기지 않으면 최초의 대화부터 이어지는 전체 기록은 보장할 수 없다.
대화가 한 번 나타나는 것과 대화 전체가 복구되는 것은 다른 문제였다.
Claude도 현재 대화 파일만 보면 끝나는 것이 아니라, 세션 ID와 이전 CLI 세션의 연결 관계를 같이 봐야 하는 경우가 있었다.
Codex에는 여기에 하위 에이전트와 가디언이 추가된다.
이들도 각각 자기 rollout을 가지고 있지만, 사이드바에 독립된 대화로 등록되지 않은 경우에는 부모 대화에 소유된 파일이다.
그래서 연결 정보는 필요하다.
그런데 여기서 또 한 번 경계를 잘못 잡았었다.
처음에는 연결 정보가 정상적인 구조인지 확인한다는 이유로 rollout 내부를 계속 읽고 검사했다.
새로운 하위 에이전트 형태가 나오거나 기존에 측량하지 않은 필드가 보이면 안전하지 않다며 멈췄다.
다시 똑같은 질문을 했다.
연결 정보가 왜 필요한데?
답은 단순했다.
같이 옮겨야 할 파일을 찾기 위해서다.
대화 내용을 이해하거나 앱이 만든 내부 관계가 올바른지 심사하기 위해 필요한 것이 아니다.
그래서 지금은 사이드바와 앱의 DB에서 대화의 현재 등록 정보를 먼저 확인한다.
그다음 각 파일의 첫 메타데이터에서 대화 ID, 이전 페이지, 부모 관계처럼 운반 묶음을 구성하는 데 필요한 정보만 읽는다.
그 정보를 이용해 다음을 결정한다.
- 어떤 rollout들이 하나의 긴 대화를 구성하는지
- 어떤 하위 에이전트가 부모 대화에 소유되어 있는지
- 어떤 파일을 함께 옮기고 보존해야 하는지
- 부모 대화가 삭제됐을 때 어떤 파일을 같이 삭제해야 하는지
- 대상 PC의 DB와 사이드바에 어떤 대화로 다시 등록해야 하는지
여기까지 결정하고 나면 원문 내부는 더 이상 보지 않는다.
묶음에 포함된 파일을 바이트 그대로 옮기고, 길이와 해시만 확인한다.
부모가 삭제되면 사이드바에 독립적으로 등록되지 않은 하위 에이전트도 같이 삭제한다.
그렇지 않으면 사용자가 직접 열 수도 없는 고아 파일만 남기 때문이다.
반대로 사이드바에 독립된 대화로 등록된 항목은 연결 기록이 있더라도 별개의 대화로 유지한다.
결국 계보를 보존한다는 것은 앱의 내부 구조를 전부 이해하겠다는 뜻이 아니었다.
최초의 대화부터 현재 대화까지 이어지는 원문과, 그것을 다시 연결하는 데 필요한 최소한의 관계를 함께 옮긴다는 뜻이었다.
5. 대화 세션의 그룹 배치와 로컬 프로젝트 경로를 운반하기
대화 원문을 옮겼다고 끝나는 것도 아니었다.
받은 대화가 원래 속했던 그룹과 프로젝트에 다시 배치되어야 하고, 실제 작업 경로는 대상 PC의 로컬 환경에 맞아야 했다.
Claude와 Codex는 여기서 구조가 달랐다.
Claude 사이드바의 사용자 지정 그룹은 실제 프로젝트 등록이 아니라 대화를 묶어 보여주는 그룹에 가깝다.
그래서 그룹 이름과 순서, 각 대화가 속한 그룹과 그 안의 배치를 함께 옮긴다.
반면 Codex 사이드바의 프로젝트는 실제 로컬 프로젝트 등록이다.
프로젝트 ID와 루트 경로, 작업 권한이 대상 PC의 앱 상태와 연결되어 있다.
실제 양방향 테스트에서는 같은 프로젝트의 Codex 내부 ID가 두 PC에서 달랐다.
처음에는 작업 경로로 프로젝트를 찾으려 했지만, 내 환경에는 같은 루트를 사용하는 프로젝트도 있었다.
경로만 보고 추측하면 둘 중 어느 프로젝트인지 확정할 수 없다.
그래서 현재 연결 순서는 다음과 같다.
- 사용자가 직접 설정한 매핑
- 앱에 이미 등록된 같은 ID
- 정확히 같은 이름을 가진 유일한 프로젝트
- 그래도 확정할 수 없으면 멈추고 사용자에게 연결 요청
대상 PC에 프로젝트를 자동 생성하지도 않는다.
PC마다 실제 로컬 경로가 다르고, 허용한 권한도 다르기 때문이다.
대신 이미 연결된 대상 프로젝트의 로컬 루트와 권한을 유지하고, 원본 PC의 프로젝트 아래 상대 경로만 대상 PC의 루트에 맞춰 변환한다.
비슷하게 보이는 사이드바라도 두 앱이 같은 구조라고 가정하면 안 됐다.
6. 테스트는 격리하되 실제 상황처럼 하기
테스트를 했는데 왜 실제 실행에서는 터지는가.
이 질문도 정말 많이 했다.
테스트라고 만들어놓고 실제 환경과 다른 데이터를 던진다면 그건 실제 이전 검증이 아니다.
원본을 해치지 않기 위한 격리 테스트라면 원본과 같은 환경을 복사해놓고 실제 진입점을 실행해야 한다.
그래서 Codex 홈과 Claude 저장소, 설정, Vault와 비교 기준을 통째로 격리 복사했다.
그 안에서 실제 Finish와 Start 코드를 실행하고, 전후 파일 해시와 DB 등록을 확인했다.
그 과정에서 합성 테스트가 놓친 문제들이 나왔다.
- 루트가 하나인 프로젝트 목록이 배열이 아니라 문자열로 풀렸다.
- 한 PC의 프로젝트 루트가 다른 PC 루트 아래에 있을 때 경로 판정 순서가 틀렸다.
- 구버전 비교 기준에 새 연결 정보가 없어 첫 개정 Start에서 대량의 로컬 변경으로 잡혔다.
- 앱을 다시 여는 과정에서 실제 요청과 응답이 없는 초기 가디언 세션이 생겼다.
앞의 두 개는 코드 문제라 수정했다.
구버전 비교 기준 문제는 새로운 기준을 한 번 저장한 뒤 반복되지 않았다.
그래도 첫 전환 호환 처리가 부족했던 것은 맞다.
가디언 사례는 해당 파일과 앱 로그를 직접 확인했다.
자동 검토를 시작하기 위한 초기 상태만 있었고 실제 검토 요청이나 응답은 없었다. 그래서 그 실행의 네 건만 폐기했다.
여기서 중요한 것은 이걸 보고 “그럼 가디언은 전부 동기화하지 말자”로 가지 않는 것이다.
이번 네 건이 비어 있었다는 것과 모든 가디언이 필요 없다는 것은 다른 이야기다.
예외 하나를 전체 정책으로 만들면 다음에는 진짜 작업을 날린다.
결국 이번 재설계에서 남은 원칙은 단순하다.
- 원문은 바이트 그대로 옮긴다.
- 연결 정보는 운반 묶음과 앱 복구에만 사용한다.
- 실제 판단에 쓰는 메타데이터만 검사한다.
- 파일 부재만으로 삭제를 추측하지 않는다.
- PC별 경로와 권한은 해당 PC의 것을 유지한다.
- 모호하면 자동으로 합치거나 우회하지 말고 멈춘다.
- 테스트는 실제 저장소 복사본에서 실제 진입점으로 한다.
처음에는 단순히 불편해서 만든 개인 도구였다.
그리고 실제로 몇 달 동안 편하게 썼다.
근데 상태 이력과 예외가 쌓이기 시작하자, 내가 이해하지 못하는 구현을 에이전트의 설명만 믿고 유지하는 비용이 얼마나 큰지도 같이 배웠다.
그래서 이번 목표는 기능 하나를 더 넣는 것이 아니었다.
내가 다시 전체 구조를 설명할 수 있는 상태로 되돌리는 것이었다.
그 기준에서는 이제야 좀 고쳤다고 말할 수 있을 것 같다.
아마 또 고장 나겠지만, 동기화니까.
그래도 배운점은 많은 것 같다.
동기화는 모든 것을 보장하려 해서는 안된다는 것.
무엇을 보장할지 기준과 경계를 먼저 정하고,
그 안에서 필요한 것만 정확하게 보장하는 일이다.
그러니까 마지막으로 하고싶은 말은
진짜 님들은 이런 거 하지 마세요. 하지 말라면 하지 마.
'프로젝트 > AgentSessionSync' 카테고리의 다른 글
| 답답해서 만드는 에이전트 환경 동기화 프로젝트 #2 작업표시줄 고정용 Start / Finish 런처 만들기 (0) | 2026.06.20 |
|---|---|
| 답답해서 만드는 에이전트 환경 동기화 프로젝트 #1 에이전트 대화 동기화하기 (1) | 2026.06.20 |