배경
#39 (PR #68) 검증 중 사용자가 Primary 응답 도중 추가 발화 시 "작업 꼬임" felt 보고. 같은 session 동시 invoke race + LangGraph messages channel 의 user-user adjacency 가 원인.
해결 방향은 Lock + Queue (batch merge) 패턴 — 사용자 메시지를 큐에 적재하고 graph idle 시점에 batch 로 묶어 처리. 의미 없는 오타 / 중단 의도 / 정정 / 보충 의도를 LLM 이 판단해서 적절히 통합.
큐 책임 위치 = 사용자와 chat 하는 에이전트 (Primary / Architect) (UG 아님).
UG 는 chat protocol routing / SSE relay 인터페이스만 담당. 의미 판단은 P/A 의 LLM 영역. UG 가 큐를 가지면 DIP 깨짐, 에이전트별 정책 분기 시 OCP 깨짐.
상태 (#75 PR 4 머지 후)
✅ 해소
Sequential 보장
SessionRuntime.lock (anyio.Lock) 으로 같은 session 의 두 번째 turn 은 첫 처리 끝날 때까지 대기 → race 자체는 해결
shared/chat_protocol/session/runtime.py 의 message-aware buffer + TTL eviction 으로 in-memory 부담도 처리
❌ 남은 작업
BatchQueueConcurrency 미구현
- 현재는 단순 lock — 두 번째 발화가 대기만 함, "queued" 상태로 사용자에게 인지 시키지 못함
- batch merge (대기 중 누적된 메시지를 단일 HumanMessage 로 concat) 도 없음
queued SSE 이벤트 미발화
ChatEventType.QUEUED schema 에 있으나 worker 가 lock 대기 시점에 발화하지 않음
- 사용자 입장에서 추가 발화가 "꼬인 건지 / 큐에 들어간 건지" 구분 안 됨
user-user adjacency persona 가이드 부재
- batch concat 으로 단일 HumanMessage 가 되면 graph state 에 user-user 연속이 안 생기지만, batch 안에서 separator (
\n\n[<seconds>초 뒤 추가 발화]\n) 의미 LLM 이 해석 가능해야
- Primary / Architect persona 에 가이드 문구 미추가
작업 plan
A. shared/chat_protocol — ThreadConcurrency hook 추상
class ThreadConcurrency(Protocol):
async def acquire_or_queue(
self, session_id: UUID, msg: ChatRequest,
) -> AcquireResult: # ENGAGED / QUEUED
class DefaultConcurrency(ThreadConcurrency):
"""sequential (현재 SessionRuntime.lock 동작). 두 번째 turn 대기."""
class BatchQueueConcurrency(ThreadConcurrency):
"""진행 중이면 in-process 큐 적재 + queued ack. graph idle 시 batch drain."""
router 가 ENGAGED 면 graph 호출, QUEUED 면 SSE queued 이벤트 emit.
B. Primary / Architect — BatchQueueConcurrency 채택
chat handler wiring 단계에서 BatchQueueConcurrency 주입 (다른 agent 는 DefaultConcurrency).
turn 끝나면 큐 검사 — 비어있지 않으면 즉시 새 batch invoke. 큐 메시지를 timestamp 메타와 함께 단일 HumanMessage 로 concat:
사용자 첫 발화
[7초 뒤 추가 발화]
사용자 둘째 발화
C. Persona 가이드 추가
Primary / Architect base.yaml persona 에 가이드:
사용자가 응답 도중 추가 발화한 메시지가 batch 로 들어올 수 있다.
separator [N초 뒤 추가 발화] 가 보이면:
- 의미 없는 오타 / 1~2 글자 noise → 무시하고 본 의도에 응답
- 보충 / 정정 → 통합해서 응답
- 명시 중단 의도 ("멈춰", "그만", "다시") → 진행 방향 재검토
D. FE 측 queued 이벤트 표시 (#71 의 C 항목과 동일)
관련
배경
#39 (PR #68) 검증 중 사용자가 Primary 응답 도중 추가 발화 시 "작업 꼬임" felt 보고. 같은 session 동시 invoke race + LangGraph messages channel 의 user-user adjacency 가 원인.
해결 방향은 Lock + Queue (batch merge) 패턴 — 사용자 메시지를 큐에 적재하고 graph idle 시점에 batch 로 묶어 처리. 의미 없는 오타 / 중단 의도 / 정정 / 보충 의도를 LLM 이 판단해서 적절히 통합.
큐 책임 위치 = 사용자와 chat 하는 에이전트 (Primary / Architect) (UG 아님).
상태 (#75 PR 4 머지 후)
✅ 해소
Sequential 보장
SessionRuntime.lock(anyio.Lock) 으로 같은 session 의 두 번째 turn 은 첫 처리 끝날 때까지 대기 → race 자체는 해결shared/chat_protocol/session/runtime.py의 message-aware buffer + TTL eviction 으로 in-memory 부담도 처리❌ 남은 작업
BatchQueueConcurrency 미구현
queuedSSE 이벤트 미발화ChatEventType.QUEUEDschema 에 있으나 worker 가 lock 대기 시점에 발화하지 않음user-user adjacency persona 가이드 부재
\n\n[<seconds>초 뒤 추가 발화]\n) 의미 LLM 이 해석 가능해야작업 plan
A. shared/chat_protocol — ThreadConcurrency hook 추상
router 가 ENGAGED 면 graph 호출, QUEUED 면 SSE
queued이벤트 emit.B. Primary / Architect — BatchQueueConcurrency 채택
chat handler wiring 단계에서
BatchQueueConcurrency주입 (다른 agent 는 DefaultConcurrency).turn 끝나면 큐 검사 — 비어있지 않으면 즉시 새 batch invoke. 큐 메시지를 timestamp 메타와 함께 단일
HumanMessage로 concat:C. Persona 가이드 추가
Primary / Architect
base.yamlpersona 에 가이드:D. FE 측
queued이벤트 표시 (#71 의 C 항목과 동일)관련