오퍼레이터 승인 루프
외부 상대방과 소통하는 에이전트는 자체 판단으로 메시지를 보내서는 안 되며, 오퍼레이터에게 내부적으로 지속적으로 알려야 합니다. 이 스킬은 계약을 정의합니다: 모든 발신 초안은 의무(obligation)로 기록되고, 오퍼레이터가 정확한 텍스트를 결정하며, 전달 원장(ledger)이 무엇이 발송되었는지 증명합니다.
사용 시기
- 에이전트가 공유 채널, 이메일 또는 채팅에서 고객, 공급업체, 투자자 또는 파트너에게 답변을 초안 작성하고, 발송 전에 사람이 승인해야 하는 경우
- 각 발신 메시지를 정확한 초안 텍스트, 승인한 오퍼레이터, 결정 시간과 연결하는 감사 추적(audit trail)이 필요한 경우
- 오래된 승인이 다시 작성된 초안을 발송하거나, 두 작업자가 동일한 승인된 메시지를 두 번 전달하는 상황을 경험한 경우
- 초안이 원장에 이미 있는 사실을 상대방에게 계속 재질문하는 경우
작동 방식
객체
| 객체 | 의미 |
|---|---|
| 의무(Obligation) | 상대방에게 해야 할 하나의 일. 상태는 drafted, approved 또는 rejected, sent 순서로 이동합니다. direction, counterparty, channel 및 updated_at 에포크를携带합니다. |
| 초안(Draft) | 정확한 초안 텍스트, sha256 해시, 원본 좌표(플랫폼, 채널, 스레드, 사용자) 및 우선순위(P0~P3)를 담는 사이드카 행. 의무당 하나이며, 재제출 시 교체됩니다. |
| 결정(Decision) | 오퍼레이터의 승인 또는 거부. 오퍼레이터 ID, 논스(nonce), 결정이 내려진 초안 에포크가 기록됩니다. |
| 승인 스냅샷(Approval snapshot) | 이미 권한이 부여된 결정 작성자가 기록한 불변의 텍스트, 해시, 에포크 및 대상. 스냅샷이 없으면 발송을 허용할 수 없습니다. |
| 클레임(Claim) | 무작위 토큰과 상태를 가진 내구성 있는 예약. 의무당 최대 하나의 활성 클레임이 허용됩니다. |
| 전달(Delivery) | 하나의 (의무, 결정) 쌍에 대한 한 번의 발송 또는 통지를 증명하는 원장 행. |
참조 스키마는 references/approval-ledger.sql에 있습니다.
초안 제출
- 입력 정리. 제어 문자 제거, 한 줄 필드의 공백 축소, 길이 제한 적용(초안, 요약, 컨텍스트, 상대방). 빈 필드나 크기가 초과된 필드는 자동으로 잘리지 않고 거부됩니다.
- 기준 게이트 실행(아래 참조). 제출을 거부할 수 있습니다.
- 초안 텍스트를 sha256으로 해시. 해시 접두사가 요약에 포함되어 승인 패널이 어떤 텍스트를 승인하는지 보여줍니다.
- Upsert. 동일한 (counterparty, channel)에 대해 열린
drafted상태의 의무가 이미 존재하면, 초안 사이드카를 교체하고 의무의updated_at을 갱신합니다. 이 갱신이 에포크 회전(epoch rotation)입니다: 이전 에포크를 키로 하는 결정은 더 이상 새 텍스트를 해제할 수 없습니다. 그렇지 않으면 상태가drafted인 새 의무를 삽입합니다. - 제출 영수증을 구성되고 확인된 내부 운영 대상(ops destination)으로만 라우팅합니다. 원본이 해당 내부 대상인 경우에만 그곳에서 확인합니다. 절대 자동 응답이 아닌 내부 보고입니다. 초안 해시, 승인 상태, 오퍼레이터 ID 및 워크플로우 메타데이터는 상대방이 볼 수 있는 채널에 노출되지 않도록 합니다. 알 수 없거나 분류되지 않은 원본은 조용히 유지됩니다. 직접 메시지가 자동으로 내부 채널로 간주되지 않습니다.
확인된 내부 운영 대상을 사용할 수 없는 경우, 제출 결과를 내부 도구 결과 또는 오퍼레이터 표면에 보관합니다. 외부 또는 알 수 없는 원본으로 대체(fallback)하지 않습니다. 외부인에게 노출된 도구 결과는 내부 표면이 아닙니다.
초안 제출은 외부 응답을 승인하지 않습니다. 정책에서 허용하는 설명 요청이나 중립적 응답은 별도의 발신 결정이며, 상대방-채널 규율(counterparty-channel-discipline)의 기존 언급, 채널, 초안 전용, 동결 및 금지 제약 조건을 따릅니다. 내부 승인 메타데이터를 공개해서는 안 됩니다.
기준 게이트
초안이 제출되기 전에 상대방의 현재 기준(시간 원장, 계약 저장소 또는 CRM)을 조회합니다:
- 기록에 서명되거나 전달된 계약이 있는 경우: 증거 및 권장 사항과 함께 제출을 거부합니다. 서명 후 상대방에게 사양에 대해 묻는 것은 이 게이트가 방지하려는 정확한 실패입니다.
- 오퍼레이터 재정의:
force_despite_signed_contract를 사용하면 제출을 허용하고 초안 컨텍스트에[BASELINE_OVERRIDE_SIGNED_CONTRACT]를 스탬프합니다. - 게이트 서비스에 연결할 수 없는 경우: 제출을 진행하고 컨텍스트에
[BASELINE_CHECK_UNAVAILABLE]를 스탬프합니다. 패널에 가드가 꺼져 있음이 표시됩니다. 오류가 게이트를 자동으로 비활성화하지 않습니다. - 사실을 사용할 수 있는 경우, 가장 최근 몇 가지를 컨텍스트에
[BASELINE FACTS: ...]다이제스트로 첨부하여 초안이 최신 진실과 함께 도착하도록 합니다.
결정
승인 패널은 상태가 drafted이고 방향이 we_owe_them인 의무를 나열합니다. 승인 또는 거부는 초안 에포크(draft_updated_ts)를 담은 결정 행을 쓰고 동일한 트랜잭션에서 의무 상태를 변경합니다. 에포크가 현재 updated_at과 일치하지 않는 결정은 오래된(stale) 것이며 아무것도 해제해서는 안 됩니다.
이미 권한이 부여된 승인 결정의 경우, 동일한 트랜잭션에서 불변의 obligation_approval_snapshots 행을 삽입합니다: 결정 및 의무 ID, 현재 초안 에포크, 정확한 텍스트 및 SHA-256, 플랫폼/채널/스레드, 종류 draft_sent. 결정 작성자는 쓰기 전에 권한을 설정해야 합니다. 참조는 오퍼레이터를 인증하거나 결정을 생성하지 않습니다. 자동 승인 정책은 참조에서 활성화되거나 확장되지 않습니다. 스냅샷이 없는 레거시 결정은 명시적 조정 또는 새 승인이 필요합니다. 현재 변경 가능한 초안에서 권한을 소급하여 생성하지 마십시오.
전달
SQLite 참조는 references/approval_claims.py에 있습니다. 발송 권한을 부여하지만 전송(transport)은 절대 호출하지 않습니다. SQL 픽스처에서 초기화된 기존 로컬 참조 데이터베이스를 사용하십시오. 모듈은 스키마 또는 프로덕션 마이그레이션을 적용하지 않습니다. 신뢰할 수 있는 결정 작성자만 승인 레코드를 채울 수 있습니다. 모든 작성자는 외래 키 및 재귀 트리거를 활성화하고 스키마 가드를 준수해야 합니다. 관리자 수준의 데이터베이스 변조는 이 모델 범위 밖입니다.
- 바인딩된 승인 초안을 발견합니다. 발견은 권한이 아닙니다.
claim()은 자체BEGIN IMMEDIATE트랜잭션을 열고, 현재 승인된 에포크, 정확한 텍스트, 계산된 SHA-256 및 전체 대상을 스냅샷과 비교하여 검증하고, 토큰을 반환하기 전에 고유 클레임을 삽입합니다. 충돌이 발생하면 작업자는 전송 전에 중단됩니다. 완료된 영수증은 다시 클레임할 수 없습니다. begin_dispatch()는 바인딩을 재검증하고 토큰을 사용하여claimed를dispatching으로 원자적으로 변경합니다. 승리한 호출자만 커밋 후 정확한draft_text및 대상을 받습니다. 텍스트를 다시 생성하거나, 전송을 위해 변경 가능한 사이드카를 다시 읽거나, 다른 시도에 페이로드를 재사용하지 마십시오. 중첩된 호출자 트랜잭션은 거부됩니다. 권한은 이후 호출자 커밋에 의존할 수 없습니다. 전송 중에 데이터베이스 트랜잭션이 열린 상태로 유지되지 않습니다.- 확인된 성공 결과는
complete()로 전달되며, 이는 전달 좌표를 원자적으로 기록하고, 클레임을 delivered로 표시하며 의무를sent로 전환합니다. 동일한 완료는 아무 작업도 하지 않습니다. 충돌하는 좌표는 실패합니다. 영수증 UNIQUE 키는 레코드를 중복 제거하며, 이전 외부 효과는 중복 제거하지 않습니다. - 예외, 시간 초과, begin-dispatch 후 작업자 사망, 또는 영수증 지속성 실패는 차단된 시도를 남깁니다.
mark_unknown()는 불확실성을 기록합니다. 알 수 없는 클레임은 만료, 재개방, 자동 재시도, 또는 해당 의무에 대한 다른 결정이 이를 우회하도록 허용하지 않습니다. 신뢰할 수 있는 호출자는 확인된 성공 좌표 및 증거와 함께reconcile()을 사용할 수 있습니다. 모듈은 증거를 검증하지 않습니다. 영수증이 없다고 해서 전달되지 않았다는 증거는 아닙니다.
보장 사항은 승인된 결정당 하나의 자동 발송 시도이며, 정확히 한 번의 외부 전달이 아닙니다. begin-dispatch 후 전송 전 충돌은 발송이 전혀 없고 클레임이 유지된 상태로 남을 수 있습니다. 알 수 없는 결과를 해제하고 새 시도를 위해 다시 사용하려면 원래 실행자를 차단하고 공급자 의미를 확인해야 합니다. 이 참조는 의도적으로 이러한 재시도 작업을 제공하지 않습니다.
| 클레임 상태 | 허용된 다음 상태 |
|---|---|
| claimed | dispatching 또는 발송 전 취소(cancelled) |
| dispatching | delivered 또는 unknown |
| unknown | 신뢰할 수 있는 조정(reconciliation)을 통해서만 delivered |
| delivered, cancelled | 최종 상태; 결정 키를 재사용할 수 없음 |
클레임이 활성화된 동안 데이터베이스 가드는 의무, 초안 및 결정 쓰기를 동결하며, 교체도 차단합니다. 스냅샷과 클레임은 삭제할 수 없습니다. 재제출 전에 토큰으로 클레임된 작업을 취소하십시오. 그러면 오래된 토큰은 아무 것도 부여하지 않습니다. 발송이 시작된 후에는 새 편집이나 취소를 조정(reconciliation)을 위해 보류하십시오. 이는 진행 중인 작업을 철회하는 척하는 대신 변경 사항을 직렬화합니다.
거부된 결정과 초안 사이드카/스냅샷이 없는 레거시 행은 이 외부 초안 발송 경로에 진입하지 않습니다. 내부 오퍼레이터 표면에 수동 처리를 위해 보고하십시오. 내부 영수증 바닥글은 내부로 유지됩니다: approved by <operator> · receipt <decision_id> · draft sha256 <prefix>. 이미 승인된 외부 텍스트를 변경하여 워크플로우 메타데이터를 추가하지 마십시오.
집중된 로컬 검증은 임시 데이터베이스, 별도 연결 및 시뮬레이션된 시도 카운터를 사용하며, 제공자나 실제 메시지를 사용하지 않습니다: python3 -m unittest discover -s tests/skills -p 'test_approval_delivery_claims.py'. 테스트는 Python 3.11+ 및 SQLite 직렬화 지원이 필요합니다. 참조는 표준 라이브러리만 사용합니다. 기존 데스크 패턴 계약 점검은 별도의 호환성 점검으로 유지됩니다.
시간 제한 자동 승인(선택 사항)
초안은 auto_send_after(에포크 초)를 가질 수 있습니다. 스위프(sweep)는 결정이 없는 상태에서 기한이 지난 초안을 승인하고, 오퍼레이터 auto-ttl을 기록한 후 정상 경로를 통해 전달을 진행합니다. 오퍼레이터 작업이 항상 우선합니다: 결정이 스위프가 보기도 전에 상태를 변경하고, 재제출은 에포크를 회전시키고 기한을 이동 또는 삭제합니다. 스위프는 쓰기 트랜잭션 내에서 상태와 에포크를 다시 확인하므로 경합은 무효 작업으로 해결됩니다. 기한이 없는 초안은 영구적으로 하드 게이트(hard-gated) 상태로 유지됩니다.
신호 연결
초안은 응답하는 인바운드 의무를 지정할 수 있습니다(signal_obligation_id). 이것이 지연 시간 측정(인바운드 신호에서 초안 응답까지)을 위한 유일한 정직한 연결이며, SLA 스캔이 해당 인바운드 항목이 응답된 것으로 처리하도록 합니다. 참조된 행이 존재하지 않으면 제출을 거부합니다.
예제
초안 제출
file_request(
draft="Thanks, we can hold the slot until Friday. Which start date works?",
counterparty="acme-supplier",
context="reply to delivery window question",
origin_platform="slack", origin_channel="#acme-shared",
origin_thread="1712345678.000100", priority="P1",
signal_obligation_id=412)
-> {obligation_id: 431, draft_sha256: "9f2c...", refiled: false}
구성되고 확인된 내부 운영 대상은 다음을 확인합니다: Draft filed for approval (P1, sha 9f2c8a1b). Waiting on operator. 상대방이 볼 수 있는 원본 채널은 제출 통지를 받지 않습니다. 확인된 내부 대상을 사용할 수 없는 경우 영수증은 내부 도구 결과 또는 오퍼레이터 표면에 유지되며, 외부 대체는 없습니다.
조정 후 재제출
오퍼레이터가 더 짧은 초안을 요청합니다. 동일한 (counterparty, channel)에 대해 다시 제출하면 refiled: true가 반환되고, 사이드카 텍스트와 해시가 변경되며 updated_at이 갱신됩니다. 이전 패널 행에서 클릭한 승인은 이전 에포크를 가지며 무시됩니다.
게이트 거부
DeskApprovalError: baseline gate refused this draft: the ledger shows a
signed contract for 'acme-supplier'. Evidence: master agreement executed
2026-08-14. Recommendation: do not ask. Re-file with
force_despite_signed_contract=true if this is genuinely a new thread.
내부 채널의 전달 바닥글
Confirmed for Friday, start date 2026-09-08.
approved by operator-a · receipt 118 · draft sha256 9f2c8a1b2d3e4f50
테스트할 불변 조건
- 제출 영수증은 구성되고 확인된 내부 운영 대상으로만 전달됩니다. 원본이 확인된 내부 대상인 경우에만 원본이 영수증을 받습니다.
- 알 수 없는 원본은 조용히 유지됩니다. 사용할 수 없는 내부 대상은 내부 도구 결과 또는 오퍼레이터 표면을 사용하며, 외부 대체는 없습니다.
- 동일한 (counterparty, channel)로 두 번 제출하면 하나의 의무, 두 개의 에포크가 생성됩니다.
- 오래된 에포크를 가진 결정은 전달 행을 생성하지 않습니다.
- 두 명의 동시 클레임자는 하나의 발송 권한을 얻습니다. 패자는 전송을 시도하지 않습니다.
- 알 수 없는 결과와 실패한 영수증 지속성은 자동 재시도를 활성화하지 않습니다.
- 성공적인 완료는 하나의 트랜잭션에서 영수증과 전송 상태를 기록합니다.
- 변경된 에포크, 텍스트, 해시 또는 대상은 클레임을 획득하거나 시작할 수 없습니다.
- 활성 클레임은 재제출을 차단합니다. 발송 전 취소만이 해당 홀드를 해제할 수 있습니다.
- 게이트를 사용할 수 없으면 마커가 스탬프됩니다. 게이트 서명된 경우 강제 없이 거부합니다.
- Auto-ttl은 오퍼레이터가 이후에 재제출한 텍스트에 대해 절대 실행되지 않습니다.