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
osascriptorlaunchdare 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:
- 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;
- 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:
- configuration.md: Boundaries for user-modifiable configuration, project configuration, and running state.
- data-contract.md: Fields for job input and output.
- source-strategy.md: Source prioritization, discovery and verification flow.
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:
- Graduation year;
- Target roles;
- Target cities;
- Only campus recruitment, or also internships and internship-to-full-time conversion;
- 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_keywordsfrom 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
onboardedtotrueand 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"
platformclue 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_anchorsto let known companies appear first;employer_anchorsare 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_lookupscompanies; - 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_companiescompanies (default 3), so users see something within seconds; - Single run: maximum
max_companies_per_runcompanies, maximummax_per_companypositions each, to prevent runaway; - After finishing a batch, if
ask_before_long_tailis 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_statusset toclosed; - Page still accepting applications:
source_statusset toopen; - 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_atdoes 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_updatetofalse(default), done, do nothing extra; - User allows: set
schedule.notify_on_updatetotrue, 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:
- Incremental refresh at session start (this is the universal "daily collector"): When this Skill is activated in a session,
notify_on_updateistrue, 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. - User-triggered: Any time the user says "check / see if there's anything new", it also re-runs immediately.
- Add
--heartbeatto 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:
- 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.
- 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.pyby default only reprocesses existing input (deduplication, change detection, deadline archiving). Therefore,config/project.json'scollector.commandis left empty by default and should remain empty (reason in thecommand_notefield). 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 acollector.commandto distribute just to make the scheduled task search for new positions.
- 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:
- First run produces new positions;
- Running the same input again does not re-deliver duplicates;
- Cross-source duplicate positions retain only the stronger source;
- Fields that change output
changedand the specific changed fields; - Changed input re-run becomes
seen; - Positions with explicit deadlines or past-due dates go into
expired; - Expired positions do not appear in the chat list;
- City, recruitment type, publication date, deadline, source, and highlights all appear in the chat list;
- 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.