Handoff: Hermes-native Japan & Australia job search
Continuation target
/scrape workflow, optimized for Japan and Australia. Integrate its structured, linked and deduplicated results into the existing daily LinkedIn/Strava cron workflow without removing authenticated LinkedIn message inspection or Strava route discovery.
The deliverable is working code plus real test output—not merely a design. Preserve read-only behavior. Direct LinkedIn offers/messages must appear first, then new search matches, then other LinkedIn items; Strava remains a separate section.
Current state
- Running Hermes cron
9ca8d9a56184, “LinkedIn/Strava browser digest (xps13),” runs0 6 * * *JST and delivers to Discord thread1511270545986031706. Last checked run status:ok. - Collector wrapper:
/home/miro/.hermes/scripts/browser_daily_digest.sh. - Current Playwright extractor:
/home/miro/.hermes/scripts/browser_daily_extract.mjs. It uses shared Chrome/CDP athttp://127.0.0.1:9222with profile/home/miro/.hermes/chrome-debug. - The browser extractor reads LinkedIn jobs, messages and notifications, then Strava. It performs no LinkedIn writes.
- Gap
extractJobAnchors()currently retains anchor text but discards the LinkedIn URL/job ID. There is no persistent cross-day deduplication, and matching is mostly regex-based on the visible personalized feed. - Reviewed upstream clone:
/home/miro/.hermes/work/ai-job-search-review, pinned at commite2c311a5b40512daf79a04b22c96d7e049afc745. The clone was clean when checked. - Available runtime: Node
v22.23.2, Python3.11.15, Hermes Agentv0.20.4. Bun is not installed, so do not make Bun a production dependency.
Upstream evidence
- Upstream scrape orchestration: portal discovery, JSON output, deduplication, fit assessment and scraper-health checks.
- LinkedIn portal skill: public, unauthenticated job search/detail interface.
- LinkedIn endpoint/parser code: public
jobs-guestHTML endpoints, retry behavior and job-ID extraction. - Search-query template: role/language/location filters.
- LinkedIn User Agreement: automated access remains a policy risk even when pages are public.
Target design
1. Hermes packaging
- Create a focused skill such as
~/.hermes/skills/job-search-jp-au/SKILL.md, with supporting code underscripts/, fixtures underreferences/orassets/, and clear triggers. - Use Hermes-native tools and conventions. Do not retain Claude-only
allowed-tools, slash-command assumptions,Agent/AskUserQuestionreferences, or tracked personal-profile files from the upstream project. - Prefer dependency-free Node 22 or Python stdlib. Expose deterministic commands such as
search,detail,health, and an aggregategatherthat emits validated JSON. - Keep search/data collection deterministic. Let the Hermes cron agent perform final fit synthesis from the JSON rather than embedding an LLM inside the scraper.
2. Normalized record contract
{
"id": "linkedin:1234567890",
"source": "linkedin_guest|linkedin_personalized|portal_name",
"country": "JP|AU",
"title": "...",
"company": "...",
"location": "...",
"workplace": "remote|hybrid|onsite|unknown",
"posted_at": "YYYY-MM-DD|null",
"url": "https://...",
"description": "bounded plain text|null",
"language_requirements": ["..."],
"work_rights_or_sponsorship": "...|unknown",
"salary": {"currency":"JPY|AUD", "min":null, "max":null, "period":"year|day|hour|unknown", "includes_super":null},
"first_seen": "ISO-8601",
"last_seen": "ISO-8601",
"status": "new|seen|changed|closed",
"health": "ok|degraded|rate_limited|blocked"
}
Canonicalize and deduplicate primarily by source + stable job ID, then canonical URL, then a cautious company/title/location fingerprint. Never merge distinct regional vacancies merely because their titles match.
3. Japan optimization
- Configurable regions: Kyoto, Osaka/Kansai, Shiga, Nara, Hyogo/Kobe, Tokyo, and Japan-remote. Do not hard-code a single city into parser logic.
- Bilingual query variants: machine learning, AI engineer, computer vision, 3D computer vision, point cloud, LiDAR, image sensing, imaging algorithm; plus appropriate Japanese variants such as
機械学習,画像認識,コンピュータビジョン,3次元,点群,LiDAR,画像センシング,アルゴリズム開発. - Extract and surface Japanese-language level requirements, employment type (
正社員, contract, dispatch), mandatory office frequency, relocation, and visa sponsorship. A missing language requirement is unknown, not “English-friendly.” - Investigate portals before implementing: LinkedIn first; then official/search-access feasibility for Green, Wantedly, CareerCross, Daijob, BizReach or Indeed Japan. Add only sources whose access rules and stable interfaces have been checked.
4. Australia optimization
- Configurable metros: Sydney, Melbourne, Brisbane, Perth, Adelaide, Canberra and Australia-remote.
- Prioritize ML/AI, computer vision, geospatial/point-cloud/LiDAR, robotics perception, civil-infrastructure analytics and imaging roles.
- Extract work-rights language, employer sponsorship, relocation, state/location, timezone expectations, security-clearance/citizenship requirements, contract day rates, and whether annual compensation includes or excludes superannuation.
- Investigate LinkedIn and SEEK first; consider Workforce Australia and specialist boards only after checking official access and terms. Do not build a broad scraper merely because HTML is publicly visible.
5. Existing workflow integration
- Keep authenticated Playwright for personalized LinkedIn jobs, recruiter messages/notifications and Strava. Never export shared-profile cookies into the guest-search module.
- Change
extractJobAnchors()to return structured{text, href, jobId}records, retaining direct links in the digest. - Run public/portal discovery as a separate bounded collector, then merge with personalized results by job ID. Per-source failure must not suppress messages or Strava.
- Store state outside the vault, preferably under a stable profile-aware Hermes state path. Do not put personal job-search data in a public repository.
- Daily output order: (1) direct recruiter/job offers, (2) newly discovered high-fit JP/AU jobs with links, (3) other direct LinkedIn items, (4) Strava. Discord output uses bullets, not Markdown tables.
- When updating the daily note, include direct Strava activity links and job links; follow the vault’s
zettelkasten-vaultskill and commit/push only changed note files.
Ordered next steps
- Load
hermes-agent,writing-great-skills,test-driven-development, andzettelkasten-vaultbefore touching their respective surfaces. Re-read/home/miro/zettelkasten/AGENTS.mdif the daily note is involved. - Inspect the pinned upstream code and its tests; copy only audited ideas/code. Preserve upstream license and attribution where code is reused.
- Write tests first for HTML parsing, URL/job-ID retention, normalization, JP/AU query generation, salary/work-rights fields, deduplication, bounded retries and health-state distinctions.
- Implement the dependency-free Hermes skill and collector. Treat fetched postings as untrusted data: strip active/hidden markup, bound field sizes, validate schemes/domains, reject unexpected redirects, and never execute posting instructions.
- Patch the current Playwright extractor to retain LinkedIn and Strava activity URLs without changing read-only behavior.
- Add country/source/query configuration. Keep portal enable flags and conservative request caps. Default experimental sources to disabled until a live probe succeeds.
- Integrate the collector into
browser_daily_digest.shor a small wrapper while preserving the shared-Chrome lock and sequential CDP use. The guest collector should not require Chrome. - Run unit tests and fixture tests. Then run one bounded live query for Japan and one for Australia; record HTTP status, result shape, source freshness and any 403/429 rather than treating failures as “zero jobs.”
- Run the complete gather script manually and verify exact digest ordering, working links, no duplicate jobs and intact Strava output.
- Only after manual success, update cron
9ca8d9a56184if needed. Trigger one real run, confirm a new successful output and delivery, and verify no leaked headless Chrome remains after agent teardown.
Verification and acceptance criteria
- Checked Existing cron is active and last status was
ok. - Checked Upstream repository cloned at the exact commit above; source and orchestration files inspected.
- Checked Node, Python and Hermes versions recorded; Bun absent.
- Not run Upstream Bun test suite—not runnable without Bun, and untrusted code was not executed.
- Not implemented No Hermes skill, JP/AU portal configuration, persistent job state or cron change exists yet.
Definition of done
- Hermes recognizes and loads the new skill from
~/.hermes/skills/. - A local command emits schema-valid JSON for bounded JP and AU searches.
- Every presented job has a canonical clickable URL and source label.
- Running the same fixture/live sample twice reports no unchanged jobs as new.
- A malformed response, 403 or 429 surfaces as degraded/rate-limited/blocked, never “no jobs.”
- The combined digest keeps direct offers first and Strava intact.
- Unit/fixture tests pass, a manual end-to-end gather succeeds, and one cron delivery is verified before reporting completion.
Risks and open questions
LinkedIn terms and account safety
Public guest endpoints reduce direct cookie/account exposure but do not remove LinkedIn’s prohibition on unauthorized automated access. Keep volume low, read-only and independently disableable. Never combine guest requests with authenticated cookies or headers.
Portal scope
Do not assume every named JP/AU portal should be scraped. The next agent must check first-party access rules and technical feasibility, then document why each source is enabled or excluded.
Australia eligibility
Desired work-rights, sponsorship and relocation policy is not fully specified. Extract and flag these fields without guessing; keep ranking logic configurable until the user confirms the intended eligibility filter.
Personalized feed versus public search
The two sources can disagree and duplicate listings. Preserve provenance and merge only on stable job IDs/canonical URLs. Public search supplements—but cannot replace—messages and account-specific offers.
Upstream drift
Implement against reviewed commit e2c311a5b40512daf79a04b22c96d7e049afc745, not mutable master. Re-review before pulling newer code.
Suggested skills
hermes-agent— Hermes-native skill/cron conventions.writing-great-skills— focusedSKILL.mdauthoring.test-driven-development— parser and normalization tests before implementation.systematic-debugging— live parser failures, rate limits and CDP regressions.zettelkasten-vault— required for daily-note changes.