#FTM:strict 모드, 실전투입
리팩토링에 최적의 모드.
몇시간 걸릴지 모르겠지만, 기대된다.

AspectScope?

특정 지역+시간대역의 Natal Chart 위상을 조사하는 앱이다.
키보드/마우스/트랙패드에 대단히 세밀하게 반응해야 한다. 전통적인 이벤트 핸들러 방식으로 만드는 건 결과는 나오겠지만 정확도 및 안정성 면에서 도저히 견디기 힘들다. 시한폭탄을 돌려막기 하는 느낌.
모든 I/O는 통제되야 한다.
그러기 위해서는 키보드/마우스/트랙패드의 데이터는 로직없이 신호로 처리되야 하고, 상태전이테이블을 통해서만 앱의 흐름이 이어져야 한다.
약 3주전 이걸 만들 때도 처음부터 엄청 신경썼지만 Codex든 Claude든 이런 완전한 상태전이테이블에 의한 코드흐름 진행 방식은 학습데이터에서 거의 없기 때문에, 내 의도와 전혀 다른 결과물만을 만들어냈다.
LLM에 지시하는 것 만으로도 스트레스 엄청 받았고, 대화를 저장하고 LLM이 태스크 관리하는 context-storage-mcp 만으로는 뭔가 부족하다는 걸 느꼈었다. 그래서 내가 지시를 하는게 아니라 MCP가 함수형 추론을 따라가도록 만든게 functional-thinking-mcp (FTM) 이다. context-storage-mcp에 기능이 더해지면서 본격적인 태스크 관리 체계로 발전하면서, 새로운 workgraph-memory-mcp (WGM) 이라는 프로젝트로 이어졌고, WGM에서는 LLM이 태스크 관리체계에 제한된 접근만 하게 했다이유는 비밀.
WGM과 FTM이 합쳐진 효과는 대단했으나, 두 서버가 각각 다른 메모리 공간에 있다보니 턴이 길어질수록 LLM이 거짓말을 내놓는 경우가 자주 생기는 것을 파악하더라도 MCP에서 자동으로 그 상태를 지적하지는 못했다.
여러 고민 끝에, 주장/판단/결론 등을 내놓을 때 증거가 없으면 오류를 발생시킴으로써 LLM이 뻥치는 것에 비용이 든다는 사실을 인지시켰다. 그리고 각각의 턴 진행이 이음(Seam)이라는 연속성을 가지게 함으로써, 세션이 달라지더라도 태스크의 진행흐름은 여전히 추적가능하고 태스크 문맥과 채팅내용을 LLM 스스로가 검색하고 참조할 수 있게함으로써, 언제 세션을 다시 시작하더라도 곧바로 이어갈 수 있게 되었다. 이것은 LLM 스스로가 잘못된 결론이나 기록/주장을 스스로 파악하게 한다.
따로 떨어진 두개의 MCP를 묶어주는건 AGENTS.md/CLAUDE.md 였기 때문에 LLM이 끼어들 여지가 있으므로 WGM+FTM 기능이 통합되야만 했다. 이것이 kontexus-mcp 를 만들 수 밖에 없었던 이유다.
#FTM:strict 라는 표현에서 #FTM은 LLM에게 알리는 지시자이다. 함수형 추론을 실질적으로 강제하는 요청이다. 이 지시자가 포함된 요청에 의해 LLM은 판단 자체를 구조적으로 기록하기 시작한다. strict는 판단과 그 증거사이의 관계를 가장 엄격하게 관리한다는 뜻인데, standard, verified, strict 세가지 모드이고, 기본은 standard 모드로써, 명시하지 않으면 standard가 기본모드다.
검사 강도
standard에서는 “이 불변 조건을 지켰습니다”라고 서술만 해도 kontexus-mcp 서버가 문제 삼지 않는다. 근거 없는 진술과 근거 있는 진술이 같은 무게로 통과한다.
이럴 때 붙인다:
- 여러 갈래 중 하나를 고르는 설계 판단
- 원인을 좁혀 가는 디버깅
- 여러 턴에 걸쳐 이어질 작업의 첫 턴
- 나중에 “그때 왜 그렇게 했는가”를 되짚을 일이 있을 작업
verified 에서는 “지켰다”는 말을 실행에 묶는다. 즉, LLM이 뭔가를 주장하고 결론내리고 보고하는 그 문장을 쓰려면 검사를 돌리게 만들고, 돌리지 않았으면 그 사실이 기록에 남게 한다.
이럴 때 붙인다:
- 여러 입력 소스가 하나의 상태 머신에 동시에 개입하는 로직
- 경우의 수가 조합으로 늘어나 눈으로는 다 못 보는 분기
- 이미 한 번 회귀가 났던 자리를 다시 건드릴 때
- 리팩터링처럼 “겉보기 동작은 그대로여야 한다”가 전제인 작업
stircit는 verified 요구를 전부 유지한 채, 여기에 하나가 더 붙는다. 실제로 실행된(계획 상태가 아닌) 다섯 종류의 Effect마다 활성바인딩식별자를 적어야 한다. 이 말은 승인 없는 실행을 막는 다는 것이다. 판단을 했다는 것이 곧 파일을 고쳐도 된다는 뜻은 아니며, strict는 그 둘을 갈라놓고, 실행 하나하나에 대해 “이것은 어느 작업으로 승인된 것인가”를 남기게 한다. 나중에 변경 이력을 거슬러 올라갈 때 각 수정이 어느 작업에 속했는지가 기록으로 남는다.
이럴 때 붙인다:
- 공유 상태를 다루는 코드에 실제 파일 수정이 들어가는 고위험 작업
- 여러 작업이 뒤섞이기 쉬워 나중에 “이 수정은 어느 작업이었나”를 반드시 가려야 할 때
- 되돌리기 어려운 실행(마이그레이션, 외부로 나가는 요청)이 섞이는 작업
어떤 서비스의 모델이든 리팩토링 기능 자체는 문제없다. 다만, 턴이 길어질 때, 진행도중 발생하는 모든 변화에 대응하면서 처음 목적과 목표를 달성하게 하는 건 AGENTS.md 또는 CLAUDE.md 만으로는 역부족이다.
kontexus-mcp는 Harness를 대체하지 않는다
에이전트 루프는 결국 하나의 식으로 표현될 수 있다: S(n+1) = f( S(n), O(n) )
S는 다음 턴으로 넘어가는 상태(state)이고, O는 관측(observing)이다.
루프를 설계할 때 손대게 되는 것은 대개 f이다 – 어떤 도구를 주고, 프롬프트를 어떻게 쓰고, 몇 번 되돌릴지.
그러나 루프의 성질을 결정하는 것은 f가 아니라 세 가지가 어디에 기록되고 유지되는가이다.
S가 어디에 저장되는가O가 누구를 경유해서f에 들어오는가- 루프를 멈출지 누가 판정하는가
KM은 이 셋 전부에 걸쳐 있고, 셋 다 위치를 옮기는 것이지 정보를 더 주는 것이 아니다.
kontexus-mcp가 없을 때 루프가 놓이는 자리
S의 수명이 컨텍스트 창의 수명과 같다. 상태가 컨텍스트 창 안에만 있으면, 창의 압축·절단·세션 종료가 전부 S의 소실이다.
문제는 소실 자체가 아니라 소실이 조용하다는 것인데, 루프는 자기가 무엇을 잃었는지 알 수 없으므로 잃은 채로 계속 돈다. 그래서 다음 등식이 성립한다: 루프의 최대 길이 = 컨텍스트 한도
즉, 며칠에 걸친 작업을 하나의 루프로 다룰 수 없다는 뜻이다.
O가 전부 모델을 경유한다. 무슨 말이냐면, 도구 출력은 관측처럼 보이지만, 다음 턴으로 넘어갈 때는 모델이 요약한 형태로만 남는다. 즉 실제로는 이렇다: O(n) = g( 모델의 서술 )
이때 루프는 되먹임이 있는 것처럼 보이지만, 되먹이는 신호가 자기 출력이므로 오차 신호가 존재하지 않는다. 드리프트가 얼마나 벌어지든 루프 내부에서 검출할 방법이 원리적으로 없다. “산출물을 보존했습니다”라는 문장이 세션과 함께 사라지는 임시 디렉터리를 가리켜도, 루프 안에서는 그 문장과 참인 문장이 구별되지 않는다.
종료 판정자와 피판정자가 같다. “완료했습니다”가 유일한 종료 조건이다. 판정하는 쪽과 판정받는 쪽이 같은 주체이므로, 판정은 작업의 함수가 아니라 문장 생성의 함수이다. 또한 작업 단위의 이름을 모델이 만들기 때문에 같은 일이 두 이름을 갖거나 다른 일이 한 이름을 갖는 것을 막는 구조가 없다. 이름이 어긋나면 이후의 기록·검색·인계가 전부 어긋난다.
Kontexus-mcp가 옮기는 것
S를 컨텍스트 밖으로 꺼낸다. MCP 호출은 ftm_seam 이라는 테이블에 기록되고 상태가 Hash 값으로 고정된다. 상태를 바이트 단위로 비교할 수 있기 때문에 같은 자리에 왔는가 여부를 알 수 있다. 이에 따라 컨텍스트 압축에 영향 받지 않으며 루프 길이에 무관해진다. 또한 관측 채널 하나를 모델 밖으로 뺀다. 즉, 모델이 만들어내지 못하는 관측 자료를 추가한다. 이로써 O(n)의 일부가 모델을 경유하지 않고 루프에 들어옴에 따라 없던 오차 신호가 여기서 생긴다. 이것은 관측이므로 에이전트의 동의와 무관하게 사실이다. 가장 달라지는 것은 종료 조건인데, 종료 판정을 말이 아니라 행동에 걸어버린다. kontexus-mcp 에서는 태스크와 태스크, 턴태스크와 턴태스크 사이의 전이에 검사가 따라 붙는다. 루프가 계속 이어지려면 반드시 그 행동을 해야하는 것이다.
규율을 프롬프트가 아니라 서버 상태로 유지한다
이것이 핵심이다.
아무리 턴이 길어져도, 도구들이 내놓은 온갖 코드가 나무해도, LLM은 말이 아니라 kontexus-mcp 서버가 내놓은 규칙을 따라 간다.
프롬프트로 유지되는 규율은 턴이 지날수록 감쇠하지만 서버 상태로 유지되는 규율은 감쇠하지 않고, 도리어 감쇠가 일어난 자리에서 소리가 난다. 그래서 사용자는 지시자를 매 프롬프트에 반복할 필요가 없다.
그러나 kontexus-mcp는 잘못된 실행을 막지 않는다. 잘못된 진술이 흔적 없이 지나가는 것을 막을 뿐이다. 이 설계는 의도적인데, 강제 게이트는 두 가지 실패를 부른다는 것을 여러번의 시행착오를 통해 배웠기 때문이다 – 진행이 멈추거나, 게이트를 만족시키는 형식만 갖춘 우회가 생기거나 하는 지능적 회피가 그것이다. 그러나 권고는 진행성을 건드리지 않고 감사 가능성만 올리므로 둘 다 피한다. 강도를 올리는 것은 문을 잠그는 일이 아니라 자국을 남기는 일이다.
값어치가 나오는 루프의 모양
앞서 언급한 kontexus-mcp 성질들을 뒤집으면 어떤 루프가 KM을 필요로 하는지가 나온다.
| 요구조건 | KM을 도입할 이유 |
|---|---|
| 하나의 작업이 여러 세션에 걸친다 | 컨텍스트 한도가 루프 길이를 자르지 않아야 한다. |
| 산출물이 실재하는지가 중요하다 | 서술이 아닌 관측이 필요하다. |
| “동작은 그대로여야 한다”가 전제다 | 침묵하는 회귀가 가장 비싼 실패일 때. |
| 나중에 “그때 왜”를 되짚어야 한다 | 기록된 원장이 흐름을 추적하게 한다. |
| 여러 작업이 뒤섞이기 쉽다 | 좌표계가 흔들리면 나머지가 전부 흔들립니다 |
거꾸로, 한 턴 안에서 끝나고 산출물이 즉시 눈에 보이며 되짚을 일이 없는 작업이라면 KM을 붙이지 않는 편이 낫다. 그래서 #FTM 지시자는 선택사항이다.
이 글을 여기까지 쓰는데 4시간 동안 리팩토링이 진행중이다. (순서를 건너 띄며 진행하고 있다)

리팩토링 결과는 추후 업데이트 할 예정.
도중 멈춰서 세션을 다시 시작하라고 하는데



여전히 사람의 개입이 필요하다. 턴태스크를 순차적으로 실행하지 않고 도중에 널띄기 할 때 부터 이런 상황이 올 것이라 예측은 했다.
#FTM:strict 모드이므로 어떤 주장도 근거없이는 받아들이지 않는다.
리팩토링은 처음 계획과 달리 도중에 얼마든지 이런 상황이 발생한다. 그럼에도 불구하고 이정도는 양호하게 진행하고 있는 편에 속한다. 아직까지는.
6시간을 넘어가고 있다

쉽지않네… 뭐 원래 그런 거긴 한데…
토큰이 버텨낼지 모르겠네.
