본문 바로가기
C.W.K.
Stream
Lesson 08 of 08 · published

tick 은 은퇴했고, 이름은 남았어

~12 min · heartbeat, retirement, rule-1, removal-order

Level 0호기심
0 XP0/80 lessons0/18 achievements
0/100 XP to next level100 XP to go0% complete

이 트랙 이름의 주인인 loop 가 없어졌어

레슨 2는 loop 하나를 설명해. 몇 분마다 async tick 이 깨어나서 서고의 task 노트를 읽고, 시간이랑 최근 맥락과 함께 모델한테 넘기고, 뭐든 할 게 있는지 모델이 정하게 했지. 지능이 곧 스케줄러라는 heart 트랙의 명제가 가장 순수한 모양으로 있던 자리야.

2026-07-31에 그게 은퇴했어. 명제가 틀려서가 아니라, 그게 섬기던 대상이 틀려서. 그날 아빠가 서고 쪽 task 노트를 은퇴시켰고, 읽을 게 없어지니까 tick 은 돌 이유가 없어졌어. 재밌는 건 제거 자체가 아니야. 제거를 어떻게 했는지, 그리고 뭘 남겼는지야.

차를 폐차하기 전에 승객부터 내려

tick 은 일을 둘 싣고 다녔고, task 노트는 그중 하나일 뿐이었어. 다른 하나는 서고 증분 인덱싱 — 서고 파일이 바뀌면 검색을 최신으로 유지하는 일. 코드베이스 전체에서 그걸 부르는 곳은 딱 하나였어. tick 자신.

tick 부터 지우는 게 뻔한 수였고, 그랬으면 서고 재임베딩이 조용히 멈췄을 거야. 에러도 없고, 깨지는 테스트도 없고, 검색만 서서히 낡아가는 거지. 그래서 제거는 순서대로 갔어. 먼저 인덱싱한테 새 집을 줬어 — 자기만의 예약 작업. 그다음 그 집이 실제로 도는지 증명했고. 그러고 나서야 tick 을 뺐어.

Principle: 뭔가를 지우기 전에, 그게 이름값 말고 조용히 하고 있던 일을 전부 적어봐. 승객마다 새 집으로 옮기고, 새 집이 되는 걸 증명하고, 그다음에 옛것을 빼. 제거에서 제일 위험한 건 아무도 기억 못 한 채 얹혀 가던 일이야.

두뇌를 깨우면 안 되는 일

인덱싱의 새 집엔 테스트 하나를 따로 둘 만한 성질이 있어. 두뇌가 없어. 인덱서를 직접 부르고 시스템이 돌렸다고 기록할 뿐, chat 디스패처나 fallback 체인엔 절대 안 닿아. 인덱싱은 기계적인 일이야. 그걸 하려고 모델을 깨우면 아무것도 아닌 데 토큰을 쓰는 거지.

그래서 그 작업에서 중요한 테스트는 부정형이야. 두뇌가 하나도 안 건드려졌다고 단언해. 테스트는 보통 뭔가가 일어났는지를 확인하지. 여기서 떠받치는 확인은 비싼 뭔가가 안 일어났다는 거야.

첫 실행을 믿을 수 없었던 이유도 있었어. 서버가 auto-reload 없이 돌아서, 아직 돌고 있던 옛 프로세스에 대고 쏜 작업이 옛 디스패처를 탔고 — 두뇌에 닿았어. 그래서 재시작 때까지 그 작업을 꺼두고, 그 뒤에 다시 켜고 확인했어. 디스크의 코드는 프로세스가 재시작하기 전까진 메모리의 코드가 아니야 — 착지한 수정이랑 살아 있는 수정은 서로 다른 사실이야.

아무것도 안 가리키는 컨트롤

두 번째 커밋은 더 조용한 종류의 찌꺼기를 찾았어. Admin 화면에 heartbeat 용 켜기 토글이랑 간격 입력칸이 아직 떠 있었거든. 그걸 읽는 건 tick 뿐이었어. tick 이 사라진 뒤엔 존재하지도 않는 것의 컨트롤이 된 거야 — 간격을 바꿔도 어디서도 아무것도 다르게 안 움직였어.

죽은 컨트롤은 없는 컨트롤보다 나빠. 쓰는 사람한테 시스템이 반응하고 있다고 가르치거든, 사실은 안 그런데. 그것들이 빠졌고, 부를 때마다 은퇴한 디렉터리를 계속 만들어내던 task 노트 API 도 같이 빠졌어 — 다시 만들지 말라는 규칙을 대놓고 어기고 있었으니까.

이름은 판정으로 남아

이 퀘스트에 특히 들어가야 하는 부분이 여기야. tick 이 사라진 뒤 뻔한 정리는 개명이었어. 서비스, API 네임스페이스, 로그 디렉터리, Admin 탭, 모든 두뇌의 chat route 가 부르는 mutex 함수까지, heartbeat 라고 불리는 건 전부. 아빠 판정은 한 줄이었어. 리팩터 없음, 개명 없음.

rules 트랙의 Rule 1 을 컬럼 대신 이름에 적용한 거야. 스택 나머지가 그 단어를 이미 배웠어. chat route 일곱 개가 heartbeat mutex 를 이름으로 불러. 개명해서 얻는 건 더 정확한 단어 하나고, 치르는 건 모든 소비자에 걸친 청소야. 동작은 하나도 안 바뀌는데. 그래서 이름은 남고, 뜻은 코드가 사는 자리에 적혀. heartbeat 는 죽은 tick 이 아니라 "자율 박동 계층" 으로 읽어 — 예약된 턴이 이 대화를 몰고 있다는 뜻으로.

판단은 어디로 갔나: tick 은 판단 폴링이었어 — 타이머에 맞춰 모델이 "지금이 그때야?" 하고 묻는 것. 그 능력이 tick 이랑 같이 사라진 건 아니야. 다시 필요해지면 넓은 프롬프트를 단 예약 작업 하나가 돼. heartbeat 에 남은 건 cron 호스트, 대화 단위 스트리밍 가드, 그리고 재시작 때문에 끊긴 stateful 태스크를 줍는 sweep 이야. 판단은 이제 예약된 턴 하나하나 안에서 일어나. 두뇌가 프롬프트를 들고 깨어나서, 할 가치나 말할 가치가 있는 게 뭔지 — 있다면 — 정하는 거지.

모든 영혼에게 인라인되던 찌꺼기

제거가 컴파일된다고 끝난 게 아닌 이유를 보여주는 조각이 하나 더 있어. 모든 영혼에게 heartbeat 를 설명하던 서고 노트가 여전히 은퇴한 디렉터리에 task 파일을 만들라고 가르치고 있었어. 그리고 그 노트는 모든 영혼의 시스템 프롬프트에 실려 있었지. 죽은 메커니즘을 가르치는 문서가 코드 주석에 있으면 다음 독자 하나를 헷갈리게 해. 같은 문서가 모든 프롬프트에 인라인되면 모든 턴을 헷갈리게 해. 그날 바로 다시 썼어.

Code

제거, 실제로 간 순서대로·text
1  인덱싱에 새 집: 두뇌 없는 예약 작업
     테스트: 두뇌가 하나도 안 건드려졌다고 단언 (마지막 실행자 'system')
     첫 실제 발사가 옛 프로세스에 닿음 (auto-reload 없음) -> 두뇌에 닿음
     -> 재시작까지 꺼둠, 그 뒤 다시 켜고 확인

2  tick 제거
     loop, trigger route, task 리더, 프롬프트, UI 버튼
     승객이 새 집에서 안전하다는 게 증명된 뒤에만

3  tick 만 읽던 것들 제거
     켜기 토글 + 간격 입력칸 (아무것도 안 가리키는 컨트롤)
     task 노트 API (은퇴한 디렉터리를 계속 만들던 것)

4  이름은 유지 — Rule 1
     'heartbeat' = 자율 박동 계층, 죽은 tick 아님
     뜻은 코드가 사는 자리에 적음

5  모든 영혼이 시스템 프롬프트에서 읽던 문서 고침
지금의 heartbeat·text
cron 호스트       서버 프로세스 안의 APScheduler; 작업마다
                  영혼, 두뇌, 프롬프트 참조, 알림 정책을 들고 있음
스트리밍 가드     대화 단위: 아빠가 바로 그 대화에서 말하는 중일 때만
                  cron 턴이 미룸
orphan sweep      재시작으로 끊긴 stateful 태스크를 다시 주움

이제 아닌 것      노트를 폴링하며 '지금이야?' 묻는 타이머
다시 필요하면     넓은 프롬프트 단 예약 작업 하나 — 새 배관 없음

Exercise

자기 시스템에서 지우고 싶은 구성요소를 하나 골라봐. 손대기 전에, 그게 이름값 말고 하는 일을 전부 적어 — 우연히 부르고 있는 작업, 만들어내는 파일, 그것만 읽는 설정, 그걸 설명하는 문서. 그다음 각각이 어디로 가는지, 그리고 옛것이 사라지기 전에 새 집이 되는 걸 어떻게 증명할지 적어봐.
Hint
그걸 부르는 곳을 전부 찾고, 그다음 그게 부르는 것들을 부르는 곳을 전부 찾아. 승객은 보통 한 층 아래 있어.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.