Open AI SDR – An Open Source Version of the Artisans Ava BDR Developer Utpal Nadiger built an open-source AI BDR agent called Open Ava, inspired by Artisan's Ava, using OpenComputer, FastAPI, SQLite, AgentMail, and Anthropic. The agent researches leads, drafts outreach, and requires human approval before sending emails, with a checkpoint for rollback. The code is available on GitHub for others to replicate the system. Build an Ava-Inspired BDR Agent That Runs on Its Own Computer Written by Utpal Nadiger · I kept seeing Artisan’s “Stop hiring humans” https://www.artisan.co/blog/stop-hiring-humans billboards around San Francisco. The moral debate aside, the product does look cool and could be helpful: Ava https://www.artisan.co/ai-sales-agent is Artisan’s AI BDR, built to source leads, write outbound, handle replies, and book meetings. Being the engineer I am, I decided to build it myself so I could use it at OpenComputer and do outreach for us. I was trying to replicate a BDR in software, and a BDR has state. It needs a CRM, an inbox, notes, reply history, suppression rules, follow-ups, and some way for a human to inspect what it is about to do before it does something irreversible. So I built a small, inspectable version of the loop and called it Open Ava. It is one FastAPI app inside one OpenComputer VM, with the VM acting as the BDR’s computer, SQLite as its CRM, AgentMail as its inbox, and Anthropic handling the structured research, scoring, drafting, and reply classification. The run went like this: - I gave it OpenComputer's product profile and ICP, - imported 12 seeded leads, - researched and scored them, - drafted three sourced outreach variants for the best lead, - blocked sending before approval, - created a checkpoint before the real send, - sent one email only to a controlled test inbox, - received a real reply through AgentMail, - classified the reply as an objection, - sent a follow-up, - and kept a durable CRM record across an app restart and checkpoint. The code for the run is on GitHub here: github.com/diggerhq/opencomputer-cookbooks/tree/main/open-ava-bdr https://github.com/diggerhq/opencomputer-cookbooks/tree/main/open-ava-bdr . Here is how you can build the same loop yourself. What you’ll build The finished app is a little BDR workspace: - A persistent OpenComputer VM that acts like the BDR's laptop. - A FastAPI dashboard and JSON API exposed through the VM preview URL. - A SQLite CRM with campaigns, leads, research notes, drafts, emails, suppression records, durable queue rows, and events. - A background worker that imports leads, dedupes them, researches their company URLs, scores fit, and drafts outreach. - A prompt-injection-aware research path that treats scraped pages and inbound emails as untrusted data. - A human approval gate before any send. - A checkpoint before the irreversible send. - AgentMail send and inbound reply handling. - Idempotency for discovery, sends, and reply processing. The tutorial uses OpenComputer itself as the product and a controlled inbox you own. The app never emails arbitrary prospects while you work through it. Why a BDR needs a computer A human BDR lives out of their computer. They open their CRM and pick up where they left off: which accounts they researched yesterday, which leads were a bad fit, which drafts are waiting for approval, which replies need an answer, and which people should not be contacted again. An agent doing BDR work needs the same kind of workspace. It needs to remember what it has already researched, keep drafts around until a human reviews them, avoid sending twice when a provider retries an event, and know the difference between a lead that is ready for follow-up and one that should stay rejected. I wanted my agent to have one computer with its own filesystem, local CRM, running process, inbox logic, preview URL, and checkpoints, so I used OpenComputer. In this build, the OpenComputer VM is the BDR’s machine, with SQLite as the CRM on disk, FastAPI as the dashboard, a worker that keeps moving leads forward, and AgentMail as the inbox. Before the app sends the approved email, the control script creates a checkpoint so there is a rollback line before the system touches the outside world. Architecture Here is the loop in product terms first: - The user enters the product and ICP once. - The worker imports or discovers lead candidates. - Each lead is researched from the company URL we already have. - The LLM produces a structured research note with source-backed facts. - The LLM scores the lead against the ICP. - Fit leads get multiple sourced email variants. - A human approves one variant. - The control process creates a checkpoint. - The app sends one email to the controlled test inbox. - AgentMail receives a real reply. - The app classifies the reply and sends the next response. - CRM state advances and can be inspected. The implementation is quite plain: OpenComputer VM FastAPI app /api/icp /api/state /api/lead/{id} /api/approve /api/send /api/poll-replies SQLite CRM campaign leads research notes drafts emails sent keys processed messages queue events suppression Background worker research - score - draft Integrations Anthropic for structured reasoning AgentMail for inbox, send, reply The app lives inside the VM. The orchestration scripts outside the VM only provision the computer, push the app, create checkpoints, and drive the demo. The BDR’s working memory stays with the BDR. Prerequisites You need: - an OpenComputer API key, - an Anthropic API key, - an AgentMail API key, - one controlled recipient inbox you own. This tutorial uses a seeded CSV of synthetic leads and fetches only the company URLs already present in the file. Start by making a project folder on your machine. The app has two sides: app/ is the FastAPI app that will run inside the OpenComputer VM. control/ is the local control plane: scripts that create the VM, push app/ into it, start the server, checkpoint before sends, and drive the demo. If you want the finished version, clone the repo: git clone https://github.com/diggerhq/opencomputer-cookbooks.git cd opencomputer-cookbooks/open-ava-bdr If you are typing it out from the article, create this shape first: mkdir open-ava-bdr cd open-ava-bdr mkdir -p app/seed control proof touch app/models.py app/db.py app/agent logic.py app/enrich.py app/mail.py touch app/worker.py app/server.py app/send once.py app/requirements.txt app/seed/leads.csv touch control/vm.py control/provision.py control/deploy.py control/drive demo.py touch control/persistence demo.py control/durability fallback.py touch requirements-control.txt .env.example .env .gitignore From here on, every filename in the tutorial is relative to that open-ava-bdr/ folder. The GitHub repo is the runnable source of truth; the snippets below show the important pieces in build order and explain why they are there. If you are recreating the app by hand, copy the complete files from the repo and use the verification commands in this post after each layer. Add the basic ignores before you do anything else: cat .gitignore <<'EOF' .env .venv/ pycache / .pyc .log ava.db ava.db- control/vm.json proof/ .txt EOF Put the local control-script dependencies in requirements-control.txt : opencomputer-sdk==0.6.3 httpx==0.28.1 Create a local virtualenv for the control scripts. These scripts run on your laptop and talk to OpenComputer. Use Python 3.10 or newer; on my Mac, that binary is python3.12 : python3.12 -m venv .venv source .venv/bin/activate pip install -r requirements-control.txt One confusing detail: the package you install is opencomputer-sdk , but the import name is still opencomputer . Do not install the unrelated opencomputer package for this tutorial. It may install, but it does not provide the Sandbox class this code uses. The repo includes .env.example with the required keys and a few optional runtime knobs: OPENCOMPUTER API KEY= ANTHROPIC API KEY= AGENTMAIL API KEY= DEMO RECIPIENT EMAIL=you@example.com Optional knobs ANTHROPIC MODEL=claude-sonnet-4-5-20250929 ANTHROPIC TIMEOUT SECONDS=60 ANTHROPIC MAX RETRIES=2 AGENTMAIL TIMEOUT SECONDS=30 Copy that file to .env , fill in your real values locally, and do not commit .env : cp .env.example .env The required values are: OPENCOMPUTER API KEY=... ANTHROPIC API KEY=... AGENTMAIL API KEY=... DEMO RECIPIENT EMAIL=you@example.com Load them in the terminal before running the control scripts: set -a source .env set +a Do not use a real prospect’s address for DEMO RECIPIENT EMAIL . Use the tutorial to prove the workflow safely before aiming anything at real outbound. Step 1: Create the project and the BDR computer The local folder is your source repo. The OpenComputer VM is where the BDR app actually runs. First, put the app’s runtime dependencies in app/requirements.txt : fastapi uvicorn pydantic =2 anthropic agentmail httpx Start by putting the OpenComputer connection helpers in control/vm.py . This excerpt is copied from the repo; it owns the API key, the local control/vm.json file that remembers which sandbox to reconnect to, and the retry wrapper used by the other control scripts: python import asyncio, json, os, sys import httpx from opencomputer import Sandbox KEY = os.environ "OPENCOMPUTER API KEY" STATE = os.path.join os.path.dirname file , "vm.json" APP TAG = "open-ava-bdr" Exceptions that mean "the network/control-plane blinked", not "your code is wrong". TRANSIENT = httpx.ReadTimeout, httpx.ConnectTimeout, httpx.ConnectError, httpx.RemoteProtocolError, httpx.PoolTimeout, The OpenComputer SDK can occasionally receive a malformed exec response without exitCode during control-plane blips; retry the call instead of failing the whole live proof. KeyError, async def retry make coro, , what="op", attempts=6, base=2.0, max delay=30.0 : """Run an awaitable factory with bounded exponential backoff on transient errors. make coro is a zero-arg callable returning a fresh coroutine each try a coroutine can only be awaited once, so we must rebuild it per attempt . """ delay = base last = None for i in range 1, attempts + 1 : try: return await make coro except TRANSIENT as e: last = e print f" retry {what}: transient {type e . name } " f" attempt {i}/{attempts} , backing off {delay:.1f}s", file=sys.stderr if i == attempts: break await asyncio.sleep delay delay = min delay 2, max delay raise last def save id sid: str : json.dump {"sandbox id": sid}, open STATE, "w" def load id : if os.path.exists STATE : return json.load open STATE .get "sandbox id" return None With that helper in place, control/provision.py can create one persistent sandbox and install the Python packages the app needs inside that sandbox. This is the core create-and-save path from the repo: python import asyncio import os import sys import httpx from opencomputer import Sandbox sys.path.insert 0, os.path.dirname file from vm import retry, save id, KEY, APP TAG PROOF = os.path.join os.path.dirname file , "..", "proof" DEPS = "fastapi", "uvicorn", "pydantic =2", "anthropic", "agentmail", "httpx" async def main : sb = await retry lambda: Sandbox.create timeout=0, metadata={"app": APP TAG}, api key=KEY , what="create" save id sb.id running = await retry lambda: sb.is running , what="is running" print "NEW VM:", sb.id, "running:", running It saves the sandbox ID in control/vm.json , which is intentionally local state. The next script should reuse the same VM instead of creating a new computer every time you run the demo. Run it from your project root: python control/provision.py Once the VM exists, control/deploy.py pushes the files from app/ into the VM and starts uvicorn there. This is the first moment where the split between local code and the BDR’s computer matters: you edit files locally, and the deploy script copies them into the running VM. APP LOCAL = os.path.join os.path.dirname file , "..", "app" APP REMOTE = "/tmp/open-ava-app" FILES = "models.py", "db.py", "agent logic.py", "enrich.py", "mail.py", "worker.py", "server.py", "send once.py", "requirements.txt", "seed/leads.csv" APP LOCAL is the app/ folder on your machine. APP REMOTE is the folder the deploy script creates inside the VM. You do not create /tmp/open-ava-app locally; it exists on the BDR’s computer. The deploy script also passes only the runtime secrets the app needs. It does not write your API keys into the repo: SECRET ENVS = "ANTHROPIC API KEY", "AGENTMAIL API KEY", "DEMO RECIPIENT EMAIL" def env block : e = {k: os.environ k for k in SECRET ENVS if os.environ.get k } e "AVA DB" = f"{APP REMOTE}/ava.db" return e Then it starts uvicorn as a background exec inside the VM: env = env block await retry lambda: sb.exec.background "python3", "-m", "uvicorn", "server:app", "--host", "0.0.0.0", "--port", "8000" , cwd=APP REMOTE, env=env, max run after disconnect=10 000 000, , what="start uvicorn" Run the deploy: python control/deploy.py At this point you can verify the local control environment in layers: python -m py compile control/ .py app/ .py That catches syntax errors in the control scripts without touching the network. python control/provision.py should print a VM ID and PROVISION OK , then python control/deploy.py should print DEPLOY OK and a preview URL. Finally, curl https://