Getting Started
live-codes/livecodes
Quick start for standalone app at livecodes.io, embedding playgrounds with CDN or npm, and self-hosting basics.
A skill your agent uses when integrating the @amd-gaia/gaia npm package — running GAIA's flagship agent, or embedding its local sidecar into a Node, TypeScript, or Electron app.
The automated check flagged lines worth reading first. See the safety section below.
$ npx skills add amd/gaia --skill integrate-gaia -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install amd/gaia integrate-gaia --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/amd/gaia.git skills-src && mkdir -p .claude/skills && cp -r skills-src/hub/agents/gaia/npm .claude/skills/integrate-gaia && rm -rf skills-srcUse ~/.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/
Install the "integrate-gaia" agent skill from https://github.com/amd/gaia/tree/main/hub/agents/gaia/npm into .claude/skills/integrate-gaia/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "integrate-gaia", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/amd/gaia/tree/main/hub/agents/gaia/npmType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add amd/gaia --skill integrate-gaia -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install amd/gaia integrate-gaia --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/amd/gaia.git skills-src && mkdir -p .agents/skills && cp -r skills-src/hub/agents/gaia/npm .agents/skills/integrate-gaia && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "integrate-gaia" agent skill from https://github.com/amd/gaia/tree/main/hub/agents/gaia/npm into .agents/skills/integrate-gaia/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "integrate-gaia", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add amd/gaia --skill integrate-gaia -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install amd/gaia integrate-gaia --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/amd/gaia.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/hub/agents/gaia/npm .cursor/skills/integrate-gaia && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "integrate-gaia" agent skill from https://github.com/amd/gaia/tree/main/hub/agents/gaia/npm into .cursor/skills/integrate-gaia/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "integrate-gaia", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/amd/gaia.git --path hub/agents/gaia/npm--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add amd/gaia --skill integrate-gaia -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install amd/gaia integrate-gaia --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/amd/gaia.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/hub/agents/gaia/npm .gemini/skills/integrate-gaia && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "integrate-gaia" agent skill from https://github.com/amd/gaia/tree/main/hub/agents/gaia/npm into .gemini/skills/integrate-gaia/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "integrate-gaia", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install amd/gaia integrate-gaiaInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add amd/gaia --skill integrate-gaia -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/amd/gaia.git skills-src && mkdir -p .github/skills && cp -r skills-src/hub/agents/gaia/npm .github/skills/integrate-gaia && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "integrate-gaia" agent skill from https://github.com/amd/gaia/tree/main/hub/agents/gaia/npm into .github/skills/integrate-gaia/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "integrate-gaia", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add amd/gaia --skill integrate-gaia -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install amd/gaia integrate-gaia --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/amd/gaia.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/hub/agents/gaia/npm .opencode/skills/integrate-gaia && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "integrate-gaia" agent skill from https://github.com/amd/gaia/tree/main/hub/agents/gaia/npm into .opencode/skills/integrate-gaia/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "integrate-gaia", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
integrate-gaiaA skill your agent uses when integrating the @amd-gaia/gaia npm package — running GAIA's flagship agent, or embedding its local sidecar into a Node, TypeScript, or Electron app.
Integrate Gaia is an agent skill from amd/gaia. Use when integrating the @amd-gaia/gaia npm package — running GAIA's flagship agent, or embedding its local sidecar into a Node, TypeScript, or Electron app. Covers the one-command path, the SHA-256 integrity gate, platform coverage, starting the sidecar, the /v1/gaia/query SSE contract, and the gotchas that will bite you.
Its SKILL.md is about 9.7k tokens, which your agent loads only when the skill is triggered. The skill folder holds 29 other files (for example `CHANGELOG.md`, `README.md` and `SCORECARD.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.
12 steps, taken from the step headings in SKILL.md.
Read from SKILL.md and the folder at commit 05fb50b. It shows what the files ask for, not the result of running them.
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.
Ships script files (TypeScript, from the files we listed), which the agent can run.
Shell commands in SKILL.md call:
npxcurlnpmFrom the folder's file list and the shell code blocks in SKILL.md.
Hosts in commands or code, which the agent is likely to contact:
amd-gaia.aiFrom URLs in SKILL.md, links to its own repository left out.
Names these keys or tokens, usually read from environment variables:
GAIA_GAIA_SIDECAR_TOKENFrom names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Integrate Gaia loads about 9.7k tokens when it runs. Until then it costs about 85 tokens; SKILL.md has 4,923 words of instructions outside code blocks.
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.
The automated check found patterns that need a careful read before installing.
_NO_DOTENV=1` stops the sidecar loading `.env` files.it.** Reads refuse secrets — `.env`, `id_rsa`, `credentials.json`, `.netrc`,`.pem`/`.key`, and everything under `~/.ssh`, `~/.aws`, `~/.gnupg`, `~/.kube` —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.
The full file from amd/gaia at commit 05fb50b, republished under its MIT licence (© amd). 4,923 words, ~9,656 tokens.
.claude/skills/integrate-gaia/SKILL.md (or your agent's skills folder). This skill also uses 27 other files; get the full folder from GitHub.@amd-gaia/gaia@amd-gaia/gaia delivers two binaries and owns their process lifecycle: the
frozen agent sidecar (gaia-agent) and the Go terminal UI (gaia-tui).
It ships no agent logic of its own, and it builds neither binary at install
time — both are published artifacts it downloads and verifies. The terminal UI
is the published terminal-hub component, the same binary a full GAIA install
runs as gaia tui, so its behaviour cannot differ from that one. Everything runs on the local machine against
a local model server — nothing you type or index leaves it.
Two ways in:
npx @amd-gaia/gaia — fetch, verify, launch the terminal UI. What a human runs.POST /v1/gaia/query
yourself. What you use when embedding GAIA in an app.This file is NOT one of the agent's own skills. It is the integration playbook: how you wire this package into an app. The agent separately loads Agent Skills into its own prompt at runtime from
gaia_agent/skills/<name>/SKILL.md. Same filename, different artifact — don't ship this one as an agent skill. See Skills.
npx @amd-gaia/gaia # no install step; fetches what it needs on first run
npm install @amd-gaia/gaia # when you want the programmatic exportsThe package is ESM-only ("type": "module") and needs Node 18+ for the
built-in fetch. Use import, not require; from CommonJS use
await import("@amd-gaia/gaia").
@amd-gaia/gaiapublishes with this release — it is not on npm yet. Until the release tag lands,npx @amd-gaia/gaiawill not resolve. Run the agent from a source checkout in the meantime (see the guide).
npx @amd-gaia/gaia actually does`${process.platform}-${process.arch}`).binaries.lock.json, the checksum manifest published with this exact
package version. Each component records its own hub lane, version, artifact
name and hash — they do not share a base URL.~/.gaia/agents/gaia/ and the TUI into
~/.gaia/npm-cache/gaia-<version>/.~/.gaia/agents/gaia/.installed — the record the daemon and the TUI
both read to decide the sidecar is installed. Written on a cache hit too, so
an install staged by an earlier release repairs itself.run deliberately does not spawn a sidecar. The TUI reaches agents through
the GAIA daemon's relay and never holds a sidecar token, and the daemon is what
spawns and supervises the sidecar — from exactly the directory step 4 wrote to.
A second sidecar started here would only fight the daemon's own for port 8141. Use
gaia serve when you want to own the process.
Other commands: gaia fetch (download + verify, print JSON, exit),
gaia serve (sidecar alone), gaia version (per-component version, source URL,
and platform matrix). Anything after a bare -- goes to the TUI verbatim.
| Component | Hub lane | Artifact names |
|---|---|---|
sidecar | agents/gaia/<agentVersion>/ | gaia-agent-<platformKey>[.exe] |
tui | agents/terminal-hub/<componentVersion>/ | gaia-<goPlatform>[.exe] |
Two things follow from that, and both bite if you assume otherwise:
lock.agentVersion is this
package's version; components.tui.componentVersion is the terminal-hub release
it consumes. They will not match.win-x64 / win-arm64, not win32-*. Platform
keys stay in Node's namespace (process.platform says win32); only the
filename crosses over. Never build a TUI URL by interpolating a platform key —
read filename from the lock entry.The SHA-256 check is this package's security boundary, and there is no flag, env var, or option that relaxes it.
IntegrityError naming expected vs actual and
leaves nothing on disk.sha256 in the lock is PENDING-replace-with-real-sha256, and
a value that is all zeros or contains PENDING (case-insensitive) is treated
as a placeholder. You get a PlatformError, not a download.If you need to run against a locally built binary, build it and point
startSidecar / runTui at it directly. The fetcher will not be talked into it.
terminal-hub publishes the TUI for all six targets. The sidecar is a PyInstaller
freeze built on the platform it targets, and there is no arm64 Linux and no arm64
Windows sidecar build.
| Platform key | Sidecar | TUI |
|---|---|---|
win32-x64 | yes | yes |
darwin-arm64 | yes | yes |
darwin-x64 | yes | yes |
linux-x64 | yes | yes |
linux-arm64 | no | yes |
win32-arm64 | no | yes |
Resolving the sidecar on those two keys raises PlatformError naming the
platform and the supported set. It is not silently skipped, and the TUI is never
launched with no agent behind it. npx @amd-gaia/gaia version prints the matrix
for the version you have.
The agent thinks with a model hosted by Lemonade Server, which this package does not install. Required before any query succeeds:
gaia daemon start is normally all that is needed (gaia init
also installs and starts it on first run). Qwen3.8 Flash Next needs
v2026.39.1 or newer, the version gaia init installs; older servers
cannot load it.gaia init. It picks
Qwen3.6-35B-A3B-GGUF where a GPU holds it (~27 GB for models,
weights plus context cache — a 64 GB+ Strix Halo or a 32 GB GPU) and
Gemma-4-E4B-it-GGUF everywhere else, CPU-only PCs included, and records the pick as
default_model in ~/.gaia/config.json. On 128 GB-class PCs the user may
switch to the multimodal user.Qwen3.8-Flash-Next-GGUF with gaia config set default_model. model.id below names whichever this machine uses.
gaia download takes no model argument — naming one makes it exit 2. To
pull a single model instead, repeat the command GET /v1/gaia/init gives
you: it names the Lemonade client this machine actually has. Do not invent
one.Do not guess — ask the sidecar. GET /v1/gaia/init is a read-only preflight
(it never pulls or loads) that probes Lemonade, compares its version to the
floor, and checks the model is present:
curl -s http://127.0.0.1:8141/v1/gaia/initIt answers 200 when ready and 503 when not, with the same body shape
either way — so branch on .ready and render .hint, never on the status code
alone:
{
"ready": false,
"lemonade": { "reachable": false, "base_url": "…", "version": null,
"min_version": "10.2.0", "compatible": null },
"model": { "id": "Gemma-4-E4B-it-GGUF", "present": false,
"loadable": null, "ctx_size": null },
// The manual fallback ("Otherwise: …") is resolved for the host's Lemonade
// install (tray app, macOS app, systemd service, or CLI) — render it verbatim.
"hint": "Local Lemonade Server is not reachable at …. GAIA starts it automatically — run `gaia daemon start` if the background service is not running. Otherwise: Start the Lemonade app from Applications, then retry. Or set LEMONADE_BASE_URL to a running server. See https://amd-gaia.ai/docs/guides/gaia."
}lemonade.compatible: null is indeterminate, not a pass — the version could
not be parsed. Render it as unknown.
GET /health is liveness only. A green /health means the REST surface is up;
it says nothing about whether a query will work.
import { fetchAll, startSidecar, shutdown } from "@amd-gaia/gaia";
// Fetch + SHA-256 verify both binaries. Build step, not per request.
const { sidecar, tui } = await fetchAll();
// Spawn -> poll /health -> check the contract apiVersion, in one call.
const proc = await startSidecar({ binaryPath: sidecar.binaryPath }); // port 8141
// ... drive proc.baseUrl ...
await shutdown(proc); // tree-kill; auto-cleanup also reaps on exitfetchAll(opts?) returns { sidecar, tui, lock }. Each result carries
binaryPath, platformKey, sha256, cached, url. For one component use
fetchBinary({ component: "sidecar" | "tui", outDir }).
startSidecar throws if the binary can't start, never becomes healthy
(HealthTimeoutError, 60 s default — a cold one-file build unpacks first), or
reports an apiVersion whose major differs from this package's
(VersionMismatchError). On any failure it shuts the sidecar down before
rethrowing, so a failed start never leaks a process.
Tree-kill is not optional. The frozen sidecar spawns a child uvicorn
process that child.kill() on the parent does not reap, which leaves port 8141
bound. shutdown kills the group (POSIX SIGTERM to -pid, escalating to
SIGKILL; Windows taskkill /T /F). autoCleanup (default true) also reaps
on exit, SIGINT/SIGTERM/SIGHUP, uncaughtException, and
unhandledRejection. A SIGKILL of your process is the one case nothing
in-process can catch.
Mint a caller token, or you are running the sidecar unauthenticated. The
sidecar requires Authorization: Bearer <token> on every /v1/gaia/*
request, and skips the check only when neither token env var is set — dev
mode, which is what startSidecar gives you, because this package mints
nothing. Loopback binding is not the boundary: this agent has shell and file
tools. Mint your own and pass it through the env option (StartOptions
extends SpawnOptions, so it merges over process.env):
import { randomBytes } from "node:crypto";
const token = randomBytes(32).toString("hex");
const proc = await startSidecar({
binaryPath: sidecar.binaryPath,
env: { GAIA_GAIA_SIDECAR_TOKEN: token }, // or ..._TOKEN_FILE, a 0600 path
});Then send authorization: "Bearer " + token on every /v1/gaia/* call.
/health, /version, and /v1/gaia/version are exempt, so startSidecar's
own health-and-version handshake works either way. If you did not spawn the
sidecar — you are talking to one the GAIA daemon started — the token is the
daemon's, delivered to the sidecar as GAIA_GAIA_SIDECAR_TOKEN_FILE (a 0600
file path); read it from there, don't invent one. A 401 whose detail names
both env vars means you sent the wrong token or none.
The programs the agent starts (shell commands, MCP servers, CLI installs and
sign-ins, native hub agents, media tools) never inherit this token
or GAIA's other internal credentials. To withhold your own variables from
them too, list the names in GAIA_CHILD_ENV_DENY (comma or space separated);
GAIA_NO_DOTENV=1 stops the sidecar loading .env files.
Or skip the code entirely and let the CLI own it:
npx @amd-gaia/gaia serve --port 8141
curl http://127.0.0.1:8141/healthPOST /v1/gaia/queryThis is the whole agent surface. There is no typed query client in this
package — call it with plain fetch. Contract version 2.15; the stream is
text/event-stream terminated by exactly one final or error.
Request body (extra: "forbid" — an unknown field is a 422, not ignored):
| Field | Required | Notes |
|---|---|---|
query | yes | Non-empty. |
run_id | yes | You mint it, and it must be a UUID (non-UUID → 422). It is the cancel handle, valid from the instant the request is sent. |
context | yes | Transcript slice, pushed in the body — may be [], never absent. Each item { role, content }; role ∈ user / assistant / system / tool. |
session_id | no | Contract ≥ 2.12. Pass it. The agent persists its indexed-document set per session — without it, it forgets a document between the turn that indexed it and the next question. 1–128 characters from A-Z a-z 0-9 . _ - (a UUID works); anything else is a 400. |
can_answer_questions | no | Set false for one-shot / batch runs so the agent resolves ambiguity itself instead of parking on a question nobody can see. |
model | no | Overrides the model id. On a retained session_id a different model is switched in place (contract ≥ 2.14), keeping the conversation and any loaded skills; a switch that fails is a 409 and leaves the session on its previous model. |
provider | no | "lemonade" (the default backend) or "claude", which sends the conversation to Anthropic's API instead of the local server. Anything else is a 400. When you name it, model must agree: under "claude" it must be a claude-* id, under "lemonade" it must not be. A mismatch is a 400, on a new session or an existing one. Omit it and the model decides instead — a claude-* id alone reaches Anthropic, any other id runs locally — so there is nothing to mismatch. On a retained session_id, naming a different provider switches the session to it in place, onto model or, without one, the provider's default model; omitting both leaves the session where it is. |
max_steps | no | ≥ 1. |
import { randomUUID } from "node:crypto";
const runId = randomUUID();
const res = await fetch(`${proc.baseUrl}/v1/gaia/query`, {
method: "POST",
headers: {
"content-type": "application/json",
accept: "text/event-stream",
authorization: `Bearer ${token}`, // required unless the sidecar is in dev mode — §6
},
body: JSON.stringify({
query: "Summarize the PDFs in ~/Documents/reports",
run_id: runId,
context: [],
session_id: "s1",
can_answer_questions: false,
}),
});
const reader = res.body!.getReader();
const dec = new TextDecoder();
let buf = "";
outer: for (;;) {
const { value, done } = await reader.read();
if (done) break;
buf += dec.decode(value, { stream: true });
let i: number;
while ((i = buf.indexOf("\n\n")) >= 0) {
const frame = buf.slice(0, i);
buf = buf.slice(i + 2);
const line = frame.split("\n").find((l) => l.startsWith("data: "));
if (!line) continue; // ":" frames are keepalive comments
const ev = JSON.parse(line.slice(6));
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; // see §8 — a refusal follows
case "needs_input": /* answer it — see below */ break;
case "final": console.log(ev.answer); break outer; // terminal
case "error": console.error(ev.detail); break outer; // terminal
default: console.warn("unsupported event", ev); // future additive type
}
}
}The canonical event shapes, as emitted:
| Event | Shape |
|---|---|
status | { type, message, phase?, words?, chars? } — progress and reasoning narration. phase (loading_model, downloading_model, reading, reasoning, tool_call) names what the model is doing; show it as the live line, not as narration |
token | { type, delta } — answer text to append |
tool_call | { type, tool, args } |
tool_result | { type, tool, data, render? } |
needs_confirmation | { type, run_id, action, summary, confirm_id?, always_scope? } — answer it, or it ends the run; see §8 |
needs_input | { type, run_id, request_id, question, options[], allow_free_text, sensitive, respond_url, timeout_seconds? } |
final | { type, answer, usage? } — terminal |
error | { type, detail, status } — terminal, surface detail verbatim |
Rules a client must respect:
: keepalive SSE comments every 10 s. Skip lines that
aren't data: and reset your read-idle timer on them — a long tool call is not
a dead stream.needs_input, don't restart. The run is parked on the same
stream. POST /v1/gaia/query/{run_id}/respond with
{ request_id, response } and keep reading the existing stream — a fresh
/query POST abandons the paused run. Unknown run → 404; a request_id that
is no longer pending → 409 (both loud, never a silent drop). Render each
option's description, and mask the input when sensitive is set.POST /v1/gaia/query/{run_id}/cancel. It returns
{ run_id, cancelled } — an unknown id reports cancelled: false with a
200, not a 404, because a cancel racing a normal completion is expected.
Dropping the HTTP connection also cancels the run.POST /v1/gaia/query/{run_id}/followup
(contract ≥ 2.15, body { text }). The run is not interrupted and no second
turn starts: the agent folds the text in at its next step boundary and
answers it alongside what it was already doing. Unknown run → 404, an
agent that cannot take one → 409; both are loud, so hold the message
rather than telling the user it was sent. /query is stateless, so put a
delivered follow-up into the next turn's context yourself, between that
turn's question and its answer./v1/gaia/query, a gated tool asks — when you can answerRead this before you design a workflow around it. This section is about the HTTP surface — the agent's other transport can collect an approval; see SPEC §5.5.
Twelve of the agent's 95 tools mutate the machine and need explicit approval
before they run. Nine sit in the base TOOLS_REQUIRING_CONFIRMATION set —
write_file, edit_file, run_shell_command,
wait_for_condition, which re-runs a shell command until it succeeds,
execute_python_file, run_python, notify_desktop, which spawns
a PowerShell child on Windows to draw the notification, and install_cli /
sign_in_cli, which install software and sign a CLI in to the user's
account — and the flagship adds three of its own
(CONFIRMATION_REQUIRED_TOOLS): install_skill, capture_skill, and
remove_skill, because installing or capturing a skill writes third-party
content under ~/.gaia/skills and removing one deletes it. A capture that does
land is additionally code-inert: its instructions load, but any
tools.py/scripts stay unregistered until a human runs
gaia skill promote <name> in a terminal. Everything else — reading,
indexing, querying, web fetching, memory, and the read-only check_cli_setup
— runs without asking.
The eight live-browser tools are gated by a hook, not a set, so a name
alone does not tell you whether a call will ask. browser_login always asks.
browser_click / browser_type ask when the call acts inside a session the
run signed in to, or when the target element's label reads irreversible, and
run unasked on the open web. The hook fails closed: if the browser cannot
be asked what state it is in, the call is treated as authenticated and prompts.
Design a workflow around the approval path, not around a fixed tool list.
Contract ≥ 2.14 can answer one. Send a session_id and leave
can_answer_questions unset (or true). The stream emits needs_confirmation
carrying a confirm_id and then stays open while the agent waits:
data: {"type":"needs_confirmation","run_id":"…","action":"run_shell_command","summary":"python -m pytest -q tests/","confirm_id":"…","always_scope":"pytest","risk":"execute"}Answer it on the same run, then keep reading the stream:
POST /v1/gaia/query/{run_id}/tool_decision
{ "decision": "allow" | "deny" | "always" | "timeout", "confirm_id": "…" }summary is the command or call itself, shown once — your UI supplies the
question. risk says what the call does (read, write, execute,
destructive); label by it rather than by tool name, since run_shell_command
is pytest on one call and rm -rf on the next. always grants the family
named in always_scope for the rest of the session (pytest covers every
spelling of a test run) — say so in your UI, because it stops asking; no
always_scope means don't offer it. If your prompt expires unanswered, send
timeout, not deny: it still refuses, but the agent tells the user the
request timed out instead of claiming they said no. Unknown run → 404; a
prompt that is no longer pending → 409; any other decision → 422. All loud,
never a silent drop. Send confirm_id: without it a late answer resolves
whichever prompt replaced the one it was typed against.
To stop being asked for a whole session:
POST /v1/gaia/sessions/{session_id}/bypass
{ "enabled": true }It applies to the very next gated tool, including one in a turn already running,
and an unknown session is a 404 rather than a new one. A malformed
session_id is a 400, same as on /query.
That bypass stops the prompts; it does not open the shell. The stdio
transport's --bypass-permissions does both — it runs gated tools unasked and
lifts the shell tool's guardrails (redirection and the other shell-only
operators, the read-only binary allowlist, the rate limit). That second half is
arbitrary code execution, appropriate for one local parent on a private pipe and
not for a bound socket, so it stays on stdio: HTTP sessions never lift the shell
gates, and the request body rejects unknown fields so a client cannot ask. See
SPEC §5.5.
A run nobody can answer is still refused. With can_answer_questions: false,
or with no session_id, the server emits needs_confirmation, follows it
immediately with a terminal final saying it stopped before running the action,
and cancels the run:
data: {"type":"final","answer":"I stopped before running 'write_file' because it needs your explicit approval, and this streaming surface cannot collect that yet. …"}That is deny-by-default, not an oversight: parking a batch run on a prompt
nobody will ever see reads as a hang. So a one-shot integration cannot run those
nine tools — pass a session_id and answer, or perform the mutation from your
own code and let the agent do the reading and reasoning.
The agent's file, document, and data tools are confined to a set of allowed paths, and the default is the user's home directory. That is the honest scope for a personal document agent, and it is still a real boundary — system directories, program files, and other users' homes are refused, with the check run against the resolved path so a symlink out of scope doesn't slip through.
Being in scope is not the same as being safe, and two denylists apply inside
it. Reads refuse secrets — .env, id_rsa, credentials.json, .netrc,
.pem/.key, and everything under ~/.ssh, ~/.aws, ~/.gnupg, ~/.kube —
even in an allowed directory. Writes additionally refuse anything that executes
on its own: shell startup files, PowerShell profiles, ~/.config/autostart/*,
systemd user units, LaunchAgents, and a repo's .git/ (hooks and config). Both
come back as a structured error naming the file and the reason, so do not plan an
integration around reading a credential file or editing a shell rc — perform
those from your own code.
The agent also gets its own scratch directory for throwaway scripts and intermediate files, so they stay out of the user's project. It is created per agent under the system temp dir, deleted when the agent closes, and is the only part of the temp dir the agent may use.
In 0.1.1 narrowing it is a construction-time setting only. The packaged
sidecar exposes no flag or env var for allowed_paths (its CLI accepts only
--host and --port), so restricting the scope means embedding GaiaAgent in
your own Python process:
from gaia_agent.agent import GaiaAgent, GaiaAgentConfig
agent = GaiaAgent(config=GaiaAgentConfig(allowed_paths=["/home/me/Documents"]))The agent is built to host Agent Skills (short playbooks loaded into its own prompt, grouped into named sets, one set active per launch), and its bundled skill directory is the highest-precedence discovery root.
Loaded skills are not all resident every turn: each turn the agent embeds
the query against every loaded skill's description and renders only the
matching bodies in full — the rest collapse to a one-line menu entry, and the
model (or the user) re-activates one by calling load_skill on it again.
GAIA_DYNAMIC_SKILLS=0 disables the per-turn selection (every loaded body
renders every turn); GAIA_DYNAMIC_SKILLS_TAU=<float> overrides the match
threshold. Manifest skills: entries are always-on and never collapse, and a
turn with only always-on skills loaded embeds nothing. If the
embedder is unavailable, selection disables itself for the session and every
body renders — capability is never silently lost to a failed match.
One skill ships enabled: gaia-voice. It is a manifest skills: entry, so
it is always on, always rendered in full, and paid on every LLM call of every
turn — budget for it. It is not a task recipe but the agent's honesty floor: do
not claim work you did not do, do not present empty output as a result, do not
substitute a near-miss and report success. Those failures corrupt an answer
whatever the task is, which is why it cannot live in an opt-in bundle. It
declares no tools, and its body measures 790 tokens (tiktoken cl100k).
document-extract ships bundled but not enabled. gaia-voice routes a
request for every item in a document to it, and it drives
extract_document_items and save_extracted_items so a long transcript yields
a complete, source-quoted inventory rather than a summary. A skill turns that
inventorying on only by listing extract_document_items in tools_required;
wording such as "find every call site" in another skill does not.
No skill set loads. gaia-agent.yaml ships its skill_sets: and
default_skill_set: blocks commented out — following the email agent's
precedent, because loading several skill bodies into every prompt costs tokens
and no eval has measured that trade for this agent yet. Re-enabling is
uncommenting two blocks; no code change.
So today there is nothing for GAIA_SKILL_SET to select — leave it unset; any
value stops the agent at startup (the sidecar exits non-zero before binding its
port). Once a release declares sets, GAIA_SKILL_SET is the selection channel
for the packaged sidecar (its CLI accepts only --host and --port), and an
undeclared name stops startup naming the valid sets rather than falling back to
a default. Beyond
gaia-voice, do not design around a skill being on by default.
When the agent's working directory is a code repository, every task starts with a project map in the system prompt: the root, the directory shape, the likely entry points, which commands are installed, and the three platform differences that change command syntax. It exists so the agent stops burning round trips on "no such file" and "command not found".
Two consequences an integrator needs to plan for:
gaia-voice's 790.search_code_index builds it, then searches. On a large monorepo that one
search takes minutes of local embedding; a task that never searches pays
nothing. GAIA_PROJECT_MAP_AUTO_INDEX=1 builds it in the background at task
start instead.The sidecar's CLI accepts only --host and --port, so pointing the map at a
specific project means GAIA_PROJECT_ROOT=/path/to/repo in its environment, or
GaiaAgentConfig(project_root=...) when embedding. A directory that is neither
a VCS checkout nor holds a recognised manifest gets no map — that is the
designed answer, not a failure.
The same root check also decides whether the shell rides along: when it
resolves to a repository, run_shell_command is offered on every turn instead
of only when semantic selection guesses the request sounds like a shell
request. No repository, no change.
| Service | Port |
|---|---|
| Agent sidecar | 8141 on 127.0.0.1 |
| GAIA daemon | assigned at start, recorded in ~/.gaia/host/instance.json |
Port 4001 is reserved repo-wide: spawnSidecar throws a RangeError and
gaia serve --port 4001 exits 2. Both services bind loopback only — this agent
speaks for the user's documents and memory and has no business on a LAN
interface.
fetchAll / fetchBinary are a build step, not per request — network plus
a full SHA-256 hash of a large artifact. Run once at install time.resolveSidecarPath / resolveTuiPath are startup, not per request. They
re-hash the binary against binaries.lock.json before handing back a path that
gets spawned, so they cost a full read of a large file. Resolve once and keep
the path. { verify: false } skips the check for a binary you built yourself.Sidecar handle for the process lifetime.
Never per request.DEBUG=gaia puts download, spawn, and sidecar output on stderr. stdout
belongs to the TUI once exec'd, and to machine-readable JSON for fetch /
version — never write diagnostics there.Every failure throws a typed error extending GaiaError, so
instanceof GaiaError catches any of ours: IntegrityError, PlatformError,
HealthTimeoutError, VersionMismatchError, BinaryNotFoundError, HttpError.
There is no silent null.
/health green ≠ ready. It never touches the model server. Use
GET /v1/gaia/init and branch on .ready; it returns 503 with a full body and
a hint, not an empty error.error whose detail starts "Local Lemonade Server is not
reachable" means Lemonade isn't running or isn't reachable — not a bug in
this package. Start it, or set LEMONADE_BASE_URL.needs_confirmation is followed by a refusal and the run ends. See §8.
The nine gated tools are unreachable over /query — the agent itself can
run them on a transport that can prompt (SPEC §5.5).binaries.lock.json blocks the fetch before any
network call. Between releases that is the expected state — it is not a
broken install, and there is no override.linux-arm64 / win32-arm64 sidecar. The TUI has both. A PlatformError
on those hosts is the design, not a missing artifact.401 from /v1/gaia/* is the caller-auth token, not a bug. The sidecar
requires Authorization: Bearer <token>; /health, /version, and
/v1/gaia/version are exempt, which is why a green health check sits happily
in front of a 401 on /query. It skips the check only in dev mode — neither
token env var set — which is what spawnSidecar and gaia serve produce,
because this package mints nothing. Do not treat loopback binding as the
boundary; mint a token and pass it (§6).run_id must be a UUID, and unknown fields in the request body are a
422 — the model forbids extras. Typos don't get ignored.gaia run needs the Python gaia CLI on PATH — the TUI shells out to
it to start the daemon. So the TUI doesn't re-invoke our own npm shim, the
child's PATH is rewritten: a directory holding nothing but our shim (an npx
temp dir) is dropped, and a shared bin directory is moved to the end
instead of removed, so the python3 / lemonade-server / real gaia beside
it stay reachable. If the Python CLI isn't installed anywhere, the daemon never
comes up. It must also be 0.25.0+: an older core's daemon starts fine but
cannot run this agent, which reads as a UI with a dead agent rather than as
a version problem.gaia-tui, never gaia — the terminal-hub artifact
is called gaia-<platform>, and a file named gaia in a cache directory would
shadow the npm bin shim. The lock's filename and executable differ for that
reason. Don't rename it back.terminal-hub version is the thing to
check — gaia version prints it and its base URL.require("@amd-gaia/gaia") fails; use import or dynamic
import().Green path, in order:
npx @amd-gaia/gaia version # per-component version + source URL + matrix
npx @amd-gaia/gaia fetch # JSON: one entry per binary with its sha256
npx @amd-gaia/gaia serve --port 8141Against a lock that still carries PENDING-… hashes, fetch is expected to
fail with a PlatformError before any download — that is the gate working, not a
broken install. Only a published release has real hashes.
Then, in another terminal:
curl -s http://127.0.0.1:8141/health # {"status":"ok","service":"gaia-agent-gaia"}
curl -s http://127.0.0.1:8141/version # {"apiVersion":"2.15","agentVersion":"0.1.1"}
curl -s http://127.0.0.1:8141/v1/gaia/init # 200 + "ready":true, or 503 + a "hint"
curl -N -X POST http://127.0.0.1:8141/v1/gaia/query \
-H 'content-type: application/json' \
-d '{"query":"What can you do?","run_id":"00000000-0000-4000-8000-000000000001","context":[],"can_answer_questions":false}'A healthy run streams status / token events and ends with one final. If
/v1/gaia/init is 503, fix what its hint names and retry — the rest of your
integration is fine.
That /query call carries no Authorization header because gaia serve starts
the sidecar in dev mode. Against one started with a token, add
-H "authorization: Bearer $TOKEN" — a 401 here and a green /health is
that and nothing else (§6).
A 503 from /query itself is a different condition: every retained
session slot is busy and none is idle enough to evict (SPEC §5.2). Do NOT
loop on /v1/gaia/init — it will report ready. Wait for a running turn to
finish (or close an idle session) and retry the same /query.
Three more refusals are yours to avoid, each naming its fix in detail
(SPEC §5.2 has the reasoning):
run_id is still in flight. You mint it, so mint a fresh UUID
per request; reusing one would leave the earlier run with no way to be
cancelled.detail names the reason.Host header is absent or empty. The loopback check fails
closed, so omitting the header is refused rather than served. Send
Host: 127.0.0.1:<port>; every real HTTP client already does.For the full wire contract, lock schema, exit codes, and timeout table, see
SPEC.md. For the user-facing overview, see README.md
and https://amd-gaia.ai/docs/guides/gaia.
The stdio TUI supports /provider for Local, Fireworks AI, and AMD LLM Gateway.
Keys are entered in a masked field and sent directly to local Lemonade's runtime
auth API, never as agent queries. /model lists supported discovered cloud and
downloaded local models; /model fireworks.gemma-4-31b-it selects Gemma 4 31B IT
when available. Cloud chat sends conversation history to the selected provider;
embeddings remain on Lemonade. The status event names the actual provider and
marks remote inference. This is a TUI/stdio capability, not an HTTP query command.
Available only when GAIA was started with --developer-mode (or
GAIA_DEVELOPER_MODE=1). If the engineering tools are missing, say that developer
mode is off; don't route around it with shell or file tools. When describing a
handoff, claim only what happened:
See the usage guide.
© amd, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
SKILL.md and 27 other files in hub/agents/gaia/npm of amd/gaia.
Open the folder on GitHubat commit 05fb50b
Integrate Gaia 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.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Integrate Gaia this skillamd/gaia | 1.6k | — | ~9.7k | Automated safety check: Warn | MIT | |
| Getting Startedlive-codes/livecodes | 1.5k | — | ~1.6k | Automated safety check: Pass | MIT | |
| Golem Add LLM TSgolemcloud/golem | 1.5k | — | ~1.3k | Automated safety check: Pass | Custom licence | |
| Duel Agents2aronS/Duel-Agents | 745 | — | ~228 | Automated safety check: Pass | MIT | |
| Duel Agents2aronS/Duel-Agents | 745 | — | ~298 | Automated safety check: Notes | MIT | |
| Gemini API Best Practicestakeshy/obsidian-gemini-helper | 117 | — | ~1.1k | Automated safety check: Pass | MIT |
live-codes/livecodes
Quick start for standalone app at livecodes.io, embedding playgrounds with CDN or npm, and self-hosting basics.
golemcloud/golem
Adding LLM and AI capabilities to a TypeScript Golem agent. An agent skill from golemcloud/golem.
2aronS/Duel-Agents
Use Duel Agents model routing in Cursor. An agent skill from 2aronS/Duel-Agents.
2aronS/Duel-Agents
Configure and use Duel Agents model routing in Claude Code. An agent skill from 2aronS/Duel-Agents.
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.
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.
amd/gaia
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.
amd/gaia
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.
amd/gaia
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.
amd/gaia
Benchmarks AMD's GAIA agent against Claude Code and across models on quality, honesty, steps, tokens, time and real cost, using gaia eval tasks.
amd/gaia
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.
amd/gaia
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.
Works with
Categories
A skill your agent uses when integrating the @amd-gaia/gaia npm package — running GAIA's flagship agent, or embedding its local sidecar into a Node, TypeScript, or Electron app. Integrate Gaia is an agent skill from amd/gaia. Use when integrating the @amd-gaia/gaia npm package — running GAIA's flagship agent, or embedding its local sidecar into a Node, TypeScript, or Electron app.
Integrate Gaia fits situations like: integrating the @amd-gaia/gaia npm package — running GAIAs flagship agent; embedding its local sidecar into a Node.
Run `npx skills add amd/gaia --skill integrate-gaia -a claude-code`. Or copy the skill folder (hub/agents/gaia/npm in amd/gaia) into .claude/skills/integrate-gaia in your project. Claude Code loads it when a task matches its description.
Run `npx skills add amd/gaia --skill integrate-gaia -a codex`. Or copy the skill folder (hub/agents/gaia/npm in amd/gaia) into .agents/skills/integrate-gaia in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add amd/gaia --skill integrate-gaia -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-gaia, .gemini/skills/integrate-gaia, .github/skills/integrate-gaia and .opencode/skills/integrate-gaia in your project.
Going by SKILL.md and its folder, Integrate Gaia needs TypeScript for the scripts in its folder, the command-line tools its instructions call (npx, curl and npm) and credentials named GAIA_GAIA_SIDECAR_TOKEN. Our summary lists: Node.js; A credential in GAIA_GAIA_SIDECAR_TOKEN.
SKILL.md names 1 domain. In commands or code: amd-gaia.ai; the agent is likely to contact it when it follows the instructions. This is read from the text; nothing was executed.
Our automated static check of SKILL.md flagged 2 warning(s): mentions a credentials file (ssh keys, cloud or package-manager tokens). Read the flagged lines before installing; the check is not a guarantee either way.
Integrate Gaia 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.
About 9.7k tokens (SKILL.md is roughly 39k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with Integrate Gaia: Getting Started (live-codes/livecodes, 1.5k stars), Golem Add LLM TS (golemcloud/golem, 1.5k stars), Duel Agents (2aronS/Duel-Agents, 745 stars) and Duel Agents (2aronS/Duel-Agents, 745 stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
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 8, 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.