Communitygithub.com

Autumn Recruitment Radar

Information collection skill for Chinese mainland campus recruitment, internships, and internship-to-full-time conversion.

What is Autumn Recruitment Radar?

Autumn Recruitment Radar is a skill designed for AI agents (such as Claude Code, Cursor, Codex, Trae) to help users gather job openings for Chinese mainland campus recruitment, internships, and internship-to-full-time conversion. It first asks for user preferences (graduation year, target roles, cities, company types), then discovers job openings from public sources with real-time web search, prioritizes verification with official enterprise recruitment websites, and uses deterministic scripts for matching, cross-source deduplication, change detection, and deadline recognition. The skill outputs a clean chat list of new or changed, non-expired positions directly into the conversation, avoiding file paths or reports. It supports cross-platform execution with pure Python 3.8+ and does not rely on system-specific commands, cookies, or paid tools. It also offers heartbeat notifications for incremental updates on daily sessions and optional scheduled tasks for automatic daily pushes, with clear communication about limitations (requires computer to be on, no cloud 24/7).

Works with✓Claude Code~Codex CLI~Cursor
npx skills add moonbeautiful/autumn-recruiting-radar

Ask in your favorite AI

Open a new chat with this agent skill pre-loaded.

Documentation

Autumn Recruitment Information Collection

An information collection Skill for Chinese mainland campus recruitment, internships, and internship-to-full-time conversion. First confirm job preferences, then discover positions from public sources, prioritize verification with enterprise recruitment official websites, and finally use deterministic scripts to complete matching, cross-source deduplication, change detection, and deadline recognition, and return the job list directly to the current conversation.

Running Environment (Open Source & Portable)

This Skill is distributed as open source—anyone can clone it to their own computer and install it. Therefore, it must meet:

  • Cross-platform: All scripts are pure Python 3.8+ standard library, running on Windows / macOS / Linux; no absolute paths are hardcoded, no system-specific commands like osascript or launchd are used.

  • Cross-model / cross-client: Position discovery uses only the "built-in background web search/webpage reading capability" of the host client, without installing extra tools, controlling the user's browser, or incurring any extra costs.

  • Only two paths:

    1. The client has background search/webpage reading capabilities (mainstream AI clients like Trae, Claude come with these) → use background web search to discover positions directly, zero user operation;
    2. The client indeed has no web search capability → prompt the user as below, then stop, and never fabricate positions, companies, links, or deadlines from memory.

    Fixed prompt when web search is unavailable:

    The current client does not have available web search capabilities. I cannot fetch job information in real time. Please run this Skill again in an AI client that supports web search.

  • Regardless of which path successfully retrieves positions, the output is uniformly written to runtime/inbox/jobs-input.json, and subsequent processing is identical.

Configuration Boundaries

Before starting, fully read:

Users must not be allowed to modify config/project.json. User preferences are written only to runtime/user-preferences.json; program state is written only to runtime/state/.

Step 0: Initialization

First run:

SKILL_DIR=$(cd "$(dirname "SKILL.md")" && pwd)
python3 "$SKILL_DIR/scripts/bootstrap.py"

Read runtime/user-preferences.json. If onboarded is not true, ask only for the five items not yet clear:

  1. Graduation year;
  2. Target roles;
  3. Target cities;
  4. Only campus recruitment, or also internships and internship-to-full-time conversion;
  5. What types of companies to look for (using discovery.company_type_options: Internet giants / AI Unicorns & LLM Companies / Vertical industries / All), plus any additional company/industry preferences.

Write the answers to runtime/user-preferences.json:

  • An empty array for cities means nationwide;
  • Company type (item 5) is used in Step 1 to generate the "company queue" and decide who to search first; the specific list is obtained by runtime real-time search, not hardcoded, users don't need to know which companies exist;
  • Company and industry preferences are used by default for sorting bonus only, not hard filtering;
  • Do not require users to fill in a keyword list; the Agent generates role_keywords from natural language targets; e.g., "AI products" should cover common names such as AI Application Product Manager, AI LLM Product Manager, AI Agent Product Manager, AIGC Product Manager, and Product Manager (AI direction);
  • After completion, set onboarded to true and confirm the configuration summary to the user.

When asking about company type, maintain the same neutral tone as other questions: explain that the specific company list will be obtained by the assistant through real-time search when running; the user only needs to choose a broad category, not list companies themselves.

Postpone asking about enabling "update notifications" until after the first result is delivered (see Step 5); do not ask during initialization.

Step 1: Discover Positions (Phased + Streaming Delivery)

Read config/project.json (especially the discovery section) and references/source-strategy.md.

Core principle: Users are not waiting for "all searches to finish", but for "the next job they can apply to". So always deliver while searching, never compute everything first and then deliver. See source-strategy.md "Phased Streaming Discovery".

Discovery is done in two phases:

1a. First discover the "company queue" (real-time, batch)

  • The company type is selected by the user in Step 0 (Internet giants / AI Unicorns & LLM Companies / Vertical industries / All); the list is not hardcoded.
  • Use aggregation/list/top rankings/college employment portals—those "one-shot can bring dozens" platform clue pages—to search in real time for a batch of "hiring company names + job clues", obtaining an ordered queue (e.g., ByteDance → Alibaba → Tencent → …).
  • Sorting can use discovery.employer_anchors to let known companies appear first; employer_anchors are only sorting anchors and occasional fallback, the actual list depends on this search.
  • This batch of clues itself can be returned to the user as a "preliminary (to be verified)" version, not wasted.

1b. Then verify and complete each company in the queue (streaming, deliver one by one)

  • Follow the queue order, concurrency not exceeding discovery.stream.max_concurrent_lookups companies;
  • For each company, first give "newly published / currently hiring" positions (in_hiring_first), then older ones;
  • After searching each company, append its positions back to the current conversation (deliver_per_company), do not wait for the entire queue to finish;
  • First wave: output up to first_batch_max_companies companies (default 3), so users see something within seconds;
  • Single run: maximum max_companies_per_run companies, maximum max_per_company positions each, to prevent runaway;
  • After finishing a batch, if ask_before_long_tail is true, ask "Would you like to continue searching AI Unicorns / vertical long-tail companies?" Only expand if user says "continue"; if user says "enough", stop immediately and organize results already delivered;
  • Companies not yet searched in the queue are retained for the next run, not discarded.

Candidate positions must comply with data-contract.md: source_tier can only be one of official, official_wechat, platform. Aggregation redirect pages/media links will be rejected directly by the script and can only serve as "clues". Products are written to (multiple appends possible):

runtime/inbox/jobs-input.json

Do not guess publication or deadline times; write unknown when not explicitly given on the page. If a source fails or is empty, record faithfully, do not repeatedly bypass access restrictions.

Step 2: Official Verification

For positions discovered from non-official sources, prioritize finding the official enterprise recruitment website detail page:

  • Found the official detail page: set it as the primary source, write the original aggregation page into alternate_sources;
  • Only found the official company WeChat official account: source tier is official_wechat;
  • Cannot find an official page: retain the true source tier, do not pretend it's official;
  • Official page explicitly closed, offline, or stopped recruitment: source_status set to closed;
  • Page still accepting applications: source_status set to open;
  • Cannot determine: set to unknown.

At most deeply verify 5 new companies each time, to avoid runaway costs.

Step 3: Matching, Deduplication, and Change Detection

Run:

python3 "$SKILL_DIR/scripts/radar.py" run \
  --input "$SKILL_DIR/runtime/inbox/jobs-input.json" \
  --preferences "$SKILL_DIR/runtime/user-preferences.json" \
  --project-config "$SKILL_DIR/config/project.json" \
  --state-dir "$SKILL_DIR/runtime/state"

The program must:

  • Match by graduation year, role, city, and recruitment type;
  • Company and industry preferences only add bonus;
  • Cross-source deduplication by same company, same role, same city, same graduation year, same recruitment type;
  • Prefer official sources, keep weaker sources as backup evidence;
  • Compare stable fields, identify new, changed, seen;
  • Output specific changed_fields;
  • If deadline is before the run date, or the official page explicitly closed, mark as expired;
  • Expired positions go to archive, not into delivery;
  • fetched_at does not participate in change determination.

Step 4: Reply Directly to Chat

The script outputs a concise chat list containing only new or changed and not yet expired positions. Reply with the full script output directly to the current chat, do not just tell the user the file path, and do not ask the user to open a report.

Internal state files:

runtime/state/
├── jobs.json
├── jobs.csv
├── seen.json
├── expired.json
├── rejected.json
├── daily/YYYY-MM-DD.md
└── outbox/YYYY-MM-DD.md

The chat list must include:

  • Company;
  • Job title;
  • City;
  • Recruitment type;
  • Salary and benefits;
  • Publication date;
  • Deadline;
  • Role keywords (important keywords extracted from job requirements);
  • Job highlights;
  • Application link;
  • Information source;
  • Status and change fields.

daily/ is an internal audit snapshot; outbox/ is an internal copy of this chat list. Users do not need to open these files. seen, expired, and duplicate positions from the same batch must not be sent repeatedly.

Step 5: After Processing Results, Ask About Enabling Update Notifications

After the first run and delivery of the job list to the user, then ask: "When there are new or changed positions, may I notify you?"

  • User declines: set schedule.notify_on_update to false (default), done, do nothing extra;
  • User allows: set schedule.notify_on_update to true, and arrange subsequent heartbeat runs per Step 6.

Users can change their mind anytime: say "I don't want notifications" to set notify_on_update back to false; even if there are new or changed positions, no proactive notification is sent. Say "turn notifications back on" to set it to true. This is a toggleable switch, not a one-time setting.

Step 6: Heartbeat Notifications (Only When notify_on_update is true)

The concept of a heartbeat is simple: at regular intervals, have the Agent re-run this Skill (re-discover → deduplicate → compare with historical state).

  • If this run finds "new" or "changed" positions, send that list to the current conversation;
  • If nothing is new or changed, send nothing, stay quiet, don't bother the user.

It relies only on "re-running + comparison with historical state"; no system alerts, emails, or webhooks. Who triggers the "re-run" comes in two layers, from general to enhanced:

6.1 Default Layer (Everyone Can Use, Zero Config, Cross-Platform, Cross-Model)

Anyone who clones this Skill uses this layer by default, no timers needed:

  1. Incremental refresh at session start (this is the universal "daily collector"): When this Skill is activated in a session, notify_on_update is true, and it has been more than a day since the last run, the Agent automatically uses the host's built-in web search to re-discover the companies the user cares about (e.g., ByteDance, Alibaba) → deduplicate → compare with historical state → only send new/changed positions to the user; if no update, stay quiet. Users don't need to explicitly say "check for me" each time, and don't need to configure any background command. Because users open their AI client every day, "finding new positions"—which requires web search—naturally happens in the first conversation each day— this is the only collection method that is zero-config, zero-cost, and not tied to any device for all GitHub users.
  2. User-triggered: Any time the user says "check / see if there's anything new", it also re-runs immediately.
  3. Add --heartbeat to the daily run command; no output on no update, only reply with the list on update:
python3 "$SKILL_DIR/scripts/run_daily.py" \
  --project-config "$SKILL_DIR/config/project.json" --heartbeat

When notify_on_update is true, run_daily.py automatically includes heartbeat, no need to manually add the parameter.

6.2 Optional Enhanced Layer: Host/System Scheduled Execution ("Automatic daily push at a fixed time")

When a user explicitly requests "automatic daily push at a fixed time", implement it with the following priority, and clearly state the trade-offs:

  1. Prefer to use the scheduling/reminder capability of the host Agent/client (if available), letting it trigger the re-run at the specified time—most convenient, no system modification needed.
  2. If the host has no scheduling capability, a cross-platform script scripts/schedule_task.py can register a native OS scheduled task (macOS launchd / Linux cron / Windows Task Scheduler). It generates the configuration at runtime based on the user's system and chosen time, without hardcoding paths or binding to the maintainer's device:
# Preview first (default print, no system change, safe):
python3 "$SKILL_DIR/scripts/schedule_task.py" --action print --time 08:00
# Then install after user confirmation (will modify this machine):
python3 "$SKILL_DIR/scripts/schedule_task.py" --action install --time 08:00
# Check / uninstall:
python3 "$SKILL_DIR/scripts/schedule_task.py" --action status
python3 "$SKILL_DIR/scripts/schedule_task.py" --action remove

Before installation, you must print the preview and obtain user consent; after consent, write the user's chosen time into schedule.time and set schedule.enabled to true.

Two real limitations that must be clearly communicated to the user (do not conceal, do not exaggerate as "24/7 cloud"):

  • The computer must be on: The system scheduled task triggers only when this computer is powered on; if shut down or asleep, it will not run and will not catch up.
  • The scheduled task itself does not "re-fetch new positions via web search": run_daily.py by default only reprocesses existing input (deduplication, change detection, deadline archiving). Therefore, config/project.json's collector.command is left empty by default and should remain empty (reason in the command_note field). This is not a flaw: real new position discovery is handled by the "session start automatic web search" in 6.1, which is zero-config and zero-cost for all users. There is no "universal, free, and runnable by all GitHub users" background web search command, so do not fabricate a collector.command to distribute just to make the scheduled task search for new positions.
  1. If the host does not support scheduling and the user doesn't want system tasks, fall back to the default layer: tell the user "next time you use me, I'll automatically incrementally update, or say 'check' anytime", never disguise a local system task as cloud auto-push.

Validation

bash "$SKILL_DIR/scripts/validate_skill.sh"

Validation must prove:

  1. First run produces new positions;
  2. Running the same input again does not re-deliver duplicates;
  3. Cross-source duplicate positions retain only the stronger source;
  4. Fields that change output changed and the specific changed fields;
  5. Changed input re-run becomes seen;
  6. Positions with explicit deadlines or past-due dates go into expired;
  7. Expired positions do not appear in the chat list;
  8. City, recruitment type, publication date, deadline, source, and highlights all appear in the chat list;
  9. Default command directly outputs the chat list; JSON is only for automated testing.

Security and Authenticity

  • Only access public pages; do not scrape login-required, internal, or non-public interfaces;
  • Do not store cookies, account passwords, or private recruiter information;
  • Do not bypass CAPTCHAs, anti-scraping measures, or access restrictions;
  • Write unknown dates as unknown, do not guess;
  • Internal state and chat lists retain source name, tier, link, and fetch time;
  • All positions should be confirmed on the official enterprise recruitment website before applying.

Related Skills