Files
jobbi-bewerbung/agent/skills/it-stellensuche/SKILL.md
T
thomasandClaude ce844412e5 Jobsuche: Agent pro Benutzer in isoliertem Docker-Container
Jeder Suchlauf läuft nun in einem frischen Container pro Benutzer, dessen
pro-Benutzer-Home als ~/.claude gemountet ist — Memories und Session-Contexte
liegen damit strikt getrennt pro Benutzer. Die Such-Skills sind Shared-Code
aus dem Image und werden im Container nur nach ~/.claude/skills verlinkt.

Der Host-Runner startet pro Lauf `docker run --rm` und führt bis zu
JOBSUCHE_MAX_PARALLEL (Default 4) Läufe parallel über verschiedene Benutzer
aus (jeder hat eigenen Ollama-Key = getrennte Rate-Limits). Der alte gemeinsame
~/.claude-Pfad wird vom Runner nicht mehr beschrieben.

- source/agent/: neues Agent-Image (Dockerfile + entrypoint + drei Skills)
- scripts/jobsuche-runner.js: agentStarten als docker run, ensureAgentDir, runPool
- scripts/jobsuche-runner.sh + bin/-Kopie: Image-Guard
- package.json: docker:build-agent (lokal, ohne Registry-Push)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-14 15:41:22 +02:00

566 lines
38 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
name: it-stellensuche
description: Search the web for NEW jobs matching the user's own search profile (cities / remote mode, taken from the Bewerbungs-Tracker) that they have NOT applied to yet, then present them and optionally import them as JobOffers into the Bewerbungs-Tracker. Use whenever the user wants to find new job postings, "Stellensuche", "neue Stellen suchen", "Jobs finden", "Systemadministrator-Stellen", or refresh open job leads. Deduplicates against existing Bewerbungen and Jobangebote via the bewerbungs-tracker skill.
---
# Stellensuche (Suchprofil des jeweiligen Benutzers)
Findet **neue** Stellen im Web — **in den Städten bzw. im Modus, die das Suchprofil des
Benutzers vorgibt** —, filtert alle raus, für die sich der Nutzer schon beworben hat
**oder die auf der Blacklist stehen**, und spielt sie auf Wunsch als JobOffers in den
Bewerbungs-Tracker ein.
> **Multi-User (WICHTIG):** Der Tracker ist eine Plattform mit mehreren Benutzern; jeder
> hat **sein eigenes Suchprofil und seinen eigenen API-Key**. Region, Modus und
> Ausschlüsse stehen **nicht mehr in diesem Skill**, sondern kommen von außen:
>
> 1. **Aus dem Prompt**, wenn er sie nennt (so ruft der Jobsuche-Runner den Skill auf —
> er baut den Prompt aus dem Suchprofil des Benutzers). Der Prompt hat immer Vorrang.
> 2. **Sonst aus dem Tracker:** `GET /templates` (typ `Lebenslauf`) liefert Rolle und
> Technologien des Benutzers; nach Orten/Modus fragen, wenn der Prompt nichts sagt.
>
> Die Beispiele weiter unten (Rollen, Buzzwords, Städte) sind **nur Muster** — niemals
> ungefragt die Städte oder das Profil eines anderen Benutzers verwenden. Der API-Key in
> `BEWERBUNG_API_KEY` bestimmt, in wessen Konto importiert wird.
> **Blacklist-Prinzip (zentral):** Jeder erfolgreich importierte Job wird
> **unmittelbar danach geblacklistet**. So taucht dieselbe Stelle in späteren
> Suchläufen nie wieder auf — die Dublett-Vermeidung bleibt bestehen, selbst wenn
> das JobOffer später gelöscht oder in eine Bewerbung überführt wird. Umgekehrt
> werden Kandidaten, die schon auf der Blacklist stehen, gar nicht erst gelistet
> oder importiert.
## Suchprofil
**Quelle des Profils (in dieser Reihenfolge):**
1. **Der Prompt** — der Jobsuche-Runner hängt den Lebenslauf/das Kurzprofil des Benutzers
direkt an („--- PROFIL --- … --- ENDE PROFIL ---"). Rollen und Buzzwords **daraus**
ableiten.
2. **Der Tracker** — sonst `GET /templates` (typ `Lebenslauf`, sonst `Profil/Kurzprofil`)
des Benutzers, dem der `BEWERBUNG_API_KEY` gehört.
3. Erst wenn beides fehlt: aus den bereits erfassten Bewerbungen (`GET /applications`)
ableiten oder nachfragen.
**Rolle:** breit fassen — dieselbe Tätigkeit läuft je nach Firma unter vielen Titeln.
Passende Bezeichnungen als Query-Varianten und für die Relevanzbewertung nutzen.
> Die folgende Liste ist ein **Beispiel für ein IT-Infrastruktur-Profil** — sie zeigt, wie
> breit zu fassen ist, ist aber **kein Default**. Für einen Benutzer mit anderem Beruf die
> Titel entsprechend aus **seinem** Lebenslauf bilden.
- **Kern (Beispiel: 2nd-Level-Sysadmin + IT-Consultant):**
Systemadministrator, IT-Administrator, IT-Systemadministrator, IT-Systemadministration,
IT-Systemadministrator 2nd Level, 2nd-Level-Administrator, System-Administrator (m/w/d),
Fachinformatiker Systemintegration, Administrator (m/w/d).
- **Support/Betrieb:** 1st/2nd/3rd-Level-Support, 2nd-Level-Support, IT-Support,
IT-Supporter, Support-Techniker, Helpdesk-Mitarbeiter, IT-Techniker,
IT-Systemtechniker, Systemtechniker, IT-Systemelektroniker, Servicetechniker IT,
Onsite-Techniker, Field-Service-Techniker, IT-Systembetreuer, IT-Betreuer,
IT-Allrounder, IT-Koordinator, IT-Mitarbeiter, IT-Operations / IT-Betrieb,
IT-Administrator EDV, EDV-Administrator, EDV-Betreuer.
- **Infrastruktur/Netz:** System Engineer, Systems Engineer, Infrastructure Engineer,
IT-Infrastruktur, Infrastruktur-Administrator, Netzwerkadministrator, Network Engineer,
Virtualisierungsadministrator, Storage-/Backup-Administrator, Rechenzentrums-Techniker,
Datacenter-Techniker.
- **Cloud/Hosting/Modern Workplace (passt zu Hetzner/OVH im CV):** Cloud-Administrator,
Cloud Engineer, Cloud-Operations-Engineer, Hosting-Administrator, Hosting Engineer,
Microsoft-365-Administrator, M365-/Modern-Workplace-Administrator,
Azure-/Entra-Administrator, DevOps-Engineer (Infrastruktur-lastig),
Linux-Administrator, Linux System Engineer, Windows-Administrator.
- **Beratung/Leitung (klein) — deckt die CV-Rolle „IT-Consultant" ab:** IT-Consultant,
IT-Berater (technisch, hands-on), IT-Systemberater, IT-Kundenberater (technisch),
IT-Teamleiter/IT-Leiter in kleinen Firmen (nur wenn hands-on).
Ausschließen bleiben reine Softwareentwickler-, Data-Science-, Vertriebs- und
SAP-only-Stellen, außer sie passen klar zum Infrastruktur-Profil.
> **Ex-Arbeitgeber IMMER ausschließen (harte Regel, unabhängig von der Schreibweise):**
> Stellen der **„IT-Problemlöser"** (Essen) — voller Firmenname u. a.
> **„IT Problemlöser Verwaltungs- und Handels GmbH"**, auch **„IT-Problemlöser GmbH"**,
> **„IT Problemlöser"** o. Ä. — werden **nie** gelistet oder importiert. Es ist der
> frühere Arbeitgeber des Nutzers (siehe Lebenslauf). Diese Firma taucht immer wieder
> mit **wechselnden Stellentiteln** auf; die `firma_stelle`-Blacklist greift dann nicht,
> weil der Titel abweicht. Deshalb **jeden Treffer verwerfen, dessen Firmenname `IT
> Problemlöser` / `IT-Problemlöser` (in jeder Rechtsform-/Schreibvariante) enthält** —
> ohne auf die Blacklist zu warten. (Firmenweite `firma`-Blacklist-Einträge greifen
> inzwischen schreibweisen-robust über `firma_slug` inkl. Präfix-Match — diese
> Modell-Regel bleibt trotzdem als zusätzliche Sicherung bestehen.)
**Region: kommt aus dem Suchprofil des Benutzers, nicht aus diesem Skill.**
Der Prompt nennt entweder eine **Städteliste** (regionaler Modus) oder **100 % Remote /
deutschlandweit** — oder beides. Es gilt strikt:
- **Regionaler Modus:** **Nur** Stellen mit Arbeitsort in einer der **genannten** Städte.
Die **erste genannte Stadt ist der Wohnort** und wird am höchsten priorisiert, danach
in der angegebenen Reihenfolge. Orte **außerhalb** dieser Liste werden **verworfen**
auch das direkte Umland, und **auch reine Homeoffice-/Remote-Stellen ohne Sitz in einer
der genannten Städte**. Sitzt der Arbeitgeber in einer der Städte und bietet zusätzlich
Homeoffice, ist die Stelle zulässig.
- **Remote-Modus:** Nur Stellen, die **zu 100 % im Homeoffice** ausübbar sind
(deutschlandweit, Firmensitz egal). Präsenz, Hybrid mit festen Bürotagen und vages
„Homeoffice möglich" werden verworfen; die 100-%-Zusage live im Anzeigentext bestätigen.
Nennt der Prompt keine Region, **nachfragen** statt zu raten — niemals auf eine im Skill
hinterlegte Städteliste zurückfallen (die gibt es bewusst nicht mehr).
**Skills/Buzzwords** (für Query-Varianten & Relevanzbewertung): Die **Kernbegriffe kommen
aus dem Lebenslauf des jeweiligen Benutzers** (höchste Gewichtung); ergänze naheliegende,
dazu passende Technologien — als Suchbegriffe und zum Erkennen passender Anzeigen, auch
wenn sie nicht wörtlich im CV stehen.
> Auch die folgende Liste ist ein **Beispiel** (IT-Infrastruktur). Für andere Profile die
> Kernbegriffe aus **deren** Lebenslauf ziehen, nicht von hier.
- **Kern (Beispiel, aus einem Infrastruktur-CV):** Windows Server, Active Directory,
Exchange, Microsoft 365 / MS365, Linux-Administration (Debian/Ubuntu),
Docker/Container, Proxmox/Virtualisierung, Netzwerk (OPNsense, pfSense,
VPN: WireGuard/OpenVPN/IPsec, LAN/WAN, TCP/IP), Firewall, Monitoring
(Prometheus, Grafana, Beszel, Uptime Kuma), Hetzner/OVH Cloud, Self-Hosting,
Automatisierung, AI/Claude/Agent-Workflows.
- **Microsoft-/Windows-Umfeld:** Entra ID / Azure AD, Intune, Endpoint Manager,
Group Policy / GPO, WSUS, PowerShell, Hyper-V, SharePoint, Teams, Windows 10/11,
Client-Management, MDM.
- **Virtualisierung/Server:** VMware vSphere / ESXi, Hyper-V, Citrix, KVM,
Terminalserver / RDS, Server-Hardware (Dell/HPE/Lenovo).
- **Backup/Storage:** Veeam, Backup & Recovery, Datensicherung, NAS/SAN, TrueNAS,
ZFS, Ceph, Synology, QNAP.
- **Netzwerk/Security:** VLAN, Routing & Switching, Cisco, Fortinet/FortiGate,
Sophos, Ubiquiti/UniFi, DNS/DHCP, Reverse Proxy (Nginx, Traefik), Let's Encrypt,
IT-Security, Patch-Management, ISO 27001, IT-Grundschutz.
- **Cloud/DevOps/Automatisierung:** Microsoft Azure, AWS, Kubernetes, Ansible,
Terraform, GitLab / CI/CD, Bash-/Shell-Scripting, Portainer, Nextcloud.
- **Datenbanken/Web:** MySQL/MariaDB, PostgreSQL, MS SQL Server, Apache, Nginx, IIS.
- **Betrieb/Organisation:** Ticketsystem (Jira, OTRS, Zammad), Helpdesk,
Rechenzentrum, On-Premise, Managed Services, IT-Dienstleister, Systemhaus.
> Aktuelles Profil (Titel, Skills, Ort) steht in der Lebenslauf-Vorlage der API:
> `bewerbungs-tracker` → `GET /templates` (typ `Lebenslauf`). Bei Bedarf dort
> gegenlesen, statt zu raten.
## Quellen-Politik (WICHTIG)
Ziel ist **immer die Original-Stellenausschreibung direkt beim echten Arbeitgeber**
— bevorzugt auf dessen **Firmen-/Karriereseite**.
- **Ausschließen (nicht listen, nicht importieren):**
- **Headhunter / Personalvermittler / Personalberatungen** (recruiten für Dritte).
- **Zeitarbeit / Arbeitnehmerüberlassung / Personaldienstleister** (z. B. Randstad,
Hays, GULP, Amadeus FiRe, Adecco, Manpower, Piening, Tempton, DIS AG, Robert Half
u. ä. — auch unbekannte, wenn die Anzeige „Arbeitnehmerüberlassung",
„im Kundenauftrag", „für unseren Kunden", „Personaldienstleister" o. Ä. nennt).
- **Stepstone** als Quelle — nicht als Board nutzen, keine `site:stepstone.de`-Query,
keine Stepstone-Links importieren.
- **Erkennungsmerkmale eines Vermittlers/Zeitarbeit** (bei Unsicherheit die
Firmen-/Impressumsseite per `WebFetch` prüfen): Formulierungen wie „für unseren
Kunden", „im Auftrag unseres Mandanten", „Arbeitnehmerüberlassung", „Direktvermittlung",
„Personaldienstleistung", „Recruiting-Partner"; oder die inserierende Firma ist erkennbar
eine Personal-/Recruiting-Agentur. → **verwerfen**.
- **Zulässig als Quelle**, wenn es zur Original-Anzeige des Arbeitgebers führt:
Firmen-Karriereseite (bevorzugt), Arbeitsagentur/Bundesagentur für Arbeit, Indeed
oder LinkedIn **nur** wenn die Anzeige eindeutig vom echten Arbeitgeber (nicht von
einem Vermittler) stammt. Führt ein Board-Treffer zu einer Firma, deren eigene
Karriereseite dieselbe Stelle direkt listet, **immer den Firmen-Direktlink** nehmen.
## Workflow
1. **Bereits erfasste Jobs + Blacklist laden** (Dedup-Basis). Dieser Schritt ist
**immer zuerst** auszuführen, damit Stellen aus früheren Suchläufen **nicht
erneut** gefunden/importiert werden. Zwei Quellen:
```bash
scripts/applied-set.sh # firma_slug<TAB>firma<TAB>stelle<TAB>ort<TAB>quelle_url<TAB>external_id
scripts/blacklist-set.sh # id<TAB>typ<TAB>firma<TAB>stelle<TAB>ort<TAB>domain<TAB>url_norm<TAB>firma_norm<TAB>firma_slug<TAB>stelle_norm<TAB>ort_norm<TAB>grund
```
- **applied-set** (Bewerbungen **und** importierte Jobangebote): **eine Zeile
pro bereits erfasster Firma** (company-level, nicht mehr pro Stelle). Je Zeile
drei Dublett-Merkmale merken: **`firma_slug`** (robuster Firmen-Schlüssel:
lowercase, Umlaute→ae/oe/ue/ss, `(m/w/d)` raus, End-Rechtsform wie gmbh/ag/kg
entfernt, mit `-` verbunden), **`quelle_url`** und **`external_id`**.
- **blacklist-set** (gesperrte Muster): je Zeile den **`typ`** und die dazu
passenden Felder merken (`url_norm`, `domain`, `firma_norm`, **`firma_slug`**,
`stelle_norm`). Die `*_norm`/`firma_slug`-Felder liefert der Server bereits
normalisiert (lowercase, Tracking-Parameter entfernt, `(m/w/d)`/Rechtsform
bereinigt).
2. **Kandidaten sammeln — strukturierte Quelle zuerst, dann WebSearch.**
**(a) Arbeitsagentur-API (primär — frisch, hohe Trefferzahl, strukturiert).** Vor
der Freitext-Suche die Jobsuche-API der Bundesagentur für Arbeit abfragen. Sie
liefert frische, deduplizierbare Treffer mit Firma, Ort, Datum und stabiler
`refnr` — genau das, was reine WebSearch nicht zuverlässig hergibt. Pro Rolle einen
Umkreis-Query:
```bash
scripts/arbeitsagentur.sh search "<Rolle>" "<Stadt>" 15 30 50
# -> firma_slug<TAB>firma<TAB>titel<TAB>ort<TAB>datum<TAB>refnr<TAB>angebotsart
```
Rollen aus dem Suchprofil durchrotieren (Systemadministrator, IT-Administrator,
Fachinformatiker Systemintegration, IT-Support, IT-Techniker, System Engineer,
Netzwerkadministrator, …). Städte: **Gladbeck** deckt per Umkreis 1520 km
Bottrop/Gelsenkirchen/Essen/Oberhausen weitgehend mit ab; **Dorsten** und **Marl**
je separat (`wo=Dorsten`, `wo=Marl`, Umkreis ~10). `tage` (Default 30) steuert die
Frische. Achtung: der Umkreis zieht auch **Nachbarorte außerhalb der sieben
Städte** rein (z. B. Haltern, Recklinghausen, Herten) — die **harte Regionsregel**
(nur die sieben Städte, Schritt 3) über die `ort`-Spalte anwenden und Fremdorte
**verwerfen**. Jede verbleibende Zeile **sofort per `firma_slug`** gegen
applied-set/blacklist prüfen (Firmen-Dedup, Schritt 1) — bereits erfasste/gesperrte
Firmen raus. Für
**überlebende** Treffer die Details holen:
```bash
scripts/arbeitsagentur.sh detail <refnr>
```
Detail liefert **Volltext** (→ `beschreibung`), **`EXTERNE_URL`** (Original-Link des
Arbeitgebers → bevorzugt `quelle_url`), **Adresse**, **Vergütung** und die Flags
**`ZEITARBEIT_AUE` / `PRIVATE_ARBEITSVERMITTLUNG`** — steht dort `True`, ist es
Zeitarbeit/Vermittler → **verwerfen** (ohne WebFetch, spart einen Schritt). Meldet
das Detail „keine aktive Anzeige", ist sie abgelaufen → **verwerfen**. Die `refnr`
ist stabil → als Existenz-Nachweis und für die `external_id` nutzbar. Führt
`EXTERNE_URL` nur auf ein Board (nicht den Arbeitgeber), die Firmen-Karriereseite
suchen und deren Direktlink bevorzugen (Quellen-Politik gilt unverändert).
**(b) WebSearch (ergänzend — für alles, was nicht in der API steht).** Mit dem
`WebSearch`-Tool **viele** Queries fahren — Rolle × Ort für **jede der sieben
Städte**, mit Fokus auf **Original-Anzeigen der Arbeitgeber**. Nur diese sieben
Orte abfragen, keine weiteren Revierstädte.
> **Query-Strategie (Recall maximieren — WICHTIG, findet die Suche „kaum noch
> Stellen"):** Lieber **viele einfache, natürlichsprachliche Einzel-Queries** als
> wenige überladene. **Boolesche Operatoren sparsam einsetzen** — `OR`, `site:`,
> `inurl:` und `-minus`-Ausschlüsse **senken bei WebSearch oft drastisch die
> Trefferzahl**. Statt eine `A OR B OR C … -stepstone -zeitarbeit`-Monsterquery
> lieber **getrennte, kurze Queries** absetzen und Vermittler/Stepstone erst
> **nachträglich beim Filtern** (Schritt 3) rauswerfen. Pro Stadt **mehrere
> Formulierungen** durchvariieren:
> **Rolle × Ort × Suffix**, mit den Suffixen `Stellenangebot`, `Job`, `Jobs`,
> `Stelle`, `Vollzeit`, `Festanstellung`, `unbefristet`, `gesucht`, `m/w/d`, `2026`
> und **ganz ohne Suffix** (nur `Rolle Ort`). Zusätzlich **Buzzword × Ort**
> (s. u.). So deckt jede Stadt 1015 verschiedene Queries ab statt 12.
**Rolle × Ort (je Stadt mehrere Suffixe durchspielen), Beispiele:**
- `IT-Systemadministrator Gladbeck Stellenangebot`, `Systemadministrator Gladbeck`,
`IT-Administrator Gladbeck Vollzeit`, `IT Systemadministrator Gladbeck m/w/d`
- `Systemadministrator Essen Jobs`, `2nd Level Support Essen`,
`IT-Administrator Essen Festanstellung`, `IT Systemadministrator Essen gesucht`
- `Fachinformatiker Systemintegration Oberhausen`, `IT-Administrator Oberhausen Stelle`,
`Systemadministrator Oberhausen unbefristet`
- `IT Administrator Gelsenkirchen Stellenangebot`, `Systemadministrator Gelsenkirchen`,
`IT-Systemadministrator Gelsenkirchen 2026`, `IT-Support Gelsenkirchen`
- `Systemadministrator Bottrop Jobs`, `IT-Administrator Bottrop`,
`IT Systemadministrator Bottrop Vollzeit`, `EDV-Administrator Bottrop`
- `IT-Administrator Dorsten Stellenangebot`, `Systemadministrator Dorsten`,
`IT-Techniker Dorsten Job`
- `Systemadministrator Marl Stellenangebot`, `IT-Administrator Marl`,
`IT Systemadministrator Marl Vollzeit`, `IT-Techniker Marl Job`
- Weitere Titel je Stadt durchrotieren: `System Engineer <Ort>`,
`IT-Systembetreuer <Ort>`, `Cloud Administrator <Ort>`, `Linux Administrator <Ort>`,
`Netzwerkadministrator <Ort>`, `IT-Consultant <Ort>`, `IT-Support <Ort>`.
- **Buzzword × Ort** (holt Anzeigen, die im Titel anders heißen, inhaltlich aber
passen — s. Skills/Buzzwords oben): `Active Directory <Ort> Stellenangebot`,
`Microsoft 365 Administrator <Ort>`, `VMware <Ort> IT Job`, `Proxmox <Ort>`,
`Hyper-V Administrator <Ort>`, `Veeam <Ort> IT`, `PowerShell Administrator <Ort>`.
- **Deutsche Jobbörsen zusätzlich anzapfen** (breitere Abdeckung, jeweils als
eigene, einfache Query — nicht stapeln): `<Rolle> <Ort> meinestadt`,
`<Rolle> <Ort> kimeta`, `<Rolle> <Ort> yourfirm`, `<Rolle> <Ort> arbeitsagentur`,
`<Rolle> <Ort> indeed`. Falls doch ein `site:`-Filter, dann **einzeln**:
`site:arbeitsagentur.de Systemadministrator <Ort>`, `site:de.indeed.com
IT-Administrator <Ort>`, `site:de.linkedin.com/jobs Systemadministrator <Ort>`.
**Kein** `site:stepstone.de`. Von Board-Treffern stets zur Original-Anzeige des
Arbeitgebers durchklicken.
- **Firmen-Karriereseiten gezielt** (bevorzugt): `IT-Administrator Essen Karriere`,
`Systemadministrator Gelsenkirchen Karriere Stellenangebot`,
`IT Jobs <Ort> Karriere` — Vermittler/Stepstone später beim Filtern aussortieren,
nicht per `-minus` in der Query.
Für Details/Ort/Firma einer Trefferseite `WebFetch` nutzen. Sieht ein Treffer nach
Firmen-Karriereseite aus: dort direkt nach der Einzelanzeige suchen.
2b. **Firmen zuerst finden, dann deren Karriereseite prüfen** (Hauptquelle für
„unentdeckte" Stellen). Board-Suchen finden nur, was breit ausgeschrieben ist —
viele Arbeitgeber posten IT-Stellen **ausschließlich auf der eigenen Website**.
Deshalb parallel zur Rolle-×-Ort-Suche **lokale Arbeitgeber in den sieben Städten
identifizieren** und deren Karriereseite direkt öffnen:
- **Arbeitgeber recherchieren** je Stadt — Firmen, die typischerweise interne IT
oder IT-Dienstleistung haben: Systemhäuser/IT-Dienstleister, Industrie &
Mittelstand, Chemie/Logistik (Essen, Oberhausen, Gelsenkirchen), Stadtwerke &
kommunale Betriebe, Krankenhäuser/Kliniken, Hochschulen, Versicherungen/Banken,
größere Handels-/Handwerksbetriebe. Beispiel-Queries:
`größte Arbeitgeber Essen IT`, `Systemhaus Gelsenkirchen`,
`IT-Dienstleister Oberhausen`, `Unternehmen Gladbeck IT-Abteilung`,
`Stadtwerke Bottrop Karriere`, `Klinikum Dorsten Stellenangebote IT`,
`Chemiepark Marl IT`, `Stadtwerke Marl Karriere`.
- **Karriereseite direkt anfahren:** zu jeder gefundenen Firma
`WebSearch "<Firma> Karriere"` bzw. `"<Firma> Stellenangebote"` und die
Jobliste per `WebFetch` öffnen; auf offene **IT-/Administrator-/Support-Stellen**
am jeweiligen Standort prüfen. Auch `inurl:karriere`, `inurl:jobs`,
`inurl:stellen` gezielt einsetzen und gängige Bewerber-Portale erkennen
(z. B. `/karriere`, `jobs.<firma>.de`, softgarden/onlyfy/Personio/Workday-Seiten).
- **Buzzwords als Türöffner:** findet die reine Titel-Suche wenig, mit den
Technologie-Buzzwords (oben) über die Stadt suchen, z. B.
`VMware Essen Stellenangebot`, `Active Directory Gelsenkirchen Job`,
`Microsoft 365 Administrator Oberhausen`, `Linux Bottrop IT` — so tauchen
Anzeigen auf, die im Titel anders heißen, inhaltlich aber passen.
- Jede so gefundene Stelle läuft durch dieselben Prüf-/Filter-/Dedup-Schritte
(37). Der Firmen-Direktlink ist hier ohnehin schon die bevorzugte `quelle_url`.
3. **Filtern & bewerten:**
- **Region (harter Ausschluss):** **nur** die sieben Städte Gladbeck, Essen,
Gelsenkirchen, Oberhausen, Bottrop, Dorsten, Marl. Jede Stelle mit Arbeitsort außerhalb
dieser sieben Städte **verwerfen** — auch übrige Revierstädte und reines
Homeoffice/Remote ohne Sitz in einer der sieben Städte. **Nach Nähe zu Gladbeck
priorisieren** (Gladbeck > Bottrop/Gelsenkirchen > Essen/Oberhausen > Dorsten/Marl).
- **Rolle:** Systemadministration/IT-Infrastruktur; keine reinen Entwickler-,
Vertriebs- oder SAP-only-Stellen (außer sie passen klar zum Profil).
- **Quelle (harter Ausschluss):** Headhunter/Personalvermittler, Zeitarbeit/
Arbeitnehmerüberlassung und Stepstone werden **verworfen** — siehe
„Quellen-Politik". Nur Original-Anzeigen echter Arbeitgeber behalten.
- **Dedup (persistent, FIRMEN-basiert):** **Eine Firma darf nur EINMAL gefunden
werden.** Treffer **verwerfen**, sobald **eines** zutrifft: (a) gleiche/sehr
ähnliche `quelle_url`, (b) gleiche `external_id`, oder (c) **die Firma steht
schon im Applied-Set** — unabhängig vom Stellentitel. Firmen-Gleichheit über
`firma_slug` prüfen: Kandidaten-Firma ebenso sluggen (lowercase, Umlaute→
ae/oe/ue/ss, `(m/w/d)` & End-Rechtsform raus, mit `-` verbunden) und als
**dieselbe Firma** werten, wenn der Slug **gleich** ist ODER **ein Slug ein
führendes Bindestrich-Präfix des anderen** ist (mind. 2 Tokens) — so zählen
**verschiedene Schreibweisen/Rechtsform-/Namensvarianten derselben Firma als
eine** (z. B. „IT-Problemlöser GmbH" = „IT Problemlöser Verwaltungs- und Handels
GmbH"; „Stadtwerke Essen" = „Stadtwerke Essen Netz GmbH"). Zusätzlich gesunder
Menschenverstand: ist offensichtlich derselbe Arbeitgeber gemeint, als Dublette
verwerfen. Distinkte Firmen mit nur gleichem ersten Wort (z. B. „Meyer IT" vs.
„Meyer Logistik") sind **nicht** dieselbe Firma. So taucht kein Arbeitgeber aus
früheren Läufen erneut auf. Im Zweifel behalten und als „evtl. Dublette" markieren.
- **Blacklist (harter Ausschluss):** Kandidat **verwerfen** (nicht listen, nicht
importieren), sobald er auf einen Blacklist-Eintrag passt — je nach `typ`:
- `url` → normalisierte Kandidaten-URL == `url_norm`.
- `domain` → Host der Kandidaten-URL == `domain` (auch Subdomains).
- `firma` → **dieselbe Firma** wie der Eintrag (Kandidaten-`firma_slug` gleich
`firma_slug` **oder** Präfix-Match wie bei der Dedup; `firma_norm` nur als
Fallback für Alt-Einträge ohne Slug).
- `firma_stelle` → dieselbe Firma (`firma_slug`, wie oben) **und** `stelle_norm`
passen (ist `ort_norm` gesetzt, muss auch der Ort passen).
- `auto` → wie die konkreten Felder, die der Eintrag trägt.
Diese Muster sind bewusst blockiert (u. a. jeder früher importierte Job, siehe
Schritt 6) — geblacklistete Stellen daher **stillschweigend überspringen**,
nicht als „evtl. Dublette“ präsentieren.
- **Relevanz:** höher gewichten, je mehr Buzzwords aus dem Profil passen.
4. **Existenz prüfen, Link prüfen, Kontakt-E-Mail & Firmenadresse recherchieren** —
für jeden Treffer, der in die engere Wahl kommt, **zwingend einzeln**:
- **Existenz-Prüfung (Pflicht):** Jede Stelle mit `WebFetch` auf dem Einzel-Link
öffnen und bestätigen, dass die Anzeige **noch aktiv** ist (Firma + Stelle
stehen dort, kein 404/„Stelle nicht mehr verfügbar"/Redirect auf Jobliste).
Snippets aus der WebSearch reichen **nicht** — Suchindizes zeigen oft schon
abgelaufene Anzeigen. Kein Live-Nachweis → Treffer **verwerfen** (nicht listen,
nicht importieren). Bei bereits importierten Angeboten, die nicht mehr existieren,
das JobOffer löschen (`DELETE /joboffers/{id}`).
- **Vollständige Stellenausschreibung erfassen (Pflicht):** Beim `WebFetch` der
Einzelanzeige den **kompletten Ausschreibungstext** übernehmen — nicht nur eine
Kurzzusammenfassung. Dazu gehören: Einleitung/Unternehmensvorstellung, **Aufgaben/
Tätigkeiten**, **Anforderungen/Profil**, **Wir bieten/Benefits**, Angaben zu
Arbeitszeit/Vertragsart/Standort, Gehalt (falls genannt), Bewerbungsweg/Kontakt
und Referenz-/Kennziffer. Text weitgehend **wortgetreu und vollständig** sichern
(nur Navigations-/Cookie-/Footer-Boilerplate der Seite weglassen), inkl. der
Gliederung/Überschriften. Dieser Volltext wandert in `beschreibung` (Schritt 6).
Ist die Seite lang/abgeschnitten, `WebFetch` gezielt erneut aufrufen, um alle
Abschnitte zu bekommen.
- **`quelle_url` verifizieren:** Muss auf die **konkrete, noch aktive
Einzelanzeige** zeigen (nicht Trefferliste/Suchseite). Deeplink defekt/Liste →
per Suche den echten Einzel-Link finden (bevorzugt Firmenwebsite/Karriereseite),
sonst verwerfen.
- **Vermittler/Zeitarbeit aussortieren (Pflicht):** Zeigt der Treffer auf eine
Personalberatung/Headhunter/Zeitarbeit statt den echten Arbeitgeber, **verwerfen**
(nicht listen, nicht importieren). Bei Unsicherheit die inserierende Firma per
`WebFetch` (Impressum/„Über uns") prüfen — Recruiting-/Personaldienstleister raus.
Existiert dieselbe Stelle direkt auf der Firmen-Karriereseite, diese
Original-Anzeige verwenden statt des Vermittler-Treffers.
- **Bewerber-Kontakt-E-Mail recherchieren:** Die E-Mail-Adresse ermitteln, an die
sich Bewerber wenden. Reihenfolge: (a) direkt in der Stellenanzeige genannte
Bewerbungs-/Kontaktadresse; (b) Karriere-/Kontaktseite der Firma
(`WebSearch` „<Firma> Karriere Kontakt Bewerbung E-Mail“, `WebFetch` der Seite);
(c) allgemeine Bewerbungsadresse (`bewerbung@`/`jobs@`/`karriere@<domain>`) nur,
wenn auf der Firmenseite belegt. **Nicht raten** — nur Adressen übernehmen, die
belegt sind; sonst `kontakt_email` leer lassen und als „nicht gefunden“ markieren.
- **Firmenadresse recherchieren (Pflicht):** Die **genaue Anschrift** des
Arbeitgebers ermitteln (Straße, Hausnummer, PLZ, Ort) — und zwar die, die
**exakt zu dieser Stelle passt**. Reihenfolge: (a) in der Stellenanzeige selbst
genannter Einsatz-/Arbeitsort (oft unter „Standort"/„Einsatzort"/am Anzeigenende);
(b) Impressum bzw. Kontakt-/Standortseite der Firma
(`WebSearch` „<Firma> Impressum Adresse <Ort>“, `WebFetch` der Seite).
⚠️ **Adresse muss zur Stelle passen:** Hat die Firma **mehrere Standorte/
Niederlassungen**, die Anschrift des **konkreten Arbeitsorts der Stelle** nehmen —
**nicht** blind die Zentrale/Hauptsitz-Adresse, wenn die Stelle an einem anderen
Standort ausgeschrieben ist. Nennt die Anzeige nur eine Stadt (`ort`), aber keine
Straße, gezielt die zu **dieser Stadt** gehörende Niederlassungsadresse suchen und
verifizieren. **Nicht raten** — nur eine belegte, zum Stellen-Ort passende Adresse
übernehmen; sonst `adresse` leer lassen und als „nicht gefunden“ markieren.
Passt die einzige auffindbare Firmenadresse **nicht** zum `ort` der Stelle
(anderer Standort), lieber leer lassen als eine falsche Zentrale eintragen.
5. **Ergebnis präsentieren** als Tabelle: Firma · Stelle · Ort · **Adresse** ·
Quelle (Board) · **verifizierter Link** · **Kontakt-E-Mail** · kurze
Passt-Begründung. Sortierung: neue, klar passende Treffer **nach Nähe zu Gladbeck**
(Gladbeck zuerst, dann Bottrop/Gelsenkirchen, dann Essen/Oberhausen, dann Dorsten/Marl).
Fehlt Link, E-Mail oder Adresse, kennzeichnen.
6. **Importieren** — im **interaktiven** Modus nur nach Rückfrage/Bestätigung des
Nutzers; im **autonomen Modus** (siehe unten) automatisch ohne Rückfrage. Jeden
neuen Treffer als JobOffer einspielen (Upsert, idempotent) über den
`bewerbungs-tracker`-Skill:
```bash
~/.claude/skills/bewerbungs-tracker/scripts/bt.sh POST /joboffers '{
"external_id": "stellensuche-<slug(firma)>-<slug(stelle)>",
// IMMER setzen und DETERMINISTISCH aus
// firma+stelle bilden — NICHT aus der
// Board-/Anzeigen-ID. Slug = normalisiert
// wie der firma_slug (lowercase, ohne (m/w/d)
// & Rechtsform, Interpunktion→"-"), z. B.
// "stellensuche-musterfirma-it-systemadministrator".
// So bekommt DIESELBE Stelle über jeden
// Lauf UND jedes Board dasselbe external_id
// → Server-Upsert greift zuverlässig statt
// eine Dublette anzulegen. (Eine flüchtige
// Board-ID würde je Quelle abweichen und so
// Duplikate erzeugen — daher NICHT nutzen.)
"quelle": "stellensuche",
"firma": "…", "stelle": "…", "ort": "…",
"adresse": "<genaue Anschrift des Arbeitsorts der Stelle: Straße Hausnr, PLZ Ort;
recherchiert & verifiziert, passend zum Stellen-Standort; sonst weglassen>",
"gehalt": "…",
"beschreibung": "<VOLLSTÄNDIGE Stellenausschreibung>",
// Der komplette, in Schritt 4 per WebFetch
// erfasste Anzeigentext: Aufgaben, Anforderungen/
// Profil, Wir-bieten/Benefits, Arbeitszeit/
// Vertragsart, Standort, Gehalt, Bewerbungsweg,
// Referenz-/Kennziffer — wortgetreu und
// vollständig, nicht kürzen/zusammenfassen.
// Gliederung/Überschriften erhalten (Markdown
// ok). Nur Seiten-Boilerplate weglassen.
"quelle_url": "<verifizierter Einzel-Link, bevorzugt Firmen-Karriereseite>",
"art": "Firmenwebsite",
"anzeige_datum": "YYYY-MM-DD",
"kontakt_email": "<recherchierte Bewerber-E-Mail, sonst weglassen>",
"labels": ["Regional"] // IMMER setzen: dieser Skill sucht ortsgebunden in
// den sieben Städten → Arbeitsort-Label "Regional".
}'
```
`art`-Enum: `E-Mail`, `Online-Portal`, `Indeed`, `StepStone`, `Firmenwebsite`,
`Post`, `Initiativbewerbung`, `Arbeitsagentur`, `Sonstiges`. **Standard ist
`Firmenwebsite`** (Original-Anzeige des Arbeitgebers). `StepStone` nie verwenden
(ausgeschlossene Quelle); `Arbeitsagentur`/`Indeed` nur, wenn der Link direkt zur
Arbeitgeber-Anzeige führt.
**`409`-Antwort beim Import:** Steht der Job (trotz Vorfilter) auf der Blacklist,
antwortet `POST /joboffers` mit **409** (`BlacklistConflict`) und nimmt ihn
**nicht** auf. Das ist **kein Fehler** — als „übersprungen (Blacklist)" zählen
und mit dem nächsten Treffer weitermachen, **nicht** mit `force` erzwingen.
7. **Direkt blacklisten (nach jedem erfolgreichen Import).** Sobald ein JobOffer
angelegt wurde (Antwort **201** / `action:created`; bei `updated` ist der Job
bereits erfasst), die **Firma sofort ganz** blacklisten, damit dieser Arbeitgeber
in künftigen Läufen **mit keinem Stellentitel** wieder auftaucht (Regel „eine Firma
nur einmal"). Per `typ: firma` sperren — der Server matcht firmenweit über
`firma_slug` und deckt so abweichende Schreibweisen/Rechtsformen mit ab:
```bash
~/.claude/skills/bewerbungs-tracker/scripts/bt.sh POST /joboffers/blacklist '{
"typ": "firma",
"firma": "<firma wie importiert>",
"grund": "auto: stellensuche-import <YYYY-MM-DD>"
}'
```
Server-Antwort **201** = geblacklistet. Normalisierung (Groß/Klein, `(m/w/d)`,
Rechtsform, Umlaute) übernimmt der Server. Nur nach **echtem** Neuimport
blacklisten — nicht bei einem 409-Skip (steht ja schon drauf) und nicht bei
bloßem `updated`.
## Autonomer Modus (headless / geplant)
Wird der Skill **nicht-interaktiv** ausgeführt — d. h. ohne Nutzer, der bestätigen
kann (z. B. `claude -p "…" --dangerously-skip-permissions`, Cron/geplanter Lauf,
`ollama launch claude … -p …`) — im **autonomen Modus** arbeiten:
- **Ohne Rückfrage importieren:** Neue, geprüfte Treffer direkt als JobOffer
einspielen (Schritt 6) **und anschließend blacklisten** (Schritt 7). Es gibt
niemanden zum Bestätigen — nicht auf Eingabe warten.
- **Alle Prüfregeln gelten unverändert und strikt:** Existenz-Prüfung (Schritt 4),
korrekter verifizierter Einzel-Link, Kontakt-E-Mail nur wenn belegt, **Firmenadresse
nur wenn belegt und zum Stellen-Ort passend** (Schritt 4), die
persistente Dedup **und der Blacklist-Filter** (Schritt 1/3) — importiere **nur**
echte, live verifizierte, noch nicht erfasste und **nicht geblacklistete** Stellen.
Im Zweifel **nicht** importieren (lieber auslassen).
- **Keine Bewerbungen anlegen/versenden**, keine Löschungen bestehender Bewerbungen,
keine Generierung anstoßen — nur neue Jobangebote (`POST /joboffers`) einpflegen
und den jeweils importierten Job blacklisten (`POST /joboffers/blacklist`).
- **Kurzbericht ausgeben** (für das Log): je Treffer `action`
(created+blacklisted / updated / skip-dedup / skip-blacklist) mit Firma, Stelle,
Ort, Link; am Ende Zähler „X neu importiert & geblacklistet, Y als Dublette/
Blacklist übersprungen, Z verworfen (nicht verifizierbar)".
## Regeln
- **Nur sieben Städte.** Es werden **ausschließlich** Stellen mit Arbeitsort in
Gladbeck, Essen, Gelsenkirchen, Oberhausen, Bottrop, Dorsten oder Marl gelistet/importiert.
Alles außerhalb dieser sieben Städte (übriges Ruhrgebiet, reines Remote/Homeoffice ohne
Sitz in einer der sieben Städte) wird **verworfen**. Priorität nach Nähe zu Gladbeck.
- **Label `Regional` setzen (Pflicht).** Jedes von diesem Skill importierte JobOffer
bekommt `labels: ["Regional"]` (ortsgebundene Stelle in einer der sieben Städte).
Kein Remote-/Homeoffice-Label — solche Stellen sind Sache des Skills
`it-stellensuche-remote`.
- **Keine erfundenen Stellen.** Nur Jobs ausgeben, die per WebSearch/WebFetch real
belegt sind. **`quelle_url` muss der korrekte, geprüfte Direktlink zur
Einzelanzeige sein** (per `WebFetch` bestätigt) — keine Such-/Listenseiten, keine
geratenen URLs. Kein verifizierbarer Einzel-Link → Treffer nicht listen/importieren.
- **Nur Original-Anzeigen echter Arbeitgeber.** Headhunter/Personalvermittler,
Zeitarbeit/Arbeitnehmerüberlassung und **Stepstone** sind ausgeschlossen — weder
listen noch importieren (siehe „Quellen-Politik"). Bevorzugt der Firmen-Direktlink;
im Zweifel (Vermittler? Zeitarbeit?) die inserierende Firma prüfen und lieber
verwerfen.
- **`beschreibung` = vollständige Ausschreibung.** Immer den kompletten, per WebFetch
erfassten Anzeigentext (Aufgaben, Anforderungen, Benefits, Konditionen, Bewerbungsweg,
Kennziffer) übernehmen — nicht kürzen oder zusammenfassen. Inhalt aus der realen
Anzeige, nicht frei ergänzen.
- **Kontakt-E-Mail nicht erfinden.** `kontakt_email` nur setzen, wenn die Adresse in
der Anzeige oder auf der Firmen-Karriereseite belegt ist; sonst weglassen.
- **Firmenadresse recherchieren, nicht erfinden — und passend zur Stelle.** `adresse`
(Straße, Hausnummer, PLZ, Ort) im Web ermitteln und nur eine **belegte** Anschrift
eintragen, die **genau zum Arbeitsort der Stelle** gehört. Bei mehreren Standorten
die Niederlassung der ausgeschriebenen Stelle nehmen, **nicht** pauschal den
Hauptsitz. Passt keine verifizierte Adresse zum Stellen-Ort, `adresse` leer lassen
statt eine falsche Zentrale einzutragen.
- **Keine Doppelfunde — eine Firma nur EINMAL, auch über Läufe hinweg.** Vor dem
Listen/Importieren immer `applied-set.sh` **und** `blacklist-set.sh` laden und
jeden Kandidaten per `quelle_url`, `external_id` **und `firma_slug` (firmenweit)**
sowie gegen die Blacklist abgleichen. **Steht die Firma schon im Applied-Set oder
auf der Blacklist — egal unter welchem Stellentitel und egal in welcher
Schreibweise/Rechtsform (`firma_slug` gleich ODER Präfix-Match, mind. 2 Tokens) —
wird kein weiterer Treffer dieses Arbeitgebers ausgegeben oder importiert.** Auch
**innerhalb eines Laufs** jede Firma nur **einmal** präsentieren (bei mehreren
Stellen derselben Firma die am besten passende wählen). Beim Import die
`external_id` **deterministisch aus `firma+stelle`** bilden, **nicht** aus einer
Board-/Anzeigen-ID — so ergibt dieselbe Stelle über jeden Lauf/jedes Board dasselbe
`external_id` (Server-Upsert statt Dublette). Der Server dedupliziert
`POST /joboffers` über `external_id` (Upsert) und die Blacklist (409, firmenweit
über `firma_slug`).
- **Blacklisten nach jedem Import (Pflicht, firmenweit).** Jeder frisch angelegte Job
(`action:created`) wird direkt anschließend per `POST /joboffers/blacklist`
(**`typ:firma`** — die ganze Firma) gesperrt (Schritt 7), damit dieser Arbeitgeber
in Folgeläufen mit keinem Titel wieder auftaucht. Nicht blacklisten bei `updated`
oder bei einem 409-Skip. Ein 409 beim `POST /joboffers` bedeutet „steht schon auf
der Blacklist" → überspringen, nie mit `force` erzwingen.
- **Interaktiv: nicht ungefragt importieren** — erst zeigen, dann auf Bestätigung
importieren. **Autonom/headless: automatisch importieren** (siehe „Autonomer
Modus"). In **keinem** Modus Bewerbungen anlegen/versenden.
- Board-Aggregatoren-Duplikate (dieselbe Stelle auf mehreren Portalen) zu einem
Eintrag zusammenfassen, bevorzugt mit Direkt-/Firmenwebsite-Link.
- Ist die Bewerbungs-Tracker-API nicht erreichbar (`GET /health`), Dedup nicht
möglich → Nutzer warnen und Treffer ohne Dedup mit Hinweis liefern.
## Abhängigkeiten
- Tools: `WebSearch`, `WebFetch`.
- Skill `bewerbungs-tracker` (für Dedup-Daten, JobOffer-Import und Blacklist:
`GET/POST /joboffers/blacklist`).
- Scripts: `scripts/arbeitsagentur.sh` (strukturierte Jobsuche-API der Bundesagentur
für Arbeit — primäre Quelle, `search`/`detail`), `scripts/applied-set.sh` (erfasste
Firmen), `scripts/blacklist-set.sh` (gesperrte Muster). `applied-set`/`blacklist-set`
nutzen den `bt.sh`-Helfer des `bewerbungs-tracker`-Skills; `arbeitsagentur.sh` nutzt
nur `curl` + `python3` (öffentliche API, kein Key nötig).