Turn a recruiter message plus Graham's approved resume/claim context into a drafted, humanized reply — webgpt writes, webkimi humanizes — with optional brave-search company research as a seed. Claim-bound (never mints facts) and human-transmitted only (never sends). Stores correspondence in Memory. Use when a recruiter emails or messages about a role and Graham wants a tailored reply drafted with comprehensive context. Composed by monitor-opportunities.
Scanned 9/23/2026
Install to Claude Code
npx -y skills add grahama1970/agent-skills --skill ops-recruiter --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Ops Recruiter?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/grahama1970-ops-recruiter)More formats (shields.io, HTML) on the badges page.
---
name: ops-recruiter
description: >
Turn a recruiter message plus Graham's approved resume/claim context into a
drafted, humanized reply — webgpt writes, webkimi humanizes — with optional
brave-search company research as a seed. Claim-bound (never mints facts) and
human-transmitted only (never sends). Stores correspondence in Memory. Use when
a recruiter emails or messages about a role and Graham wants a tailored reply
drafted with comprehensive context. Composed by monitor-opportunities.
triggers:
- draft recruiter reply
- respond to recruiter
- recruiter response
- reply to this recruiter message
- ops-recruiter
provides:
- recruiter-reply-draft
- recruiter-correspondence-memory
---
# ops-recruiter
## Immutable goal
> Given a recruiter message, the target role, and Graham's approved resume/claim
> context, produce a clear, humanized, claim-bound reply DRAFT — never fabricating
> facts, never sending anything. The human always transmits.
This skill composes existing skills; it does not reimplement browser transport,
LLM routing, or mailbox access.
## Pipeline
```
recruiter message + role text + approved resume/claims
recall recruiter_correspondence memory -> prior thread + relationship flag
research $brave-search -> company/role AND recruiter (LinkedIn page + history) seed (cited, degradable)
build context packet (incl. prior thread) + claim ledger + browser-prompt preflight
gate claim-bind check: every factual assertion maps to an approved claim
draft $ask webgpt -> comprehensive first draft
humanize $ask webkimi -> clarity + humanized-prose edit of that draft
T0 gate run.sh gate -> deterministic token floor (fail-closed)
T1 review PROJECT AGENT -> semantic claim-bind verdict vs ledger
on FAIL: structured findings -> webgpt redraft -> webkimi -> T0 -> T1
(<=3 rounds, each carrying a specific correction; else fail closed to human)
store $memory /store -> recruiter_correspondence collection
return DRAFT to the human. No send. Ever.
```
`run.sh` owns steps `build`, `gate`, and `store`. The `draft`/`humanize` steps
are `$ask` (webgpt then webkimi); `run.sh build` emits the exact preflighted
`$ask` commands so the composition boundary stays in `$ask`.
## Load-bearing contract (this is the skill, not the two model calls)
- **Claim-bound, never fact-minting — two-tier, independently reviewed.** The
fact ledger is the approved `career_profile` claim snapshot or `RESUME.md`.
webgpt/webkimi may reword, order, tighten, and choose emphasis; they may not
invent employers, titles, dates, metrics, clearances, or a capability claim the
ledger does not back. Two gates, in order:
- **T0 deterministic (`run.sh gate`)** — numeric/fact-token floor; fails closed
on any metric/date absent from the ledger. Cheap, automated, in the e2e.
- **T1 semantic claim-bind — the PROJECT AGENT is the reviewer of record.** The
creator (webgpt) and humanizer (webkimi) never certify their own draft. The
project agent reads the produced draft against the ledger and issues a
per-claim verdict (each factual sentence traces to a claim, or the draft is
rejected), fail-closed, before the draft is handed to the human to send. This
is the same boundary as everywhere else: a model seat's PASS is only
evidence; the project agent verifies against local evidence before acceptance.
A self-certifying model seat is not a substitute for the project-agent review.
- **Bounded course-correction loop on T1 FAIL.** If the project-agent review
rejects a claim as unsupported, it does not stop and it does not blindly
retry. It emits a structured finding (offending sentence, the unsupported
claim, and what the ledger actually supports) and feeds that exact correction
back to webgpt (redraft) -> webkimi (rehumanize) -> T0 -> T1 again. A retry
that does not carry a specific correction is spray-and-pray and is forbidden.
Cap at 3 rounds; on exhaustion, fail closed and hand the draft plus surviving
findings to the human. Never send, never loop unbounded.
- **Relationship-aware, never re-introduces.** `build` recalls prior
`recruiter_correspondence` for the recruiter/thread and sets a relationship
flag (`existing`/`new`/`unknown`). An existing thread must not be written as
first contact; if recall is unavailable the opening stays relationship-neutral
rather than falsely claiming first contact.
- **Human-transmitted only.** Draft-and-return. No email send, no LinkedIn
action. Mirrors monitor-opportunities `who transmits = the human`.
- **`$brave-search` is a seed, not authority.** Covers both company/role context
and recruiter deep-research (their LinkedIn page and background/history) so the
reply is informed by who is actually reaching out. Cited, and honestly
degradable to "no research" without failing the draft.
## Correspondence memory
ALL correspondence with a recruiter or hiring contact — every channel, no
exceptions — persists in the single `recruiter_correspondence` Memory collection
via `$memory` `/store` (never raw AQL, never inline vector arrays). `source` is an
OPEN vocabulary: known values below, but the store never rejects a new channel,
so email, LinkedIn, call/interview transcripts, SMS, referral intros, ATS
messages, and manual notes all land on the same thread. One document per
message with deterministic `_key` (`<source>:<thread-id>:<message-id>`), fields:
`direction` (`inbound`/`draft`/`sent`/`meeting`), `source`
(`gmail`/`linkedin`/`manual`/`google-meet`/`live-evidence`), `recruiter`,
`company`, `role`, `body`, `role_ref`, `claim_keys[]`, `draft_ref`,
`received_at`, `tags`, `schema` (`ops_recruiter.correspondence.v1`). Optional
(webgpt-reviewed, graph-enabling): `reply_to_message_id` (exact predecessor —
enables causal `message_replies_to` edges; never inferred from timestamps),
`occurred_at` (canonical event time for draft/sent/meeting), `recruiting_company`
+ `employer_company` (normalized; preferred over parsing `company`). Optional but
VALIDATED `signals` block with closed enums (fail-closed): `disposition`
(`PURSUE|DEFER|DECLINE_LOW_RATE|DECLINE_PUSHY|DECLINE_OFF_MANDATE|NEEDS_HUMAN`),
`rate` (`ABOVE_FLOOR|AT_FLOOR|BELOW_FLOOR|RATE_UNKNOWN`), `tone`
(`NORMAL|PERSISTENT|PUSHY|UNKNOWN`) — so recall can filter
`/list {"filters":{"signals.disposition":"PURSUE"}}` and mine threads without
parsing prose. A call/interview transcript is stored as
`direction=meeting` on the same thread. Thread continuity fetches this thread
exactly via Memory `/list` filters; cross-thread semantic `$memory recall`
lights up once the memory repo registers `recruiter_correspondence` in
`builtin_sources()` (memory-repo change; ops-recruiter must not wire views or AQL).
## Ingestion boundaries (read before wiring any source)
- **Email → `/gmail`, NOT `$ops-google`.** `$ops-google` is the Gemini API
budget skill and has nothing to do with mailbox access. Graham's
`graham@grahama.co` inbox is reached only through the `/gmail` capability (the
same one `mailbox-mining` delegates to). ops-recruiter consumes gmail messages
that `/gmail` has already fetched; it does not talk to Gmail directly.
- **Meeting/call transcripts → `$ops-google-meet` / `$live-evidence`.**
ops-recruiter does not record or transcribe. It consumes transcripts those
skills already produced (Google Meet prep/companion and the live interview
copilot) and stores them on the recruiter thread as `direction=meeting`,
`source=google-meet` or `live-evidence`, so a call becomes part of the same
thread history and future context.
- **LinkedIn research is delegated to `$ops-linkedin`.** ops-recruiter does not
drive LinkedIn itself. `$ops-linkedin` (lead-gen lane) owns the authorized
read-only recruiter/company-page research — a `$surf` headed tab in Graham's
own session, human-supervised, after `--accept-account-risk`. Engagement
(connecting, messaging/InMail, posting, commenting, background automation,
bulk scraping) stays forbidden there. Login-free recruiter/company research
still prefers `$brave-search`. ops-recruiter consumes the captured evidence
into `recruiter_correspondence`.
## Commands
```bash
./run.sh status --json
./run.sh build --recruiter-message MSG.md --role ROLE.md --resume RESUME.md \
[--recruiter R --thread-id T] [--research] [--out DIR] # recalls prior thread; packet + $ask commands
./run.sh gate --draft DRAFT.md --claims CLAIMS.json # fail-closed claim-bind check
./run.sh store --message MSG.json # write one correspondence doc to Memory
./sanity.sh
```
## Compliance
- `$best-practices-skills`: this frontmatter, progressive disclosure (pipeline
detail in `references/pipeline.md`), `sanity.sh`, retained `$agentic-evals`
fixture asserting the claim-bind gate rejects an unbacked assertion.
- `$best-practices-readme`: README voice, with an explicit non-claims section:
ops-recruiter does not send email, does not touch LinkedIn, and does not mint facts.
## Related skills
- `$ask` (webgpt draft, webkimi humanize) — composition transport.
- `$memory` — recruiter_correspondence collection.
- `$brave-search` — optional research seed.
- `/gmail` — email ingestion. `$ops-linkedin` — read-only LinkedIn evidence.
- `$monitor-opportunities` — composes this skill into the morning report.
## References
- `references/pipeline.md` — packet format, claim ledger, and the exact ask chain.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!