본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 05. 15. 00:59

나는 AI 어시스턴트가 모든 메시지를 가로채도록 내버려 두는 것을 그만두었다

요약

기존 AI 툴링에서 어시스턴트가 모든 사용자 메시지를 가로채면서 발생하는 '의도 재작성' 및 '세션 연속성 상실' 문제를 해결하는 아키텍처 개선에 대한 글입니다. 필자는 코딩 워크플로우를 '현재 런타임 세션 유지'와 '상위 수준 조정(Supervision)'이라는 두 가지 명확한 의도로 분리하고, 이를 로컬 AI 게이트웨이인 CliGate에서 구현했습니다. 개선된 아키텍처는 일반 메시지는 간섭 없이 직접 런타임 경로로 전달하여 안정성을 확보하고, 어시스턴트 협업은 오직 상태 검사, 작업 조정 등 감독자 역할에만 집중하도록 명확히 분리했습니다. 이 접근 방식은 개발자들이 '마법 같은 기능'보다 제품의 예측 가능성과 세션 연속성이라는 핵심 가치에 더 집중하게 만들었습니다.

핵심 포인트

  • 코딩 워크플로우는 '현재 런타임 대화 유지'와 '상위 수준 조정(Supervision)' 두 가지 의도로 명확히 분리되어야 한다.
  • AI 어시스턴트가 모든 메시지를 가로채는 것은 세션 연속성을 방해하고 사용자의 혼란을 야기한다.
  • CliGate는 로컬 AI 게이트웨이로서, 일반 메시지는 직접 런타임 경로로 전달하고, 어시스턴트 호출 시에만 명시적인 감독자 역할을 수행하도록 아키텍처를 개선했다.
  • 안정적이고 예측 가능한 툴링은 '지루하게' 느껴질 만큼 단순한 규칙(간섭 최소화)을 따르는 것이 가장 중요하다.

AI 툴링 (AI tooling)을 구축하는 동안 저는 똑같은 문제에 계속 부딪혔습니다. 어시스턴트가 똑똑해 보일수록, 제품은 예측 불가능하게 느껴졌습니다. 당신은 현재의 코딩 세션 (coding session)을 계속하고 싶어서 메시지를 보냅니다. 하지만 시스템은 당신이 아마도 "새로운 작업을 시작"하려는 의도였을 것이라고 판단하여 의도 (intent)를 다시 작성하고, 갑자기 당신은 당신이 사용하고 있다고 생각했던 런타임 (runtime)과 더 이상 대화하고 있지 않게 됩니다. 매일 사용하려고 하면 이 문제는 작게 들리지 않습니다. 문제는 모델의 품질이 아니었습니다. 실패 모드 (failure mode)는 기반이 되는 실행기 (executor)가 Codex인지 Claude Code인지와는 거의 관련이 없었습니다. 진짜 문제는 제어 (control)였습니다.

코딩 워크플로 (coding workflow)에는 적어도 두 가지 매우 다른 의도가 있습니다: 1. 현재 런타임 세션과 계속 대화하고 싶다. 2. 상위 수준의 어시스턴트가 전체 상황을 살펴보고, 무엇을 할지 선택하며, 나를 위해 작업을 조정하기를 원한다. 만약 이 두 경로가 동일한 기본 진입점 (entry point)을 공유한다면, 제품은 너무 많은 추측을 하기 시작합니다. 그 추측은 비용이 많이 듭니다. 그것은 세션의 연속성 (session continuity)을 변화시키고, 멘탈 모델 (mental model)을 방해하며, 사용자로 하여금 시스템이 실제로 듣고 있는 것인지 아니면 단순히 패턴 매칭 (pattern-matching)을 하고 있는 것인지 의구심을 갖게 만듭니다.

CliGate에서 우리가 변경한 점
CliGate는 Claude Code, Codex CLI, Gemini CLI, OpenClaw, 웹 채팅 (web chat), 그리고 채널 기반 워크플로 (channel-based workflows)를 위한 우리의 로컬 AI 게이트웨이 (AI gateway)입니다. 우리는 "어시스턴트"를 보편적인 기본값으로 취급하는 대신, 상호작용 모델을 두 가지 명시적인 모드로 분리했습니다: 직접 런타임 어시스턴트 협업 (Direct Runtime Assistant Collaboration).

이것은 UI의 세부 사항처럼 들리지만, 아키텍처 (architecture)를 바꾸었습니다. 직접 런타임 (Direct Runtime): 의도적으로 지루하게 만들기
직접 런타임 모드에서 규칙은 간단합니다: 당신의 메시지는 현재 런타임 경로로 전달됩니다. 의도 가로채기 (intent interception)는 없습니다. 놀라운 감독 레이어 (supervision layer)도 없습니다. "먼저 다른 무언가를 함으로써 도와줘야 할지도 몰라" 같은 것도 없습니다. 그 경로는 중요합니다. 왜냐하면 안정적인 툴링 (tooling)은 가장 좋은 의미에서 지루하게 느껴지기 때문입니다. 만약 사용자가 이미 활성화된 Codex 또는 Claude Code 세션 안에 있다면, 사용자가 명확하게 다른 것을 요청하지 않는 한 다음 메시지는 해당 세션을 계속해야 합니다.

우리의 코드에서 그 구분은 일반적인 라우팅 경로(routing path)가 시작되기 전에 강제됩니다: const assistantResult = await this.assistantModeService.maybeHandleMessage({ conversation, text, defaultRuntimeProvider, cwd, model }); if (assistantResult) { return assistantResult; } const result = await this.messageService.routeUserMessage({ message: { text }, conversation, defaultRuntimeProvider, cwd, model });. 만약 어시스턴트 모드(assistant mode)가 활성화되어 있지 않다면, 메시지는 런타임 경로(runtime path)로 직접 전달됩니다. 이 하나의 결정이 많은 모호함을 제거했습니다.

어시스턴트 협업: 명시적 감독 (explicit supervision)
어시스턴트 경로는 여전히 유용합니다. 다만 런타임 경로를 사칭해서는 안 됩니다. 사용자가 명시적으로 CliGate Assistant를 호출할 때, 그들은 다른 종류의 도움을 요청하는 것입니다: 현재 상태 검사, 기존 세션을 재사용할지 아니면 새로운 세션을 시작할지 결정, Codex 또는 Claude Code 선택, 승인(approvals), 대기 중인 질문, 실패 및 완료 사항 추적, 그리고 결과를 하나의 답변으로 요약하여 다시 전달하기. 이것은 감독자(supervisor) 역할이지, 터미널(terminal) 역할이 아닙니다.

우리가 정착한 멘탈 모델(mental model)은 다음과 같습니다: 사용자(User) -> CliGate Assistant -> Codex / Claude Code로 위임(delegate) -> 실행자(executor)가 구체적인 작업 수행 -> 어시스턴트가 합성된(synthesized) 결과 반환. 일단 이 경계를 수용하고 나니, 여러 설계 결정이 훨씬 쉬워졌습니다.

왜 혼합하는 것이 잘못된 느낌이었는가
이러한 분리가 이루어지기 전에는, 어시스턴트를 기본적으로 "똑똑하게" 만들고 싶은 유혹이 있었습니다: 자연어 의도(natural language intent) 감지, 일반 채팅 가로채기, 이것이 질문인지, 작업인지, 혹은 운영(operation)인지 결정하기. 그러한 접근 방식은 데모를 보여주기에는 좋습니다. 하지만 시간이 지나면서 그 가치가 떨어집니다. 실제 사용 환경에서 개발자들은 마법 같은 기능보다는 제품이 세션 연속성(session continuity)을 유지하는지에 더 관심을 가집니다. 만약 그들이 이미 작동 중인 런타임 안에 있다면, 갑작스러운 오케스트레이션(orchestration)은 시스템이 운전대를 가로챈 것처럼 느껴집니다.

그래서 우리는 철학을 바꾸었습니다: 일반적인 메시지는 간섭을 최소화해야 하며, 어시스턴트의 개입은 명시적이어야 합니다. 어시스턴트는 침해적인 존재가 아니라 협력적인 존재로 느껴져야 합니다.

가장 중요했던 구현 세부 사항
모드 전환(mode switch)은 의도적으로 작게 설계되었습니다. assistant-core/mode-service.js 내부에서, 우리는 대화가 이미 어시스턴트 모드(assistant mode)이거나 사용자가 /cligate로 명시적으로 트리거했을 때만 어시스턴트 흐름(assistant flow)에 진입합니다.

if ( ! parsed && ! assistantModeActive ) { return null ; }

return null이 아주 많은 일을 수행합니다. 이는 어시스턴트가 모든 일반적인 메시지를 재해석할 기회를 갖지 않음을 의미합니다. 어시스턴트는 사용자가 실제로 요청했을 때만 실행됩니다. 이와 일치하는 탈출구(escape hatch)인 /runtime도 있습니다. 이는 대화를 다시 직접적인 런타임 모드(direct runtime mode)로 되돌립니다. 이는 모든 문장에서 의도(intent)를 추론하려고 시도하는 것보다 훨씬 더 존중하는 느낌을 주었습니다.

어시스턴트가 실제로 책임지는 것
우리는 또한 코드베이스 내에서 역할 경계(role boundaries)를 더 엄격하게 정해야 했습니다.

CliGate Assistant가 책임지는 것:

  • 오케스트레이션 (orchestration)
  • 관찰 (observation)
  • 승인 및 차단 (approvals and blockers)
  • 작업 추적 (task tracking)
  • 결과 구성 (result composition)

Codex와 Claude Code가 여전히 책임지는 것:

  • 파일 편집 (editing files)
  • 명령 실행 (running commands)
  • 브라우저 작업 (browser work)
  • 구체적인 작업 실행 (concrete task execution)

당연하게 들릴 수도 있지만, 어시스턴트가 스스로 실행자(executor)인 척하기 시작하면 시스템은 복잡해집니다. 일단 어시스턴트를 만능 채팅 브레인(universal chat brain)이 아닌 감독관(supervisor)으로 취급하자, 아키텍처를 추론하기가 더 쉬워졌습니다:

  • assistant-core는 어시스턴트의 의미론(semantics)과 상태(state)를 소유합니다.
  • assistant-agent는 LLM 감독관 루프(supervisor loop)를 소유합니다.
  • agent-* 모듈은 실행 및 런타임 기질(substrate)로 남습니다.

사용자에게 보이는 결과
이제 제품은 영리한 라우터(router)라기보다 실제 팀원처럼 작동합니다. 활성 런타임 세션(active runtime session)을 계속하고 싶다면 그냥 계속하면 됩니다. 시스템이 한 걸음 물러나 더 넓은 상황을 살펴보고 세션 간의 작업을 조정하기를 원한다면, 의도적으로 어시스턴트를 호출하면 됩니다.

그 분리는 즉시 세 가지를 개선했습니다: 세션 연속성 (session continuity)을 더 신뢰할 수 있게 되었고, 작업 위임 (task delegation)을 더 설명하기 쉬워졌으며, 어시스턴트가 모든 턴을 가로채지 않고도 감독할 수 있게 됨에 따라 모바일 및 채널 워크플로 (workflows)가 더 타당해졌습니다. 저는 더 많은 AI 도구들이 이러한 분리를 필요로 한다고 생각합니다. 많은 AI 제품들이 더 단순하게 느껴진다는 이유로 "어시스턴트 (assistant)"와 "실행자 (executor)"를 하나의 대화로 모호하게 통합합니다. 저는 그 단순함이 가짜라고 생각합니다. 제품에 장기 실행 세션 (long-running sessions), 승인 (approvals), 재시도 (retries), 재개 가능한 작업 (resumable work) 또는 여러 실행자 (multiple executors)가 포함되는 즉시, 두 가지 모드가 필요합니다: 하나는 현재의 런타임 (runtime) 내부에 머물기 위한 모드이고, 다른 하나는 감독자 (supervisor)에게 해당 런타임을 중심으로 작업을 조정하도록 요청하는 모드입니다. 이러한 분리가 없다면, 시스템은 단순히 듣고만 있어야 할 때조차 계속해서 추측을 하게 됩니다. 여러분은 여러분의 도구에서 이를 어떻게 처리하고 계신가요? Repo: github.com/codeking-ai/cligate

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
2

댓글

0