---
name: amdahl-onboarding
description: 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.
---

# Amdahl onboarding skill

Seven steps, in order. Stop and tell the user at any step that fails; do not skip ahead.

1. Connect
2. Check the workspace has data (this decides step 5 onward; it does not gate steps 3 and 4)
3. Find where the user's outbound drafts live
4. First win: optimize a few of their real drafts (no data needed)
5. Once conversations are synced: pick the use case
6. First Search: real customer quotes
7. 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 `message` exactly 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 the `rules` in `context` (or a fact they confirm in `facts`), 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 at `optimize.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.
- `403` with `forbidden` and "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_count` is `0`: 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 returns `not_applicable` with `empty_corpus`.
- A `row_count` is `null`: 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_at` on `interactions`). 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:

1. **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.
2. **The conversation and your memory.** The user may have already named their tool, pasted a draft, or shared a style guide.
3. **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/optimize` with header `X-API-Key`, body `{"message": "...", "channel": "email"}`, and read the rewrite from the `message` field inside `data`. Use it as returned. Leave `include_tries` out of this path: it is for showing a person how a draft improved, and a send pipeline only needs `message` (at most, send it from a review screen).
- MCP: the `messages` tool 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`):

```json
{
  "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" for `null`, 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: true` as 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.used` is `true`, it was also graded against their customers' conversations, and the claims nothing in them backs are listed in `unsupported_claims` (below); otherwise it was judged on style only, and if `evidence.reason` is `evidence_unavailable` or `corpus_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 `rules` in `context`, for example "End by asking for a 15-minute call."
- If `unchanged` is `true`, no rewrite beat their draft, so their version was kept. Say "we kept your version", not "here is the improved version". If the result carries `kept_suggestions`, relay them: each `says` is safe to repeat as written, `span` (when present) is the exact text in their draft it is about, and `why` is the grader's reasoning, never wording to put in the message. If it carries `ask`, the draft was too weak to rewrite and nothing was tried: put each `asks` question 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"` and `evidence.used` is `true`; otherwise it is `null`, which means claims were not checked (so pass `evidence` whenever 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 than `removed`, do not call the draft ready to send. Go through them with the user one claim at a time, quoting `span` and `why`, and ask:
  - Is it true, and do they know it first-hand? Re-run that draft with it added to `facts` inside `context`, 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 `remove` on your own: the claim may be the one thing the user knows that the conversations do not.
- If `notes` or `summary` say 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's `says` text 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 `connections` tool's `catalog` action 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:

1. **Grounded outbound**: draft a first-touch message in your buyers' own words, then grade it.
2. **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_reason` is `read_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 until `data.status` is `complete`, `failed` or `canceled`. The value is `complete`, never `completed`.
- MCP: the `evals` tool, `{"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 the `markdown` field of the `report` object inside `data`.
- MCP: the finished `status` result carries `report`; paste its `markdown`.

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. Read `not_applicable_reason` to 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_used` or `unsure`.
- `evidence`: `reported_to_me` when the user told you, `did_it_myself` only if you applied the change yourself in this session, `inferred` if 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>"` beside `inputs`, so only the copy changed.
- Put the eval in a pipeline as a send/hold check: `"mode": "gate"` inside `inputs`, then read `GET /eval-runs/{run_id}/gate`.
- For repeat work, the `ground-and-draft` and `grade-and-report` skills in the [Amdahl cookbook](https://github.com/amdahlco/amdahl-cookbook/tree/main/skills).

## A. Grounded outbound

Search, with the persona's pain in plain words:

```json
{
  "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:

```json
{
  "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:

```json
{
  "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:

```json
{
  "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.
