@amd-gaia/gaia 0.0.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +93 -0
- package/LICENSE +21 -0
- package/README.md +200 -6
- package/SKILL.md +473 -0
- package/SPEC.md +393 -0
- package/binaries.lock.json +78 -0
- package/dist/cli.d.ts +41 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +368 -0
- package/dist/cli.js.map +1 -0
- package/dist/errors.d.ts +32 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +42 -0
- package/dist/errors.js.map +1 -0
- package/dist/fetch.d.ts +110 -0
- package/dist/fetch.d.ts.map +1 -0
- package/dist/fetch.js +217 -0
- package/dist/fetch.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/lifecycle.d.ts +145 -0
- package/dist/lifecycle.d.ts.map +1 -0
- package/dist/lifecycle.js +397 -0
- package/dist/lifecycle.js.map +1 -0
- package/dist/logger.d.ts +13 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +30 -0
- package/dist/logger.js.map +1 -0
- package/dist/platform.d.ts +100 -0
- package/dist/platform.d.ts.map +1 -0
- package/dist/platform.js +202 -0
- package/dist/platform.js.map +1 -0
- package/dist/url.d.ts +4 -0
- package/dist/url.d.ts.map +1 -0
- package/dist/url.js +22 -0
- package/dist/url.js.map +1 -0
- package/package.json +57 -4
package/SKILL.md
ADDED
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: integrate-gaia
|
|
3
|
+
description: 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.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Integrating `@amd-gaia/gaia`
|
|
7
|
+
|
|
8
|
+
`@amd-gaia/gaia` delivers **two binaries** and owns their process lifecycle: the
|
|
9
|
+
frozen **agent sidecar** (`gaia-agent`) and the Go **terminal UI** (`gaia-tui`).
|
|
10
|
+
It ships no agent logic of its own, and it **builds neither binary at install
|
|
11
|
+
time** — both are published artifacts it downloads and verifies. The terminal UI
|
|
12
|
+
is the published `terminal-hub` component, the same binary a full GAIA install
|
|
13
|
+
runs as `gaia tui`, so its behaviour cannot differ from that one. Everything runs on the local machine against
|
|
14
|
+
a local model server — nothing you type or index leaves it.
|
|
15
|
+
|
|
16
|
+
Two ways in:
|
|
17
|
+
|
|
18
|
+
- **`npx @amd-gaia/gaia`** — fetch, verify, launch the terminal UI. What a human runs.
|
|
19
|
+
- **The programmatic exports** — fetch, spawn the sidecar, drive `POST /v1/gaia/query`
|
|
20
|
+
yourself. What you use when embedding GAIA in an app.
|
|
21
|
+
|
|
22
|
+
> **This file is NOT one of the agent's own skills.** It is the integration
|
|
23
|
+
> playbook: how *you* wire this package into an app. The agent separately loads
|
|
24
|
+
> **Agent Skills** into its own prompt at runtime from
|
|
25
|
+
> `gaia_agent/skills/<name>/SKILL.md`. Same filename, different artifact —
|
|
26
|
+
> don't ship this one as an agent skill. See [Skills](#10-skills--opt-in-and-empty-in-010).
|
|
27
|
+
|
|
28
|
+
## 1. Install
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npx @amd-gaia/gaia # no install step; fetches what it needs on first run
|
|
32
|
+
npm install @amd-gaia/gaia # when you want the programmatic exports
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The package is **ESM-only** (`"type": "module"`) and needs **Node 18+** for the
|
|
36
|
+
built-in `fetch`. Use `import`, not `require`; from CommonJS use
|
|
37
|
+
`await import("@amd-gaia/gaia")`.
|
|
38
|
+
|
|
39
|
+
> `@amd-gaia/gaia` **publishes with this release** — it is not on npm yet. Until
|
|
40
|
+
> the release tag lands, `npx @amd-gaia/gaia` will not resolve. Run the agent from
|
|
41
|
+
> a source checkout in the meantime (see
|
|
42
|
+
> [the guide](https://amd-gaia.ai/docs/guides/gaia)).
|
|
43
|
+
|
|
44
|
+
## 2. What `npx @amd-gaia/gaia` actually does
|
|
45
|
+
|
|
46
|
+
1. Resolves the host platform key (`` `${process.platform}-${process.arch}` ``).
|
|
47
|
+
2. Reads `binaries.lock.json`, the checksum manifest published with this exact
|
|
48
|
+
package version. Each component records its own hub lane, version, artifact
|
|
49
|
+
name and hash — they do not share a base URL.
|
|
50
|
+
3. Downloads **both** binaries, each from its own lane, and **SHA-256 verifies
|
|
51
|
+
each against the lock**.
|
|
52
|
+
4. Installs the sidecar into `~/.gaia/agents/gaia/` and the TUI into
|
|
53
|
+
`~/.gaia/npm-cache/gaia-<version>/`.
|
|
54
|
+
5. Execs the TUI, whose exit code becomes ours.
|
|
55
|
+
|
|
56
|
+
`run` deliberately does **not** spawn a sidecar. The TUI reaches agents through
|
|
57
|
+
the GAIA daemon's relay and never holds a sidecar token, and the daemon is what
|
|
58
|
+
spawns and supervises the sidecar — from exactly the directory step 4 wrote to.
|
|
59
|
+
A second sidecar started here would only fight the daemon's own for port 8141. Use
|
|
60
|
+
`gaia serve` when you want to own the process.
|
|
61
|
+
|
|
62
|
+
Other commands: `gaia fetch` (download + verify, print JSON, exit),
|
|
63
|
+
`gaia serve` (sidecar alone), `gaia version` (per-component version, source URL,
|
|
64
|
+
and platform matrix). Anything after a bare `--` goes to the TUI verbatim.
|
|
65
|
+
|
|
66
|
+
### Where each binary comes from
|
|
67
|
+
|
|
68
|
+
| Component | Hub lane | Artifact names |
|
|
69
|
+
| --------- | ----------------------------------------- | ------------------------- |
|
|
70
|
+
| `sidecar` | `agents/gaia/<agentVersion>/` | `gaia-agent-<platformKey>[.exe]` |
|
|
71
|
+
| `tui` | `agents/terminal-hub/<componentVersion>/` | `gaia-<goPlatform>[.exe]` |
|
|
72
|
+
|
|
73
|
+
Two things follow from that, and both bite if you assume otherwise:
|
|
74
|
+
|
|
75
|
+
- **The two components version independently.** `lock.agentVersion` is this
|
|
76
|
+
package's version; `components.tui.componentVersion` is the terminal-hub release
|
|
77
|
+
it consumes. They will not match.
|
|
78
|
+
- **The TUI's artifact names use `win-x64` / `win-arm64`, not `win32-*`.** Platform
|
|
79
|
+
*keys* stay in Node's namespace (`process.platform` says `win32`); only the
|
|
80
|
+
`filename` crosses over. Never build a TUI URL by interpolating a platform key —
|
|
81
|
+
read `filename` from the lock entry.
|
|
82
|
+
|
|
83
|
+
## 3. The integrity gate — it will stop you, by design
|
|
84
|
+
|
|
85
|
+
The SHA-256 check is this package's security boundary, and there is no flag,
|
|
86
|
+
env var, or option that relaxes it.
|
|
87
|
+
|
|
88
|
+
- Bytes are hashed **in memory and compared before anything is written** to the
|
|
89
|
+
cache path. A mismatch raises `IntegrityError` naming expected vs actual and
|
|
90
|
+
leaves nothing on disk.
|
|
91
|
+
- A **placeholder hash blocks the fetch before any network call** — between
|
|
92
|
+
releases every `sha256` in the lock is `PENDING-replace-with-real-sha256`, and
|
|
93
|
+
a value that is all zeros or contains `PENDING` (case-insensitive) is treated
|
|
94
|
+
as a placeholder. You get a `PlatformError`, not a download.
|
|
95
|
+
- A cache hit **re-hashes the on-disk file**. A cached binary whose bytes drifted
|
|
96
|
+
is re-downloaded, not reused.
|
|
97
|
+
|
|
98
|
+
If you need to run against a locally built binary, build it and point
|
|
99
|
+
`startSidecar` / `runTui` at it directly. The fetcher will not be talked into it.
|
|
100
|
+
|
|
101
|
+
## 4. Platform coverage — the sidecar has two gaps
|
|
102
|
+
|
|
103
|
+
`terminal-hub` publishes the TUI for all six targets. The sidecar is a PyInstaller
|
|
104
|
+
freeze built on the platform it targets, and there is **no arm64 Linux and no arm64
|
|
105
|
+
Windows sidecar build**.
|
|
106
|
+
|
|
107
|
+
| Platform key | Sidecar | TUI |
|
|
108
|
+
| -------------- | :-----: | :-: |
|
|
109
|
+
| `win32-x64` | yes | yes |
|
|
110
|
+
| `darwin-arm64` | yes | yes |
|
|
111
|
+
| `darwin-x64` | yes | yes |
|
|
112
|
+
| `linux-x64` | yes | yes |
|
|
113
|
+
| `linux-arm64` | **no** | yes |
|
|
114
|
+
| `win32-arm64` | **no** | yes |
|
|
115
|
+
|
|
116
|
+
Resolving the sidecar on those two keys raises `PlatformError` naming the
|
|
117
|
+
platform and the supported set. It is not silently skipped, and the TUI is never
|
|
118
|
+
launched with no agent behind it. `npx @amd-gaia/gaia version` prints the matrix
|
|
119
|
+
for the version you have.
|
|
120
|
+
|
|
121
|
+
## 5. Prerequisite — a local Lemonade server
|
|
122
|
+
|
|
123
|
+
The agent thinks with a model hosted by **Lemonade Server**, which this package
|
|
124
|
+
does not install. Required before any query succeeds:
|
|
125
|
+
|
|
126
|
+
1. Lemonade **10.2.0 or newer**, running (`lemonade-server serve`).
|
|
127
|
+
2. The default model downloaded (`gaia download Gemma-4-E4B-it-GGUF`, or
|
|
128
|
+
`gaia init`).
|
|
129
|
+
|
|
130
|
+
Do not guess — ask the sidecar. `GET /v1/gaia/init` is a read-only preflight
|
|
131
|
+
(it never pulls or loads) that probes Lemonade, compares its version to the
|
|
132
|
+
floor, and checks the model is present:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
curl -s http://127.0.0.1:8141/v1/gaia/init
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
It answers **200** when ready and **503** when not, with the **same body shape
|
|
139
|
+
either way** — so branch on `.ready` and render `.hint`, never on the status code
|
|
140
|
+
alone:
|
|
141
|
+
|
|
142
|
+
```jsonc
|
|
143
|
+
{
|
|
144
|
+
"ready": false,
|
|
145
|
+
"lemonade": { "reachable": false, "base_url": "…", "version": null,
|
|
146
|
+
"min_version": "10.2.0", "compatible": null },
|
|
147
|
+
"model": { "id": "Gemma-4-E4B-it-GGUF", "present": false,
|
|
148
|
+
"loadable": null, "ctx_size": null },
|
|
149
|
+
"hint": "Local Lemonade Server is not reachable at … — start it with `lemonade-server serve`, or set LEMONADE_BASE_URL to a running server."
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
`lemonade.compatible: null` is **indeterminate**, not a pass — the version could
|
|
154
|
+
not be parsed. Render it as unknown.
|
|
155
|
+
|
|
156
|
+
`GET /health` is liveness only. A green `/health` means the REST surface is up;
|
|
157
|
+
it says nothing about whether a query will work.
|
|
158
|
+
|
|
159
|
+
## 6. Start the sidecar
|
|
160
|
+
|
|
161
|
+
```ts
|
|
162
|
+
import { fetchAll, startSidecar, shutdown } from "@amd-gaia/gaia";
|
|
163
|
+
|
|
164
|
+
// Fetch + SHA-256 verify both binaries. Build step, not per request.
|
|
165
|
+
const { sidecar, tui } = await fetchAll();
|
|
166
|
+
|
|
167
|
+
// Spawn -> poll /health -> check the contract apiVersion, in one call.
|
|
168
|
+
const proc = await startSidecar({ binaryPath: sidecar.binaryPath }); // port 8141
|
|
169
|
+
|
|
170
|
+
// ... drive proc.baseUrl ...
|
|
171
|
+
|
|
172
|
+
await shutdown(proc); // tree-kill; auto-cleanup also reaps on exit
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
- `fetchAll(opts?)` returns `{ sidecar, tui, lock }`. Each result carries
|
|
176
|
+
`binaryPath`, `platformKey`, `sha256`, `cached`, `url`. For one component use
|
|
177
|
+
`fetchBinary({ component: "sidecar" | "tui", outDir })`.
|
|
178
|
+
- `startSidecar` throws if the binary can't start, never becomes healthy
|
|
179
|
+
(`HealthTimeoutError`, 60 s default — a cold one-file build unpacks first), or
|
|
180
|
+
reports an `apiVersion` whose **major** differs from this package's
|
|
181
|
+
(`VersionMismatchError`). On any failure it shuts the sidecar down before
|
|
182
|
+
rethrowing, so a failed start never leaks a process.
|
|
183
|
+
- **Tree-kill is not optional.** The frozen sidecar spawns a child uvicorn
|
|
184
|
+
process that `child.kill()` on the parent does not reap, which leaves port 8141
|
|
185
|
+
bound. `shutdown` kills the group (POSIX `SIGTERM` to `-pid`, escalating to
|
|
186
|
+
`SIGKILL`; Windows `taskkill /T /F`). `autoCleanup` (default `true`) also reaps
|
|
187
|
+
on `exit`, `SIGINT`/`SIGTERM`/`SIGHUP`, `uncaughtException`, and
|
|
188
|
+
`unhandledRejection`. A `SIGKILL` of *your* process is the one case nothing
|
|
189
|
+
in-process can catch.
|
|
190
|
+
|
|
191
|
+
Or skip the code entirely and let the CLI own it:
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
npx @amd-gaia/gaia serve --port 8141
|
|
195
|
+
curl http://127.0.0.1:8141/health
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## 7. Call `POST /v1/gaia/query`
|
|
199
|
+
|
|
200
|
+
This is the whole agent surface. There is **no typed query client** in this
|
|
201
|
+
package — call it with plain `fetch`. Contract version **2.12**; the stream is
|
|
202
|
+
`text/event-stream` terminated by **exactly one** `final` or `error`.
|
|
203
|
+
|
|
204
|
+
Request body (`extra: "forbid"` — an unknown field is a **422**, not ignored):
|
|
205
|
+
|
|
206
|
+
| Field | Required | Notes |
|
|
207
|
+
|---|---|---|
|
|
208
|
+
| `query` | yes | Non-empty. |
|
|
209
|
+
| `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. |
|
|
210
|
+
| `context` | yes | Transcript slice, pushed in the body — may be `[]`, never absent. Each item `{ role, content }`; `role` ∈ `user` / `assistant` / `system` / `tool`. |
|
|
211
|
+
| `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. |
|
|
212
|
+
| `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. |
|
|
213
|
+
| `model` | no | Overrides the model id for this run. |
|
|
214
|
+
| `provider` | no | Local inference only — anything but `"lemonade"` is a **400**. |
|
|
215
|
+
| `max_steps` | no | ≥ 1. |
|
|
216
|
+
|
|
217
|
+
```ts
|
|
218
|
+
import { randomUUID } from "node:crypto";
|
|
219
|
+
|
|
220
|
+
const runId = randomUUID();
|
|
221
|
+
const res = await fetch(`${proc.baseUrl}/v1/gaia/query`, {
|
|
222
|
+
method: "POST",
|
|
223
|
+
headers: { "content-type": "application/json", accept: "text/event-stream" },
|
|
224
|
+
body: JSON.stringify({
|
|
225
|
+
query: "Summarize the PDFs in ~/Documents/reports",
|
|
226
|
+
run_id: runId,
|
|
227
|
+
context: [],
|
|
228
|
+
session_id: "s1",
|
|
229
|
+
can_answer_questions: false,
|
|
230
|
+
}),
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
const reader = res.body!.getReader();
|
|
234
|
+
const dec = new TextDecoder();
|
|
235
|
+
let buf = "";
|
|
236
|
+
outer: for (;;) {
|
|
237
|
+
const { value, done } = await reader.read();
|
|
238
|
+
if (done) break;
|
|
239
|
+
buf += dec.decode(value, { stream: true });
|
|
240
|
+
let i: number;
|
|
241
|
+
while ((i = buf.indexOf("\n\n")) >= 0) {
|
|
242
|
+
const frame = buf.slice(0, i);
|
|
243
|
+
buf = buf.slice(i + 2);
|
|
244
|
+
const line = frame.split("\n").find((l) => l.startsWith("data: "));
|
|
245
|
+
if (!line) continue; // ":" frames are keepalive comments
|
|
246
|
+
const ev = JSON.parse(line.slice(6));
|
|
247
|
+
switch (ev.type) {
|
|
248
|
+
case "status": console.log(ev.message); break;
|
|
249
|
+
case "token": process.stdout.write(ev.delta); break;
|
|
250
|
+
case "tool_call": console.log(`→ ${ev.tool}`, ev.args); break;
|
|
251
|
+
case "tool_result": console.log(`← ${ev.tool}`, ev.data); break;
|
|
252
|
+
case "needs_confirmation": break; // see §8 — a refusal follows
|
|
253
|
+
case "needs_input": /* answer it — see below */ break;
|
|
254
|
+
case "final": console.log(ev.answer); break outer; // terminal
|
|
255
|
+
case "error": console.error(ev.detail); break outer; // terminal
|
|
256
|
+
default: console.warn("unsupported event", ev); // future additive type
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
The canonical event shapes, as emitted:
|
|
263
|
+
|
|
264
|
+
| Event | Shape |
|
|
265
|
+
|---|---|
|
|
266
|
+
| `status` | `{ type, message }` — progress and reasoning narration |
|
|
267
|
+
| `token` | `{ type, delta }` — answer text to append |
|
|
268
|
+
| `tool_call` | `{ type, tool, args }` |
|
|
269
|
+
| `tool_result` | `{ type, tool, data, render? }` |
|
|
270
|
+
| `needs_confirmation` | `{ type, run_id, action, summary }` — no `confirm_url`; see §8 |
|
|
271
|
+
| `needs_input` | `{ type, run_id, request_id, question, options[], allow_free_text, sensitive, respond_url, timeout_seconds? }` |
|
|
272
|
+
| `final` | `{ type, answer, usage? }` — terminal |
|
|
273
|
+
| `error` | `{ type, detail, status }` — terminal, surface `detail` verbatim |
|
|
274
|
+
|
|
275
|
+
Rules a client must respect:
|
|
276
|
+
|
|
277
|
+
- **An idle run emits `: keepalive` SSE comments every 10 s.** Skip lines that
|
|
278
|
+
aren't `data:` and reset your read-idle timer on them — a long tool call is not
|
|
279
|
+
a dead stream.
|
|
280
|
+
- **Never treat stream close without a terminal event as success.** The server
|
|
281
|
+
guarantees one; a close without one means something broke on your side.
|
|
282
|
+
- **Answer `needs_input`, don't restart.** The run is parked on the *same*
|
|
283
|
+
stream. `POST /v1/gaia/query/{run_id}/respond` with
|
|
284
|
+
`{ request_id, response }` and keep reading the existing stream — a fresh
|
|
285
|
+
`/query` POST abandons the paused run. Unknown run → **404**; a `request_id` that
|
|
286
|
+
is no longer pending → **409** (both loud, never a silent drop). Render each
|
|
287
|
+
option's `description`, and mask the input when `sensitive` is set.
|
|
288
|
+
- **Cancel with `POST /v1/gaia/query/{run_id}/cancel`.** It returns
|
|
289
|
+
`{ run_id, cancelled }` — an unknown id reports `cancelled: false` with a
|
|
290
|
+
**200**, not a 404, because a cancel racing a normal completion is expected.
|
|
291
|
+
Dropping the HTTP connection also cancels the run.
|
|
292
|
+
|
|
293
|
+
## 8. Confirmation-gated tools are **refused, not prompted**
|
|
294
|
+
|
|
295
|
+
Read this before you design a workflow around it.
|
|
296
|
+
|
|
297
|
+
Three of the agent's 55 tools write to disk or execute a command, and sit in the
|
|
298
|
+
base `TOOLS_REQUIRING_CONFIRMATION` set: **`write_file`**, **`edit_file`**, and
|
|
299
|
+
**`run_shell_command`**. Everything else — reading, indexing, querying, web
|
|
300
|
+
fetching, memory — runs without asking.
|
|
301
|
+
|
|
302
|
+
Over `/v1/gaia/query` there is **no way to collect an approval**, so the stream
|
|
303
|
+
does not prompt. When the agent reaches one of those tools it emits a
|
|
304
|
+
`needs_confirmation` event, and the server **immediately follows it with a
|
|
305
|
+
terminal `final`** whose `answer` says it stopped before running that action,
|
|
306
|
+
then cancels the run. There is no `confirm_url`, no resume, and no
|
|
307
|
+
`/query/{run_id}/confirm` endpoint — it is a deliberate deny-by-default stub, not
|
|
308
|
+
an oversight.
|
|
309
|
+
|
|
310
|
+
Concretely, your client sees:
|
|
311
|
+
|
|
312
|
+
```
|
|
313
|
+
data: {"type":"needs_confirmation","run_id":"…","action":"write_file","summary":"Run 'write_file'?"}
|
|
314
|
+
data: {"type":"final","answer":"I stopped before running 'write_file' because it needs your explicit approval, and this streaming surface cannot collect that yet. …"}
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
So: **`/query` cannot write files, edit files, or run shell commands.** If your
|
|
318
|
+
integration needs that, drive the agent from a surface that can prompt (the
|
|
319
|
+
terminal UI or the Agent UI), or perform the mutation yourself from your own code
|
|
320
|
+
and let the agent do the reading and reasoning. Treat `needs_confirmation` as an
|
|
321
|
+
early warning that the run is about to end, not as a question you can answer.
|
|
322
|
+
|
|
323
|
+
## 9. File-access scope
|
|
324
|
+
|
|
325
|
+
The agent's file, document, and data tools are confined to a set of allowed
|
|
326
|
+
paths, and **the default is the user's home directory**. That is the honest scope
|
|
327
|
+
for a personal document agent, and it is still a real boundary — system
|
|
328
|
+
directories, program files, and other users' homes are refused, with the check
|
|
329
|
+
run against the *resolved* path so a symlink out of scope doesn't slip through.
|
|
330
|
+
|
|
331
|
+
**In 0.1.1 narrowing it is a construction-time setting only.** The packaged
|
|
332
|
+
sidecar exposes no flag or env var for `allowed_paths` (its CLI accepts only
|
|
333
|
+
`--host` and `--port`), so restricting the scope means embedding `GaiaAgent` in
|
|
334
|
+
your own Python process:
|
|
335
|
+
|
|
336
|
+
```python
|
|
337
|
+
from gaia_agent.agent import GaiaAgent, GaiaAgentConfig
|
|
338
|
+
|
|
339
|
+
agent = GaiaAgent(config=GaiaAgentConfig(allowed_paths=["/home/me/Documents"]))
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
## 10. Skills — opt-in, and empty in 0.1.1
|
|
343
|
+
|
|
344
|
+
The agent is built to host **Agent Skills** (short playbooks loaded into its own
|
|
345
|
+
prompt, grouped into named sets, one set active per launch), and its bundled
|
|
346
|
+
skill directory is the highest-precedence discovery root.
|
|
347
|
+
|
|
348
|
+
Loaded skills are **not** all resident every turn: each turn the agent embeds
|
|
349
|
+
the query against every loaded skill's description and renders only the
|
|
350
|
+
matching bodies in full — the rest collapse to a one-line menu entry, and the
|
|
351
|
+
model (or the user) re-activates one by calling `load_skill` on it again.
|
|
352
|
+
`GAIA_DYNAMIC_SKILLS=0` disables the per-turn selection (every loaded body
|
|
353
|
+
renders every turn); `GAIA_DYNAMIC_SKILLS_TAU=<float>` overrides the match
|
|
354
|
+
threshold. Manifest `skills:` entries are always-on and never collapse. If the
|
|
355
|
+
embedder is unavailable, selection disables itself for the session and every
|
|
356
|
+
body renders — capability is never silently lost to a failed match.
|
|
357
|
+
|
|
358
|
+
**Nothing ships enabled in 0.1.1.** The bundled skill library is empty, and
|
|
359
|
+
`gaia-agent.yaml` ships its `skills:` / `skill_sets:` / `default_skill_set:`
|
|
360
|
+
blocks **commented out** — following the email agent's precedent, because skill
|
|
361
|
+
bodies cost prompt tokens and no eval has measured that trade for this agent yet.
|
|
362
|
+
Re-enabling is uncommenting two blocks; no code change.
|
|
363
|
+
|
|
364
|
+
So today: no skill set loads, and there is nothing for `GAIA_SKILL_SET` to
|
|
365
|
+
select — leave it unset. Once a release declares sets, `GAIA_SKILL_SET` is the
|
|
366
|
+
selection channel for the packaged sidecar (its CLI has no `--skill-set` flag),
|
|
367
|
+
and an undeclared name raises naming the valid sets rather than falling back to a
|
|
368
|
+
default. Do not document or design around skills being on by default.
|
|
369
|
+
|
|
370
|
+
## 11. Ports
|
|
371
|
+
|
|
372
|
+
| Service | Port |
|
|
373
|
+
|---|---|
|
|
374
|
+
| Agent sidecar | `8141` on `127.0.0.1` |
|
|
375
|
+
| GAIA daemon | assigned at start, recorded in `~/.gaia/host/instance.json` |
|
|
376
|
+
|
|
377
|
+
Port **4001 is reserved repo-wide**: `spawnSidecar` throws a `RangeError` and
|
|
378
|
+
`gaia serve --port 4001` exits 2. Both services bind loopback only — this agent
|
|
379
|
+
speaks for the user's documents and memory and has no business on a LAN
|
|
380
|
+
interface.
|
|
381
|
+
|
|
382
|
+
## 12. Running in a server or long-lived app
|
|
383
|
+
|
|
384
|
+
- **`fetchAll` / `fetchBinary` are a build step**, not per request — network plus
|
|
385
|
+
a full SHA-256 hash of a large artifact. Run once; `resolveSidecarPath` /
|
|
386
|
+
`resolveTuiPath` at runtime.
|
|
387
|
+
- **Spawn once at boot** and hold the `Sidecar` handle for the process lifetime.
|
|
388
|
+
Never per request.
|
|
389
|
+
- **Low concurrency.** One local Lemonade model slot, so parallel queries
|
|
390
|
+
serialize. Cap inflight runs.
|
|
391
|
+
- **The package does not restart a crashed sidecar.** It reaps one; supervision
|
|
392
|
+
is the daemon's job (or yours).
|
|
393
|
+
- **`DEBUG=gaia`** puts download, spawn, and sidecar output on **stderr**. stdout
|
|
394
|
+
belongs to the TUI once exec'd, and to machine-readable JSON for `fetch` /
|
|
395
|
+
`version` — never write diagnostics there.
|
|
396
|
+
|
|
397
|
+
Every failure throws a typed error extending `GaiaError`, so
|
|
398
|
+
`instanceof GaiaError` catches any of ours: `IntegrityError`, `PlatformError`,
|
|
399
|
+
`HealthTimeoutError`, `VersionMismatchError`, `BinaryNotFoundError`, `HttpError`.
|
|
400
|
+
There is no silent null.
|
|
401
|
+
|
|
402
|
+
## Gotchas — read before debugging
|
|
403
|
+
|
|
404
|
+
- **`/health` green ≠ ready.** It never touches the model server. Use
|
|
405
|
+
`GET /v1/gaia/init` and branch on `.ready`; it returns 503 with a full body and
|
|
406
|
+
a `hint`, not an empty error.
|
|
407
|
+
- **A terminal `error` whose `detail` starts "Local Lemonade Server is not
|
|
408
|
+
reachable"** means Lemonade isn't running or isn't reachable — not a bug in
|
|
409
|
+
this package. Start it, or set `LEMONADE_BASE_URL`.
|
|
410
|
+
- **`needs_confirmation` is followed by a refusal and the run ends.** See §8.
|
|
411
|
+
`write_file` / `edit_file` / `run_shell_command` are unreachable over `/query`.
|
|
412
|
+
- **A placeholder hash in `binaries.lock.json` blocks the fetch before any
|
|
413
|
+
network call.** Between releases that is the *expected* state — it is not a
|
|
414
|
+
broken install, and there is no override.
|
|
415
|
+
- **No `linux-arm64` / `win32-arm64` sidecar.** The TUI has both. A `PlatformError`
|
|
416
|
+
on those hosts is the design, not a missing artifact.
|
|
417
|
+
- **There is no caller-auth token at 0.1.1.** Unlike `@amd-gaia/agent-email`, this
|
|
418
|
+
sidecar mints none and this package sends none — don't add an `Authorization`
|
|
419
|
+
header looking for one, and don't rely on its absence as a security boundary.
|
|
420
|
+
Loopback binding is what protects it.
|
|
421
|
+
- **`run_id` must be a UUID**, and unknown fields in the request body are a
|
|
422
|
+
**422** — the model forbids extras. Typos don't get ignored.
|
|
423
|
+
- **`gaia run` needs the *Python* `gaia` CLI on `PATH`** — the TUI shells out to
|
|
424
|
+
it to start the daemon. The package deliberately strips its own npm bin
|
|
425
|
+
directory from the child's `PATH` so the TUI doesn't re-invoke the npm shim; if
|
|
426
|
+
the Python CLI isn't installed, the daemon never comes up.
|
|
427
|
+
- **The TUI is installed as `gaia-tui`, never `gaia`** — the terminal-hub artifact
|
|
428
|
+
*is* called `gaia-<platform>`, and a file named `gaia` in a cache directory would
|
|
429
|
+
shadow the npm bin shim. The lock's `filename` and `executable` differ for that
|
|
430
|
+
reason. Don't rename it back.
|
|
431
|
+
- **The TUI comes from a lane this package doesn't publish.** If a fetch 404s on
|
|
432
|
+
the TUI but not the sidecar, the pinned `terminal-hub` version is the thing to
|
|
433
|
+
check — `gaia version` prints it and its base URL.
|
|
434
|
+
- **ESM-only.** `require("@amd-gaia/gaia")` fails; use `import` or dynamic
|
|
435
|
+
`import()`.
|
|
436
|
+
|
|
437
|
+
## Verify the integration
|
|
438
|
+
|
|
439
|
+
Green path, in order:
|
|
440
|
+
|
|
441
|
+
```bash
|
|
442
|
+
npx @amd-gaia/gaia version # per-component version + source URL + matrix
|
|
443
|
+
npx @amd-gaia/gaia fetch # JSON: one entry per binary with its sha256
|
|
444
|
+
npx @amd-gaia/gaia serve --port 8141
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
Against a lock that still carries `PENDING-…` hashes, `fetch` is *expected* to
|
|
448
|
+
fail with a `PlatformError` before any download — that is the gate working, not a
|
|
449
|
+
broken install. Only a published release has real hashes.
|
|
450
|
+
|
|
451
|
+
Then, in another terminal:
|
|
452
|
+
|
|
453
|
+
```bash
|
|
454
|
+
curl -s http://127.0.0.1:8141/health # {"status":"ok","service":"gaia-agent-gaia"}
|
|
455
|
+
curl -s http://127.0.0.1:8141/version # {"apiVersion":"2.12","agentVersion":"0.1.1"}
|
|
456
|
+
curl -s http://127.0.0.1:8141/v1/gaia/init # 200 + "ready":true, or 503 + a "hint"
|
|
457
|
+
curl -N -X POST http://127.0.0.1:8141/v1/gaia/query \
|
|
458
|
+
-H 'content-type: application/json' \
|
|
459
|
+
-d '{"query":"What can you do?","run_id":"00000000-0000-4000-8000-000000000001","context":[],"can_answer_questions":false}'
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
A healthy run streams `status` / `token` events and ends with one `final`. If
|
|
463
|
+
`/v1/gaia/init` is 503, fix what its `hint` names and retry — the rest of your
|
|
464
|
+
integration is fine.
|
|
465
|
+
|
|
466
|
+
**A 503 from `/query` itself is a different condition**: every retained
|
|
467
|
+
session slot is busy and none is idle enough to evict (SPEC §5.2). Do NOT
|
|
468
|
+
loop on `/v1/gaia/init` — it will report ready. Wait for a running turn to
|
|
469
|
+
finish (or close an idle session) and retry the same `/query`.
|
|
470
|
+
|
|
471
|
+
For the full wire contract, lock schema, exit codes, and timeout table, see
|
|
472
|
+
[`SPEC.md`](./SPEC.md). For the user-facing overview, see [`README.md`](./README.md)
|
|
473
|
+
and <https://amd-gaia.ai/docs/guides/gaia>.
|