Skip to Content

Magnet

한 줄 소개: 마케팅 팀장의 직무를 통째로 갖는 에이전트. 광고 예산·크리에이티브·소셜 응대·위기 대응을 자동화. 39개 MCP 도구, hash-chained decisions ledger, brand voice 중앙화.

정체성

magnet (당기다) ↔ stream (흐르다)
마케팅이 수요를 당기면 운영이 공급을 흐르게 한다.

domain 분업: magnet 은 자기 도메인의 외부 채널(광고·소셜) + 의사결정만 소유. 판매·SCM 은 stream MCP 가 owner.

기술 스택

항목
언어Python 3.12+
프레임워크mcp[cli] ≥1.27 (FastMCP)
DBPostgreSQL 16 (multi-tenant + RLS)
Schedulersupercronic (Asia/Seoul TZ)
의존성pyyaml ≥6, psycopg[binary] ≥3.2, openpyxl ≥3.1

포트

포트용도
8770MCP HTTP/SSE (magnet-mcp)
5432PostgreSQL (magnet-postgres 컨테이너, 127.0.0.1 loopback 만)

39 MCP Tools (6 prefix)

Prefix모듈도구 수주요
meta_ads_*meta-ads5get_campaigns, propose/execute_budget_change
meta_capi_*meta-ads2send_event, send_purchase
social_*social19post_to_fb/ig/threads, reply_to, like, get_insights
naver_search_ad_*naver-ads3get_campaigns, propose_keyword_bid (skeleton)
marketing_*performance + content9get_ad_spend, daily_brief, weekly_brief, compose_post_prompt
decisions_*decisions3get_decisions, verify_chain_integrity, get_lifecycle
stream_bridge_*stream-bridge4send_purchase_from_stream, handle_inventory_alert
marketing_compose_*marketing-compose2compose_post_prompt, validate_copy
threads_browser_*threads-browser-host4init, like, action, status (호스트 stdio MCP)

디렉토리 구조

/Users/axe/magnet/ ├── mcp/ │ ├── server.py ★ 통합 daemon — importlib 로 6 모듈 흡수 │ ├── meta-ads/ Meta Ads + CAPI │ ├── social/ FB + IG + Threads │ ├── naver-ads/ 네이버 검색광고 (skeleton) │ ├── performance/ ROAS · CAC · spend 조회 │ ├── content/ 콘텐츠 발주 (truvia-marketing-agents 연동) │ ├── decisions/ decisions ledger 조회·시각화 │ ├── stream-bridge/ stream 신호 receiver (2026-05-11) │ ├── marketing-compose/ 카피·답글 생성 (brand SoT 주입) │ └── threads-browser-host/ Threads 브라우저 자동화 (별도 stdio MCP) ├── scripts/ │ ├── automation/ 6+ 자동화 (LaunchAgent 흡수) │ ├── marketing/ MCP 도구 구현 + CLI │ ├── db/ DB 연결 + sync │ ├── admin/ 운영자 CLI │ ├── decisions/ decisions 분석 │ └── entity/ 멀티 엔터티 레지스트리 ├── brand/ ★ 마케팅 identity SoT │ ├── personas/operator.yaml 화자 정체성 │ └── voice/ 채널별 톤 │ ├── _global.yaml 전 채널 (광고법·금기) │ ├── threads.yaml │ ├── instagram.yaml │ └── fb.yaml ├── data/ │ ├── decisions/realchoice.jsonl ★ hash-chained ledger (append-only) │ ├── reports/YYYY-MM-DD.md │ ├── pending-monitor-actions/ │ └── pending-user-actions.md ├── sql/ 스키마 1,300 LOC (10 파일) │ ├── 000_schema.sql campaign_metrics_daily, posts, threads_post_snapshots │ ├── 050_multi_tenant_baseline.sql ★ RLS + tenant_id │ ├── 060_threads_clone.sql Threads snapshot (사고 회복) │ ├── 100_views.sql │ └── 200_personas.sql (nemotron-personas 로 migrate 예정) ├── docs/ 핸드오프 + 결정 + 가이드 │ └── incidents/ │ └── 2026-05-14-threads-self-delete.md 사고 리뷰 ├── crontab ★ 10 자동화 ├── docker-compose.yml 3 service (magnet-mcp + magnet-cron + magnet-postgres) ├── CLAUDE.md ★ 자동 로드 컨텍스트 └── SECURITY.md

데이터 모델 (multi-tenant + RLS)

테이블역할
campaign_metrics_daily광고 성과 (spend, impr, conv, roas, cac)
posts_fb / posts_ig / posts_threads소셜 게시물 + insights
threads_post_snapshotsThreads snapshot (사고 회복 자산)
decisions의사결정 audit ledger
campaign_budgets_proposed예산 변경 안 (HMAC 서명, TTL)
copy_ab_testsA/B 테스트 메타

RLS: POSTGRES_USER=magnet_app (non-superuser, BYPASSRLS=false), connection stage 에서 SET app.tenant_id = <id> 주입. 기본값 tenant_id=1 (realchoice).

Crontab (10 task)

주기작업
매시 정각sync_meta_live.py --since 0 + refresh_daily_kpi.py
매시 정각sync_threads_live.py
매시 10분learning_reset_monitor.py — 광고 학습 reset 회복
30분 주기ingest_decisions.py realchoice
매일 01:00naver_searchad_sync.py
매일 03:00sync_meta_live.py --since 2 — 어제/그제 재폴링
매일 09:00ad_xray_daily.py — DataLab + keyword sync
15분 주기scheduled_carousel_publish.py — 운영자 시간 트리거
매시 30분sync_threads_full.py — 게시물 snapshot
20분 주기scheduled_reply_publish.py — 답글 자동 게시
매일 00:05logrotate.sh
09:00 외daily_monitor.py, faq_responder.py, roas_safety_net.py, refund_spike_safeguard.py, negative_feedback_alert.py, threads_token_refresh.py

Decisions Ledger (hash-chained)

data/decisions/realchoice.jsonl 에 모든 의사결정 append-only 저장:

{ "ts": "2026-05-20T15:00:00+09:00", "actor": "magnet-agent", "type": "ad_paused", "context": {"campaign_id": "23845...", "reason": "ROAS < 1.5"}, "prev_hash": "sha256:abc..." // 이전 line 의 SHA-256 }

decisions_verify_chain_integrity() 도구로 체인 무결성 검증.

Brand SoT (operator + voice)

모든 카피·답글은 다음 2단계 강제:

  1. marketing_compose_post_prompt({...}) — brand SoT 자동 주입
  2. marketing_validate_copy(text) — 광고법·금기 키워드·해시태그 정책 검증

brand/personas/operator.yaml:

identity: "F&B 10년차 두 친구, 매장 비하인드" disclose: false hide: - real_name - exact_store_name

brand/voice/threads.yaml:

tone: "친구 톤, 농담 OK" no_hashtag: true # Threads 는 해시태그 X forbidden: - "최고", "1위", "유일" # 광고법 length_max: 500

Threads 사고 (2026-05-14, 영구 기록)

Threads 브라우저 자동화의 selector 결함으로 본 게시물 3개 + 모든 반응 영구 손실.

항목
Root causethreads_browser.py selector 가 답글 menu 에서 parent thread menu 잡음
Impact운영자 carousel 본 게시물 3개 + 모든 반응
Recoverydata/pending-monitor-actions/2026-05-15-carousel-relaunch.md (Graph API 재게시)
GuardMAGNET_THREADS_DANGER_ENABLED=1 환경변수 없으면 delete/unrepost raise

이후 모든 destructive Threads op (delete, unrepost) manual only, snapshot 매시간 백업으로 회복 자산 확보.

자가발전 에이전트

  • 매시간 in-session loop + 매일 04:00 KST deep round
  • 산물: data/reports/YYYY-MM-DD.md, config/thresholds.yaml (자율 조정)
  • Token cap: 100K/round
  • [NEEDS_REVIEW] 섹션으로 본 세션·사용자 결정 위임

외부 의존성

시스템통합
Meta Ads + CAPIMarketing API v19+
Naver SearchAdAPI (HMAC-SHA256)
Threads / IG / FBGraph API + 브라우저 자동화 (호스트 macOS Playwright)
stream MCP신호 수신 (purchase, inventory_alert)
nemotron-personas페르소나 조회 (별도 MCP)
truvia-marketing-agents콘텐츠 발주 인터페이스

Tenant ID Mapping (D-magnet-tenant-map-1)

magnet 의 MAGNET_TENANT_IDservice-internal RLS tenant_id 이며, AXE Labs platform 의 customer ID (customers.yaml key) 와 별개 namespace 입니다. 본 섹션은 두 ID 의 mapping rule 을 명시합니다.

두 ID 의 분리

식별자출처예시변경 책임
AXE customer IDcustomers.yaml 의 top-level keyaxe, realchoice운영자 (액스코퍼레이션)
MAGNET_TENANT_SLUGmagnet .env.local 의 customer-name stringrealchoiceservice operator
MAGNET_TENANT_IDmagnet DB 의 RLS row identifier (magnet.tenant.id, integer)1 (realchoice)DB migration

같은 customer 가 두 ID 모두 가질 수 있으나 string ↔ integer 매핑 이 별도 lookup 필요.

SSOT — customers.yamlservice_tenant_map (D-magnet-tenant-map-1, 2026-05-23)

신설 필드. 각 customer 블록 안에서 customer 가 사용하는 각 service 의 internal tenant id 를 명시:

customers: realchoice: ... service_tenant_map: # NEW field (D-magnet-tenant-map-1) magnet: tenant_slug: "realchoice" tenant_id: 1 stream: tenant_id: 1 # truvia_ssot (port 5433) 의 tenant row axe: ... service_tenant_map: # axe customer 가 magnet 사용 시 tenant_id=2 부여 (충돌 회피) magnet: tenant_slug: "axe" tenant_id: 2

신규 customer 가 magnet 사용 시 — 충돌 회피 절차

  1. 운영자가 customers.yaml 의 신규 customer 블록에 service_tenant_map.magnet 등재 — tenant_id현재 최대값 + 1
  2. magnet DB migration 또는 admin tool 로 magnet.tenant 에 row INSERT (id=신규)
  3. customer macmini 의 magnet .env.localMAGNET_TENANT_SLUG/MAGNET_TENANT_ID 등재 (axe ship 이 customers.yaml manifest 에서 자동 주입 — B-magnet-tenant-env-injection 향후 작업)
  4. RLS 검증: SET app.tenant_id=<신규> 후 cross-tenant read 가 빈 결과 보장 (테스트)

realchoice 측 코드 자산 호환성

~/magnet 의 코드 자산은 변경 없음 — RLS 구조 + tenant_id=1 (realchoice) 유지. 본 신설 mapping 은 운영자 측 manifest 가시성 만 추가 (DB schema 영향 0).

향후 axe 가 magnet 사용 시

axe customer 가 magnet 도입을 결정하면:

  • customers.yaml > customers.axe.service_tenant_map.magnet.tenant_id = 2 등재
  • magnet DB 에 INSERT INTO magnet.tenant (id, slug) VALUES (2, 'axe')
  • 양 customer 의 magnet 데이터는 RLS 로 격리, 충돌 0

환경 변수 (140+ 라인 .env.example)

# DB POSTGRES_DB=magnet POSTGRES_USER=magnet_app POSTGRES_PASSWORD= POSTGRES_HOST= # Multi-tenant — service-internal namespace (Tenant ID Mapping 섹션 참조) MAGNET_TENANT_SLUG=realchoice MAGNET_TENANT_ID=1 # Meta Ads META_ADS_ACCESS_TOKEN= META_AD_ACCOUNT_ID= MAGNET_PROPOSAL_HMAC_KEY= META_PIXEL_ID= # Naver NAVER_AD_CUSTOMER_ID= NAVER_AD_API_KEY= NAVER_AD_SECRET_KEY= MAGNET_NAVER_AD_LIVE=1 # Transport MAGNET_TRANSPORT=sse MAGNET_HTTP_HOST=127.0.0.1 MAGNET_HTTP_PORT=8770 # Decisions MAGNET_DECISIONS_LOG_PATH=/data/decisions/realchoice.jsonl # Threads guard MAGNET_THREADS_DANGER_ENABLED= # 미설정이 안전 (delete/unrepost raise)

관련 문서

Last updated on