Files
web-page-backend/docs/superpowers/specs/2026-06-11-insta-autonomous-card-issuance-design.md
gahusb c99017e68c docs(spec): insta 자율 카드 발급 (스마트 에이전트 3번) 설계
선별 지능(4신호)+카드별 승인 게이트+상태머신/발행이력. 접근법 A: insta-lab 선별·상태 소유, agent-office 오케스트레이션·텔레그램 승인.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 02:05:51 +09:00

9.3 KiB
Raw Blame History

insta 자율 카드 발급 (스마트 에이전트 3번) — 설계

작성 2026-06-11. InstaAgent를 "후보 푸시/단순 auto_select"에서 선별 지능 + 승인 게이트 + 카덴스/추적을 갖춘 자율 발급 파이프라인으로 확장.

1. 목표

매일 09:30, InstaAgent가 발행할 가치 있는 주제만 자율 선별해 카드를 생성·렌더하고, 카드별 승인 게이트로 사람이 최종 결정(브랜드 안전)한 뒤 업로드용 카드를 발급한다. 발행 상태·이력을 추적해 중복 회피·카덴스 판단에 환류한다.

Instagram Graph API는 사용하지 않는다(수동 업로드). "발행(published)" = 승인되어 업로드 준비가 끝난 카드 상태 + 텔레그램으로 전달.

2. 현재 상태 (배경)

  • insta-lab: 뉴스수집→키워드추출→슬레이트 생성(POST /slates)→Redis push→Windows 워커 렌더→webhook이 card_assets 등록. (2026-06-11 렌더 갭 복구 완료, slate 상태 draft→rendered.)
  • agent-office InstaAgent: 09:30 cron에서 collect+extract 후 (기본) 텔레그램 후보 버튼 푸시 / (auto_select=True) 카테고리 1위 키워드 자동 렌더+미디어그룹 발송. 버튼 탭 → render_{kid} 콜백 → 슬레이트 생성·렌더·발송.
  • account_preferences(카테고리 가중치) 존재. 발행 성과 추적은 없음.

즉 "생성→렌더→전달"은 동작한다. 본 설계는 그 앞단의 자율 선별과 뒷단의 승인·추적을 추가한다.

3. 요구사항 (확정)

  • 선별 신호 4종: ① 중복 회피(최근 발행/반려 주제 제외) ② 신선도(뉴스 최신성) ③ 계정 컨셉 적합도(카테고리 가중치) ④ Claude 판단(카드가치·흥미·리스크). 가중합 → threshold 게이트.
  • 카덴스: 에이전트 결정 — 매일 09:30, threshold 이상인 픽만 max_per_day까지(0~N 가변). 가치 없으면 발행 안 함.
  • 승인: 카드별 게이트. 자동 생성 후 텔레그램 프리뷰 [✅승인][❌반려][🔄재생성]. 승인만 published.
  • 추적: slate 상태 ∈ {draft, rendered, rejected, published} + 발행 이력. decision=approved→published, decision=rejected→rejected("approved"는 별도 저장 상태가 아니라 decision 액션). 성과 지표(좋아요·도달)는 범위 외(YAGNI — IG API 없어 수동).

4. 아키텍처 (접근법 A: 데이터 있는 곳에서 선별, 에이전트는 오케스트레이션)

[09:30 cron] InstaAgent.on_schedule (autonomous_issue=True)
 1. collect + extract                                  (기존 재사용)
 2. GET /api/insta/keywords/ranked?threshold&limit     ← insta-lab: 4신호 점수
 3. eligible 픽마다(max_per_day): create_slate → wait render  (기존 재사용)
 4. 텔레그램 프리뷰(커버1장+요약) + [✅][❌][🔄] + agent_task(requires_approval) → waiting

[telegram webhook] → InstaAgent.on_callback
   issue_approve_{id} → POST /slates/{id}/decision{approved} → published + 10장 미디어그룹 + /package zip
   issue_reject_{id}  → POST /slates/{id}/decision{rejected}
   issue_regen_{id}   → 같은 키워드로 슬레이트 재생성(새 카피) → 새 프리뷰 (이전 슬레이트 폐기)

경계: insta-lab = 선별 점수 + 상태머신(DB 소유), agent-office = cron 오케스트레이션 + 텔레그램 승인.

5. insta-lab 상세

5.1 app/selection.py (순수 함수)

입력: 후보 키워드 리스트, 발행/반려 이력, 카테고리 선호 가중치, (선택) Claude 판단 점수. 출력: 후보별 {keyword_id, final_score, breakdown:{dedup,freshness,account_fit,claude}, eligible}.

신호별 정의:

  • dedup (0 또는 1, exclude 게이트): 최근 dedup_window_days(기본 14) 내 published/rejected 슬레이트와 동일 키워드(정규화 후 exact/substring) + 동일 카테고리면 eligible=False로 제외.
  • freshness (0~1): 키워드 suggested_at이 최근일수록 높음(예: 24h=1.0, 선형 감쇠, 7일+=0).
  • account_fit (0~1): account_preferences[category].weight(정규화) × 키워드 자체 score.
  • claude (0~1): Claude Haiku가 후보 일괄 평가(아래 5.3). 실패 시 이 항 제외하고 나머지로 정규화(graceful).
  • final_score = 가중합 w_fresh*freshness + w_fit*account_fit + w_claude*claude (dedup 제외 통과한 것만). 기본 가중치 {fresh:0.3, fit:0.3, claude:0.4}. eligible = (dedup 통과) and (final_score >= threshold).

5.2 엔드포인트

  • GET /api/insta/keywords/ranked?limit=N&threshold=T
    • 내부에서: 미사용 키워드 조회 + 발행/반려 이력 조회 + 선호 조회 + Claude 일괄 호출 → selection.py → 정렬된 후보 + breakdown + eligible 반환.
  • POST /api/insta/slates/{id}/decision body {"decision": "approved"|"rejected"}
    • approved → status='published', published_at=now, decision_at=now (멱등: 이미 published면 no-op).
    • rejected → status='rejected', decision_at=now.

5.3 Claude 판단 프롬프트 (insta-lab, 기존 ANTHROPIC 클라이언트 재사용)

  • 1회 호출로 후보 N개 일괄 평가. 입력: 각 후보 {keyword, category}. 출력: JSON [{keyword_id, score(0~1), reason}].
  • 기준: 카드뉴스로 만들 가치(흥미·시의성·정보성) 및 리스크(민감·논란). 모델 ANTHROPIC_MODEL_HAIKU.
  • 실패/파싱오류 → 빈 결과 반환 → selection이 claude 항 제외.

5.4 스키마 (idempotent ALTER)

  • card_slatespublished_at TEXT NULL, decision_at TEXT NULL 추가.
  • 상태값: draft → rendered → approved/rejected → published. (approved는 과도기 상태 없이 decision=approved 시 바로 published로 둔다 — 단순화. rejected는 종결.)
  • 발행 이력 = SELECT keyword, category, published_at FROM card_slates WHERE status IN ('published','rejected') AND COALESCE(published_at,decision_at) >= datetime('now', '-D days').

6. agent-office 상세

6.1 InstaAgent.on_schedule

  • custom_config.autonomous_issue 분기. False면 기존 동작 유지(candidate-push / auto_select) — 하위호환.
  • True면: collect+extract(기존) → service_proxy.insta_ranked(threshold, limit=max_per_day)eligible 픽 순회(최대 max_per_day):
    • 슬레이트 생성·렌더 대기(기존 _render_and_push의 생성·대기 부분 재사용/분리) → 프리뷰 발송(6.3) → create_task(requires_approval=True)waiting 상태.
  • eligible 0개 → "오늘 발행할 가치 있는 주제 없음" 1통.

6.2 콜백 (telegram webhook → on_callback)

  • issue_approve_{slate_id}: insta_decision(slate_id, "approved") → 전체 10장 미디어그룹 + /package zip 전달 + " 발행 완료" → 해당 task succeeded.
  • issue_reject_{slate_id}: insta_decision(slate_id, "rejected") → " 반려됨" → task 종료.
  • issue_regen_{slate_id}: 해당 슬레이트의 키워드로 새 슬레이트 생성(새 Claude 카피)·렌더 → 새 프리뷰. 이전 슬레이트는 rejected 처리.

6.3 텔레그램 프리뷰 (미디어그룹은 인라인 키보드 불가)

  • 커버(01.png) 단장 사진 + 캡션: 키워드·카테고리·final_score·breakdown 요약 + inline [✅승인][❌반려][🔄재생성] (callback_data=issue_*_{slate_id}).

6.4 설정 (agent_config.custom_config)

  • autonomous_issue (bool, 기본 false), select_threshold (기본 0.6), max_per_day (기본 2), dedup_window_days (기본 14).

6.5 service_proxy 추가

  • insta_ranked(threshold, limit)GET /keywords/ranked
  • insta_decision(slate_id, decision)POST /slates/{id}/decision

7. 에러 처리 / 엣지

  • ranked의 Claude 실패 → 룰 점수만으로 진행(graceful), 경고 로그.
  • eligible 0개 → 안내 1통(또는 무음 옵션, 기본 안내).
  • 렌더 실패 → task failed 통지, 프리뷰 미발송.
  • 승인 미응답 → 슬레이트 pending(rendered) 유지, 자동 발행 안 함(안전). 만료 없음.
  • 멱등: 중복 승인/반려 no-op. cron 재실행 시 이미 발행/반려 주제는 dedup으로 회피.
  • regen 무한루프 방지: regen은 사용자 트리거(버튼)라 자동 반복 없음.

8. 테스트

  • insta-lab: selection.py 순수 단위테스트(dedup 최근 제외 / freshness 정렬 / account_fit 가중 / 가중합·threshold 게이트 / claude 실패 시 정규화). ranked 엔드포인트(Claude mock). decision 엔드포인트(approved→published+published_at, rejected, 멱등).
  • agent-office: 자율 on_schedule(proxy mock: ranked eligible→슬레이트 생성→프리뷰 발송 + task requires_approval). 콜백 approve/reject/regen(proxy·messaging mock).

9. 범위 외 (YAGNI)

  • 발행 성과 지표(좋아요·도달) 수집/학습 — IG API 미사용, 수동 입력 부담으로 제외.
  • 신뢰도 하이브리드 자동발행(승인 생략) — 승인 게이트로 통일.
  • 임베딩 기반 유사도 dedup — 정규화 exact/substring + 카테고리로 충분(추후 필요 시 확장).

10. 영향받는 파일

  • insta-lab: app/selection.py(신규), app/main.py(ranked·decision 라우트), app/db.py(컬럼 ALTER + 발행이력/상태 헬퍼), tests/.
  • agent-office: app/agents/insta.py(자율 경로·콜백), app/service_proxy.py(2 헬퍼), app/webhook.py(issue_* 콜백 디스패치), tests/.
  • web-backend/CLAUDE.md insta API 목록 + service_insta.md 메모리 갱신.