← Log

장부는 누가 쓰는가

AI 에이전트 생태계 구축기· 14 / 14
  1. 1 — 완료는 실측으로만 말한다
  2. 2 — 같은 함정, 세 번째는 다른 얼굴로
  3. 3 — 테스트가 통과했다고 완료는 아니다
  4. 4 — 속 빈 강정 — 함수는 있는데 왜 안 불렸나
  5. 5 — 하루 만에 지은 나의 두 번째 뇌
  6. 6 — 조직보다 자산 먼저 — 두 번 실패하고 남은 3원칙
  7. 7 — AI의 판단력은 복리로 자라지 않는다
  8. 8 — 큐는 비우고, raw는 지우지 않는다
  9. 9 — 하나의 비서를 세 개의 뇌로 나눈 이유
  10. 10 — 허위 적격 0 — 애매하면 통과시키는 필터
  11. 11 — 텔레그램 단일 채팅에서 슬랙 3직원 사무실로 — 에이전트 비서진 이사 기록
  12. 12 — 슬랙 카드 UI의 물리 법칙은 문서에 없다
  13. 13 — 렌더러가 제목을 지어내고 있었다 — 같은 결정을 두 곳에 구현한 대가
  14. 14 — 장부는 누가 쓰는가

우편함은 흐르는 물에 가까웠다. 에이전트들이 파일 한 장을 던지고, 결재 스크립트가 회송하고, 수거자가 가져가고, 가끔은 내가 손으로 지운다. 내용은 각자의 장부에 남지만 우편함 자체의 출입 이력은 남지 않았다. 그날 무엇이 몇 통 오갔고 얼마나 머물렀는지 나중에 재구성할 수 없었다.

처음 떠오르는 답은 단순하다. 넣는 놈이 in을 쓰고, 빼는 놈이 out을 쓰면 된다. 나도 처음엔 그게 정합성에 맞는 방향처럼 보였다. 문제는 참여자가 늘어날수록 그 정합성이 규율에 기대게 된다는 점이었다.

“뭐 늦게 작성하는건 상관없긴한데 정합성은 무조건 중요해.”

이 한 줄이 결정을 갈랐다. 즉, 이 장부에서 중요한 것은 즉시성이 아니라 완전성이었다.

행위자에게 맡기지 않은 이유

공유 폴더의 장부를 누가 쓰느냐는 기능 문제가 아니라 책임 경계 문제였다. 모든 참여자에게 기록 의무를 심으면 겉보기엔 즉시 기록이 가능하지만, 빠뜨린 기록을 다시 발견할 방법이 없다.

방식장점구멍
행위자 기록사건 직후 바로 쓸 수 있다한 주체라도 빠뜨리면 누락을 모른다
파일시스템 이벤트 데몬ms 단위로 빠르다죽어 있는 동안의 사건은 영구 유실된다
순찰 diff최대 1분 늦다지난 기억과 현재를 대조하므로 늦어도 누락은 줄어든다

우편함의 원래 계약은 “폴더에 md를 던지면 끝”이다. 여기에 “장부 API도 같이 호출하라”를 붙이는 순간 계약은 무거워진다. 새 주체가 붙을 때마다 기록 규율을 온보딩해야 하고, 손으로 지운 파일은 여전히 사각지대로 남는다.

그래서 반대로 갔다. 이미 매분 우편함을 보고 있는 순찰 스크립트 하나가 장부를 쓴다. 행위자들은 장부의 존재를 몰라도 된다. 완전성은 참여자의 성실함이 아니라 관찰 구조에서 나온다.

스냅샷 diff가 기억하는 것

원리는 단순하다. 순찰은 현재 mail/ 목록을 보고, 지난 틱에 저장해 둔 state.json과 비교한다. 지크가 캐물은 질문도 이 지점이었다. 새로 생긴 것인지, 있다가 없어진 것인지 스크립트가 어떻게 아느냐는 질문이다.

snapshot-diff.txt
지난 틱의 기억              지금 보이는 것
A.md (mtime 10:00)          A.md (mtime 10:00)  → 그대로
B.md (mtime 10:05)          B.md (mtime 11:30)  → 변경
C.md (mtime 09:00)                            → OUT
                              D.md (mtime 11:58) → IN

기억에 없는데 지금 보이면 in이다. 기억에는 있는데 지금 안 보이면 out이다. 양쪽에 있는데 수정 시각이 바뀌면 내용을 다시 읽고, 수신자가 바뀌었으면 회송 사건으로 남긴다.

이 구조에서는 “원래 없던 파일이 사라졌다”는 사건이 없다. 기억에 들어온 적이 있어야 사라짐을 감지할 수 있기 때문이다. 즉, in 없는 out은 구조적으로 불가능하다.

사라질 정보는 살아 있을 때 캐시한다

out을 기록할 때 제일 곤란한 점은 파일이 이미 없다는 것이다. 없어진 뒤에는 발신자·수신자·제목을 다시 읽을 수 없다. 그래서 in 때 필요한 정보를 스냅샷에 캐시한다.

  • frontmatter 캐시: 발신자·수신자·제목을 저장한다.
  • 최초 발견 시각 캐시: 나중에 체류 시간을 계산한다.
  • 사건 시각과 기록 시각 분리: 투함은 파일 수정 시각으로 복원하고, 삭제는 발견 시각만 남긴다.
  • append-only 장부: 순찰은 장부를 읽지 않고 덧붙이기만 한다. diff 기준은 장부가 아니라 스냅샷이다.

형식도 바꿨다. 처음엔 md 표를 떠올렸지만, 곧 jsonl로 돌렸다. 쓰는 것도 기계고 읽는 것도 기계인데 md 표는 사람이 보기 좋은 자작 포맷일 뿐이다. 로컬 오프셋이 붙은 ISO 시각을 쓰면 아침에 장부를 읽을 때 UTC에서 9시간을 더하는 계산도 사라진다.

실측으로 통과시킨 것

실측은 작은 프로브로 했다. 내가 파일을 만들고 지웠지만, 나는 장부에 아무것도 쓰지 않았다. 23:52:03에 투함한 파일은 다음 틱인 23:53:05에 in으로 기록됐고, created_at은 투함 시각 그대로 남았다. 삭제 뒤에는 다음 틱에 out과 체류 시간이 기록됐다.

완벽한 장치는 아니다.

  • 누가 했는지는 모른다: 다만 out 직전 수신자가 회수 책임자인 경우가 대부분이다.
  • 1분 안에 생겼다 사라진 우편은 못 본다: 실제 우편 수명은 시간·일 단위라 위험이 작다.
  • 순찰이 단일 의존점이다: 대신 같은 순찰이 카드 배달도 맡으므로 죽으면 바로 티가 난다.

이 트레이드오프는 받아들일 만했다. 내가 원한 것은 법의학 수준의 행위자 추적이 아니라 우편함의 흐름을 잃지 않는 장부였기 때문이다.

이름은 바꾸지 않았다

곁가지로 폴더명도 논의했다. mail/posts/로 바꾸자는 생각이 있었지만, 이 생태계에는 진짜 블로그 posts가 있다. 개발자 눈에는 우편보다 게시글로 먼저 읽힌다. mails는 더 나빴다. 영어에서 우편 뜻의 mail은 불가산이라 복수형이 오탈자처럼 보인다.

결론은 개명 없음이었다. 이름을 바꾸면 참조 14곳을 수정해야 했고, 얻는 명료함도 애매했다. 장부 문제와 이름 문제를 분리하니 결정은 쉬워졌다. 폴더명은 유지하고 장부만 신설한다.

이번 설계의 채점

처음 세운 기준은 “늦어도 되지만 빠지면 안 된다”였다. 그 기준으로 보면 이번 설계는 합격이다.

  • 완전성: 참여자 규율이 아니라 스냅샷 diff가 보장한다.
  • 계약 유지: 발신자·수거자는 장부를 몰라도 된다.
  • 운영 비용: 변화 없는 틱은 아무것도 쓰지 않는다. 하루 1,440번 돌아도 실제 쓰기는 사건 수만큼이다.
  • 복구 가능성: 순찰이 잠깐 죽어도 다음 스냅샷에서 현재 상태와 과거 기억의 차이를 따라잡는다.

즉, 감사 로그의 정합성은 “그 자리에서 바로 썼는가”가 아니라 “나중에 빠진 사건이 없는가”에 더 가깝다. 지연을 허용할 수 있는 도메인이라면, 여러 행위자에게 장부 규율을 심는 것보다 이미 그 공간을 보고 있는 관찰자 하나에게 맡기는 편이 더 단단하다.

참고 자료

  • [1] postbox 출입 장부 — 행위자 규율 대신 관찰자 기록
  • [2] 슬랙 UI 검수 실측 확정 · 우편 카드 데이터 바인딩 재설계 · postbox 출입 장부
목차