Technical Evidence

What Cato can do: demonstrated, not described.

Architecture, workflows, content pipeline, API integrations, and test coverage. The code shown here is from the actual codebase.

01

Workflow Engine

16 GitHub Actions workflows, zero human initiation
community_monitor.yml
Polls Bluesky mentions, Discord keyword hits, and GitHub issues across RevenueCat repos. Classifies each signal (bug report / feature request / question) and writes to community_signals table.
Every 6 hours
community_monitor.yml
# Triggered every 6h via cron: '0 */6 * * *'
signals = community_monitor.monitor_bluesky()
signals += community_monitor.monitor_github_issues()
# Each signal: {platform, signal_type, body, author, source_url}
# Stored in Supabase community_signals table
# Audit logged on every insert
02

Content Pipeline

Four passes, then four quality checks
generation pipeline
Pass 1
Initial Generation
Persona + voice + context
Pass 2
Structure Disruption
Break AI paragraph patterns
Pass 3
Specificity Editing
Replace vague with concrete
Pass 4
Judge + Revision
Score, revise 2x, pass/reject
quality checks (click to expand implementation)
L1Banned Word Filter40+ AI-telltale words rejected. delve, unlock, game-changer, transformative, revolutionize, cutting-edge...
Hide
def check_banned_words(content: str) -> list[dict]:
    violations = []
    for word in BANNED_WORDS:  # 40+ words
        pattern = rf"\b{re.escape(word)}\b"
        matches = list(re.finditer(pattern, content, re.IGNORECASE))
        if matches:
            violations.append({"word": word, "count": len(matches)})
    return violations  # [] = pass
L2AI DetectionPattern density < 0.5 threshold. Heuristic calibrated against GPTZero.
Expand +
L3LLM JudgeStructured JSON scoring. Requires >= 7.0 AND zero accuracy issues.
Expand +
L4Fact AccuracySDK versions checked against versions.json + changelogs.
Expand +
03

LLM Visibility

SEO, GEO, AEO: content for humans and AI
Traditional SEO

Keywords, meta descriptions, structured headings, internal linking.

GEO / AEO

Generative Engine Optimization. Structured for AI retrieval systems.

LLM Context

Designed to be cited by AI assistants. Self-contained, factual.

content structure for agent consumption
content_structure.json
{
  "title": "Why getOfferings() Returns nil After iOS 18.4",
  "target_audience": "iOS developers using RevenueCat SDK 5.x",
  "problem_statement": "Silent nil return after iOS 18.4 update",
  "root_cause": "StoreKit 2 cache invalidation timing change",
  "sdk_versions_affected": ["5.0.0", "5.28.2"],
  "ios_versions_affected": ["18.4"],
  "error_codes": [],
  "solution": {
    "immediate": "invalidateCustomerInfoCache() before first call",
    "production_safe": "#if DEBUG || targetEnvironment(simulator)"
  },
  "verified_against": "versions.json + SDK changelog"
}
04

API Integrations

Four live integrations, no wrappers
RevenueCat MCP ServerTypeScript / MCP

A standalone MCP server that exposes 3 tools for any LLM to query RevenueCat Charts API data. Content generation can ground itself in real subscription data, not just documentation.

mcp-server/index.ts
tools: [
  "list_metrics",      // All 10 RC Charts metrics with descriptions
  "get_snapshot",      // MRR, active subs, trials, churn vs prior month
  "query_time_series"  // Any metric, any resolution, country filter
]

// Real API call with retry:
const url = `${RC_API_BASE}/charts/${metric}?${params}`;
const resp = await fetch(url, {
  headers: { Authorization: `Bearer ${RC_API_KEY}` }
});
Charts API AnalyzerPython / httpx

Pulls subscription overview, trial conversion funnel, and MRR trends directly from RevenueCat v2 API. Extracts data as prose summary for LLM grounding.

charts_analyzer.py
def extract_content_insights(api_key, app_id) -> str:
    overview = get_subscription_overview(api_key, app_id)
    funnel = get_conversion_funnel(api_key, app_id)
    # Returns plain text for LLM grounding:
    # "Active subscriptions: 3,680"
    # "MRR: $51,600"
    # "Trial conversion rate: 10.9%"
    # "Monthly churn: 4.3%"
Discord Community Botdiscord.py / Oracle VM

Monitors configured Discord channels for keyword matches, classifies messages (bug/feature/question), and logs signals to the community_signals table. Runs as a persistent systemd service.

discord_bot/bot.py
@bot.event
async def on_message(message):
    if message.channel.id not in MONITORED_CHANNELS:
        return
    # Keyword classification: bug, feature, question
    signal_type = classify_signal(message.content)
    db.insert("community_signals", {
        "platform": "discord",
        "signal_type": signal_type,
        "body": message.content[:2000],
        "author": str(message.author),
        "source_url": message.jump_url,
    })
Multi-Platform PublishingGraphQL + REST + AT Proto

Approved content publishes to Hashnode (GraphQL mutation), Dev.to (REST API), and optionally as a Bluesky thread (AT Protocol). Platform URLs archived back to DB.

bluesky.py
def post_thread(text: str, handle: str, password: str):
    client = atproto.Client()
    client.login(handle, password)
    chunks = _split_thread(text, max_chars=300)
    parent = None
    for i, chunk in enumerate(chunks):
        record = _create_record(client, chunk, parent)
        if i == 0:
            parent = {"uri": record.uri, "cid": record.cid}
05

Sample Content Drafts

Six pieces, all major RevenueCat content types
Troubleshooting Guide

Why getOfferings() Returns nil After iOS 18.4, and How to Fix It

8.4/10

iOS 18.4 changed when the system invalidates its local StoreKit product cache. RevenueCat SDK 5.x (StoreKit 2 mode) relies on that cache for the initial getOfferings() call. When the cache is stale, you get nil back with no error, no log, and no network request. This happens most often after TestFlight re-installs, because the bundle ID stays the same but the sandbox receipt context changes silently. // Before your first getOfferings() call in debug/TestFlight: Purchases.shared.invalidateCustomerInfoCache() let offerings = try await Purchases.shared.offerings() // Now guaranteed to hit the network instead of serving stale cache The underlying issue (GitHub #4954) is a StoreKit 2 behavior change, not a RevenueCat bug. Apple changed cache invalidation timing in iOS 18.4 beta 3 and it shipped to production. The SDK is working as designed; the OS contract changed beneath it. Don't ship the invalidation call to production. Gate it: #if DEBUG || targetEnvironment(simulator) Purchases.shared.invalidateCustomerInfoCache() #endif

#iOS#SDK 5.x#getOfferings#StoreKit 2#GitHub #4954
quality gate results (all 6 samples)
Banned words: 0 violationsAI detection: avg 0.31Judge score: avg 8.35/10Fact accuracy: 0 issues
06

Data Schema

Supabase PostgreSQL + pgvector. 13 tables.
content_itemsid, title, content_type, status, judge_score, hashnode_url, devto_url, client_slug
quality_assessmentsid, content_id, banned_words_passed, ai_score, judge_score, fact_issues, assessed_at
community_signalsid, platform, signal_type, body, author, source_url, processed, client_slug
feature_requestsid, title, priority, category, source_signal_id, status, filed_at
skill_versionsid, version, content, rationale, avg_score_before, created_at, client_slug
weekly_reportsid, week_start, content_published, interactions, avg_judge_score, report_markdown
audit_logid, action, entity_type, entity_id, details, created_at, client_slug
knowledge_chunksid, source_url, chunk_text, embedding (vector 768), client_slug
pgvector: 768-dimAudit: 100%Tenant isolation: client_slug
07

Test Suite

120 passing tests. All external APIs mocked.
src/llm/client.py
892%
src/testing/quality_checks.py
1188%
src/memory/knowledge_base.py
1278%
src/agents/content_writer.py
984%
src/agents/community_monitor.py
1081%
src/agents/self_improver.py
776%
terminal
$ pytest tests/unit/ tests/quality/ -q
........................................................
120 passed in 2.34s

# Coverage: 63% (unit + quality)
# Remaining 37% = live API paths tested in integration suite

This is the first look.

The architecture, workflows, and content samples shown here are real, not mocked up for the application. What comes next depends on the conversation. Happy to go deeper on any part of this at the right stage.

← Read the application letter