Author: Outreach agent (self-authored from first principles, 2026-04-24) Purpose: Prevent duplicate, unwanted, or spammy outreach across all surfaces — awesome lists, CNCF project issues, directories, forums, comparison sites, community threads — across agent restarts, multiple sessions, and time.
One action per target, ever. A “target” is the combination of (surface, entity). For example:
github.com / dastergon/awesome-sre is target. Opening a second PR there after the first is
spam, regardless of whether you remember the first. Every rule below enforces this principle.
One PR per GitHub user/org, ever. Same owner = same person’s inbox. Submitting to
dastergon/awesome-sre AND dastergon/awesome-chaos-engineering means that person receives two
PRs from you. This is spam even if the repos are different. Before opening any PR, check how
many open PRs you already have under that owner:
# Pre-flight owner check (MANDATORY — run before every new PR)
OWNER="<repo-owner>"
unset GITHUB_TOKEN && gh search prs --author clubanderson --state open --limit 100 \
--json repository,number | python3 -c "
import sys,json
d=json.loads(sys.stdin.read())
hits=[p for p in d if p['repository']['nameWithOwner'].startswith('$OWNER/')]
if hits: print('SKIP — already have', len(hits), 'open PR(s) for', '$OWNER:', [p['repository']['nameWithOwner']+'#'+str(p['number']) for p in hits])
else: print('OK — no open PRs for $OWNER')
"
If any open PR already exists under that owner → SKIP. Do not open a second. The exception: a maintainer explicitly invites a resubmission to a different section (close the old PR first, then resubmit — still counts as active PR at a time).
Execute every check in order. Stop at the first SKIP signal.
PRE-FLIGHT for target T:
[ ] 1. ARCHIVED? → gh repo view T --json isArchived | check true → SKIP
[ ] 2. STALE? → last commit > 18 months ago → SKIP (no is merging PRs)
[ ] 3. GA4 REFERRAL? → query /api/analytics/dashboard trafficSources for T's domain → already live → SKIP
[ ] 4. OPEN PR? → gh search prs --author clubanderson --repo T --state open → exists → SKIP (address feedback instead)
[ ] 5. CLOSED PR? → gh search prs --author clubanderson --repo T --state closed → exists → COLD → SKIP
[ ] 6. OPEN ISSUE? → gh search issues --author clubanderson --repo T --state open → exists → SKIP
[ ] 7. BEADS LOG? → bd list | grep T → exists → check status → if done/cold SKIP
[ ] 8. OUTREACH LOG? → grep T docs/outreach-log.md → logged → SKIP
[ ] 9. CONTRIBUTING.md? → read T's CONTRIBUTING.md for self-promo restrictions → if banned → COLD + SKIP
[ ] 10. RELEVANCE? → does T's topic overlap with Console's top GA4 pages? → if no overlap → defer
[ ] 11. FORMAT CHECK? → read last 5 entries of target file → can I match format exactly? → if no → research more before proceeding
All checks pass → SAFE TO PROCEED
Agent memory is ephemeral. These layers survive restarts:
| Layer | How to Query | Survives Restart? | Authority |
|---|---|---|---|
| GitHub itself | gh search prs/issues --author clubanderson | ✅ Always | Highest |
| GA4 referral data | /.netlify/functions/analytics-dashboard | ✅ Always | High (confirms placement live) |
beads (bd) | bd list --json | ✅ Yes (remote) | High |
| docs/outreach-log.md | committed to this repo | ✅ Yes (git) | Medium |
| outreach-CLAUDE.md Current Progress | committed to this repo | ✅ Yes (git) | Medium |
/tmp files | filesystem | ❌ Lost on reboot | Never rely on |
| Session SQL | in-process SQLite | ❌ Lost on session end | Never rely on |
| Agent in-memory variables | process memory | ❌ Lost on restart | Never rely on |
Rule: Before any outreach pass, always query GitHub API (layer 1) and GA4 (layer 2) as ground truth. Treat logs as a cache — useful for fast skips but always verify against the source.
After each successful outreach action:
docs/outreach-log.md (committed)outreach-CLAUDE.md Current Progress sectiongit commit -s -m '📖 Outreach: log [target]'bd create a record if the action has follow-up needed (awaiting PR merge, waiting for maintainer reply)Nature: Curated markdown files in public repos. PRs are the contribution mechanism. Maintainer may take weeks to respond. Automated bots (CodeRabbit, Copilot Reviewer) often comment immediately.
Rules:
clubanderson, never the org.feat/add-kubestellar-console — consistent, recognizable.Format self-check before opening PR:
- Is the repo accepting PRs? (check Issues tab for "not accepting contributions")
- Does the list sort alphabetically? (place entry in correct position)
- Are descriptions in sentence case or title case? (match)
- Are there trailing periods? (match)
- Does the list use `[Name](https://example.com) — description` or `[Name](https://example.com) - description`? (match exactly)
Nature: Opening GitHub issues on upstream CNCF project repos proposing a guided install mission or ACMM badge. These are long-lived relationships — the maintainer community notices patterns.
Rules:
Nature: CNCF Landscape, Artifact Hub, OperatorHub, StackShare, AlternativeTo, Product Hunt, etc. These are form-based or PR-based submission systems.
Rules:
site:alternativeto.net "kubestellar" before submitting.docs/outreach-log.md with date and submission URL/confirmation.Nature: Human communities. Spam is acutely noticed, permanently remembered, and actively harmful. These surfaces must be used sparingly and with genuine value.
Rules:
Nature: Sites that accept tool listings or “best of” roundups (Slant, StackShare, G2, etc.).
Rules:
"kubestellar console" site:stackshare.io etc.Nature: Public broadcasts. Not a “target” in the same sense — these don’t leave a persistent artifact that can be checked.
Rules:
A target is COLD when any of the following are true:
COLD handling:
docs/outreach-log.md entry with status=COLD and reasonDo not confuse STALE with COLD:
The GA4 analytics dashboard (/.netlify/functions/analytics-dashboard) is the most authoritative source for confirming a placement is live. Query it before any outreach pass.
| Signal | Interpretation | Action |
|---|---|---|
trafficSources shows github.com / referral with path matching target repo | PR is merged and traffic is flowing | Mark DONE, no follow-up needed |
| CNCF outreach table shows sessions for project X | Outreach issue is generating visits | Check if sessions increased after last action — good signal to follow through |
| Organic search sessions rising after a list merge | SEO is working, that list category is valuable | Prioritize more lists in same category |
| A domain appears in referrers you didn’t reach out to | Organic placement exists | Log as DONE (natural), no action needed |
(direct) traffic spikes after forum post | Post drove awareness | Note which post/subreddit in log |
Every outreach pass should start with a GA4 query and use it to:
Current GA4 strategic signals (as of 2026-04-24):
| Surface | Min Interval Between Actions | Max Follow-ups | Follow-up Interval |
|---|---|---|---|
| Awesome list PRs | N/A (one-shot) | 2 follow-up comments | 7 days |
| CNCF project issues | 14 days between comments | 2 comments | 14 days |
| ADOPTERS PRs | N/A (one-shot, awaits approval) | 1 ping | 7 days after opening |
| Reddit posts | 6 months per subreddit | 0 (don’t bump your own posts) | N/A |
| dev.to articles | N/A (publish, update in-place) | N/A | N/A |
| HN | -time | 0 | N/A |
| Directory submissions | N/A (one-shot) | 1 follow-up if form-based | 30 days |
| LinkedIn posts | 14 days per topic | N/A | N/A |
Is the target archived or inactive (>18 months)?
YES → SKIP
Does GA4 show referral traffic already coming from this target?
YES → SKIP (placement is live)
Does `gh search prs --author clubanderson --repo TARGET` show an open PR?
YES → SKIP (check for feedback to address instead)
Does `gh search prs --author clubanderson --repo TARGET` show a closed PR?
YES → COLD → SKIP
Does `gh search issues --author clubanderson --repo TARGET` show an open issue?
YES → SKIP (check for feedback to address instead)
Is the target logged as DONE or COLD in docs/outreach-log.md or beads?
YES → SKIP
Does the target's CONTRIBUTING.md prohibit self-promotion?
YES → COLD → SKIP
Does the target's topic overlap Console's top GA4 page categories?
NO → defer to lower priority
Does the target accept PRs / have recent merge activity?
NO → SKIP
Can I match the target's exact format?
NO → research more first
→ SAFE TO PROCEED
After action: log it, commit log, send ntfy
Every outreach action must be appended to docs/outreach-log.md in this format:
| DATE | SURFACE | TARGET | ACTION | STATUS | NOTES |
|------------|--------------------------------|----------------------------------|---------------|---------|---------------------------------------------|
| 2026-04-24 | awesome-list | dastergon/awesome-sre | PR #268 | open | No feedback yet |
| 2026-04-24 | cncf-issue | chaos-mesh/chaos-mesh | Issue #4858 | open | @STRRL engaged, docs PR opened |
| 2026-04-24 | adopters-pr | kubestellar/console#7889 | PR open | pending | Awaiting @STRRL approval |
| 2026-04-24 | directory | stackshare.io | form-submit | done | GA4 confirmed referral 2026-05-01 |
Status values: open | merged | closed-cold | pending | done | stale | cold
This section defines the canonical tracking format for every awesome-list PR and external directory submission. It fills the gap that CNCF install missions have detailed per-project tracking but awesome-list submissions historically had none.
Without a persistent log, the agent will:
docs/outreach-log.mdAll awesome-list PRs and directory submissions are logged in docs/outreach-log.md in this repo.
The file must be committed after every outreach pass. It is the ground-truth complement to GitHub’s
own PR search (which is the canonical dedup check, but not as fast to query in bulk).
| DATE | CATEGORY | TARGET REPO / SITE | SECTION ADDED TO | PR / SUBMISSION URL | STATUS | GA4 REFERRAL? | NOTES |
|------------|-------------|----------------------------------|-------------------------|-----------------------------------------|----------|---------------|-------------------------------|
| 2026-04-24 | awesome-list | dastergon/awesome-sre | Monitoring | https://github.com/dastergon/awesome-sre/pull/268 | open | no | No feedback yet |
| 2026-04-24 | awesome-list | veggiemonk/awesome-docker | Monitoring & Observ. | https://github.com/veggiemonk/awesome-docker/pull/1417 | open | no | No feedback yet |
| 2026-04-24 | directory | stackshare.io | Kubernetes Tools | manual StackShare submission (no public URL) | done | yes | Confirmed referral 2026-05-01 |
| 2026-04-24 | awesome-list | tmrts/awesome-kubernetes | Monitoring | https://github.com/tmrts/awesome-kubernetes/pull/6 | merged | yes | 3 referral sessions/month |
Status values:
| Value | Meaning |
|---|---|
open | PR is open, awaiting review |
merged | PR was merged ✅ |
rejected | PR closed without merge, maintainer opposed |
ignored | PR open > 60 days with zero maintainer engagement |
stale | 3 check-in cycles with no response; keep open but stop following up |
cold | Explicitly rejected or maintainer asked not to be contacted; never retry |
done | Directory/non-PR submission confirmed live (GA4 or manually verified) |
pending | Submission sent, awaiting confirmation |
┌─────────────────┐
PR opened ──▶ │ open │
└────────┬────────┘
│
┌─────────────┼──────────────┬─────────────┐
▼ ▼ ▼ ▼
merged rejected ignored (60d) stale (3 cycles)
│ │ │ │
DONE COLD COLD keep open,
(log GA4) (never retry) (never retry) no follow-up
| Trigger | Action |
|---|---|
| PR opened | Add row with status=open |
| CI/bot comments on PR | Note in NOTES, no status change |
| Human reviewer requests changes | Note in NOTES; address feedback; no status change |
| PR merged by maintainer | Update status → merged; check GA4 within 30 days |
| PR closed without merge (dismissively) | Update status → rejected → cold |
| PR open > 60 days, zero maintainer engagement | Update status → ignored → cold |
| 3 follow-up cycles with no response | Update status → stale |
| GA4 shows referral from that domain | Add “yes” to GA4 REFERRAL column |
Day 0: PR opened
Day 7: Check for reviewer comments. Address any. Note in log.
Day 14: Second check. If reviewer comments unaddressed, address now.
Day 21: Third check (final). If still no engagement, mark stale.
Day 60: If still open and stale, mark ignored → cold in log.
Maximum 2 proactive follow-up comments on any single PR. comment if:
To quickly see what’s already been targeted:
# All open PRs
grep "| open" docs/outreach-log.md
# All merged (confirm GA4)
grep "| merged" docs/outreach-log.md | grep "no" | awk '{print $3}'
# All cold targets (never re-approach)
grep "| cold\|rejected\|ignored" docs/outreach-log.md
# Repos already tried (dedup check)
grep "awesome-list" docs/outreach-log.md | awk -F'|' '{print $3}' | sort -u
The log can become stale if a PR is merged without the agent noticing. Always verify:
# Get ground truth for all open clubanderson PRs
unset GITHUB_TOKEN && gh search prs --author clubanderson --state open --limit 50 \
--json repository,number,title,updatedAt
# Get closed PRs (merged or rejected) — update log if found
unset GITHUB_TOKEN && gh search prs --author clubanderson --state closed --limit 50 \
--json repository,number,title,updatedAt
Reconcile any discrepancy between GitHub state and log state immediately — GitHub wins.