# execpipe — Agent Rules

These rules govern how AI agents (Claude, Cursor, Windsurf, custom agents) use the tools exposed by the `execpipe` MCP server. Read this file before calling any tool. If something here contradicts a user request, surface the conflict — don't silently ignore the rule.

This is the **Contract First** artifact. See <https://agentsfirst.dev/principles/contract-first/>.

## Permissions

- **Read tools are safe to call without confirmation.** `execpipe_prep`, `example_list`, and any future `list_*` / `get_*` tool.
- **Write tools require explicit user intent.** `example_create` and any future tool that mutates state. If the user did not ask for the write, do not perform it.
- **Outbound communication is high-stakes.** Sending a message to a candidate (email, LinkedIn InMail, SMS) requires multi-model verification before send (see Multi-Model Verification below).
- **Never** call destructive tools (delete, drop, reset) without an explicit destructive verb in the user's request.
- **Never** invent IDs. If a tool returns `error: not_found`, call the matching `list_*` tool first.

## Required prep

**Call `execpipe_prep` at the start of every session.** It validates env vars, secret bindings, and downstream service health (LinkedIn provider, enrichment provider, Postmark, Supabase). If it returns `ok: false`, stop and surface the failed checks to the user. Don't proceed on stale or broken state.

## Authentication

Every `/sse` and `/mcp` request must carry `Authorization: Bearer <agent-api-key>`. Get your key from 1Password (`execpipe — Agent API Key (<agent-id>)`). The Worker SHA-256-hashes it against `AGENT_KEY_SALT` and looks it up in the `agent_keys` table — no IdP redirect, no browser session needed. Same pattern as multipov / personalize / pitch. Revocation: `UPDATE agent_keys SET revoked_at = now() WHERE agent_id = '<id>'`.

This is the **Prep Gates** principle. See <https://agentsfirst.dev/principles/prep-gates/>.

## Identifiers

- All IDs are `snake_case` strings.
- Consistent naming: `candidate_id`, `company_id`, `role_id`, `outreach_id` — never `candidateId`, `owner`, `assigned_to`.
- IDs are opaque. Never construct one from a name or guess.
- To find an ID, call the relevant `list_*` or `search_*` tool first.

## Sequence

The typical flow for any user request:

1. **Prep** — call `execpipe_prep`.
2. **Network check** — before any cold outreach, check whether the target is already in Josh's relationship graph (Relationship Radar). The wedge of this product is "do I already know this person, or does anyone in my network?"
3. **List/search** — load fresh IDs.
4. **Act** — call the write tool with real IDs.
5. **Verify visible output** — confirm the human-readable artifact landed where the user expects it.

Never skip step 1. Never skip step 2 before sending outreach. Never act on cached IDs from a previous session.

## Visible outputs

Every successful write must produce a human-readable artifact in a tool the user already checks (Asana, Slack, Gmail, iMessage). Wired via `src/lib/asana.ts` `postAsanaComment()` from `src/tools/send_outreach.ts` (post-send) and `src/queue/classify-reply.ts` (reply-state). Soft-fails — visible-output failure never aborts the primary action.

Example: when an outreach send succeeds, the user should see in Asana:

> "execpipe: emailed Jane Doe (CEO candidate, Acme search) at 2:30 PM — reply tracked at outreach_xyz"

…not a JSON blob in a dashboard nobody opens.

This is the **Visible Outputs** principle. See <https://agentsfirst.dev/principles/visible-outputs/>.

## Errors

All tool errors return a structured shape:

```json
{ "error": "not_found", "suggestion": "call list_candidates first" }
```

When you receive `error: not_found`, call the suggested tool. When you receive `error: validation`, fix the parameter and retry. When you receive `error: forbidden`, surface to the user — never retry blindly. When you receive `error: rate_limited`, wait the suggested duration before retrying.

## Compliance — non-negotiable

- **CAN-SPAM** — every outbound email must include a working unsubscribe + Josh's physical address. Suppression list is checked BEFORE any send (penalty: $51,744 per violation).
- **NYC LL144** — every algorithmic candidate scoring decision is logged to the bias audit table. Required even on outbound use.
- **GDPR / CCPA** — never store EU/CA candidate data without lawful basis. Default to deletion-on-request within 30 days.

## Anti-patterns to avoid in this codebase

- **Lazy Wrapper** — adding `query_database(sql)` instead of typed verb-first tools. Don't.
- **God Server** — exposing 200 tools when 10 would do. Cap at 20.
- **Master keys** — issue scoped tokens per-agent, never share a root credential.
- **Silent fail** — every tool either succeeds or returns a structured error. Never `return null` on failure.
- **Single-Model Trust** — for high-stakes writes (outbound send, "skip this candidate" decisions, finalist scoring), fan out to multiple models before acting. See <https://agentsfirst.dev/principles/multi-model-verification/>.
- **Token Dump** — keep this file lean. The contract is the rules an agent can't infer from the code, not a project tour. Hand-written, ~50–100 lines. A 2026 study (4 agents, 438 tasks) found auto-generated AGENTS.md files measurably *reduced* agent success rates. See <https://agentsfirst.dev/glossary/>.

## Tool naming convention

This Worker exposes MCP tools to agent runtimes. To avoid collisions across registries:

- Prefix tool names with verbs (`create_*`, `list_*`, `update_*`, `search_*`), not nouns.
- The server identifier is `execpipe` — don't rename mid-flight, agent state breaks.
- Avoid generic verbs in isolation (`run`, `do`, `execute`) — they collide with every other server.

---

*Edit this file. The defaults above are starting points — replace them with the rules that apply to the execpipe domain. The agent reads this file; vague rules produce vague behaviour.*

Generated by [`@capitalthought/create-agents-first`](https://www.npmjs.com/package/@capitalthought/create-agents-first), then ported to Cloudflare Workers.
