Skip to content

Commit a6f9f51

Browse files
committed
feat(cmo): encode cutover policy and autonomous posthoc review guardrails
1 parent 93f629d commit a6f9f51

File tree

5 files changed

+256
-45
lines changed

5 files changed

+256
-45
lines changed

ops/cmo-automation/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,14 @@ Current mode
1919

2020
Structure
2121
- config/cmo_accounts.yaml
22+
- config/operating_policy.json
2223
- scripts/collect_x_data.py
2324
- scripts/analyze_x_cmo.py
2425
- scripts/generate_engagement_queue.py
2526
- scripts/review_engagement_queue.py
2627
- scripts/reconstruct_fiverr_playbook.py
2728
- reports/CMO-AUTOMATION-IMPLEMENTATION-PLAN.md
29+
- reports/CMO-CUTOVER-48H-RUNBOOK.md
2830

2931
Quick start
3032
1) Export credentials (or source ~/.claude/secrets.env)
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
{
2+
"transition": {
3+
"fiverr_overlap_hours": 48,
4+
"objective": "replace outsourced engagement with in-house automation without activity pause"
5+
},
6+
"success_targets_30d": {
7+
"qualified_follower_growth_pct": 8,
8+
"median_engagement_per_post_lift_pct": 25,
9+
"short_generic_reply_share_max_pct": 30
10+
},
11+
"automation": {
12+
"mode": "hybrid-autonomous",
13+
"founder_autonomy": "autonomous_with_posthoc_audit",
14+
"dry_run_default": true
15+
},
16+
"account_strategy": {
17+
"TheCesarCross": {
18+
"role": "founder",
19+
"mix_pct": {"root": 35, "reply": 50, "quote": 15},
20+
"daily_reply_cap": 8,
21+
"time_windows_est": ["09:00-11:00", "18:00-20:00"]
22+
},
23+
"sovren_software": {
24+
"role": "brand",
25+
"mix_pct": {"root": 45, "reply": 40, "quote": 15},
26+
"daily_reply_cap": 10,
27+
"time_windows_est": ["10:00-11:00", "14:00-15:00", "19:00-20:00"]
28+
},
29+
"mrhaven_agent": {
30+
"role": "product-agent",
31+
"mix_pct": {"root": 60, "reply": 25, "quote": 15},
32+
"daily_reply_cap": 6,
33+
"time_windows_est": ["08:00-09:00", "13:00-14:00", "21:00-22:00"]
34+
}
35+
},
36+
"targeting": {
37+
"strategy": "core-whitelist-plus-discovery",
38+
"exploration_budget_pct": 20,
39+
"core_whitelist_pct": 80,
40+
"minimum_relevance_score": 0.7,
41+
"founder_scope": "whitelist_plus_approved_discovery"
42+
},
43+
"founder_denylist": {
44+
"preset": "strict",
45+
"categories": [
46+
"mainstream_consumer_brands",
47+
"meme_bait",
48+
"partisan_politics",
49+
"token_price_speculation",
50+
"celebrity_gossip",
51+
"giveaway_airdrop_threads"
52+
],
53+
"keywords": [
54+
"skittles",
55+
"giveaway",
56+
"airdrop",
57+
"win free",
58+
"retweet to win",
59+
"$btc to",
60+
"$eth to",
61+
"meme coin",
62+
"pump",
63+
"moon",
64+
"vote red",
65+
"vote blue"
66+
]
67+
},
68+
"risk_controls": {
69+
"max_replies_per_hour_per_account": 8,
70+
"max_replies_to_same_user_per_24h": 1,
71+
"avoid_repeated_same_user_within_hours": 24,
72+
"random_delay_seconds_min": 45,
73+
"random_delay_seconds_max": 180
74+
}
75+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# CMO 48-Hour Cutover Runbook (No-Pause)
2+
3+
Objective
4+
- Stop Fiverr after a 48-hour overlap window and maintain uninterrupted engagement cadence with in-house automation.
5+
6+
Decisions captured
7+
- Overlap: 48h
8+
- Volume: 80% of Fiverr volume, higher quality
9+
- Primary KPI: qualified audience growth
10+
- Automation: hybrid-autonomous, including founder account
11+
- Targeting: 80% whitelist core + 20% discovery
12+
- Founder policy: strict deny-list (no unrelated consumer-brand/meme/politics/price-gambling engagement)
13+
- Fallback on failures/rate-limits: root posts only
14+
15+
## T-48h (start now)
16+
1) Run full pipeline and verify outputs:
17+
- collect_x_data.py
18+
- analyze_x_cmo.py
19+
- generate_engagement_queue.py
20+
- review_engagement_queue.py
21+
- reconstruct_fiverr_playbook.py
22+
2) Ensure global recommendation is not "hold".
23+
3) Keep Fiverr active during overlap.
24+
4) Record baseline metrics snapshot for all 3 accounts.
25+
26+
## T-24h
27+
1) Re-run pipeline.
28+
2) Compare quality vs prior cycle:
29+
- short-generic reply share
30+
- per-account avg impressions
31+
- approved action counts
32+
3) If any account degrades materially, throttle replies for that account and keep roots active.
33+
34+
## T-0h (Fiverr off)
35+
1) Disable Fiverr service.
36+
2) Keep in-house automation active with current caps.
37+
3) Post-hoc audit sample (minimum 20% of sent actions).
38+
39+
## T+24h and T+48h
40+
1) Rebuild playbook report and trend deltas.
41+
2) If founder account drifts from strategy, tighten founder scope to whitelist-only for next cycle.
42+
3) If rate limits trigger, switch to root-only fallback for affected cycle.
43+
44+
## Exit criteria (first 30 days)
45+
- Qualified follower growth >= 8%
46+
- Median engagement per post lift >= 25%
47+
- Short-generic reply share <= 30%
48+
49+
## Failure criteria
50+
- Repeated unrelated founder engagements
51+
- Sustained decline in median impressions over 3+ cycles
52+
- High rejection rate from review gate due low relevance
53+
54+
## Owner checklist
55+
- [ ] Fiverr cancellation timestamp recorded
56+
- [ ] Cron status healthy
57+
- [ ] Review gate report reviewed each cycle
58+
- [ ] Weekly KPI review published

ops/cmo-automation/scripts/generate_engagement_queue.py

Lines changed: 65 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,52 +5,100 @@
55
from pathlib import Path
66

77
ROOT = Path(__file__).resolve().parents[1]
8-
REPORT = ROOT / "reports" / "cmo-analysis.json"
8+
ANALYSIS = ROOT / "reports" / "cmo-analysis.json"
9+
POLICY = ROOT / "config" / "operating_policy.json"
910
OUT = ROOT / "data" / "engagement-queue.json"
1011

1112

12-
def main() -> None:
13-
if not REPORT.exists():
14-
raise SystemExit("Run analyze_x_cmo.py first")
13+
def load_json(path: Path):
14+
if not path.exists():
15+
raise SystemExit(f"Missing {path}")
16+
return json.loads(path.read_text())
17+
18+
19+
def split_counts(total: int, core_pct: int):
20+
core = int(round(total * core_pct / 100.0))
21+
discovery = max(0, total - core)
22+
return core, discovery
1523

16-
r = json.loads(REPORT.read_text())
24+
25+
def main() -> None:
26+
analysis = load_json(ANALYSIS)
27+
policy = load_json(POLICY)
1728

1829
queue = {
1930
"generated_at": datetime.now(timezone.utc).isoformat(),
20-
"mode": "dry-run",
31+
"mode": "dry-run" if policy.get("automation", {}).get("dry_run_default", True) else "live",
32+
"policy_ref": str(POLICY),
2133
"actions": [],
2234
}
2335

24-
# Very conservative starter policy: propose root posts + selective replies.
25-
for handle, stats in r["accounts"].items():
26-
# root content recommendation
36+
targeting = policy.get("targeting", {})
37+
core_pct = targeting.get("core_whitelist_pct", 80)
38+
39+
for handle, stats in analysis.get("accounts", {}).items():
40+
account_policy = policy.get("account_strategy", {}).get(handle, {})
41+
daily_reply_cap = int(account_policy.get("daily_reply_cap", 6))
42+
windows = account_policy.get("time_windows_est", [])
43+
44+
# Always include one root post anchor action per run cycle.
2745
queue["actions"].append(
2846
{
2947
"account": handle,
3048
"action": "root_post",
3149
"priority": "high",
32-
"why": "Keep account voice anchored with original thesis/product narrative.",
50+
"window_est": windows,
51+
"why": "Anchor narrative with original voice-aligned post.",
3352
"constraints": {
34-
"min_gap_minutes": 180,
3553
"must_pass_voice_review": True,
54+
"must_align_strategy": True,
55+
"min_gap_minutes": 180,
3656
},
3757
}
3858
)
3959

40-
# reply recommendation from top targets, but cap volume
41-
targets = [u for u, _ in stats.get("top_reply_targets", [])][:5]
42-
for t in targets:
60+
# Build candidate pools from observed high-frequency targets.
61+
observed_targets = [u for u, _ in stats.get("top_reply_targets", [])]
62+
core_count, discovery_count = split_counts(daily_reply_cap, core_pct)
63+
64+
# Core (whitelist-style): historically engaged users.
65+
for target in observed_targets[:core_count]:
4366
queue["actions"].append(
4467
{
4568
"account": handle,
4669
"action": "reply",
47-
"target_user": t,
70+
"target_user": target,
71+
"target_pool": "core",
4872
"priority": "medium",
49-
"why": "User appears in recent interaction graph.",
73+
"window_est": windows,
74+
"why": "Continuation of existing interaction graph.",
5075
"constraints": {
5176
"max_replies_to_same_user_per_24h": 1,
52-
"min_delay_seconds": random.randint(45, 180),
5377
"must_be_contextual": True,
78+
"must_clear_relevance_score": targeting.get("minimum_relevance_score", 0.7),
79+
"min_delay_seconds": random.randint(
80+
policy["risk_controls"].get("random_delay_seconds_min", 45),
81+
policy["risk_controls"].get("random_delay_seconds_max", 180),
82+
),
83+
},
84+
}
85+
)
86+
87+
# Discovery budget: placeholders to be filled by candidate-harvest stage.
88+
for _ in range(discovery_count):
89+
queue["actions"].append(
90+
{
91+
"account": handle,
92+
"action": "reply",
93+
"target_user": None,
94+
"target_pool": "discovery",
95+
"priority": "low",
96+
"window_est": windows,
97+
"why": "Exploration budget for net-new qualified audience.",
98+
"constraints": {
99+
"must_be_contextual": True,
100+
"must_clear_relevance_score": targeting.get("minimum_relevance_score", 0.7),
101+
"must_not_hit_founder_denylist": handle == "TheCesarCross",
54102
},
55103
}
56104
)

0 commit comments

Comments
 (0)