Skip to content

인바운드 턴 응답이 DM 대신 lastActive 공개 채널로 배달된다 (동시 턴 경합) #474

Description

@lidge-jun

증상

DM 으로 물어본 답변이 공개 채널 로 배달됐다. 사용자가 직접 지적한 사고이며, 하트비트/리마인더 경로(#437/#438)가 아니라 인바운드 턴의 응답 경로 다.

원인

sendChannelOutput 이 명시 target 없이 호출되면 채널당 하나뿐인 휘발 슬롯(lastActive / latestSeen)으로 목적지를 정한다. 그 슬롯은 "누가 봇에게 가장 최근에 말했나"를 담고 있어서 인바운드 메시지마다 덮어써진다.

멀티세션이 켜지면 DM 턴과 채널 턴이 동시에 돈다. DM 턴의 에이전트가 답을 만드는 사이 공개 채널에 누가 한 마디 쓰면 슬롯이 그 채널로 바뀌고, DM 답변이 거기로 나간다.

에이전트가 target 을 빠뜨리는 것도 우연이 아니다: 한 번 거절당하면 "target 을 빼면 되겠지"로 학습해서 그 다음부터 계속 생략한다.

t0  DM 턴 시작            slot = DM
t1  공개 채널에 누가 발화   slot = #channel   ← 덮어씀
t2  DM 턴 응답 (target 없음) → slot 조회 → #channel 로 배달

왜 슬롯으로는 못 고치나

슬롯은 "누가 마지막에 말했나"를 답한다. 필요한 건 "지금 내가 누구에게 답하는 중인가"이고, 그 값은 턴이 사는 동안 바뀌지 않는다. 서버 전역에 하나만 둘 수 있는 값으로는 동시에 도는 여러 턴을 구분할 수 없다.

환경변수로도 못 고친다

에이전트 프로세스 환경변수에 주소를 심는 방법은 codex-app 에서 깨진다 — 풀이 프로세스를 재사용 하고 env 는 생성 시점에만 설정하기 때문에, 재사용된 프로세스는 자기를 처음 띄운 턴의 주소를 계속 답한다. 멀티세션이 꺼져 있으면 모든 대화가 한 스코프를 공유해서 이 오배송이 상시로 난다.

해결 방향

주소를 턴 프롬프트 로 실어 보내고(reply_to=) 에이전트가 그대로 돌려주게 한다(turn_conversation). 턴마다 새로 만들어지는 값이라 프로세스 재사용과 무관하다.

우선순위: 명시 target > 턴 주소 > lastActive > latestSeen. 턴 주소도 동일한 allowlist 검증 을 받는다 — 기본값을 좁히는 것이지 접근을 넓히는 게 아니다.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions