기능 (읽기 vs. 쓰기)
여기 모든 것을 예측하는 하나의 규칙: Pulsy는 자유롭게 읽고, 모든 Amazon 쓰기는 실행이 아니라 제안되며, 세 게이트를 통과한 뒤에야 적용된다. “예산을 20% 줄일게요” 라는 챗 응답은 제안이 대기 중이라는 뜻이지, 무언가 바뀌었다는 뜻이 아닙니다.
이 페이지는 그 경계의 내부 SSOT입니다. 코드(ai-api + pulsead-agents)에서 작성했고, 검증할
수 있도록 심볼을 인용합니다.
읽기 표면 (접근 범위 외 게이트 없음)
섹션 제목: “읽기 표면 (접근 범위 외 게이트 없음)”읽기는 챗 턴 도중 인라인으로 실행되며 승인이 필요 없습니다 — 해석된 범위(팀 + country + 활성 프로필)만 있으면 됩니다. 범위:
- Amazon Ads 엔티티 — 캠페인·애드그룹·광고·타겟·키워드·네거티브 키워드·포트폴리오(SP/SB)
list/get,
pulsead-agents/shared/amazon_ads.py경유. Amazon 읽기 엔드포인트 (/adsApi/v1/query/*)를 호출. - 웨어하우스 — Snowflake의 분석·과거 지표. 쿼리 경로는 구조적으로 읽기 전용:
pulsead-agents/shared/snowflake.py가SELECT/WITH로 시작하지 않는 것을 거부. - 퍼포먼스 진단 — ROAS/ACoS 분석, 후보 엔티티 탐지
(
ai-api/app/services/pulsy/performance_diagnose_service.py). - AMC — 쿼리 결과 및 CSV 미리보기(
agent_amc.py). - 히스토리 — optimize-cycle, 실행, 캠페인 히스토리 집계.
읽기는 닫힌 쪽으로 실패합니다: 범위를 해석할 수 없으면 더 넓은 기본값이 아니라 빈 값/불허가 반환됩니다. 빈 결과는 종종 “데이터 없음”이 아니라 “범위 미해석”을 뜻합니다.
쓰기 표면 (여기는 전부 게이트됨)
섹션 제목: “쓰기 표면 (여기는 전부 게이트됨)”Pulsy는 다음 Amazon 변경을 제안할 수 있습니다 — 예산 조정, 입찰 조정, 캠페인 일시중지, 캠페인 업데이트, 키워드 생성/업데이트/일시중지, 타겟 생성/업데이트, 캠페인 생성, optimize-cycle 리밸런스 실행. (리포트 스케줄도 “쓰기”지만 Amazon이 아니라 Pulsy 자체 DB만 건드립니다.)
어느 것도 인라인으로 실행되지 않습니다. 각자가 직렬 세 게이트를 통과합니다:
게이트 1 — 사람 승인 밸브
섹션 제목: “게이트 1 — 사람 승인 밸브”뮤테이션 도구는 @require_approval로 감싸여 있습니다
(ai-api/app/agents/tools/mutation_tools/_decorator.py). 에이전트가 호출하면 변경을 실행하지
않고:
- 보류 중
action_request행(정규화된 변경)을 기록하고, - 챗에
:::approval카드를 내보내고 턴을 일시정지 — 도구는 아무것도 적용하지 않고 요약을 반환하고, - 사람이
POST /pulsy/action-chats/approve를 호출해야만 적용되며, 승인된 변경을pulsead-agents로 디스패치합니다.
보류 요청은 ~5분 TTL을 갖습니다 — 너무 늦게 승인하면 410. 라이프사이클은 상태 머신입니다:
pending → approved → executed | failed, 더해서 canceled / edited / expired / superseded.
즉 “만료,” “이미 적용,” “취소”는 버그가 아니라 예상된 결과입니다.
게이트 2 — viewer 역할 차단
섹션 제목: “게이트 2 — viewer 역할 차단”ai-api/app/services/pulsy/campaign_mutation_guard.py는 viewer 역할 멤버가 캠페인
뮤테이션을 승인하려 하면 403(VIEWER_MUTATION_FORBIDDEN)을 냅니다. 게이트 집합은
MUTATION_TOOL_NAMES; 읽기(ads_query_*)와 리포트 스케줄 쓰기는 의도적으로 제외되어 viewer도
그것들은 승인할 수 있습니다.
게이트 3 — 백엔드 검증, dry-run, 브랜드 화이트리스트
섹션 제목: “게이트 3 — 백엔드 검증, dry-run, 브랜드 화이트리스트”승인된 변경은 pulsead-agents(web/backend/routers/ads.py)로 모입니다:
- Dry-run —
dry_run이 설정되면_execute_mutation이 단락 처리: Amazon을 호출하지 않고 미리보기를 반환하며status="dry_run"을 기록. - 예산 가드레일 — per-action 예산 핸들러(예:
adjust_budget)가shared/guardrails.py:validate_budget_change를 호출해 변경을 상대 ≤50%, 절대 ≤$10,000, 하한 ≥$1로 제한. - 승인 상태 —
validate_approval_before_execution는approved/auto_approved, 미만료 요청을 요구.
라이브 vs. 프리뷰 — 가장 중요한 caveat
섹션 제목: “라이브 vs. 프리뷰 — 가장 중요한 caveat”자율(비-챗) optimize-cycle 예산 변경은 오늘 정확히 한 파일럿 브랜드에서만 라이브입니다.
pulsead-agents/shared/oc_live_brands.py가 LIVE_BUDGET_UPDATE_BRANDS = {"KISS"}를
하드코딩합니다. 나머지 모든 브랜드에서는 옵티마이저가 결정을 계산해 oc_allocations에 쓰되
Amazon으로는 아무것도 보내지 않습니다(그래도 "success"로 기록). 그래서 누군가 “Pulsy가
예산을 자동 최적화한다”고 하면, 정직한 내부 진술은: 한 파일럿 브랜드에 한해서이며, 나머지는
프리뷰/스텁.
관련된 두 사실:
- 직접 챗
adjust-budget경로에는 그런 화이트리스트가 없습니다 — 승인되고 dry-run이 아니면 어느 브랜드든 라이브. (그 비대칭이 의도적인지는 ops 확인 사항 — 아래 참고.) - 자동 승인은
pipeline_auto_approval행이 브랜드/스테이지에 대해 활성화하면 사람 클릭을 건너뛸 수 있습니다. 여전히 가드레일과 화이트리스트는 통과합니다 — 안전이 아니라 클릭만 제거.
Pulsy는 AOP가 아닙니다 — 표면을 혼동하지 마세요
섹션 제목: “Pulsy는 AOP가 아닙니다 — 표면을 혼동하지 마세요”aop-hermes(오퍼레이터 코크핏, 이름 지도 참고)도 Amazon Ads를 운영하지만,
Pulsy 제품이 아니라 PulseAd 직원용 별도 도구입니다. 쓰기 표면이 훨씬 크고(캠페인·애드그룹·
타겟·키워드·광고·크리에이티브·오디언스에 걸쳐 ~45개 생성/업데이트/삭제 액션) 도구별 dry-run
플래그로만 게이트됩니다 — 제안→승인 밸브가 없습니다. 이 문서가 “Pulsy가 승인하에 X를 쓸 수
있다”고 할 때 그것은 게이트된 제품 표면이며, AOP의 비게이트 오퍼레이터 표면은 다른 것입니다.
알려진 공백 — ops 확인 필요 (여기서 단정하지 않음)
섹션 제목: “알려진 공백 — ops 확인 필요 (여기서 단정하지 않음)”코드가 결론짓지 않는 사실 미상 항목들; 사실로 취급하기 전에 확인하세요:
- 설정된
SNOWFLAKE_ROLE이 Snowflake grant 수준에서도 읽기 전용인지(코드는SELECT/WITH가드만 강제). - 어떤 브랜드/스테이지에 자동 승인이 켜져 있는지(
pipeline_auto_approval테이블에 있고 코드엔 없음). - 화이트리스트 없는 직접
adjust-budget경로가 의도적인지, 아니면 optimize-cycle의KISS-only 게이트와 맞춰야 하는지.