나의 코딩 에이전트가 작업을 완료했다. 그런데 왜 스레드가 끊겼을까?
요약
코딩 에이전트 개발 시 작업 완료(completed) 상태가 런타임 세션을 종료시켜 대화 맥락이 끊기는 문제를 분석합니다. 대화, 런타임 세션, 실행 턴의 개념을 분리하여 세션 유지력을 높이는 설계 방안을 제시합니다.
핵심 포인트
- 대화(Conversation), 런타임 세션(Runtime Session), 실행 턴(Turn)의 개념적 분리 필요
- 작업 완료가 세션 종료로 이어지지 않도록 세션 유지(Sticky Session) 설계
- 사용자의 후속 요청(Retry, Continue 등)을 처리하기 위한 맥락 유지의 중요성
- 명시적인 세션 분리 모델 구축을 통한 에이전트 사용자 경험 개선
코딩 에이전트에서 가장 짜증 나는 후속 버그는 크래시(crash)가 아닙니다.
첫 번째 작업은 성공했는데, 다음 메시지가 마치 기억상실증에 걸린 것처럼 느껴질 때입니다.
저는 로컬 어시스턴트를 통해 Claude Code나 Codex에게 무언가를 요청하고, 작업이 끝나기를 기다린 다음, 세상에서 가장 평범한 후속 요청을 보냈습니다:
make the button green
사용자 입장에서는 이것이 분명히 동일한 스레드(thread)의 일부입니다.
하지만 시스템 입장에서는 completed(완료)를 "이 세션은 끝났으니, 다음에는 처음부터 다시 시작하라"로 처리하기가 너무 쉬웠습니다.
그것이 바로 버그였습니다.
진짜 문제는 세 가지 서로 다른 것을 혼동한 것이었습니다
CliGate를 구축하면서, 저는 세 가지 레이어(layer)를 모호하게 섞어버렸다는 사실을 깨달았습니다:
- 채팅 대화 (the chat conversation)
- 런타임 세션 (the runtime session)
- 현재 실행 턴 (the current execution turn)
이것들은 동일한 객체가 아닙니다.
대화(conversation)는 사용자가 계속해서 이야기를 나누는 장기적인 공간입니다.
런타임 세션(runtime session)은 해당 대화에 연결된 작업 스레드(working thread)입니다.
턴(turn)은 해당 세션 내에서의 단 한 번의 실행입니다.
이렇게 정리하고 나니 실수가 명확해졌습니다. completed는 현재 턴이 완료되었음을 의미해야 합니다. 전체 스레드를 분리(detached)해야 한다는 의미가 아니어야 합니다.
왜 이전의 동작이 그렇게 빨리 잘못되었다고 느껴졌을까
문제가 있는 흐름은 다음과 같았습니다:
사용자가 작업 요청
-> 런타임 세션 시작
-> 작업 완료
...
휴대폰이나 바쁜 웹 채팅에서 사용할 때까지는 이것이 그렇게 나쁘게 들리지 않을 수도 있습니다.
실제 후속 요청은 짧습니다. 사람들은 다음과 같이 말합니다:
- 그거 다시 시도해줘 (retry that)
- 이 파일에도 똑같이 적용해줘 (do the same for this file)
- 버튼을 초록색으로 만들어줘 (make the button green)
- 에러를 설명해줘 (explain the error)
- 계속해줘 (continue)
이러한 메시지들은 에이전트가 해당 메시지들이 어떤 스레드에 속해 있는지 여전히 알고 있을 때만 작동합니다.
만약 시스템이 작업 하나가 끝나는 즉시 세션을 분리해 버린다면, 사용자는 다시 모든 것을 처음부터 설명해야 하는 모드로 강제 전환됩니다. 에이전트는 기술적으로 작동하지만, 스레드는 가짜처럼 느껴집니다.
해결책은 더 많은 프롬프팅(prompting)이 아니었습니다
저의 첫 번째 본능은 후속 요청 분류(follow-up classification)를 개선하는 것이었습니다.
그것이 약간의 도움이 되긴 했지만, 핵심적인 해결책은 아니었습니다.
진짜 해결책은 바인딩 모델(binding model)을 명시적으로 만드는 것이었습니다:
- 대화는 지속적이다 (conversation is persistent)
- 런타임 세션 (runtime session)은 기본적으로 고정되어 있다 (sticky)
- 완료(completed) 또는 실패(failed)는 현재 턴 (turn)만 종료한다
- 새로운 세션은 명시적이어야 하거나, 실제 호환성 변경에 의해 발생해야 한다
다시 말해, 나는 작업 완료를 세션의 종료로 취급하는 것을 그만두었습니다.
이 작은 의미론적 (semantic) 변화가 제품의 전체적인 느낌에 영향을 미칩니다.
새로운 세션은 언제 실제로 시작되어야 하는가?
모든 후속 작업이 이전 세션을 영원히 재사용해야 하는 것은 아닙니다.
하지만 그 경계는 의미가 있어야 합니다.
CliGate에서 새로운 세션을 정당화하는 사례는 다음과 같습니다:
- 아직 바인딩된 세션이 없는 경우
- 사용자가 명시적으로 새로운 세션을 요청하는 경우
- 제공자 (provider) 또는 모델이 재사용이 불가능할 정도로 변경된 경우
이는 마지막 턴이 완료되었으므로 스레드 (thread)를 버려야 한다는 말과는 매우 다릅니다.
첫 번째 규칙은 사람들이 생각하는 방식과 일치합니다.
두 번째 규칙은 취약한 배관 (plumbing) 구조가 생각하는 방식과 일치합니다.
이는 웹 채팅과 모바일 채널 모두에서 중요했습니다
좋은 점은 동일한 모델이 한 곳 이상의 장소에 적용된다는 것입니다.
웹 채팅 창에서 사용자는 하나의 탭이 하나의 지속적인 스레드처럼 동작하기를 기대합니다.
Telegram, Feishu 또는 DingTalk의 경우 기대치는 훨씬 더 강력합니다. 전화상의 대화는 이미 압축되어 있습니다. 사용자는 작고 모호한 후속 질문들 사이에서 문맥 (context)을 보존하기 위해 시스템에 의존합니다.
따라서 제가 원했던 제품 동작은 간단했습니다:
- 첫 번째 메시지가 런타임 세션 (runtime session)을 생성한다
- 이후의 메시지는 기본적으로 해당 세션을 계속 사용한다
completed또는failed가 조용히 스레드를 떨어뜨리지 않는다/new명령어나 실제 설정 드리프트 (config drift)가 새로운 세션을 시작할 수 있다
이렇게 하면 에이전트가 단순한 명령 실행기 (command launcher)가 아니라, 실제 작업이 진행되는 스레드에 훨씬 더 가깝게 느껴집니다.
미묘한 UX의 승리는 경계를 명확하게 설명하는 것이었습니다
대화 (conversation), 세션 (session), 턴 (turn)을 분리하고 나면, UI를 추론하기도 더 쉬워집니다.
사용자는 다음과 같이 이해할 수 있습니다:
- 이 대화는 여전히 세션 (session)에 연결되어 있다
- 마지막 턴 (turn)이 완료되었다
- 나는 후속 질문 (follow-ups)을 계속할 수 있다
- 만약 깨끗한 시작을 원한다면, 명시적으로 새로운 세션을 요청해야 한다
이는 도구가 여전히 무언가를 기억하고 있는지 사용자가 추측하게 만드는 것보다 훨씬 더 나은 멘탈 모델 (mental model)입니다.
또한 디버깅을 더 쉽게 만들어 줍니다. 만약 새로운 세션이 나타난다면, 그것은 사용자가 요청했거나 무언가 중요한 것이 변경되었기 때문이어야지, 상태 머신 (state machine)이 성공을 조용히 폐기 (disposal)로 처리했기 때문이어서는 안 됩니다.
내가 지키고 있는 규칙
만약 코딩 에이전트 (coding agent)를 구축하고 있다면, 하나의 성공적인 작업이 스레드 (thread)를 끊어버리게 두지 마세요.
지속 가능한 단위는 대화 (conversation)입니다.
재사용 가능한 작업자는 런타임 세션 (runtime session)입니다.
성공 또는 실패 시 종료되는 것은 턴 (turn)입니다.
이 세 가지 계층을 분리하고 나니, 후속 질문 (follow-ups)이 무작위로 느껴지지 않고 대화처럼 느껴지기 시작했습니다.
이것은 현재 제가 Claude Code, Codex CLI, Gemini CLI, 채널 (channels), 그리고 그 위의 상주 어시스턴트 계층을 위해 사용하는 로컬 컨트롤 플레인 (local control plane)인 CliGate를 형성하는 방식의 일부가 되었습니다.
에이전트 워크플로 (agent workflows)를 구축하고 있다면, 당신은 완료 (completion)를 하나의 턴의 끝으로 취급하고 있습니까, 아니면 전체 스레드의 끝으로 취급하고 있습니까?
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기