> ## Documentation Index
> Fetch the complete documentation index at: https://docs.oneshotagent.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Finders and scheduling

> Choose discovery signals, review readiness, and schedule bounded runs.

**Finders** discover prospects, ICP-filter them, and enqueue into `/queue` for one-click approve or reject. Each runs as a trigger with its own interval and spend cap.

| Finder              | Signal                                                                                                                                                                                                                                                                                                                                             |
| ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `show-hn`           | same-day Show HN posts, via the HN Algolia API                                                                                                                                                                                                                                                                                                     |
| `post-funding-auto` | funding announcements by ICP-derived industry × round                                                                                                                                                                                                                                                                                              |
| `accelerator-batch` | new cohorts — yc-oss directory, websearch fallback for Techstars / Antler / 500 / AI Grant                                                                                                                                                                                                                                                         |
| `job-change`        | `joined as <persona>` announcements, filtered by persona and company                                                                                                                                                                                                                                                                               |
| `hiring-signal`     | open roles on Greenhouse / Lever / Workable / Ashby implying a need                                                                                                                                                                                                                                                                                |
| `podcast-guest`     | recent guests across Latent Space, Lenny's, 20VC, Acquired, Invest Like the Best                                                                                                                                                                                                                                                                   |
| `github-topics`     | repos by topic, then a manifest scan (`package.json`, `pyproject.toml`, `requirements.txt`) that detects the vendor stack deterministically — needs `GITHUB_TOKEN`                                                                                                                                                                                 |
| `github-stars`      | recent stargazers of repos you watch; tag each repo `competitor` or `adjacent` to route the play — needs `GITHUB_TOKEN`                                                                                                                                                                                                                            |
| `luma-events`       | upcoming events from Luma's own city pages, gated per event by a topic + ICP check before any spend; pitches the hosts and featured guests Luma exposes publicly                                                                                                                                                                                   |
| `gov-solicitation`  | SAM.gov Sources Sought / Presolicitation notices by NAICS code — pitches the notice's own published point of contact, no `findEmail`/`verifyEmail` spend at all — needs `SAM_GOV_API_KEY`                                                                                                                                                          |
| `civic-agenda`      | city/county council agenda items via the Legistar/Granicus Web API, keyword-gated free before any paid call; pitches the meeting body's own published contact                                                                                                                                                                                      |
| `breakup-revive`    | your own ledger — prospects cold for 60–90 days. No LLM or OneShot spend                                                                                                                                                                                                                                                                           |
| `x-reposters`       | people who repost/quote X accounts you watch, in two lanes: builders who'd adopt (founder lane → email cadence) and dev accounts with reach who'd boost a launch (amplifier lane → one-touch email, or a hand-sent DM draft when no email is found) — needs X API OAuth1 keys, or `TWITTERAPI_IO_KEY` for the \~55x cheaper engine                 |
| `local-business`    | main-street businesses via `peopleSearch`/`companySearch` (job title × industry × location × company size) — routed to the `free-pilot` play; a candidate carrying `best_work_email` skips `findEmail`/`verifyEmail` entirely, but `qualifyPostEnrich` may still perform paid `enrichProfile` lookups when `fillGaps` is enabled                   |
| `local-registry`    | newly-licensed/enumerated main-street businesses over free, keyless public registries (Socrata business licenses, NPPES NPI, FMCSA Company Census, Socrata health inspections) — recent matches route to `new-business`, older ones to `free-pilot`; fmcsa carries a published email on the record, so it skips `findEmail`/`verifyEmail` entirely |

Only `show-hn` and `post-funding-auto` are on by default; enable the rest from `/queue`. A trigger missing required config reads as **not ready** — the toggle and Run button disable with the reason, and the API returns `409`, so scripted callers can't bypass the gate either.

Per-run caps (`maxCostUsd` on a finder, `maxSpendPerRun` on x-reposters) bound one call; they don't stop independently-scheduled finders and automatic drains from collectively overspending across a day. `config spend-ceiling <amount>` (or the Wallet card on `/setup`) sets an install-wide daily USD ceiling, checked before every automated finder run and drain — a reservation held for the call's duration closes the race between two concurrent automated paths, so they can't both slip under the ceiling before either one's spend has posted. Once reached, scheduled/run-now finders and drains halt with a named reason (`daily spend ceiling reached ($X.XX/$Y.YY spent today)`) visible on the trigger cards and in `doctor`; manual `/queue` sends (approve, reject, mark-sent, send-draft) are never gated by it — a founder reviewing and sending one email by hand is a deliberate decision the ceiling should never block. The counter resets at local midnight, same boundary the per-identity send caps use. Unset (the default) is unlimited, matching the historical behavior.

Qualified first-touch rows receive a product dossier before the trigger completes: up to two known first-party pages plus quick external research covering the product, ecosystem, architecture, and business model. Set `productResearch: false` on a trigger to disable it. Research counts toward that trigger's `maxCostUsd`; a failure or exhausted cap leaves the row reviewable with an explicit warning. `find research-products` backfills the same context onto active/replied prospects and pending queue rows (`--dry-run`, `--limit`, and `--refresh` are supported). Use `--first-party-only` for a resilient bulk backfill when the external research provider is unavailable.

Before any paid `findEmail`, a prescreen skips dud domains (`*.vercel.app`, social hosts, link aggregators, personal email providers) and inputs whose "name" is obviously a username. LinkedIn URLs are captured on every finder path and verified to belong to the person before they're stored.

Two ICP gates run per candidate, not one. The **topic gate** judges the source — the repo, event, or announcement — and keeps whole categories of noise out before any spend. The **person gate** judges the human's role, staged by cost: free role text the finder already holds (an event bio, an extracted title), then the job title off the enrichment every verified email already pays for, then — only when still ambiguous and a LinkedIn URL exists — one extra \~\$0.005 lookup. It judges capability to build and self-adopt, not job-title seniority: students shipping hackathon projects and consultants building agent systems for clients pass; a Marketing Manager at a brilliant AI company doesn't. Only a *positive* reject drops a candidate — ambiguity escalates or proceeds, never silently discards. Rejections land in `/queue` as auditable `auto: role — <reason>` rows you can override, count as `role-drop` on trigger cards, and a prospect judged off-ICP after contact stops receiving cadence follow-ups (terminal status `off-icp`).

The dashboard server runs an in-process scheduler, so enabling a trigger is enough — no separate daemon. `find watch` stays useful for cron and headless boxes. Approved rows ship via the **Drain** button or `find drain <play>`.

### Background monitoring as a service

`find watch --install-service` generates a service file that keeps the watch daemon running in the background — a launchd user agent on macOS, a systemd user unit on Linux. It prints to stdout (redirect-friendly); add `--write` to drop it at the platform-conventional path. Every path is embedded absolute at generation time — the bun binary, the CLI entry, and the active `ONESHOT_GTM_HOME` (so `--workspace acme find watch --install-service` pins the service to that workspace) — because service managers don't source your shell profile. Regenerate with `--write` after moving bun or the checkout.

**macOS (launchd).** Logs go to `<home>/find-watch.log`; the agent restarts on crash and survives reboots.

```bash theme={null}
oneshot-gtm find watch --install-service            # inspect the plist
oneshot-gtm find watch --install-service --write    # → ~/Library/LaunchAgents/com.oneshot-gtm.find-watch.plist
launchctl load ~/Library/LaunchAgents/com.oneshot-gtm.find-watch.plist

# uninstall
launchctl unload ~/Library/LaunchAgents/com.oneshot-gtm.find-watch.plist
rm ~/Library/LaunchAgents/com.oneshot-gtm.find-watch.plist
```

**Linux (systemd user unit).** Logs go to the user journal: `journalctl --user -u oneshot-gtm-find-watch`.

```bash theme={null}
oneshot-gtm find watch --install-service --write    # → ~/.config/systemd/user/oneshot-gtm-find-watch.service
systemctl --user daemon-reload
systemctl --user enable --now oneshot-gtm-find-watch

# uninstall
systemctl --user disable --now oneshot-gtm-find-watch
rm ~/.config/systemd/user/oneshot-gtm-find-watch.service
```

On a headless box, also run `loginctl enable-linger $USER` once so the user unit starts at boot rather than at first login.

**Windows (Task Scheduler).** There's no user-service template; schedule the cron-style `find watch --once` instead, which runs all due triggers and exits:

```powershell theme={null}
$watchAction = New-ScheduledTaskAction -Execute 'C:\Users\you\.bun\bin\bun.exe' `
  -Argument '"C:\path\to\oneshot-gtm\apps\cli\src\main.ts" find watch --once --quiet'
$watchTrigger = New-ScheduledTaskTrigger -Once -At (Get-Date).AddMinutes(1) `
  -RepetitionInterval (New-TimeSpan -Minutes 15)
Register-ScheduledTask -TaskName 'oneshot-gtm find watch' -Action $watchAction -Trigger $watchTrigger

# uninstall
Unregister-ScheduledTask -TaskName 'oneshot-gtm find watch' -Confirm:$false
```

The classic cron route works on POSIX hosts; Windows users should use Task Scheduler: `*/15 * * * * ONESHOT_GTM_HOME=$HOME/.oneshot-gtm /path/to/bun /path/to/apps/cli/src/main.ts find watch --once --quiet`.

**Exit codes for scheduled runs.** `find watch --once` exits `1` when a due trigger errored. Add `--fail-on-empty` and a run that worked but produced nothing exits `2` instead of `0`, with one line on stderr naming the triggers and the zero count — enough for a cron wrapper to tell a dry run from a productive one without reading the ledger. `find drain <play>` takes the same flag. Both are opt-in: without it, exit codes are exactly what they were.

|       | `find watch --once`                                | `find drain <play>`                                     |
| ----- | -------------------------------------------------- | ------------------------------------------------------- |
| **0** | candidates queued, or `--fail-on-empty` not passed | rows drained, or `--fail-on-empty` not passed           |
| **1** | a due trigger errored (with or without the flag)   | with the flag: a row errored, or the drain itself threw |
| **2** | with the flag: ran clean, queued nothing           | with the flag: no approved rows to drain                |

Errors win over emptiness: a run that both errored and produced nothing exits `1`, not `2`. "Queued nothing" counts candidates that reached the queue, not raw hits scanned — a poll whose every hit was a duplicate or off-ICP left the ledger untouched and reads as empty. `--fail-on-empty` needs `--once`; on the daemon (which never ends on its own) it's rejected rather than ignored.

```bash theme={null}
oneshot-gtm find watch --once --quiet --fail-on-empty
case $? in
  0) ;;                                  # candidates queued
  2) echo "nothing found this tick" ;;   # idle, not broken
  *) echo "watch failed" >&2 ;;
esac
```
