Amdahl onboarding skill
First-run setup for Amdahl. Connects over MCP or an API key, checks the setup in one call, finds where the user's outbound drafts already live (a sequencer or SDR tool, a CRM, a spreadsheet, a Drive or Notion folder, or the code that generates them), then gives every user a first result that needs no data (a few of their real drafts rewritten in their own voice by the Message Optimizer, before and after). If the workspace has synced conversations, it goes on to one use case end to end (a scoped Search for real customer quotes, and a graded and improved Eval of the user's draft) and pastes the server's report card. Use when a user says "set me up on Amdahl", asks to have their outbound emails optimized, pastes an amdhl_ key, or names a use case such as grounded outbound or a positioning check.
Seven steps, in order. Stop and tell the user at any step that fails; do not skip ahead.
- Connect
- Check the workspace has data (this decides step 5 onward; it does not gate steps 3 and 4)
- Find where the user's outbound drafts live
- First win: optimize a few of their real drafts (no data needed)
- Once conversations are synced: pick the use case
- First Search: real customer quotes
- First Eval: grade and improve the user's draft, then close the loop
Search and Eval are the two primitives Amdahl is built on; they read the workspace's own customer conversations. The optimizer is the on-ramp: the fastest result, for any workspace, before any data has synced.
The rules
- Paste the eval report card verbatim. Do not summarise it or restate its numbers. If you quote a number, quote the submitted checks fraction (for example "2 of 5 checks"): it is the only number on the card about the user's own writing.
- Never write to the user's systems without asking. Do not edit a sequence, update a CRM record, change a file, commit code or send a message until the user has approved that specific batch. Showing a rewrite is always safe; applying it is the user's call.
- Show the optimizer's
messageexactly as returned. Do not edit, polish or merge it with your own rewrite before the user sees it: the checks that kept their names, numbers and merge fields exact ran on that text. If the user wants it changed, re-run the optimizer with their wish as one of therulesincontext(or a fact they confirm infacts), or let them edit it themselves. Never layer your own rewrite on top.
1. Connect
If the Amdahl tools are available (search, evals, agents, connections, messages), use them and skip to the health check below. Otherwise connect once. In Claude Code, Codex or Cursor, run npx @amdahl/cli install claude-code (or codex, cursor) and follow the sign-in step it prints. In Claude or ChatGPT, add a connector with the URL https://app.amdahl.ai/mcp and sign in with your work email. No workspace yet? Try the optimizer at console.amdahl.ai/try, which also joins the beta waitlist. An admin of an existing workspace at their company can add them instead. On a server or in CI, use an API key in AMDAHL_KEY and REST:
- Base URL:
https://app.amdahl.ai/api/platform/v1 - Header:
X-API-Key: amdhl_…(Authorization: Bearer amdhl_…works the same) - Every successful JSON response is wrapped: read
.data.
Over MCP there is no key: signing in is the connection. Clients such as Claude ask the user to allow each Amdahl tool the first time it is called ("Allow once" or "Always allow"). Tell the user to expect that prompt.
The user creates a key in their own terminal with npx @amdahl/cli login, then npx @amdahl/cli keys create --name "Onboarding" --preset agent, and approves it in the console. Or in the console: Settings, then Developer, then Create key, with Access kept at Customer agent (the default). Customer agent carries messages:execute for step 4 and evals:execute for step 7. A Read only key can search but can run neither the optimizer nor an eval. The key belongs in the environment as AMDAHL_KEY, not in the chat.
Health check. Before step 2, check the setup. MCP: the connections tool with {"action": "setup_status"}. REST: GET /setup/status. It names the workspace and the caller's role, and optimize.allowed says whether step 4 will work. When it is false, optimize.blocker says why:
missing_scope: the key is Read only or predates the Message Optimizer, or the connector was authorized before it existed. Ask for a new Customer agent key, or have the user disconnect and reconnect the connector.role_too_low: the user is a Viewer. Ask a workspace admin to raise their role to Editor or above.quota_exhausted: the workspace used this month's optimizations. It resets atoptimize.quota.resets_at. Steps 2, 5 and 6 still work.
The same read reports connections: how many sources are connected, how many are healthy, and which need attention. Mention any that need attention; they are why data can look thin in step 2.
Other failures at this step:
401: the key is missing, wrong, revoked or expired. Ask for a new one.403withforbiddenand "Missing required scope(s): evals:execute" (at step 7): the key is Read only. Ask for a Customer agent key.
2. Check the workspace has data
REST: GET /search/overview. MCP: read the resource search-overview://current; if the client cannot read resources, call the search tool with {"action": "query", "mode": "filter", "surface": "interactions", "metrics": [{"fn": "count"}]}.
The overview returns one entry per surface (interactions, deals, deal_qualification) with row_count, earliest_at, latest_at and time_field.
- Every
row_countis0: the workspace is empty. Say so, run steps 3 and 4, and do not run steps 5 to 7: search returns empty results that look successful, and the eval returnsnot_applicablewithempty_corpus. - A
row_countisnull: that read failed. It is not zero. Retry once before saying anything about the data. - Otherwise report: how many conversation rows, how many deals, and the newest conversation date (
latest_atoninteractions). Then run step 3.
3. Find where the user's outbound drafts live
Most users already run outbound from somewhere. Find it with what you already have before asking anything.
Look first, in this order:
- Your own tools. List the MCP servers, connectors and tools connected in this session besides Amdahl. A sequencer or SDR tool (anything that holds sequences, campaigns, steps or scheduled emails) is the best source. A CRM (HubSpot, Salesforce) may hold email templates or sequence steps. Google Drive, Notion or a file system may hold a drafts doc or a spreadsheet of emails.
- The conversation and your memory. The user may have already named their tool, pasted a draft, or shared a style guide.
- A repo, if you have one open. Search it for where outbound copy is written: email templates, prompt files that generate emails, or calls to a send API (for example a sequencer's, SendGrid's or Resend's).
Then ask only what you could not work out, at most three short questions, in one message. For example:
- "Where do your outbound drafts live?" (only if you found no source, or found several)
- "Which campaign or sequence should I start with?"
- "How many should I do first? I suggest 3 to 5."
If you find nothing and the user has no system, ask them to paste one draft and go on to step 4 with that.
Pull a small batch: 3 to 5 real drafts that have not been sent yet (upcoming sequence steps, unsent drafts, the newest rows of a sheet). Read only. Keep a note of where each one came from (sequence and step, row, file path) so you can show it and, if the user asks, put the rewrite back in the same place.
While you look, collect context if it is there:
rules: hard constraints from a style guide or the user's instructions ("never mention pricing", "under 80 words"). Up to 20.voice_examples: emails the user wrote themselves and liked, such as past sends with good replies. Up to 5. The rewrite matches their voice and does not copy their words.facts: what the user knows first-hand about a recipient (a meeting, something the recipient said in person). Up to 10. Never write a fact yourself: a fact licenses a claim, so an invented one launders an invented claim.
When a draft says something the recipient said or did in person or in an earlier conversation ("great meeting you at the dinner", "on our call", "you mentioned"), ask the user to confirm it in one question before step 4, for example: "Your draft to Priya says you met at the GTM dinner and she said her SDRs spend hours rewriting drafts. Is that right?" If they confirm, record it as one fact per entry, in their words, against that draft only. Do not add detail they did not give. If they do not confirm, send no fact and tell them the line will grade as unsupported.
If the outbound copy is generated in code
When you have repo access and the drafts come out of code (a template, a prompt that writes the email, a job that calls a send API), run step 4 on a few real outputs first. Then show the user where an optimize call would go: after the draft is produced and before it is queued or sent.
- REST:
POST https://app.amdahl.ai/api/platform/v1/messages/optimizewith headerX-API-Key, body{"message": "...", "channel": "email"}, and read the rewrite from themessagefield insidedata. Use it as returned. Leaveinclude_triesout of this path: it is for showing a person how a draft improved, and a send pipeline only needsmessage(at most, send it from a review screen). - MCP: the
messagestool with{"action": "optimize", ...}, for code that already talks to an MCP client.
Write the change as a diff and show it. Handle the failure path in the diff: when ok is false, send the original draft and log the reason. Each call usually takes under 30 seconds and can take up to 170, so it belongs in a background job, not a request a user waits on. Do not commit, push or open a pull request until the user approves the diff.
4. First win: optimize a few real drafts
Every user gets this first, with or without data. Use the batch from step 3. Rewrite each draft with the Message Optimizer, one call per draft.
REST: POST /messages/optimize with {"message": "<the draft, verbatim>", "include_tries": true}. MCP: the messages tool, {"action": "optimize", "message": "<the draft, verbatim>", "include_tries": true}. include_tries adds tries, every version the optimizer scored, draft first, so the user can watch their email improve. Add "channel": "email" or "linkedin" when you know it (a sequencer step usually says). Add "context": {"rules": [...], "voice_examples": [...], "facts": [...]} with whatever step 3 found; facts go only with the draft they are about. If step 2 found conversation rows (an interactions row_count above 0), add "evidence": "workspace" and tell the user what it does: it also checks each draft against what their own customers said in those conversations, and it makes each call slower. On an empty workspace leave evidence out; the default is a fast style pass. Tell the user each one usually takes under 30 seconds, longer with evidence; allow up to 170 seconds per call. It returns on the same call; there is nothing to poll.
A draft with a confirmed fact (MCP shown; over REST, drop action):
{
"action": "optimize",
"include_tries": true,
"channel": "email",
"message": "Hi Priya, great meeting you at the GTM dinner last week. You mentioned your SDR team spends hours rewriting...",
"context": {
"facts": ["Met Priya at the GTM dinner last week; she said her SDR team spends hours rewriting AI drafts."]
}
}For each draft, show where it came from, then message from the result exactly as returned (the optimizer's choice comes first, before anything you say about it), then summary, then the original, then every entry in tries.
Show tries in round order, after the optimizer's choice:
- Label round 0 "Your draft", then "Try 1", "Try 2" and so on.
- Give its Optimizer score (
score) and Human tone (human_tone), both out of 5. Write "not scored" fornull, never 0. - Say in plain words what it aimed at (
aimed_at, empty on round 0), for example "a clearer ask and a shorter email". - Mark the one with
returned: trueas what came back. When that is round 0, say nothing beat their draft.
Then add one honest line: "These scores are the optimizer's own grading during the search. They show the path, not proof." Never quote the first try's score against the last as an improvement; lift is the only before-and-after to quote. If tries is null or missing, show the original and message only. Details: https://docs.amdahl.ai/endpoints/message-optimizer#reading-tries
Then say plainly:
-
This rewrite is judged on how it reads and how likely it is to get a reply. If
evidence.usedistrue, it was also graded against their customers' conversations, and the claims nothing in them backs are listed inunsupported_claims(below); otherwise it was judged on style only, and ifevidence.reasonisevidence_unavailableorcorpus_empty, say no conversations could be read for it. The graded eval in step 7 checks every claim line by line, with the quotes behind each verdict. -
If a rewrite replaced a meeting request with a question, say so: by default the optimizer prefers a close the reader can answer in one line. To keep the meeting ask, re-run that draft with it as one of the
rulesincontext, for example "End by asking for a 15-minute call." -
If
unchangedistrue, no rewrite beat their draft, so their version was kept. Say "we kept your version", not "here is the improved version". If the result carrieskept_suggestions, relay them: eachsaysis safe to repeat as written,span(when present) is the exact text in their draft it is about, andwhyis the grader's reasoning, never wording to put in the message. If it carriesask, the draft was too weak to rewrite and nothing was tried: put eachasksquestion to them as written, then run the draft again with their answers added to it. -
Read
unsupported_claims. It is produced only when you passed"evidence": "workspace"andevidence.usedistrue; otherwise it isnull, which means claims were not checked (so passevidencewhenever step 2 found conversation rows). Each entry is a claim in the draft that nothing in the workspace's conversations backs, and by default it was kept in the rewrite (status: "kept"). While any entry has a status other thanremoved, do not call the draft ready to send. Go through them with the user one claim at a time, quotingspanandwhy, and ask:- Is it true, and do they know it first-hand? Re-run that draft with it added to
factsinsidecontext, in their words. - Does it have a source? Add the evidence, or check it with
evals.run(step 7). - Neither? Offer to re-run with
"on_unsupported": "remove", which lets the rewrite soften or cut it.
Never remove a claim without asking, and never re-run with
removeon your own: the claim may be the one thing the user knows that the conversations do not. - Is it true, and do they know it first-hand? Re-run that draft with it added to
-
If
notesorsummarysay a rewrite was dropped because it changed a number, date, meeting length or other specific detail, tell the user that in plain words: the optimizer keeps exact details as written and drops a rewrite that alters one. Repeat the note'ssaystext as written. Offer to re-run once the user says which details may change (for example "the call can be 15 minutes instead of 20"). -
If every draft in the batch came back
unchanged, do not stop at "kept". Say what it means: on how they read and how likely they are to get a reply, none of the rewrites beat the user's drafts, which is a real and common result, not a failure. It says nothing about whether the claims in them hold up. Then offer the grounded check below (steps 5 to 7) if the workspace has data, or the detail re-run above if a note named one.
If ok is false, nothing was rewritten. Read reason (or error.code) to the user; do not present their own draft as a result. quota_exceeded (429 over REST) means the workspace used this month's 1,000 optimizer calls for API and OAuth keys. Stop the batch there.
After the batch, ask what to do with the rewrites. Offer to put the ones they approve back where they came from (the same sequence step, row or file). Write back only what they approved, only in the place you read it from, and send nothing. If you have no tool that can write there, give them the text to paste.
Then frame the next step:
- The workspace has data: offer the deeper, grounded check, and offer it just the same when every draft was kept. Steps 5 to 7 grade the same kind of draft against what their customers actually said, pass or fail per rubric line, with the quotes behind each verdict. Continue if the user wants it.
- The workspace is empty: stop here. Tell the user to connect a CRM or call recorder (over MCP, the
connectionstool'scatalogaction lists what can be connected). Once conversations sync, come back to step 2 and run steps 5 to 7.
5. Once conversations are synced: pick the use case
If the user already named one, use it. Otherwise offer:
- Grounded outbound: draft a first-touch message in your buyers' own words, then grade it.
- Positioning check: grade your homepage or pitch copy against what customers say.
Ask for the one missing input: the persona they sell to (use case A), or the copy to check (use case B). Use cases A and B below give the exact calls.
6. First Search
Always send mode: "semantic", hydrate: true (so rows carry quotable text) and an explicit limit.
REST: POST /search/query. MCP: the search tool with "action": "query" and the same fields.
Show the user 5 to 10 quotes with the company and date for each. Then one line from the response's corpus block: which store answered and how many rows.
corpus.empty_reasonisread_failed: an error. Retry; do not report "nothing found".weak_matches_only: rephrase the query once, then carry on.no_match: nothing in the corpus matches. Say so, and carry on to step 7 anyway.
The semantic lane holds customer-side speech only, so these are your buyers' words, not your reps'. A query containing a phrase in double quotes runs a different lane (exact-term matching); leave quotes out of this query.
7. First Eval
REST: POST /evals/run. MCP: the evals tool with "action": "run". Use "eval": "prompt-and-message-eval" and the inputs from use case A or B. Pass the same "context": {"facts": [...]} you gathered in step 3, beside inputs (never inside it), when the draft leans on what the user knows in person. The report lists the facts each side relied on as caller_facts: present them as the user's own note, never as a customer quote.
Tell the user a full run takes a few minutes. The response is a handle, not a verdict. Hand the user the console_url from it.
Wait for the result. Do not write a sleep loop; the server blocks for you:
- REST:
GET /eval-runs/{run_id}?wait_ms=30000, repeated untildata.statusiscomplete,failedorcanceled. The value iscomplete, nevercompleted. - MCP: the
evalstool,{"action": "status", "run_id": "…", "wait_ms": 30000}, repeated the same way.
Then get the report card and paste it:
- REST:
GET /eval-runs/{run_id}/report, then paste themarkdownfield of thereportobject insidedata. - MCP: the finished
statusresult carriesreport; paste itsmarkdown.
Say these out loud after the card:
- The improved message is an illustration produced so the difference could be measured (
usage: "illustration_only"). The improved prompt is what to keep. Do not send the illustration as written. - One run is one draw. The same draft can score differently on another run.
- If the run came back
not_applicable, nothing was graded. Readnot_applicable_reasonto the user; it is not a failing grade.
Close the loop
Ask what the user did with the result, then record it.
REST: POST /eval-runs/{run_id}/feedback with {"outcome": "…", "evidence": "…"}. MCP: the evals tool, {"action": "feedback", "run_id": "…", "outcome": "…", "evidence": "…"}.
outcome:used,used_with_edits,not_usedorunsure.evidence:reported_to_mewhen the user told you,did_it_myselfonly if you applied the change yourself in this session,inferredif you are guessing.
Then offer the next steps:
- Grade an edited draft against the same quotes: run again with
"evidence_from_run": "<this run_id>"besideinputs, so only the copy changed. - Put the eval in a pipeline as a send/hold check:
"mode": "gate"insideinputs, then readGET /eval-runs/{run_id}/gate. - For repeat work, the
ground-and-draftandgrade-and-reportskills in the Amdahl cookbook.
A. Grounded outbound
Search, with the persona's pain in plain words:
{
"mode": "semantic",
"query": "what slows RevOps teams down when headcount grows fast",
"hydrate": true,
"limit": 10
}Draft a short message from the quotes: their problem first, in their words; one proof point you can back; no invented numbers or company names. Then grade it with the instruction the user would give a writer:
{
"eval": "prompt-and-message-eval",
"inputs": {
"prompt": "Write a short first-touch email to a VP of RevOps at a mid-market SaaS company.",
"message": "<the draft you wrote from the quotes>",
"audience": "VP of RevOps"
}
}Add "account": "<company name as the CRM spells it>" inside inputs only when the target company is already in the corpus. The default mode is rewrite, which returns an improved prompt and message.
B. Positioning check
Search for why customers chose the product:
{
"mode": "semantic",
"query": "why we chose them and what problem we were trying to solve",
"hydrate": true,
"limit": 10
}Grade the user's copy exactly as they gave it, with anchored suggestions rather than a rewrite:
{
"eval": "prompt-and-message-eval",
"inputs": {
"message": "<the user's copy, verbatim>",
"mode": "advisory",
"artifact_type": "landing_copy"
}
}artifact_type: "landing_copy" tells the grader the copy is written for a market rather than one named buyer, so it is not failed for missing a single recipient. Point out any quote marked contradicts: it is where the copy and the customers disagree.
Reference
- Search: https://docs.amdahl.ai/endpoints/search
- Message Optimizer: https://docs.amdahl.ai/endpoints/message-optimizer
- Evals API: https://docs.amdahl.ai/endpoints/evals
- The eval instrument: https://docs.amdahl.ai/endpoints/evals/prompt-and-message-eval
- Errors: https://docs.amdahl.ai/api-reference/errors
- What else Amdahl can do, and which tool does each job: https://docs.amdahl.ai/skills/amdahl/SKILL.md. Answer "what else can I do?" from it; do not call tools to find out.