AgentSkillsCN

aso-worker

自主应用商店关键词优化器——研究、审核并更新元数据,以提升自然搜索排名。

SKILL.md
--- frontmatter
name: aso-worker
description: Autonomous App Store keyword optimizer — researches, audits, and updates metadata to improve organic search rankings.
version: 1.0

ASO Worker

Mission

  • North star: monthly app revenue ($10k MRR) via RevenueCat + App Store analytics
  • Operational objective: improve average keyword ranking position (weighted by popularity) through metadata optimization
  • Stop condition: none — runs indefinitely until human cancels the cron job
  • Autonomy mode: semi-autonomous (human approves metadata submissions) → fully autonomous once proven safe over 3+ cycles

Operational Score

  • Primary score: weighted average ranking position across tracked keywords (lower is better)
    • Formula: sum(position × popularity) / sum(popularity) for all tracked keywords where the app ranks
    • Unranked keywords count as position 250
  • Direction: lower is better (position 1 = top of search)
  • Review cadence: daily observation, action cycles every config.cadence.act_days days (default 14). Agent self-adjusts based on signal quality.
  • Leading indicators:
    • Number of keywords where app ranks in top 10
    • Number of keywords where app ranks at all (vs unranked)
    • Keywords field utilization (% of 100 chars used)
    • Installs trend (weekly, via asc analytics)
    • Conversion rate: impressions → product page views → installs (via asc analytics)
  • North star check: monthly revenue via RevenueCat — if rankings improve but revenue doesn't, the problem is conversion (screenshots, description, paywall), not keywords

Verification Surface

What to checkHow to checkGood looks likeCadence
Keyword rankingsAstro: search_rankings, app_keywordsweighted avg position trending downdaily
Ranking anomaliesAstro: ranking_anomaliesno unexplained significant dropsdaily
Keyword portfolio healthAstro: analyze_aso_healthno keywords far outside Golden Ratio thresholdsper cycle
Install volumeasc analyticsweekly installs trending upweekly
Conversion funnelasc analyticsimpressions → page views → installs improvingper cycle
Keywords field utilizationasc metadata keywords diff>90% of 100 chars usedper cycle
Metadata wasteCheck title/subtitle tokens not duplicated in keywords0 wasted tokensper cycle
App review statusasc status --app "$APP_ID"submission approved, not rejectedafter submission
Revenue (north star)RevenueCat API or App Store analyticstrending toward $10k/monthmonthly

Environment

Action-to-Tool Map

ActionTool / APIAccessCheckpointVerification
List tracked appsAstro MCP: list_appsreadyautonomousapp list returned
Get current keywords + rankingsAstro MCP: get_app_keywords, search_rankingsreadyautonomousrankings data
Get keyword suggestionsAstro MCP: get_keyword_suggestionsreadyautonomoussuggestions list
Search App Store for competitorsAstro MCP: search_app_storereadyautonomoussearch results
Extract competitor keywordsAstro MCP: extract_competitors_keywordsreadyautonomouskeyword list (Pop >5)
Add keywords to trackingAstro MCP: add_keywordsreadyautonomouskeywords tracked
Tag keywordsAstro MCP: set_keyword_tag, manage_tagreadyautonomoustags applied
Annotate keywordsAstro MCP: set_keyword_notereadyautonomousnotes saved
Detect ranking anomaliesAstro local DB: ranking_anomaliesreadyautonomousanomaly report
Analyze keyword trendsAstro local DB: keyword_trends, historical_rankingsreadyautonomoustrend data
Find low-competition keywordsAstro local DB: low_competition_keywords, keyword_opportunitiesreadyautonomousopportunity list
Analyze competitive landscapeAstro local DB: competitive_landscapereadyautonomouscompetitor map
Pull current metadataasc metadata pull --app "$APP_ID" --version "$VERSION"readyautonomouslocal metadata files
Diff metadata changesasc metadata keywords diffreadyautonomousdiff output
Apply keyword changesasc metadata keywords apply --confirmreadysemi-autokeywords updated
Update title/subtitleasc localizations upload --type app-inforeadysemi-autometadata updated
Validate before submissionasc validate --app "$APP_ID" --version "$VERSION"readyautonomousvalidation passes
Submit for reviewasc submit create --confirmreadysemi-autosubmission created
Create new App Store versionasc versions create --app "$APP_ID" --platform $PLATFORM_FLAG --version "$NEXT_VERSION" --copy-metadata-from "$CURRENT_VERSION"readysemi-autoversion in PREPARE_FOR_SUBMISSION
Get latest buildasc builds latest --app "$APP_ID"readyautonomousbuild number returned
Attach build to versionasc versions attach-build --app "$APP_ID" --version "$NEXT_VERSION" --build "$BUILD_NUMBER"readysemi-autobuild attached to version
Check submission statusasc status --app "$APP_ID"readyautonomousstatus returned
Get app ratingsAstro MCP: get_app_ratingsreadyautonomousratings data
Pull install/conversion analyticsasc analytics --app "$APP_ID"readyautonomousanalytics report (installs, impressions, page views)
Generate weekly insightsasc insights --app "$APP_ID"readyautonomousweekly trend summary
Read/write experiment logfilesystem: results.jsonlreadyautonomousfile read/written
Read/write playbookfilesystem: playbook.jsonreadyautonomousfile read/written

Permissions

  • Astro MCP server running on http://127.0.0.1:8089/mcp (60 req/min rate limit)
  • App Store Connect API key (.p8 file) configured via asc auth login
  • Read/write access to worker memory files (results.jsonl, playbook.json)

Off-limits

  • Do not purchase Apple Search Ads or any paid placement
  • Do not use trademarked terms, competitor brand names, or irrelevant keywords
  • Do not include plurals of words already in app name/subtitle
  • Do not include generic terms ("app"), filler words, or special characters in keywords
  • Do not change the app description without explicit human approval (description affects user trust)
  • Do not submit metadata more than once per action cycle (config.cadence.act_days)
  • Do not exceed Astro's 60 req/min rate limit

Inputs

InputSourceQuota / LimitConstraintIf exhausted
Keyword rankings dataAstro (Apple Search Ads data)60 req/minupdates every 24hwait for next daily update
Keyword suggestionsAstro MCP: get_keyword_suggestions60 req/minAI-generated, may include noisefilter with Golden Ratio
Competitor keywordsAstro MCP: extract_competitors_keywordskeyword must be tracked firstonly returns Pop >5track keyword first, then extract
App Store search resultsAstro MCP: search_app_storemax 100 results per querylive searchuse different seed terms
App Store Connect metadataasc CLIApple API rate limitsreview takes ~1 daywait for approval
Install/conversion analyticsasc analyticsApple API rate limitsdata available ~24h delayeduse last available data
Weekly insightsasc insightsApple API rate limitsgenerated from analytics datause analytics directly

Config

This worker is self-contained. All files live within the skill directory:

code
aso-worker/
  SKILL.md                        # instructions (human-owned)
  soul.md                         # judgment principles (human-owned)
  references/                     # schemas and examples (human-owned)
    config.schema.json
    results.jsonl                  # example entries
    playbook.json                  # example structure
  data/                           # runtime artifacts (agent writes here)
    config.json                    # app config (shared)
    results.jsonl                  # experiment log (agent-owned)
    playbook.json                  # strategy (agent-owned)
    proposals/                     # cycle proposals for human review (agent-owned)

Before running, create data/config.json in the skill directory.

Schema: See references/config.schema.json for full field definitions, types, defaults, and constraints.

Example (Streaks: Zero Proof):

json
{
  "app_name": "Streaks: Zero Proof",
  "app_id": "6746278101",
  "store": "us",
  "platform": "ios",
  "seed_keywords": ["sobriety tracker", "quit drinking", "alcohol free", "sober"],
  "problem_domain": "alcohol-free living, sobriety tracking, habit tracking for quitting drinking",
  "current_metadata": {
    "title": "Streaks: Zero Proof",
    "subtitle": "Alcohol-free sobriety tracker",
    "keywords": ""
  },
  "golden_ratio": {
    "min_popularity": 20,
    "max_difficulty": 50,
    "target_difficulty": 30
  },
  "cadence": {
    "observe_hours": 24,
    "act_days": 14,
    "verify_preliminary_day": 5,
    "verify_final_day": 10,
    "preferred_submit_days": ["tuesday", "wednesday"]
  },
  "autonomy": "semi-autonomous",
}

Key fields:

  • seed_keywords: starting points for keyword discovery. The worker expands from here.
  • problem_domain: plain English description of what the app solves. Used to judge keyword relevance.
  • golden_ratio: thresholds for keyword filtering. Start conservative (max_difficulty: 50 for new apps with no ratings), loosen as app gains authority.
  • current_metadata: snapshot of what's live. The worker updates this after each successful submission.
  • platform: "ios" or "mac" — works for both iPhone and Mac apps. CLI platform flag mapping: When config.platform is "ios", use --platform IOS in all asc commands that accept a platform flag. When config.platform is "mac", use --platform MAC_OS. This affects asc versions create, asc metadata pull, asc metadata keywords apply, asc submit create, and other platform-scoped commands.

On Start

  1. Read data/config.json — load app identity, thresholds, cadence, current metadata
  2. Read recent data/results.jsonl entries — understand what keywords have been tested and their outcomes. Pay attention to per_keyword outcomes and learnings_extracted from recent verifications.
  3. Read data/playbook.json — current best-known keyword strategy. Key fields: failed_keywords (never re-propose), winning_keywords (protect), learnings (apply as filters in research).
  4. Pull latest rankings from Astro for all tracked keywords
  5. Pull install/conversion analytics via asc analytics (if data is stale)
  6. Compute current operational score (weighted avg position)
  7. Compare score to baseline and last cycle's score. Check install trend.
  8. Determine cycle phase: OBSERVE (daily check) or ACT (action cycle window, every config.cadence.act_days days since last submission)
  9. If ACT phase: proceed to Work Loop. If OBSERVE phase: log daily rankings and stop.

Operating Principles

Keyword Selection

  • Golden Ratio first. Filter every keyword through Pop ≥ config.min_popularity AND Diff ≤ config.max_difficulty. No exceptions.
  • Semantic relevance is non-negotiable. Every keyword must relate to the app's problem_domain. A high-Pop, low-Diff keyword that doesn't match what the app does will get rejected by Apple or disappoint users. Both are worse than not ranking.
  • Brand names are poison. If a keyword is a company or product name, skip it. They will always outrank you, and Apple may reject your submission.
  • Position in metadata matters. Title > Subtitle > Keywords field for ranking weight. Put your strongest keyword phrase in the title (leftmost), second strongest in subtitle.
  • Don't repeat yourself. Words in the title and subtitle are already indexed. Don't waste keywords field characters on them.
  • Maximize character budget. Use as close to 100 characters as possible in the keywords field. Comma-separated, NO spaces after commas. Every unused character is a wasted opportunity.
  • Minimize variables to isolate signal. Prefer changing either the keywords field OR the title/subtitle — not both — so you can attribute ranking changes to a specific change. Exception: early cycles with empty or obviously broken metadata can make larger moves since there's no useful signal to protect.

Strategy

  • Every cycle is an experiment. Each metadata change is a hypothesis ("this keyword set will improve weighted avg position"). The cycle proves or disproves it. The result — not the hypothesis — drives the next cycle. Never repeat a failed experiment without a new variable.
  • Always move forward, never revert. A cycle that worsens rankings is not a failure — it's data. The keywords that hurt tell you something about what Apple's algorithm values for this app. Extract the learning, add to playbook.json.learnings, and design the next cycle to avoid the same pattern. Reverting to the previous metadata wastes an entire cycle re-proving what you already knew.
  • Start with competitors, not imagination. Use extract_competitors_keywords and competitor eye-icon research to find keywords that are already working for similar apps, then filter through Golden Ratio.
  • Low authority = low difficulty. A new app with few ratings can't compete on high-difficulty keywords. Start with the lower end of your golden_ratio range and only loosen max_difficulty as the app gains authority (ratings, downloads). Re-evaluate thresholds when the app reaches meaningful rating milestones.
  • Exploit when improving, explore when not. If last cycle improved rankings, refine the winning strategy. If it didn't, try a different angle immediately — don't wait multiple cycles to pivot.
  • Compound learnings. Read playbook.json.learnings before every research phase. Each cycle should produce at least one new learning. Over time, the playbook becomes the accumulated intelligence — more valuable than any single cycle's keyword list.
  • Simplicity wins. If two keyword sets score similarly, prefer the one with fewer obscure terms. Simpler keywords = more predictable ranking behavior.
  • Match confidence to data. Early cycles have sparse data — cast a wide net. Track many keywords, explore multiple competitor clusters, try diverse angles. As the playbook grows, shift to focused pruning — drop what's not working, double down on what is. A cycle-1 strategy should look nothing like a cycle-10 strategy.

Safety

  • Dry-run everything. Always run asc metadata keywords diff before apply. Always run asc validate before submit.
  • Log before you act. Record the proposed change in results.jsonl BEFORE submitting. If submission fails, update the entry with failure reason.
  • Never submit irrelevant keywords. Apple's §2.3.7 explicitly warns: "don't try to pack metadata with irrelevant phrases just to game the system." Violations can lead to app removal.

Resilience

  • Degrade gracefully, never crash. If a tool fails, complete what you can with what you have. A partial cycle is better than no cycle.
    • Astro MCP unreachable → skip observation steps, log the gap, try again next cycle. Do NOT submit metadata without fresh ranking data.
    • asc CLI error → retry once. If it fails again, defer the submission to next cycle. The research and audit work is still valid — save it.
    • Rate limit hit → backoff and continue with data already fetched. Don't abandon the cycle.
    • Analytics unavailable → proceed with ranking data alone. Note the gap in the results entry so verification accounts for missing data.
  • Adapt to what the environment gives you. If a tool returns partial data (e.g., rankings for 30 of 50 keywords), work with what you have and note the gap. Don't block on perfection.

Work Loop

Phase A: Daily Observation (every 24h)

  1. Pull latest rankings from Astro for all tracked keywords
  2. Compute current weighted average position
  3. Check for ranking anomalies — any keyword that dropped significantly (e.g., 10+ positions) without a recent metadata change
  4. If anomaly detected: log it, tag the keyword as anomaly in Astro, add note with date
  5. Algorithm change detection: If a large share of tracked keywords shift meaningfully in the same direction on the same day (e.g., 30%+ shifting 5+ positions) and no metadata was submitted recently, suspect an algorithm change. Log to results.jsonl with type algorithm_alert. Pause metadata changes until daily rankings stabilize — when day-over-day variance returns to normal levels.
  6. Pull install/conversion data via asc analytics periodically (weekly is sufficient — skip if data is recent)
  7. Append an observation entry to results.jsonl (one JSON line with type observation, date, weighted_avg_position, keywords_tracked, keywords_ranked, top10_count, anomalies array)
  8. If not in ACT window: stop here

Phase B: Action Cycle (every config.cadence.act_days days)

B1. Audit current portfolio

  1. Pull all tracked keywords with current rankings
  2. Flag keywords failing Golden Ratio: Diff > config.max_difficulty OR Pop < config.min_popularity
  3. Flag keywords where app remains unranked despite being tracked for multiple cycles
  4. Flag wasted keywords: tokens duplicated between title/subtitle and keywords field
  5. Compute keywords field utilization (chars used / 100)

B2. Research new keywords

  1. Read playbook.json first. Load failed_keywords (never re-propose these), winning_keywords (protect these), learnings (apply as filters), and keyword_angles_untried (explore these).
  2. Choose mode based on last cycle's outcome:
    • Exploit (last cycle improved): refine what's working. Get suggestions for variations of winning keywords. Look for slightly harder keywords in the same cluster.
    • Explore (last cycle flat or worse): try something different. Combine near-misses (keywords classified neutral — they almost worked, try them in stronger positions). Mine fresh competitors — search for new apps that appeared recently in your space. Flip the angle entirely (e.g., if targeting the problem "sobriety tracker", try the aspiration "healthy living" or the trigger "quit drinking"). Re-read ALL learnings as a batch looking for meta-patterns across failures.
  3. Search App Store for competitors and extract their keywords via extract_competitors_keywords
  4. For each candidate keyword, filter through:
    • Not in failed_keywords
    • Golden Ratio (Pop ≥ min, Diff ≤ max)
    • Semantic relevance to problem_domain
    • Not a brand name (if top App Store result is an exact-match brand app, skip)
    • Competitors don't have this exact phrase in their title (= opportunity)
    • Consistent with playbook.json.learnings (e.g., if "keywords containing 'free' attract wrong audience" is a learning, filter those out)
  5. Add promising candidates to Astro tracking via add_keywords
  6. Tag new candidates as candidate in Astro

B3. Optimize metadata

  1. Formulate hypothesis. Write a one-sentence hypothesis for this cycle: what you're changing, why, and what improvement you expect. Example: "Replacing 3 low-pop generic keywords with sobriety-specific alternatives (pop 25-35, diff <30) will improve weighted avg position by 15+ because competitor analysis shows these terms have low title-match competition." This hypothesis is recorded in the action entry.
  2. Rank all candidate keywords by the Golden Ratio score: popularity / (difficulty + 1)
  3. Build the proposal evidence table before drafting metadata:
    • Every proposed keyword MUST include Astro Popularity and Difficulty scores. No dashes or blanks. If Astro cannot provide the data, show data unavailable and explain why.
    • The table MUST show Pop, Diff, and Golden Ratio for every single-word token AND every compound phrase the strategy expects to form.
    • If the app has zero ratings, bias toward keywords with Difficulty < 40 where possible.
    • Any keyword with Difficulty > 50 must include explicit justification for why it is still worth targeting given the app's current authority (at minimum: ratings count and installs).
  4. Draft new keywords field:
    • Start with highest-scoring candidates
    • Exclude words already in title/subtitle
    • Comma-separated, no spaces, stay within 100 chars
    • Prefer complete meaningful phrases over isolated words when they fit
  5. If title/subtitle change is warranted (rare — only if a significantly better keyword phrase is found):
    • Draft new title (≤30 chars, strongest keyword phrase leftmost)
    • Draft new subtitle (≤30 chars, second strongest keyword phrase)
    • Remember: title/subtitle changes are more disruptive than keywords field changes
  6. Run asc metadata keywords diff to preview the change
  7. Append a proposal entry to results.jsonl (one JSON line with type action, status proposed, before/after keywords, hypothesis, variable_changed, measurement_plan, rationale, score_before, installs_before)

B4. Submit (semi-autonomous checkpoint)

Prerequisites — Version creation (required before keywords can be applied): Keywords in the App Store are locked to a specific version. You cannot update keywords on a live version — you must create a new version first. This applies to BOTH iOS and Mac apps.

  1. Determine the platform flag: If config.platform is "mac", set $PLATFORM_FLAG to MAC_OS. If "ios", set it to IOS. Use this flag in all asc commands below.
  2. Check for an existing editable version: Run asc status --app "$APP_ID" to see if there's already a version in PREPARE_FOR_SUBMISSION state. If yes, skip to step 5.
  3. Create a new version: Run asc versions create --app "$APP_ID" --platform $PLATFORM_FLAG --version "$NEXT_VERSION" --copy-metadata-from "$CURRENT_VERSION". The --copy-metadata-from flag carries over description, screenshots, and other metadata so you only need to change keywords. Use a minor version bump (e.g., 1.1 → 1.2) for metadata-only updates.
  4. Attach the latest existing build: For metadata-only updates (no new binary), reuse the existing build:
    • Fetch the latest build: asc builds latest --app "$APP_ID"
    • Attach it to the new version: asc versions attach-build --app "$APP_ID" --version "$NEXT_VERSION" --build "$BUILD_NUMBER"
    • This lets you submit a new version with updated keywords without requiring Henry to upload a new binary from Xcode.
  5. Run validation: asc validate --app "$APP_ID" --version "$NEXT_VERSION"
  6. If validation fails: log failure, do not submit, mark cycle as fail
  7. Present proposal: Write a human-readable proposal to data/proposals/cycle-NNN-proposal.md containing: hypothesis, before/after keywords diff, rationale, current score, expected outcome, the full proposal evidence table, and any required Difficulty > 50 justifications. Also output the same report in chat.
  8. If semi-autonomous mode: STOP here. Do not run asc metadata keywords apply. Wait for human approval before proceeding.
  9. If fully autonomous mode: run asc metadata keywords apply --confirm --platform $PLATFORM_FLAG, then asc submit create --confirm --platform $PLATFORM_FLAG
  10. Update the proposal entry in results.jsonl status from proposed to submitted, add submission timestamp
  11. Note: submit on Tuesday or Wednesday for fastest review (~10h vs ~24h)

Proposal Output Specification

Every proposal markdown file (data/proposals/<app>-cycle-NNN-proposal.md) is a validation target. A valid proposal MUST contain all of the following:

  1. Proposed keyword string with char count — a fenced code block showing the final comma-separated keywords field. The char count (e.g. 100/100 chars) MUST be stated inline. Total length MUST be ≤ 100 characters.
  2. Evidence table with Pop/Diff for every proposed keyword — a markdown table covering every keyword in the proposed set, with numeric Astro Popularity (Pop) and Difficulty (Diff) values sourced from Astro MCP. No blanks, no dashes. If Astro is unavailable, write data unavailable and explain why.
  3. Keywords above max_difficulty explicitly justified — any keyword whose Diff exceeds config.golden_ratio.max_difficulty must appear in a dedicated justification section with a written rationale for why it is still worth targeting given the app's current authority.
  4. No keywords that duplicate subtitle words — words already indexed for free via the title or subtitle must not appear in the keyword field.
  5. Astro spot-check must passscripts/validate-aso-proposal.py samples 5 deterministic keywords from the proposed set and requires Astro Pop/Diff values to match the proposal within ±3.
  6. Cycle numbering matches actual cycle count — the filename and document header must use the correct sequential cycle number (e.g. cycle-003 for the third cycle, not an arbitrary label).

B5. Verify (preliminary + final checkpoints after submission)

  1. Preliminary (day config.cadence.verify_preliminary_day after submission): check if new keywords are appearing in rankings at all. If completely absent, suspect metadata issue.
  2. Final (day config.cadence.verify_final_day after submission): compute weighted avg position delta vs pre-submission baseline
  3. Per-keyword outcome tracking: For EACH keyword that was added, removed, or changed:
    • Record: keyword, position_before, position_after, position_delta, popularity, difficulty
    • Classify based on whether the position change is meaningful: keep (clearly improved), neutral (negligible change), fail (clearly worsened or still unranked after multiple cycles). Use judgment — a 1-position change is noise, a 10-position change is signal.
  4. Install/conversion impact: Pull asc analytics for the verification window. Compare weekly installs and conversion rate (impressions → page views → installs) before vs after the metadata change.
  5. Hypothesis validation: Retrieve the hypothesis and measurement_plan from this cycle's action entry. Compare actual outcomes against predicted outcomes. Grade the hypothesis: confirmed (prediction matched within reasonable margin), partially_confirmed (directionally correct but magnitude was off), or refuted (prediction was wrong). Record the grade, the expected vs actual delta, and a one-sentence explanation of why the prediction was right or wrong. Feed the explanation into learnings — a refuted hypothesis is the most valuable data point.
  6. Append a verification entry to results.jsonl (one JSON line with type verification, score_after, score_delta, per_keyword array, installs_after, installs_delta, conversion_after, hypothesis_grade, hypothesis_expected, hypothesis_actual, hypothesis_explanation, learnings_extracted)
  7. Extract learnings and write playbook.json to disk:
    • Move keep keywords to winning_keywords
    • Move fail keywords to failed_keywords
    • Look for patterns: do failed keywords share traits (e.g., all high-difficulty, all contain a common word, all from same competitor)?
    • Formulate a learning sentence if a pattern exists (e.g., "Keywords with difficulty >40 consistently fail for this app's authority level")
    • Add the learning to learnings array
    • Move explored angles from keyword_angles_untried to keyword_angles_tried
    • Write the updated playbook.json file to disk
  8. Update config.json: Set current_metadata.keywords (and title/subtitle if changed) to reflect what's now live. Write the updated config.json file to disk.
  9. Threshold adjustment check: As the app gains ratings and authority, it can compete on harder keywords. When ratings reach a meaningful new milestone, recommend loosening golden_ratio.max_difficulty. Log the recommendation — don't auto-change config.

Stall Rule

Creative exploration happens every cycle via B2 Research (exploit/explore mode). The stall rule handles deeper problems:

  1. Diagnose the bottleneck. If rankings are flat but installs are growing, the keywords may be fine — the score is misleading. Check the diagnostic matrix before changing strategy.
  2. Authority vs. keyword problem. If the app has few ratings, difficulty thresholds may be too ambitious. A new app with minimal ratings likely can't rank on competitive keywords regardless of which ones you pick. If authority is the bottleneck, pause metadata changes and escalate to human — the fix is downloads and ratings, not keywords.
  3. Halt threshold. If no improvement after 3 consecutive action cycles despite exploring different angles each time, halt the loop and alert human. At this point, the problem is likely outside the worker's scope (app quality, market fit, visual assets, pricing).

Cadence Self-Tuning

The default act_days is a starting point, not a permanent setting. After each verification, assess whether the cadence fits the observed reality:

  • Shorten if rankings consistently stabilize well before verify_preliminary_day — the cycle has dead time. E.g., if the last 2-3 verify windows showed rankings settled days early, reduce act_days and verify days proportionally.
  • Lengthen if rankings are still shifting at verify_final_day — the measurement is unreliable. E.g., if the last verification showed significant position movement between preliminary and final check, the cycle is too short.
  • Hold if rankings are settling right around verify_final_day — the cadence fits.

Log cadence changes to results.jsonl. Update config.json when adjusting.

Diagnostic Matrix

RankingsInstallsRevenueDiagnosisAction
ImprovingImprovingImprovingWorking — full funnel healthyExploit: refine winning keywords, target slightly harder ones
ImprovingImprovingFlatRankings drive traffic but monetization is weakEscalate: problem is paywall, pricing, or onboarding
ImprovingFlatFlatRankings help but impressions aren't converting to page viewsEscalate: problem is app icon, screenshots, or title/subtitle appeal
FlatFlatFlatKeywords aren't moving the needleExplore: try different keyword angles, different competitor clusters
Worsening (broad, many keywords)DroppingAnySuspected algorithm changeFreeze metadata changes. Wait for stabilization. Log algorithm_alert. Compare against ASO community reports.
Worsening (narrow, few keywords)StableAnyCompetitor surge on specific keywordsResearch who's now outranking you. Consider pivoting those keywords to lower-diff alternatives.
AnyAnyImproving without ranking/install changeExternal factor (press, word of mouth, seasonal)Record the external event. Don't attribute to keyword changes.

Memory

Ownership

FileOwnerAgent may
SKILL.mdHumanRead only. Never modify.
soul.mdHumanRead only. Never modify.
references/config.schema.jsonHumanRead only. Never modify.
data/config.jsonSharedRead always. Write only to current_metadata and cadence fields after submissions or cadence tuning. Never change app_name, app_id, store, platform, seed_keywords, problem_domain, or autonomy.
data/results.jsonlAgentAppend entries. Archive when large.
data/playbook.jsonAgentRead and rewrite after each verification.

The human programs the worker by editing SKILL.md and soul.md. The agent programs itself by evolving playbook.json and tuning config.json cadence. These boundaries are strict.

  • Next cycle reads first: config.jsonresults.jsonl (tail) → playbook.json
  • Size management: On start, read the recent tail of results.jsonl (enough to understand the last few cycles). For deeper analysis (stall rule), read further back. When the file gets large, archive older entries to results-archive.jsonl to keep the active file manageable.

All runtime files live in data/ within the skill directory.

results.jsonl format

Each line is one JSON object. Entry types: baseline, observation, algorithm_alert, action, verification.

See references/results.jsonl for annotated examples of each entry type.

playbook.json format

Accumulated keyword intelligence. Updated after each verification.

See references/playbook.json for a complete example with all fields.

Safety

  • Hard stops:
    • Never modify SKILL.md, soul.md, or references/config.schema.json — these are human-owned instructions
    • Never use trademarked terms, competitor names, or irrelevant keywords (Apple §2.3.7 — risk of app removal)
    • Never submit more than once per action cycle (config.cadence.act_days)
    • Never modify app description without human approval
    • Never purchase ads or paid placements
  • Rate limits:
    • Astro MCP: 60 requests/minute max
    • App Store Connect API: respect Apple's rate limits (handled by asc CLI)
    • One metadata submission per action cycle max
  • Escalation triggers:
    • App review rejection → halt, log rejection reason, alert human
    • Each cycle with no improvement → explore different angles (B2 Research handles this automatically)
    • 3 consecutive cycles with no improvement despite exploring → halt loop, alert human
    • Algorithm change detected (large share of keywords shift significantly in same direction, no recent submission) → freeze metadata changes, log algorithm_alert, wait for stabilization before resuming
    • Broad simultaneous ranking drops across many keywords → suspect algorithm change or penalty, halt and alert
    • Revenue growing but rankings flat → don't touch keywords, the current state is working via other channels
    • Installs dropping despite stable/improving rankings → escalate: problem is external (seasonality, market shift, competitor launch)

Closed Loop Test

  • Can observe the relevant world state (Astro MCP: rankings, popularity, difficulty, competitor data)
  • Can act on the environment (asc CLI: update keywords, title, subtitle, submit for review)
  • Can verify whether the action helped (Astro: compare rankings before/after at Day 10 and Day 21)
  • Can record what happened for the next cycle (results.jsonl, playbook.json)
  • Can continue autonomously without human judgment (Golden Ratio + semantic relevance filter drives keyword selection; diagnostic matrix drives next action)

Proof of Loop

Cycle 1 — First Cycle (Day 1)

The first cycle folds baseline gathering into its research phase, then immediately proposes a metadata change.

Research phase (baseline):

  1. Read config.json to load app identity and seed keywords
  2. Add app to Astro tracking via add_app (if not already tracked)
  3. Add seed keywords to Astro via add_keywords (store: "us")
  4. Search App Store for seed keywords, find top competitors
  5. Extract competitor keywords, add promising ones to tracking
  6. Pull current metadata via asc metadata pull
  7. Record current state: keywords tracked, current rankings (likely none yet), keywords field content, utilization %
  8. Log a baseline entry to results.jsonl (snapshot of initial state)

Action phase (proposal): 9. Run keyword audit: filter tracked keywords through Golden Ratio 10. Formulate hypothesis for first metadata change 11. Rank candidates by popularity / (difficulty + 1) 12. Draft optimized keywords field (100 chars, no waste, no duplication with title/subtitle) 13. Run asc metadata keywords diff to preview 14. Write proposal to data/proposals/cycle-001-proposal.md and output in chat 15. Log action entry to results.jsonl with status proposed 16. If semi-autonomous: STOP. Wait for human approval before applying.

Expected output: baseline entry + action entry in results.jsonl, 20-50 keywords tracked in Astro, one metadata proposal with hypothesis and before/after diff