Drafts the offer in PandaDoc, blocks on the hiring manager's approval in Slack, then emails the candidate their e-signature link. Every call runs as the recruiter who triggered it, never a shared HR bot.
This repo uses a RECRUITER_USER env var (with per-connector overrides) to simulate one recruiter. In production, pass each recruiter's real ID as the identifier on every Scalekit call, and send them an authorization link whenever their connector status is not ACTIVE.
_EMAIL_RE = re.compile(r"^[^@\s]+@[^@\s]+\.[^@\s]+$")
def validate_offer_request(raw: dict) -> OfferRequest:
errors = []
email = (raw.get("candidate_email") or "").strip()
if not _EMAIL_RE.match(email):
errors.append(f"candidate_email is not a valid email address: {email!r}")
base_salary = (raw.get("base_salary") or "").strip()
if not _SALARY_RE.match(base_salary.replace("k", "000").replace("K", "000")):
errors.append(
f"base_salary must be a plain number like '180000' or '$180,000': {base_salary!r}"
)
start_date = (raw.get("start_date") or "").strip()
parsed = _parse_date(start_date)
if parsed is None:
errors.append("start_date must be a valid date in YYYY-MM-DD format")
elif parsed <= date.today():
errors.append(f"start_date must be in the future: {start_date}")
if errors:
raise ValidationError("; ".join(errors))
return OfferRequest(
candidate_email=email,
base_salary=_normalize_salary(base_salary),
start_date=start_date,
...
)Four things you'd otherwise build: PandaDoc OAuth, Slack auth, Gmail tokens, refresh. Handled.
Clone github.com/scalekit-developers/workflow-agents-demos/tree/main/offer-letter-routing-agent and adapt it for my hiring stack.
I am building for: my recruiting team (or: an ATS product) ← edit this line
Connectors my users need: PandaDoc, Slack, Slack MCP, Gmail ← edit this line
Trigger: run once per offer — CLI, form, or ATS webhook. Deliver: e-signature link emailed to the candidate after manager approval.
Steps:
1. Create Scalekit AgentKit connections named pandadocmcp, slack, slackmcp, gmail. The code resolves connections by name and passes connection_name on every execute_tool call.
2. Map identifier to each recruiter's real user ID (not a shared HR bot). Per-connector *_USER overrides exist if identities differ per service.
3. If I swap a connector, mirror an existing connectors/*.py module: execute_tool(tool_name, identifier, connection_name, tool_input) → read result.data.
4. Keep the approval gate: approval_gate.wait_for_approval polls slackmcp_slack_get_reactions until ✅/❌ or timeout. REQUIRE_APPROVAL=false falls back to notify-only.
5. Point PANDADOC_TEMPLATE_UUID at our reviewed offer template (tokens: candidate_name, role_title, base_salary, start_date, company_name; recipient role defaults to "Client").
6. Run python run_flow.py with one test offer, then describe how to deploy for many recruiters.
Each one runs on delegated identity, scoped per user.