버튼을 누른 뒤 응답을 받기 전에 연결이 끊기면 다시 누르고 싶어진다. 하지만 브라우저와 서버는 이미 처리했을 수 있다. 나는 북극성의 연결 오류를 업무 실패와 분리하고, 같은 행동을 다시 보내기 전에 기존 결과를 읽게 했다.

응답을 못 받은 사실은 하나뿐이다

소켓이 닫혔거나 시간 제한이 끝났다는 오류가 알려 주는 것은 호출자가 완전한 응답을 받지 못했다는 사실이다. 입력이 전송됐는지, 서버가 처리했는지, 결과만 늦었는지는 그 오류 한 개로 알 수 없다. 이를 모두 실패로 묶으면 복구가 중복 전달로 바뀐다.

그래서 중계기와 클라이언트 오류에는 automaticReplay=false가 붙는다. 문구만 붙인다고 모든 중복이 사라지는 것은 아니지만 호출자에게 다음 행동의 의미를 명시한다. 새 전송보다 기존 task와 run, 페이지 결과를 읽는 경로가 먼저 필요하다는 뜻이다.

두 종류의 요청 번호를 혼동하지 않는다

stdio 클라이언트의 JSON-RPC 번호와 로컬 파이프의 UUID는 다른 번호다. 서버의 bridgeCall은 기본으로 새 UUID를 만든다. 같은 도구 이름과 인자를 다시 보내거나 JSON-RPC 번호를 비슷하게 썼다고 이전 파이프 영수증이 자동 재사용되는 것은 아니다.

프로세스 수명 안의 동일 ID 영수증 보호는 그 ID와 같은 내용으로 조회할 때의 계약이다. 일반 호출자가 새 tools/call을 만드는 일을 안전한 재조회라고 부르면 안 된다. 나는 연결 오류 뒤 복구를 설명할 때 이 구분을 남겨 중복 방지 기능의 범위를 과장하지 않게 했다.

기존 작업을 다시 읽을 수 있는 입구를 둔다

브라우저 객체의 resume은 현재 context를 읽어 taskId와 running 상태를 확인한다. 다른 작업이면 이어 붙이지 않는다. 같은 작업이더라도 pendingOutcome이 있으면 blocked로 시작한다. 재연결했다는 이유로 불확실한 결과를 지우지 않는다.

플러그인은 run ID의 상태를 읽는 입구가 있고 실행 기록의 범위를 함께 확인한다. status는 새로운 실행 요청과 다르다. 작업이 이미 끝났거나 재시작으로 상태가 달라졌다면 그 사실에 맞춰 처리해야 한다. resume이라는 이름을 모든 과거 작업의 무조건 재개로 해석하지 않는다. 호출자는 시작 응답의 task와 run 식별자를 남겨 연결을 복구할 때 현재 실행과 대조할 수 있어야 한다.

결과 확인은 원래 기대를 대상으로 한다

verifyOutcome은 미확정 입력의 결과를 읽는 경로다. 클릭을 다시 보내 성공 화면을 만드는 기능이 아니다. 확인되면 다음 단계로 갈 수 있지만 다시 시간 초과가 나면 막힌 상태가 남는다. 사용자가 멈추면 그 결정을 보존한다.

편의 API도 모호한 오류 뒤 다음 fill을 차단하고 읽기를 허용한다. 결과를 못 읽었다고 다른 문구를 성공 조건으로 바꾸어 통과시키면 검증의 의미가 사라진다. 원래 요청의 결과와 현재 관찰을 대조해야 늦게 온 성공과 확인되지 않은 작업을 구별할 수 있다.

재전송을 막았는지 횟수도 본다

관련 fixture 시험은 불확실한 Enter 뒤 입력 변경을 거절하고 finish도 막는지 확인한다. 그동안 실제 변경 횟수가 1인지, 읽기는 가능한지 본다. 늦은 결과를 확인하는 시험도 최초 전달 뒤 변경 횟수가 늘지 않아야 한다.

이 글은 현재 클라이언트와 페이지 API, 실제 시험 코드를 읽어 썼다. 네트워크를 끊어 실제 예약이나 업로드를 중복시키는 새 운영 시험은 하지 않았다. 테스트 대역에서 확인한 제어 흐름과 외부 서비스에서의 저장 보장은 다르며 영속 원장과 서비스별 확인이 더 필요할 수 있다.

복구 안내도 다음 행동을 정하는 인터페이스다

사용자에게 “연결 오류, 다시 시도”만 보여 주면 같은 행동을 반복하기 쉽다. 나는 어떤 요청을 보냈고 현재 결과가 무엇인지 읽을 수 있는 안내가 필요하다고 봤다. 통신을 회복하는 절차와 업무를 다시 시작하는 판단을 분리해야 한다.

다른 업무 프로그램에서도 마찬가지다. 결제·저장·발송처럼 결과가 남는 행동에는 읽기 전용 확인 입구와 불확실 상태를 마련한다. 전송되지 않았다는 근거가 있을 때와 전달 여부를 모를 때를 구별한다. 에이전트가 편하게 다루는 도구일수록 이 구분을 자동 재시도 뒤에 숨기지 않아야 했다.