Agent skill

Integrate Agent Email

by amd in amd/gaia

A skill your agent uses when integrating the @amd-gaia/agent-email npm package — embedding the GAIA email agent (a local triage/draft/send sidecar) into a Node, TypeScript, or Electron app.

MITAuto-check passedAI & LLM Engineering

Install Integrate Agent Email

skills CLI
$ npx skills add amd/gaia --skill integrate-agent-email -a claude-code

Project install by default; add -g for ~/.claude/skills/.

GitHub CLI
$ gh skill install amd/gaia integrate-agent-email --agent claude-code

Project scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).

Manual copy
$ git clone --depth 1 https://github.com/amd/gaia.git skills-src && mkdir -p .claude/skills && cp -r skills-src/hub/agents/email/npm .claude/skills/integrate-agent-email && rm -rf skills-src

Use ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.

Claude Code skills documentation · loads skills from .claude/skills/

Facts

Skill name
integrate-agent-email
GitHub stars
1.6k
Token cost
~7.4k tokens
SKILL.md length
3,215 words
Files
44 (incl. assets)
Skills in repo
44
Repo updated
First seen
Licence
MIT

At a glance

A skill your agent uses when integrating the @amd-gaia/agent-email npm package — embedding the GAIA email agent (a local triage/draft/send sidecar) into a Node, TypeScript, or Electron app.

  • Works in 5 steps: Install → Pick the right entry point → Fetch the binary and start the sidecar… → …
  • Integrating the @amd-gaia/agent-email npm package — embedding the GAIA email agent (a local triage/draft/send sidecar) into a Node
  • SKILL.md covers 1. Install, 2. Pick the right entry point, 3. Fetch the binary and start… and 4. Call the typed client, plus 9 more sections
  • Runs TypeScript and JavaScript scripts from its folder; calls npx, npm and pip

What it does

Integrate Agent Email is an agent skill from amd/gaia. Use when integrating the @amd-gaia/agent-email npm package — embedding the GAIA email agent (a local triage/draft/send sidecar) into a Node, TypeScript, or Electron app. Covers install, spawning the sidecar, calling the typed client, prerequisites, and the common gotchas.

Its SKILL.md is about 7.4k tokens, which your agent loads only when the skill is triggered. The skill folder holds 46 other files, including assets (for example `CHANGELOG.md`, `EVALUATION.md` and `README.md`).

It sits in AI & LLM Engineering, covering Embeddings. It works with npm, Electron and TypeScript. The repository describes itself as: Build AI agents for your PC. The licence is MIT.

When your agent uses it

  • Integrating the @amd-gaia/agent-email npm package — embedding the GAIA email agent (a local triage/draft/send sidecar) into a Node
  • Tasks that involve Embeddings

Example prompts

  • “/integrate-agent-email”

Requirements

  • Node.js

Workflow steps

5 steps, taken from the step headings in SKILL.md.

  1. Install
  2. Pick the right entry point
  3. Fetch the binary and start the sidecar (Node)
  4. Call the typed client
  5. From a renderer (Electron / browser)

What it can do on your machine

Read from SKILL.md and the folder at commit 6c3bb5c. It shows what the files ask for, not the result of running them.

  • Tool permissions

    Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.

    From allowed-tools in the SKILL.md frontmatter.

  • Runs code

    Ships script files (TypeScript and JavaScript, from the files we listed), which the agent can run.

    Shell commands in SKILL.md call:

    • npx
    • npm
    • pip

    From the folder's file list and the shell code blocks in SKILL.md.

  • Network

    No URLs in SKILL.md. Its commands use npx, npm and pip, which can reach the network depending on how they are called.

    From URLs in SKILL.md, links to its own repository left out.

  • Credentials

    Names no API keys, tokens, secrets or passwords.

    From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.

Context cost

Integrate Agent Email loads about 7.4k tokens when it runs. Until then it costs about 74 tokens; SKILL.md has 3,215 words of instructions outside code blocks.

Always · name and description, kept in context so the agent knows when to use it
~74
When it runs · the whole SKILL.md, loaded when a task matches
~7.4k

Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.

Safety

Auto-check passed

The automated check found no risky patterns in SKILL.md.

Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.

SKILL.md

The full file from amd/gaia at commit 6c3bb5c, republished under its MIT licence (© amd). 3,215 words, ~7,365 tokens.

Download SKILL.mdSave it as .claude/skills/integrate-agent-email/SKILL.md (or your agent's skills folder). This skill also uses 43 other files; get the full folder from GitHub.
name
integrate-agent-email
description
Use when integrating the @amd-gaia/agent-email npm package — embedding the GAIA email agent (a local triage/draft/send sidecar) into a Node, TypeScript, or Electron app. Covers install, spawning the sidecar, calling the typed client, prerequisites, and the common gotchas.

Integrating @amd-gaia/agent-email

@amd-gaia/agent-email embeds the GAIA email agent in a JS/TS app. It triages, drafts, and sends email locally on AMD Ryzen AI — no cloud LLM. This package is the client: it downloads a frozen native sidecar binary, spawns it, and talks to it over local HTTP. There is no Python and no separate GAIA install.

Follow these steps to wire it into an app.

This file is NOT one of the agent's own skills. It is the integration playbook — how you wire this npm package into an app. The sidecar separately bundles six Agent Skills at gaia_agent_email/skills/<name>/SKILL.md, which are instructions the email agent itself would load into its own prompt at runtime — currently disabled, so none of them loads. Same filename, different artifact: don't load those into your assistant, and don't ship this one as an agent skill. See Skill sets below.

1. Install

bash
npm install @amd-gaia/agent-email

The package is ESM-only ("type": "module"). Use import, not require. From a CommonJS file, use await import("@amd-gaia/agent-email").

2. Pick the right entry point

  • Node / main process → the default entry @amd-gaia/agent-email. It can fetch the binary and spawn/own the sidecar (uses node:fs, node:child_process).
  • Browser / Electron renderer → the @amd-gaia/agent-email/client subpath. It has zero Node built-ins and only talks to an already-running sidecar over HTTP.

The desktop pattern: spawn the sidecar once from the Node/main process, then drive it from the renderer via ./client.

3. Fetch the binary and start the sidecar (Node)

ts
import { fetchBinary, startSidecar, shutdown } from "@amd-gaia/agent-email";

// Build time (or first run): download + SHA-256-verify the platform binary.
const { binaryPath } = await fetchBinary({ outDir: "resources" });

// Runtime: spawn -> wait for /health -> version-check, in one call.
const sidecar = await startSidecar({ binaryPath, port: 8131 });

// ... use sidecar.client ...

await shutdown(sidecar); // graceful stop — auto-cleanup also reaps on exit
  • fetchBinary writes a verified binary into outDir. SHA-256 is mandatory; a bad download is rejected and not left on disk. Run it at build time or guard it to run once.
  • startSidecar throws if the binary can't start, never becomes healthy, or the contract MAJOR version mismatches — and cleans up so a failed start leaks nothing. If the port is already taken (e.g. a playground still running) it throws PortInUseError without spawning; to reuse a running server, use connectSidecar({ baseUrl }) instead.
  • The sidecar is auto-reaped when your process exits, crashes, or is signalled (default autoCleanup), so a missed shutdown won't orphan the frozen binary's child. shutdown(sidecar) is the graceful, awaited stop; autoCleanup: false opts out. If the sidecar survives the forced kill, shutdown rejects with an error naming the pid and the command to kill it — catch it and surface it rather than ignoring it.

4. Call the typed client

ts
const res = await sidecar.client.triage({
  payload: {
    kind: "single",
    principal: { email: "me@example.com" },
    message: {
      message_id: "m1",
      from: { name: "Sarah Chen", email: "sarah@example.com" },
      subject: "Prod incident follow-up",
      body: "Please review the report and reply by Friday.",
    },
  },
});
console.log(res.result.category, res.result.summary);

To classify many messages at once, use triageBatch — an items array (1–100) in, a parallel results array out, order-preserved. It's additive (the single triage above is unchanged). Per-item failures isolate, so an HTTP 200 can still carry errored items — inspect each results[].error, never just the status:

ts
const batch = await sidecar.client.triageBatch({
  items: [
    { kind: "single", principal: { email: "me@example.com" },
      message: { message_id: "m1", from: { email: "sarah@example.com" },
        subject: "Prod incident", body: "Reply by Friday." } },
  ],
});
for (const r of batch.results) {
  if (r.error) console.warn(`item ${r.index} failed: ${r.error.message}`);
  else console.log(`item ${r.index}:`, r.result!.category);
}

The interface:

CallNeedsNotes
triage(req)Local LLM onlyClassify / summarize / extract action items + phishing signals on the message you pass. No mailbox read. Action items also persist to the sidecar's local task list (keyed by message_id, de-duplicated on re-triage) — the response shape is unchanged.
triageBatch(req)Local LLM onlySame as triage for an items array (1–100). Parallel results array; per-item failures isolate (200 can carry errored items — inspect results[].error).
search(req)A connected mailboxRead-only inbox search by query/labels; returns message metadata (id, subject, sender, snippet, labels), no body. No token. No mailbox → 503, two+ → 400.
prescan(req?)A connected mailboxRead-only inbox pre-scan → triage-card envelope (kind: "email_pre_scan"), whose needs_you (schema 2.11) is the ONE worklist the card renders — up to 5 things that need you, plus bulk for the filtered remainder. Also carries suspicious/suspicious_total (schema 2.13): the phishing/spam-flagged subset of actionable, each item tagged is_phishing/is_spam. No mailbox connected → 503; 2+ → 400. Heuristic-only, no Lemonade call. NeedsYouItem.detail is reserved on the wire but always empty today on every surface — see CHANGELOG.md.
draft(req)Nothing externalReturns a single-use confirmation token. Optional attachments (schema 2.2): { filename, mime_type, content_base64 } each, ≤ 25 MB decoded.
send(req)Draft token + a connected mailboxGate fires first: no/invalid draft token → 403; valid token but no mailbox connected on the host → 503. Attachments must exactly match the confirmed draft's (the token binds their content digests).
confirmAction(req)Nothing externalMints a single-use token for "archive"/"quarantine", bound to the (action, message_id).
archive(req)confirm token + a connected mailboxRemoves from inbox. Gate fires first (no/invalid token → 403). Returns a batch_id undo handle (+ post_archive_id for the Outlook id change).
unarchive(req)A connected mailboxRestores within the 30s window (ungated — pass batch_id); expired/unknown → 409.
quarantine(req)confirm token + a connected Gmail mailboxApplies GAIA_PHISHING_QUARANTINE + archives a phishing message. Refuses is_phishing:false → 400; Gmail-only (Outlook → 400).
unquarantine(req)A connected mailboxRestores prior labels within the 30s window (ungated — pass action_id); expired/unknown → 409.
listCalendarEvents(opts?)Connected mailbox + calendar scopeRead-only view of the primary calendar. Optional timeMin/timeMax — omitting both defaults to a forward window (now → +30 days); provider only when >1 account. Missing scope → 403 + reconnect CTA.
previewCalendarEvent(req)Nothing externalMints a single-use confirmation token bound to the event (calendar analogue of draft).
createCalendarEvent(req)Preview token + connected calendarToken gate fires first: no/invalid token → 403, then the calendar checks.
respondToCalendarEvent(req)Connected calendarRSVP accepted/declined/tentative to an existing invite.
query(req)A connected mailbox (for mailbox tools)The agent loop (schema 2.4): async iterator of the seven typed SSE events. You mint run_id; push the transcript slice in context. See "Canonical agent-loop query" below.
cancelQuery(runId)Nothing externalCancel an in-flight query() run between steps (pass the run_id you minted). Not in flight → 404.

Build the standalone surface (triage, draft, confirmAction, previewCalendarEvent) with zero connector setup. The read-only search and prescan read the live inbox (a connected mailbox, no token); send, the mailbox actions (archive / quarantine), and the calendar actions (view / create / respond) need a connected mailbox whose relevant scope was granted. Mint the gate token with draft (for send), confirmAction (for archive / quarantine), or previewCalendarEvent (for createCalendarEvent); archive and quarantine are reversible inside a 30s window via the ungated unarchive / unquarantine. Every non-2xx response throws HttpError (status, url, bodyText) — handle it; there is no silent null.

Scheduled daily briefing (#1608, REST-only): the sidecar can run prescan on a daily timer with no prompt. Off by default — launch with startSidecar({ env: { GAIA_EMAIL_BRIEFING_ENABLED: "true" } }) (fire time GAIA_EMAIL_BRIEFING_TIME, 24h local HH:MM, default 08:00), then pull the latest run from GET /v1/email/briefing with plain fetch (no client wrapper yet). 404 until the first scheduled run; an invalid env value fails sidecar startup loudly. The response is a cached run, so it also carries cache_age_seconds and stale (#2759) — stale is true once the briefing is at least 24 hours old. Read them and say the age before you show the contents; the endpoint labels an old briefing rather than refusing it or regenerating it, so a host that ignores those two fields presents last week's inbox as this morning's.

5. From a renderer (Electron / browser)

The sidecar serves same-origin only — no CORS. A renderer on a different origin cannot fetch http://127.0.0.1:8131 directly; the browser blocks it. So:

  • Recommended: spawn the sidecar in the Electron main process (step 3) and expose triage/draft to the renderer over your own IPC. Don't call the sidecar from the renderer directly.
  • The ./client entry (zero Node built-ins) is only usable from a same-origin or proxied page:
ts
import { EmailClient } from "@amd-gaia/agent-email/client";
// Pass the sidecar's session token (from sidecar.authToken in the main process,
// forwarded over IPC) — without it every /v1/email/* call is 401.
const client = new EmailClient({ baseUrl: "http://127.0.0.1:8131", authToken });

Canonical agent-loop query (POST /v1/email/query, schema 2.6)

The v2 keystone (#2016): NL request in, the agent reasons and chains its tools, the canonical Server-Sent Event types out — status / token / tool_call / tool_result / needs_confirmation / needs_input / final / error, terminated by exactly one final or error. This is the one loop every v2 front-door relays to. The host mints run_id and pushes the transcript slice in context, so the sidecar stays stateless. The typed client wraps it (#2097): query() returns an async iterator of typed QueryEvents; cancelQuery(runId) stops the run between steps:

ts
const runId = crypto.randomUUID(); // host-minted; also the cancel handle
for await (const ev of sidecar.client.query({
  query: "Triage my inbox",
  run_id: runId,
  context: [], // pushed transcript slice; [] for a fresh conversation
})) {
  switch (ev.type) {
    case "status":       console.log(ev.message); break;
    case "token":        process.stdout.write(ev.delta); break;
    case "tool_call":    console.log(`→ ${ev.tool}`, ev.args); break;
    case "tool_result":  console.log(`← ${ev.tool}`, ev.data); break;
    case "needs_confirmation": break; // run then ends with a final refusal (D1)
    case "needs_input":               // PAUSED — answer, then keep iterating
      await sidecar.client.respondToQuery(runId, ev.request_id, await askUser(ev));
      break;
    case "final":        console.log(ev.answer); break;   // terminal
    case "error":        console.error(ev.detail); break; // terminal, verbatim
    default:             console.warn("unsupported event", ev); // future additive type
  }
}
// Mid-run, from anywhere that knows runId:
// await sidecar.client.cancelQuery(runId);

Rules an integration must respect:

  • Mint run_id yourself (crypto.randomUUID()) and keep it — it is the cancel handle, valid from the instant the request is sent.
  • Exactly one terminal event. A terminal error is yielded (surface detail verbatim); transport/contract failures throw (HttpError non-2xx, QueryStreamError for a non-SSE response / malformed event / stream that closes without a terminal). Never treat iterator completion without a final as success — the client already throws for you.
  • Gate can_answer_questions on the peer's version. Call version() first: a sidecar below apiVersion 2.6 does not know the field and answers 422 to every request carrying it — including false. Omit it below 2.6 and treat mid-run questions as unavailable.
  • Declare can_answer_questions honestly. It defaults to false. Set it true only when a human is watching a UI that renders the question; a one-shot or batch job must leave it off, and then gets an immediate actionable refusal instead of a run parked on a question nobody can see.
  • Answer needs_input, do not restart. The run is parked on the SAME stream. Call respondToQuery(runId, ev.request_id, value) and keep iterating the existing iterator — issuing a fresh query() abandons the paused run. value is an option's value (its label also works) or free text when allow_free_text. Render every option's description: the label alone does not tell the user what they are agreeing to. When sensitive is set, mask the input and never log it. Ignoring the question is safe but wasteful — the run ends with an error after timeout_seconds.
  • Handle the default branch. A type outside the canonical vocabulary arrives as { type: "unknown", eventType, raw } — render an "unsupported event" placeholder or log it; it is never silently dropped.
  • Long runs are normal. timeoutMs bounds time-to-first-response only. To abort from the client side pass query(req, { signal }) AND call cancelQuery so the sidecar stops the loop, not just the socket.

A confirmation-requiring step (a destructive tool such as send_now) emits needs_confirmation then ends with a final refusal pointing at the fixed-function route — mint a token via draft(), then send() (stateless stub, epic decision D1; confirm_url omitted). That is an approval and stays terminal and deny-by-default; a question (needs_input) is the resumable one. Do not treat them alike.

Mailbox setup is the agent's job now (#2469). When the agent has no usable mailbox — not connected, credentials broken, missing a scope, or connected-but-not-granted — it asks the user about that specific problem via needs_input and fixes it, rather than returning an error telling them to run a CLI command. Two cases are worth knowing: the connected-but-not-granted case needs no browser at all (a local permission write), and connecting Google still requires the user to supply their own OAuth client ID and secret, so expect a sensitive: true question on that path.

Mail-required, calendar-optional (#2730). Every setup/reconnect path — this self-repair flow included — requests the full mail + calendar scope union at consent time, but only the mail scopes gate whether the flow reports success. A user who declines calendar still ends up with a working mailbox; calendar tools raise their own actionable error, naming the exact scope, the first time one is actually called. Do not "fix" a self-repair flow that requests only mail scopes — that narrower request is the bug this issue removed, not a simplification to reintroduce.

Stateful agent surface (/v1/email/agent/*, 0.4.0)

Everything above is stateless — you send a payload, the sidecar analyzes it, no memory, no conversation. The sidecar also hosts a session-scoped, conversational agent that runs the full EmailTriageAgent (memory, personalization, every agent tool) over HTTP. This is the surface the Agent UI uses. It is not wrapped by the typed EmailClient yet — call it directly with fetch against the sidecar's baseUrl:

js
const base = "http://127.0.0.1:8131";
// 1. Start a session (builds the agent; reports memory availability).
await fetch(`${base}/v1/email/agent/session`, {
  method: "POST", headers: { "content-type": "application/json" },
  body: JSON.stringify({ session_id: "s1" }),
});

// 2. Run a turn — the reply streams back as Server-Sent Events.
const res = await fetch(`${base}/v1/email/agent/query`, {
  method: "POST", headers: { "content-type": "application/json", accept: "text/event-stream" },
  body: JSON.stringify({ session_id: "s1", message: "Triage my inbox" }),
});
const reader = res.body.getReader(); const dec = new TextDecoder(); let buf = "";
for (;;) {
  const { value, done } = await reader.read(); if (done) break;
  buf += dec.decode(value, { stream: true });
  let i; while ((i = buf.indexOf("\n\n")) >= 0) {
    const line = buf.slice(0, i).split("\n").find(l => l.startsWith("data: "));
    buf = buf.slice(i + 2);
    if (!line) continue;
    const ev = JSON.parse(line.slice(6));           // {type: "thinking"|"step"|"permission_request"|"run_complete"|...}
    if (ev.type === "permission_request") {         // a gated tool (send/forward/delete/...) is waiting
      await fetch(`${base}/v1/email/agent/confirm-tool`, {
        method: "POST", headers: { "content-type": "application/json" },
        body: JSON.stringify({ session_id: "s1", approved: true }),
      });
    }
    if (ev.type === "run_complete") console.log("answer:", ev.answer);
  }
}

Other endpoints: POST /cancel, DELETE /session/{id}, GET /session/{id}/history, and the runtime memory toggle POST /memory + GET /memory/{id} (enabling memory that was never initialized returns 409, never a silent no-op). One turn at a time per session — an overlapping /query returns 409. See SPEC.md for the full table.

Show full SKILL.md (1,309 more words)Show less
Full autonomy (/v1/email/agent/autonomy/*)

The agent can run proactively at the earn_trust level: it archives low-signal (promotional/spam) mail and marks FYI mail read on its own where your explicit preferences already sanction it (a low-priority sender, or a category you default to archive) or a sender/category has earned enough trust, and always asks before anything destructive (send / forward / RSVP / quarantine). There is no permanent-delete — the agent only ever moves mail to Trash, which is always reversible. Reply drafting is not yet wired into this proactive loop (the policy layer supports it, but no candidate reaches it today). Turn it on and inspect the earned trust:

js
// Turn on full autonomy (levels: off | suggest | earn_trust | full; "off" = kill switch)
await fetch(`${base}/v1/email/agent/autonomy`, {
  method: "POST", headers: { "content-type": "application/json" },
  body: JSON.stringify({ session_id: "s1", level: "earn_trust" }),
});

// Run one observe→decide→act cycle now (the daemon/scheduler drives this in production)
const r = await fetch(`${base}/v1/email/agent/autonomy/run`, {
  method: "POST", headers: { "content-type": "application/json" },
  body: JSON.stringify({ session_id: "s1", max_messages: 25 }),
});
const report = await r.json();
// { level, executed:[…], proposals:[…], decisions:[…], skipped }
// decisions[] explains EVERY candidate considered: { message_id, tool, action, outcome, reason, sender }

// Inspect the earned-trust ledger — autonomy is never a black box
const status = await (await fetch(`${base}/v1/email/agent/autonomy/s1`)).json();
// { level, enabled, trust_min_samples, trust_threshold, trusted_scope_count, scopes:[…] }

The agent learns from your corrections: undoing an auto-executed action — POST /v1/email/agent/autonomy/undo with { session_id, action_id } from the executed[] entry, or the conversational undo_archive_batch tool for a batch archive — is captured as a negative outcome that pulls the sender/category back below the trust bar. (Positive-outcome accrual — trust rising as suggestions are accepted or left standing — is not yet wired, so today the ledger only ratchets trust down.) Every auto-action is reversible with undo. A bad level returns 400; an unknown session returns 404; undoing an unknown/expired action_id returns 409; /run while the level is off returns 409 too — it refuses rather than returning the same 200 shape a real, found-nothing cycle would (#2528).

The Python host also ships a thin-client CLI over this same surface: gaia email autonomy {status|set-level|pause|resume|run|trust|kill} (#2516).

Skill sets — disabled in this release

The sidecar bundles six Agent Skills (personal: inbox-triage, newsletter-digest, travel-itinerary; work: inbox-triage, meeting-scheduling, action-item-extraction, escalation-routing), but the agent's manifest currently declares no sets, so none of them loads. A personal and a work mailbox get identical behaviour. This is deliberate: the skills are held back until an eval run shows they improve triage.

What that means for your integration:

  • Do not pass --skill-set or GAIA_EMAIL_SKILL_SET. Any value fails at startup with ... but this agent declares no skill sets — Agent Skills are switched off in this build. There is no working name. This is fail-loud behaviour, not a bug to work around.
  • GAIA_EMAIL_ACCOUNT_TYPE still validates but selects nothing.
  • Nothing in the API changes either way — same endpoints, same tools, same permissions. Re-enabling happens inside the agent's gaia-agent.yaml; your code does not change.

Running in a server / long-lived app

  • fetchBinary is a build step, not per request (network + SHA verify). Run it once; resolveBinaryPath at runtime.
  • Spawn once at boot, hold the Sidecar handle for the process lifetime — never per request.
  • Low concurrency. One local Lemonade model slot, so parallel triage calls serialize. Cap inflight calls.
  • Cleanup is automatic (default autoCleanup): the sidecar's child is reaped on exit/crash/signal. Call shutdown for a graceful stop (it rejects, naming the pid, if the sidecar survives the forced kill), or autoCleanup: false to wire signals yourself. The package does not restart a crashed sidecar.

Fast local iteration (when you need to fix the agent, not just call it)

The steps above spawn a frozen binary — you can't edit it. To debug or improve the agent, run its Python source and attach the same client. The frozen binary is that source frozen, so the contract is identical; only the base URL changes.

bash
pip install -e hub/agents/email/python     # editable install
gaia-agent-email serve --reload            # source server on 127.0.0.1:8131, auto-reload
ts
import { connectSidecar } from "@amd-gaia/agent-email";
// Attaches (health + version check), spawns nothing, token off in dev:
const dev = await connectSidecar({ baseUrl: "http://127.0.0.1:8131" });
await dev.client.triage({ payload: { /* … */ } });
// Edit the Python under gaia_agent_email/, save → reload → re-run. Seconds.

npx @amd-gaia/agent-email dev launches the serve process for you (--python <path> to use a specific venv). There's no child on the returned handle and nothing to shutdown() — you own the serve process (Ctrl+C). Switch back to production by using startSidecar (frozen binary) instead of connectSidecar; the client calls are unchanged.

Prerequisites — the agent needs a local model

The sidecar runs the LLM via Lemonade Server, which this package does not install. Before triage/draft/send succeed, the host must have:

  1. A running Lemonade Server. GAIA's daemon starts and supervises one, so gaia daemon start is normally all that is needed.
  2. The model pulled (gaia init installs Lemonade and downloads the default model).

Until then the binary boots, but the first triage returns HTTP 502.

Gotchas (read before debugging)

  • Every /v1/email/* call needs the session token (#1706). sidecar.client carries it automatically; a client you construct yourself must pass authToken (from sidecar.authToken) or every call is 401. Absent or non-loopback Host → 400, non-loopback browser Origin → 403. /health · /version · /v1/email/spec · /v1/email/playground are exempt.
  • health() is liveness-only. A green /health means the REST surface is up, NOT that triage will work. For real readiness call init() (GET /v1/email/init, #1795) — it probes Lemonade + the triage model and returns the InitResponse on both the ready (200) and not-ready (503) paths, so branch on .ready / read .hint. POST /v1/email/init streams a model-pull (no wrapper yet).
  • HTTP 502 from triage → Lemonade isn't running/reachable, or the model isn't pulled. It is not a bug in this package.
  • Addresses are objects, not strings. to (and triage's from / principal) are { email, name? }; to is a non-empty array of them. A plain string → 422.
  • send needs the draft confirmation_token (missing/invalid → 403), but it takes no OAuth token — the mailbox is resolved from the host's GAIA connector store (no mailbox connected → 503). The read-only search / prescan resolve the mailbox the same way (503 with none, 400 with 2+). Triage and draft need no connector.
  • Attachments bind to the token (schema 2.2). Re-send the exact attachments array you drafted with — the metadata-only draft echo has no content_base64, so spreading the echo into send loses the files. A swapped/extra/missing attachment → 403; bad base64, a malformed MIME type, or > 25 MB decoded → 422; Outlook additionally rejects files over 3 MB (Graph simple-attach limit).
  • archive / quarantine are gated like send, but their token comes from confirmAction (not draft) and is bound to the (action, message_id) — a token for one can't authorize the other. Undo with unarchive (pass the returned batch_id) / unquarantine (pass the action_id) within 30s; past the window the reversal returns 409 (restore manually in the mail client). For Outlook, use the post_archive_id from the archive response — the folder move changes the id.
  • Cleanup is automatic by default — the sidecar is reaped on exit/crash/signal; only autoCleanup: false (or a hard SIGKILL of your process) can orphan the child. shutdown stays the graceful stop.
  • OAuth forward-out is daemon-only (sidecar contract 2.5, #2154). The /v1/connections/{provider} intake exists for the GAIA Agent UI daemon to forward short-lived access tokens to the sidecar (the sidecar never holds the refresh token). A standalone integrator using this package does not call it — keep resolving the mailbox from the host's GAIA connector store as before. There is no client.forwardConnection() method, by design.
  • Some capabilities are agent-loop-only — no REST endpoint, no client method. Scheduled send / snooze (#1609), voice / style-matched drafting (#1607 — build_voice_profile learns a local style profile from Sent mail so drafts come out in the user's own voice), follow-up tracking (#1606 — check_followups flags sent mail still awaiting a reply, detection only), and waiting-on-you detection (#2581 — list_waiting_on_you flags INBOUND mail awaiting the user's reply; it only qualifies a message that has both a genuine ask/meeting-time signal and corroboration that it's real correspondence) all run in the agent tool loop. The REST contract has no routes for them yet, so don't look for client.scheduleSend() / client.snooze() / a voice, follow-up, or waiting-on-you method — they don't exist (and none of these moves SCHEMA_VERSION).
  • --skill-set / GAIA_EMAIL_SKILL_SET always fail right now. Agent Skills are disabled in this release, so the agent declares no sets and every name is invalid. Don't wire either into your spawn options.
  • ESM-only. require("@amd-gaia/agent-email") fails; use import / dynamic import().

Verify the integration

A green path looks like: fetchBinary succeeds → startSidecar resolves → client.triage(...) returns a result with a category and summary. If triage 502s, start Lemonade and pull the model, then retry — the rest of your integration is fine.

To eyeball the agent by hand without writing any code, run npx @amd-gaia/agent-email playground — it fetches the binary, starts the sidecar, and opens an interactive page where you can fire triage/draft and see a stack-health check.

For the full endpoint list, lifecycle internals, and connector details, see SPEC.md next to this file.

© amd, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file

Files

SKILL.md and 43 other files (assets) in hub/agents/email/npm of amd/gaia.

  • SKILL.md
  • .gitignore
  • CHANGELOG.md
  • EVALUATION.md
  • LICENSE
  • README.md
  • SCORECARD.md
  • SPEC.md
  • assets/architecture.html
  • assets/architecture.webp
  • binaries.lock.json
  • examples/demo.mjs
  • package-lock.json
  • package.json
  • src/cli.ts
  • src/client-entry.ts
  • src/client.ts
  • src/errors.ts
  • … and 26 more

Open the folder on GitHubat commit 6c3bb5c

Compare with similar skills

Integrate Agent Email next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.

Integrate Agent Email compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
Integrate Agent Email this skillamd/gaia1.6k—~7.4kAutomated safety check: PassMIT
Getting Startedlive-codes/livecodes1.5k—~1.6kAutomated safety check: PassMIT
Golem Add LLM TSgolemcloud/golem1.5k—~1.3kAutomated safety check: PassCustom licence
Duel Agents2aronS/Duel-Agents746—~228Automated safety check: PassMIT
Duel Agents2aronS/Duel-Agents746—~298Automated safety check: NotesMIT
Gemini API Best Practicestakeshy/obsidian-gemini-helper117—~1.1kAutomated safety check: PassMIT

Similar skills

  • Getting Started

    live-codes/livecodes

    Quick start for standalone app at livecodes.io, embedding playgrounds with CDN or npm, and self-hosting basics.

    1.5k GitHub stars~1.6k tokensUpdated today
    AI & LLM EngineeringAuto-check passed
  • Golem Add LLM TS

    golemcloud/golem

    Adding LLM and AI capabilities to a TypeScript Golem agent. An agent skill from golemcloud/golem.

    1.5k GitHub stars~1.3k tokensUpdated today
    AI & LLM EngineeringAuto-check passed
  • Duel Agents

    2aronS/Duel-Agents

    Use Duel Agents model routing in Cursor. An agent skill from 2aronS/Duel-Agents.

    746 GitHub stars~228 tokensUpdated 3 mo ago
    AI & LLM EngineeringAuto-check passed
  • Duel Agents

    2aronS/Duel-Agents

    Configure and use Duel Agents model routing in Claude Code. An agent skill from 2aronS/Duel-Agents.

    746 GitHub stars~298 tokensUpdated 3 mo ago
    AI & LLM EngineeringAuto-check: notes
  • Gemini API Best Practices

    takeshy/obsidian-gemini-helper

    Reviews and corrects Gemini API integration code against Google's published practices, covering SDK choice, safety settings, finishReason checks, tools, streaming and thinking.

    117 GitHub stars~1.1k tokensUpdated today
    AI & LLM EngineeringAuto-check passed
  • Frontmcp Extensibility

    agentfront/frontmcp

    A skill your agent uses when extending FrontMCP beyond the core SDK by integrating external npm packages, libraries, or third-party services into providers and tools.

    146 GitHub stars~3.2k tokensUpdated today
    AI & LLM EngineeringAuto-check passed

More from amd/gaia

All 44 skills in this repo
  • Adds a release eval scorecard to a GAIA hub agent by writing a harness adapter, running a real eval, and wiring the result into the agent's README and release gate.

    1.6k GitHub stars~2.6k tokensUpdated today
    Auto-check passed
  • Walks through releasing a GAIA sidecar agent as a frozen binary plus npm client through the tag-triggered Agent Hub CI pipeline, with a human gate before publishing.

    1.6k GitHub stars~3.6k tokensUpdated today
    Auto-check passed
  • Mines local Claude Code session transcripts with a deterministic Python pipeline to show what the agent is actually used for, how often it fails and what it costs.

    1.6k GitHub stars~2.3k tokensUpdated today
    Auto-check passed
  • Benchmarks AMD's GAIA agent against Claude Code and across models on quality, honesty, steps, tokens, time and real cost, using gaia eval tasks.

    1.6k GitHub stars~1.8k tokensUpdated today
    Auto-check passed
  • Guides safe code changes by finding the right file with grep or semantic search, reading before editing, reproducing bugs first, and proving a fix with a real test run.

    1.6k GitHub stars~2.1k tokensUpdated today
    Auto-check passed
  • Walks through scaffolding, writing and testing a new GAIA agent as a Python class with the SDK, from the base Agent subclass to registered tool methods.

    1.6k GitHub stars~1.5k tokensUpdated today
    Auto-check passed

Questions about Integrate Agent Email

What does Integrate Agent Email do?

A skill your agent uses when integrating the @amd-gaia/agent-email npm package — embedding the GAIA email agent (a local triage/draft/send sidecar) into a Node, TypeScript, or Electron app. Integrate Agent Email is an agent skill from amd/gaia. Use when integrating the @amd-gaia/agent-email npm package — embedding the GAIA email agent (a local triage/draft/send sidecar) into a Node, TypeScript, or Electron app.

When should I use Integrate Agent Email?

Integrate Agent Email fits situations like: integrating the @amd-gaia/agent-email npm package — embedding the GAIA email agent (a local triage/draft/send sidecar) into a Node; tasks that involve Embeddings.

How do I install Integrate Agent Email in Claude Code?

Run `npx skills add amd/gaia --skill integrate-agent-email -a claude-code`. Or copy the skill folder (hub/agents/email/npm in amd/gaia) into .claude/skills/integrate-agent-email in your project. Claude Code loads it when a task matches its description.

How do I install Integrate Agent Email in Codex?

Run `npx skills add amd/gaia --skill integrate-agent-email -a codex`. Or copy the skill folder (hub/agents/email/npm in amd/gaia) into .agents/skills/integrate-agent-email in your project. Codex loads it when a task matches its description.

Can I use Integrate Agent Email in Cursor, Gemini CLI or GitHub Copilot?

Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add amd/gaia --skill integrate-agent-email -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/integrate-agent-email, .gemini/skills/integrate-agent-email, .github/skills/integrate-agent-email and .opencode/skills/integrate-agent-email in your project.

What does Integrate Agent Email need to run?

Going by SKILL.md and its folder, Integrate Agent Email needs TypeScript and JavaScript for the scripts in its folder and the command-line tools its instructions call (npx, npm and pip). Our summary lists: Node.js.

Does Integrate Agent Email access the network?

SKILL.md contains no URLs. Its commands use npx, npm and pip, which can reach the network depending on how they are called. This is read from the text; nothing was executed.

Is Integrate Agent Email safe to install?

Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.

What licence does Integrate Agent Email use?

Integrate Agent Email is published under the MIT licence (from the LICENSE file in the skill folder). It allows redistribution, so the full SKILL.md is shown on this page.

How many tokens does Integrate Agent Email use?

About 7.4k tokens (SKILL.md is roughly 29k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.

What are the alternatives to Integrate Agent Email?

Skills that share tags, products or a category with Integrate Agent Email: Getting Started (live-codes/livecodes, 1.5k stars), Golem Add LLM TS (golemcloud/golem, 1.5k stars), Duel Agents (2aronS/Duel-Agents, 746 stars) and Duel Agents (2aronS/Duel-Agents, 746 stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.

Who maintains Integrate Agent Email?

amd (a GitHub organization) maintains it in amd/gaia, which has 1,580 GitHub stars. The repository holds 44 skills in this directory. The repository was last updated on October 6, 2026.

Source: amd/gaia on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.