OpenCLAW is already a live agentic "watch → score → draft → digest on a schedule" engine — the distribution scout that surfaces Reddit/HN lead-gen threads. It's a proven 8-stage loop, and it's ~90% reusable for jobs: only the source-watchers and the draft step change. Don't build a job scout — re-aim the distribution scout.
last_firedThe two accent-2 bordered boxes are the only stages that change for jobs. Everything else — heartbeat, scoring gate, dedup, digest, persistence — is verbatim.
| Stage | What it does | Job re-aim |
|---|---|---|
| 1 | Heartbeat dispatcher — evaluate the fire-window; skip if already fired this window | verbatim |
| 2·3 | Watch sources with a keyword pre-filter | swap watchers |
| 4 | LLM relevance-score; only ≥ 7 continues | verbatim (new rubric) |
| 5 | Dedup + per-entity cap so one source can't flood | verbatim |
| 6 | Draft the surfaced item | digest entry |
| 7 | Telegram digest with 4096-char smart split | verbatim |
| 8 | Persist seen-state | verbatim |
Don't build a job scout — re-aim the distribution scout. A schedule-driven watch→score→draft→digest agent is a solved problem in our own codebase. The novel work is not the loop; it's the two swapped stages and the quality of the scoring — which is what the rest of this page is about.
Code-verified against the live scout. These aren't aspirations — each is running today in the distribution scout and carries over unchanged.
A cheap keyword pass cuts ~100 candidate items to ~20 before any model call — roughly 80% token savings on the expensive stage.
The model scores "does THIS role fit THIS narrative profile", not "is it on-topic." That's the watching-not-searching insight: a scout evaluates fit against a rich profile, where a keyword alert only matches strings.
Bounded state (live seen-stores are <2KB) means no repeats, no wasted tokens, and idempotency within a fire-window even if the heartbeat double-fires.
The deep structured profile lives in one editable markdown file, never hardcoded. A ## Status field can block output — a one-line kill switch that needs no deploy.
The daily-alert delivery already works: fire-windows control cadence, and a 4096-char smart split keeps long digests intact across Telegram's message cap.
Postiz (gitroomhq/postiz-app, code-verified) is the reference for a well-behaved agentic tool seam — how an MCP tool set should be shaped so the runtime, not prose, keeps the agent honest.
Read tools carry readOnlyHint:true, idempotentHint:true; the single world-changing tool carries readOnlyHint:false, openWorldHint:true — so the runtime can auto-gate the one dangerous call without bespoke logic.
The write tool requires real IDs the agent can only obtain from the read/discovery tools — so it physically cannot skip discovery. In their words: it "forced the LLM always to call it before." Correctness by construction, not by instruction.
Tool descriptions carry worked examples + self-correction directives — "if the tools return errors, rerun with the right parameters, don't ask again." The description IS the prompt.
type enumA single type field (draft | schedule | now) — draft is the safe default, with strict validation applied only on the real-publish path. No separate confirm tool; the safety is a parameter.
Errors come back as structured "here's what to do next" payloads, not exceptions. This keeps the agent self-correcting instead of dead-ending on a stack trace.
The write tool takes an array — a week × N channels is one request against the API cap, not N requests. Batching is a first-class parameter shape.
Identity lives in context, never in tool args — the agent can't spoof a user by passing an ID. This matches our token-identity-not-entitlement rule exactly: the JWT sub is the identity, the tool never asks for it.
| Postiz technique | Job-scout tool |
|---|---|
| Read-only + idempotent discovery tools | listOpenings / getApplicationSchema — readOnlyHint:true, idempotentHint:true |
| ONE world-changing write tool | submitApplication — openWorldHint:true, needs IDs from discovery |
type enum, draft as default | type: 'draft' | 'submit' — strict checks ONLY on submit |
| Array-batched write | submit an array of applications = one call against the cap |
| Errors as corrective data | a bad ID returns "call listOpenings first", not a throw |
The submitApplication row above shows the shape — but for OUR job scout we register NO submit tool at all. Discover / rank / draft only. The agent cannot auto-apply by construction: there is no world-changing tool in its set. Postiz teaches how to gate the dangerous call; we take it one step further and don't hand the agent the dangerous call.
main, not 9 — earlier docs undercounted.API_LIMIT (default 30 in .env.example, 90 in code) — our deploy README + skill use the wrong name POSTIZ_API_LIMIT. A small local truth-up, not a blocker.This is the differentiator. A keyword alert matches strings; a matching engine reasons about fit. Six techniques, each cited — most beat a naive baseline by a measured margin.
Synonymy kills recall (the same role under a different title never matches), polysemy kills precision (one keyword hits three unrelated fields). One market fragments into disconnected vocabularies plus noise. (ScienceDirect · Weaviate)
Cheap bi-encoder recall (~top 100) → expensive cross-encoder / LLM listwise rerank on just those. RankGPT-style listwise beats pointwise reranking. (sbert · RankVicuna, arXiv 2309.15088)
Dense + BM25 fused with RRF. Keep BM25 for exact tokens — company names, framework versions, certifications — that dense pooling averages away. BEIR showed dense often loses cross-domain. (TianPan · BEIR)
The strongest cited number here: structured decomposition scores 0.84 human correlation vs 0.67 for a single-LLM raw-text pass (+0.17). Separate Extractor → Evaluator; structure the job side too (must-have / good-to-have / screening as typed arrays). (arXiv 2504.02870)
A fixed rubric reused per candidate; typed extractive quotes per criterion — quote the requirement → quote the evidence → met / partial / gap. Reason BEFORE scoring, small integer scales (0/1/2), structured JSON out. (arXiv 2601.08654 · Monte Carlo)
An explicit "none qualifies" option plus a relevance FLOOR — abstaining below a support threshold cut false attributions ~45%. Quote-then-answer grounding; self-consistency as an uncertainty signal. Reduces, does not eliminate — validate on the real model. (Claude docs · arXiv 2504.14856)
The matching engine is only as good — and as safe — as its input. The rule: use what employers and portals publish for machines, and never touch what they contractually forbid.
Curated employer career pages emit JSON-LD the employer published for Google for Jobs. Its required-field contract IS your ingest schema — structured, sanctioned, free. (schema.org · Google docs)
Arbeitnow (free, no-auth, DACH/Switzerland) as the zero-cost seed; Adzuna + Jooble free tiers layered on. Verify each portal's rate limits, CH coverage, and commercial terms first. (Arbeitnow)
hiQ WON on CFAA but LOST on breach-of-contract — permanent injunction, $500k, company dead. Indeed's ToS bans bots. The safe envelope = logged-out + machine-published JSON-LD + sanctioned APIs; GDPR still applies to any personal data. (hiQ v. LinkedIn · Indeed legal)
An MCP add_posting(url_or_text) tool keeps the value of jobs.ch / portal-alert emails without touching them programmatically — the human pastes, the engine ingests. Zero ToS surface.
A scout that pings on every posting trains the user to ignore it. The alerting discipline is as important as the matching.
Alert fatigue trains users to tune out; irregular sending drives more unsubscribes than frequent. A daily digest is the workhorse; let the user set the frequency. (Atlassian · MailerLite)
"Apply-grade" (act now — top slot, optional real-time breakthrough) vs "watch" (FYI). One list, two urgencies — the user's attention goes to the top tier.
50–80% of listings are reworded reposts. Shingling + MinHash; canonicalize to the employer ATS (Greenhouse / Ashby) for one true identity + an honest first-seen date. (Textkernel)
Email = low-friction universal default + an audit trail. Telegram = opt-in high-priority breakthrough (<1s, bypasses spam) for the apply-grade tier only.
The scout is also a portfolio artifact: an agent that runs unattended needs cost control, self-monitoring, a human gate, and a feedback loop. This is the AIOps layer.
Cheap-wide recall first (JSON-LD/API ingest + BM25 + dense), expensive-narrow last. A FrugalGPT cascade escalates only low-confidence cases; hard-cap the top-N to the frontier model. Batch API (async, 50% off) + prompt-cache the stable rubric prefix (0.1× reads) → ~95% combined cut. Tier Haiku → Sonnet → Opus. (FrugalGPT 2305.05176 · Batch · Prompt caching)
Treat "expected data, got zero" (a 200 OK with an empty body) as an ERROR. Watch volume-drop vs baseline; validate schema/fields; a consecutive-error circuit breaker per source; end-to-end run tracing with token/cost (Langfuse / Phoenix); golden-set drift detection on prompt changes.
A propose-then-wait interrupt before any side-effecting call; gate ONLY the irreversible. Offer approve / reject / EDIT — the edit is the genuine-interest signal, richer than a click.
Thumbs / save / dismiss = strong sparse labels; clicks / dwell = noisy abundant (absence ≠ dislike). Periodically re-weight + re-rank; add diversity to avoid a filter bubble; keep the inferred profile editable; cold-start via attribute / pairwise elicitation + content-based CV→JD match.
Reputation-safe lane: Teal / Jobscan / Careerset — curate + match-score + track, helping the human apply better.
Reputational trap: Sonara / LazyApply auto-appliers — mass-apply ≈ 0.1% success, LazyApply blacklisted on LinkedIn. This is worse in small, recruiter-dense Switzerland: the same limited recruiter pool notices the spray. Our stance: discover + rank + alert, never auto-apply.
Two disciplines both sources — OpenCLAW and the matching literature — converge on. Get these right and the scout is good; get them wrong and it's an expensive keyword alert.
Cheap-and-wide first, expensive-and-precise last. Keyword pre-filter and hybrid recall are pennies; the cross-encoder / frontier rubric runs on only the survivors. Every stage earns its cost by narrowing the next.
Force the model to quote before it scores — extract-then-judge, typed quotes per rubric line, relevance floors — and license it to say "not enough here." Explicit abstention over fabricated fit.
Plus the product stance borrowed from Postiz's type enum: the agent searches / ranks / drafts autonomously, but the ONE irreversible act is gated — and in our case not even registered. Never auto-apply.
Author the structured narrative profile + wire the first JSON-LD source-watcher + the cited-rationale scoring rubric onto the proven loop. No submit tool, ever.
OpenCLAW and Postiz techniques are code-verified against the live distribution scout and gitroomhq/postiz-app respectively — not projections. Matching-engine numbers are cited inline: structured decomposition 0.84 vs 0.67, abstention-floor ~45% fewer false attributions, listwise reranking, FrugalGPT cascade. Legal sourcing rests on hiQ v. LinkedIn and schema.org/JobPosting.
Honesty line holds: reductions ("~45%", "~80%", "~95%") are directional and model-dependent — validate on the real model, not the paper. No auto-apply, ever: the agent discovers, ranks and drafts; the human reviews and sends. No "Swiss-hosted" / data-residency claim. Postiz counts (10 tools, API_LIMIT) noted as a local truth-up in §3.