Operator Dossier · Setup Guide

DHURANDHAR

“Dhurandhar” — one who bears the burden; an expert, relied on to carry the operation through. Everything below is the operator's version — install steps, every flag and its default, the exact legal read before enabling either channel.

CHANNELS: LinkedIn + Email LINKEDIN CONNECT: disabled OPERATOR: you, end to end
🔒 Operator access

This page is gated

Field names, defaults, and commands for running Dhurandhar directly. If you're meant to have this, you already have the passphrase.

Install, configure, and run both channels — LinkedIn discovery and email send — gated by flags you control end to end.

LINKEDIN_CONNECT_ENABLED = False LLM: Ollama primary, paid fallback Email: plain SMTP, self-owned mailbox
01

Install

A real, ordered sequence — each step depends on the one before it. Windows/PowerShell, run from the project root.

python -m venv .venv
.\.venv\Scripts\Activate.ps1
python -m pip install -U pip
pip install -r requirements\local.txt
python -m playwright install chromium
python manage.py migrate

The LinkedIn browser driver installs straight from GitHub, not PyPI — the import name stays the same either way, so nothing downstream needs to know which source it came from.

02

LLM routing

One model drives search-keyword generation, lead qualification, and email drafting — set once, editable in Admin or at first-run onboarding.

Point it at a self-hosted Ollama model to run qualification for free, on your own hardware — a LAN host is tried first, a tunnelled remote host second, so the daemon keeps working off-network. An optional second, paid model only engages as a fallback: when the primary is genuinely unreachable, not when it just answers badly.

FieldWherePurpose
ai_modelSite configPrimary model, e.g. ollama:qwen2.5:latest
llm_api_keySite configPrimary provider key (Ollama accepts any non-empty string)
llm_fallback_modelSite configSecond model, e.g. openai:gpt-4o. Blank = no fallback.
OLLAMA_BASE_URLScore configOrdered list of Ollama hosts — LAN, then tunnel
03

Two channels, one funnel

A qualified lead forks onto exactly one of two channels. Each has its own setup, its own credentials, and its own risk profile.

LinkedIn

Discovery channel

Login, People-search, and profile scraping run through a stealthed browser session against a real account — this automates a real LinkedIn account and is against its User Agreement; read Legal & risk before enabling it.

FieldDefaultMeaning
username / passwordReal LinkedIn login; session cookies are saved after the first login
legal_acceptedfalseMust be true before the browser session runs
connect_daily_limit20Max connection requests/day — inert unless connect is enabled
follow_up_daily_limit25Max LinkedIn follow-ups/day — inert unless connect is enabled
Email

Send channel

Plain SMTP, from a mailbox the operator owns — no third-party sending platform in between. A lead only forks here once a work email resolves and a mailbox exists to send from.

FieldDefaultMeaning
host / portsmtp.gmail.com : 587SMTP send
imap_host / portimap.gmail.com : 993Reply-read, for the follow-up agent
username / passwordGoogle account + app password (2-Step Verification required)
daily_limit30Warm-safe sends/day, enforced per mailbox
04

Coordination — LinkedIn feeds Email

One qualification funnel, two entry points, both landing in the same place. A lead that resolves a work email forks straight to email. A lead that doesn't sits at the LinkedIn boundary instead — and goes no further while connect stays off.

QUALIFIED email resolves no email found READY_TO_EMAIL held for review READY_TO_CONNECT stops here — connect off human clears draft SENT IMAP reply Follow-up agent unattended, no review gate
Only the first email of a thread is review-gated. Once a reply exists, the follow-up agent keeps the conversation moving on its own.

Two complete setups, depending on how much of the pipeline should be live:

Email-fed leads only (LinkedIn stays find-only) — arm the campaign, confirm a mailbox exists, leave connect off. Every lead that doesn't resolve an email simply stops at the LinkedIn boundary; that's the entire effect of leaving connect off.

Both entry points live — everything above, plus flipping the global LinkedIn-connect switch on. That's a real decision on real ToS exposure (see Legal & risk), not reduced by anything else on this page, and it re-arms connect for every armed campaign at once, not just the one intended.

05

Campaigns

A campaign is the unit everything else hangs off — what's being offered, who to target, and which channels it's allowed to use.

FieldTypeMeaning
product_docstextWhat's being offered — fed into every agent prompt
campaign_objectivetextWho to target and why — drives search, qualification, drafting
booking_linkURLIncluded in follow-ups when suggesting a meeting
userslinkWhich operator account(s) run it — unlinked = fully inert
06

Safety flags

Four switches, two scopes. A global switch keeps LinkedIn connect off by policy; a per-campaign switch is the real activate/deactivate control — stage a campaign fully, arm it only when ready.

LINKEDIN_CONNECT_ENABLED global

False by default, meant to stay that way — search, scrape, and qualify still run; a connection request is simply never sent.

campaign.coordination_enabled per-campaign

The activate switch. Off (default) — the campaign is fully dormant: no discovery, no email, no follow-up, checked before anything else.

campaign.require_opener_review per-campaign

On by default — a first send waits for a human to clear the draft. Never gates a follow-up reply once a thread exists.

campaign.email_only per-campaign

Blocks LinkedIn discovery/connect for one campaign only, independent of every other flag — for leads sourced outside LinkedIn entirely.

before flipping the global connect switch on

It re-arms connect for every armed, non-email_only campaign at once — not just the one intended. Check each campaign's flags first.

07

Running it

One process, one loop.

python manage.py rundaemon

On startup: migrate → onboard, if unconfigured → validate → the task-queue loop. The loop plans discovery/qualify/connect/email slots per active campaign and works through them at a human-paced rhythm — bursts of 45–65 minutes, breaks of 10–20 — reconciling on every idle cycle. Everything the daemon is doing is browsable and editable from the admin console.