Skip to main content
The lead engine is the non-engineering archetype: an outbound research agent that turns raw signups and prospect lists into researched dossiers and ready-to-send outreach. A lead arrives by webhook — from a signup form, a CRM automation, or a curl — and the agent researches the person and company from public sources, scores the fit honestly, drafts a short opening email and a follow-up bump, and posts the whole package to #sales for a human to approve. Feedback in the thread routes back to the same session for revisions. The human always pulls the trigger on sending; the agent’s hard limit is that it never contacts a prospect. It is also the cleanest demonstration that auto agents are general agents: this one has no repository mount at all.

How it works

  1. A lead POSTs in. Any system that can send JSON hits the endpoint’s ingest URL with a bearer token and a body like { "event": "lead.created", "name": ..., "email": ..., "company": ... }. The body’s event field becomes the event key webhook.lead.created, which matches the agent’s webhook trigger and spawns a session.
  2. The agent researches. From its sandbox it reads the company’s website, docs, careers page, changelog, and the person’s public professional presence, then builds a cited dossier and scores the fit — strong, moderate, or weak, with “weak fit, recommend skip” as a first-class outcome.
  3. It drafts, in your voice. One opening email under 120 words with a hook taken from the dossier, plus a shorter follow-up bump. Claims a human should verify get flagged.
  4. The package lands in Slack. One top-level line in #sales (name, company, source, score), the full package threaded beneath it, and the agent subscribes to the thread.
  5. Humans disposition in-thread. “Make it shorter and mention the SOC2 page” is delivered back into the same session, which revises in place. Sending, always, is a human’s move.

The configuration

Two files — and no mount, because nothing here touches code.
.auto/fragments/environments/agent-runtime.yaml
.auto/agents/lead-researcher.yaml
The example assumes a Slack connection named slack.

Walkthrough

The webhook trigger declares its own endpoint

Custom webhook triggers replace connection: with endpoint: plus an auth: block. On apply, auto reserves the endpoint and returns a receipt with its globally unique ingest URL — POST <base>/api/v1/webhook-endpoints/<slug>/events — which an agent can look up later with the auto.webhooks.get tool. Auth is one of three kinds: bearer_token (caller sends Authorization: Bearer <secret>), hmac_sha256 (caller signs the raw body into x-auto-signature-256), or none. Both authenticated kinds resolve secretRef against the project’s secret store. See cron and webhooks and secrets. Create the secret before the trigger applies: a project secret named lead-webhook-secret, set from the web app’s Secrets settings so the value never touches the repository. Secrets are write-only by default — no surface returns the plaintext back.

The event key comes from the body

A webhook body must be a JSON object. Its top-level event string becomes the event key with a webhook. prefix — "event": "lead.created" routes as webhook.lead.created. A body with no event field routes under the fixed fallback key webhook.received; if your lead source cannot shape its payload, author the trigger on event: webhook.received (optionally with fallback: true as a catch-all) and discriminate with where clauses on the raw body instead. An optional top-level dedupKey string deduplicates redeliveries — same key, no second session; without it every delivery is a fresh event. The endpoint answers 202 with { eventRecordId, created: true } on first delivery and 200 with created: false on a dedup hit.

The payload is whatever you POST

Unlike provider events, webhook payloads are not normalized — the raw request body is the payload. That means the initialPrompt template reads your fields directly: {{name}}, {{email}}, {{company}}, {{source}}, {{notes}}. Missing fields render as empty strings, so a sparse lead still spawns cleanly. If you want routing-level filtering — say, only leads from the demo-request form — add a where clause on your own fields:

Attributions split fresh mentions from thread feedback

The two chat triggers cover the same chat.message.mentioned event with opposing filters on $.auto.attributions, the payload field linking a thread to the sessions already working it: exists: false spawns a conversational session for a fresh mention (someone pasting lead details directly in Slack), while exists: true delivers into the attributed session — the one that posted the package and subscribed via auto.chat.subscribe. This pairing is enforced at apply time, as is the $.auto.authored: false filter on the deliver arm. The result is the revision loop: every reply in a lead’s thread reaches the exact session that owns that lead. See triggers.

Guardrails live at three layers

  • Structural: no mount, and only two tools — the auto platform tools and Slack chat. There is no email tool, no CRM write, no repository. The agent cannot contact a prospect because nothing it can call sends email.
  • Prompt: the hard-limits paragraph makes non-contact an explicit standing order, and confines lead details to the #sales thread.
  • Process: output is a recommendation (send / revise / skip), and disposition is a human replying in the thread.
When you do want the agent writing to external systems — logging dossiers to a CRM — add the CRM’s MCP server as a mcp_remote tool and extend the prompt deliberately; see tools.

Coming Soon reference

Lead Researcher is Coming Soon and is not currently installable. This page is a design reference until the role’s catalog disposition changes.

Adapt it

  • Replace slack and #sales, and map the webhook payload to your real lead source’s fields — the five in initialPrompt are an example shape, not a contract.
  • Encode your actual ICP and messaging guidelines in the system prompt. That is what makes drafts sound like your company instead of a mail merge.
  • Keep honest scoring. An outbound engine that can say “skip” is the one sales teams learn to trust.
  • For batch prospecting, add a kind: heartbeat trigger that processes a queue (a CRM view, a spreadsheet) on a daily cadence — see the research loop for heartbeat mechanics.
  • Lead details are personal data: keep the prompt’s rule that they appear nowhere except the approval surface.

Try it

Merge the PR and let GitHub Sync apply the resources, then copy the ingest URL from the apply receipt (from a session, the auto.webhooks.get tool returns it too) and POST a smoke lead:
Expect a 202 with created: true, a fresh lead-researcher session in the web app, and a dossier-plus-drafts thread in #sales. Reply in the thread with a revision request and confirm the same session posts the revision. POST the same body again and confirm the dedupKey suppresses a second session.