@aexhq/sdk 0.40.6 → 0.40.8

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.
Files changed (53) hide show
  1. package/README.md +159 -159
  2. package/dist/_contracts/asset-upload-helper.d.ts +38 -0
  3. package/dist/_contracts/asset-upload-helper.js +111 -0
  4. package/dist/_contracts/internal.d.ts +5 -9
  5. package/dist/_contracts/internal.js +5 -79
  6. package/dist/_contracts/operations.d.ts +9 -2
  7. package/dist/_contracts/operations.js +141 -16
  8. package/dist/_contracts/retry-core.d.ts +27 -0
  9. package/dist/_contracts/retry-core.js +75 -0
  10. package/dist/_contracts/runtime-types.d.ts +6 -0
  11. package/dist/_contracts/stable.d.ts +10 -5
  12. package/dist/_contracts/stable.js +10 -5
  13. package/dist/asset-upload.d.ts +2 -1
  14. package/dist/asset-upload.js +28 -61
  15. package/dist/asset-upload.js.map +1 -1
  16. package/dist/cli.mjs +297 -80
  17. package/dist/cli.mjs.sha256 +1 -1
  18. package/dist/client.d.ts +5 -0
  19. package/dist/client.js +8 -13
  20. package/dist/client.js.map +1 -1
  21. package/dist/retry.js +13 -41
  22. package/dist/retry.js.map +1 -1
  23. package/dist/version.d.ts +1 -1
  24. package/dist/version.js +1 -1
  25. package/docs/authentication.md +125 -125
  26. package/docs/billing.md +118 -118
  27. package/docs/concepts/agent-tools.md +66 -66
  28. package/docs/concepts/composition.md +37 -37
  29. package/docs/concepts/providers-and-runtimes.md +54 -54
  30. package/docs/concepts/runs.md +49 -49
  31. package/docs/concepts/subagents.md +88 -88
  32. package/docs/credentials.md +125 -125
  33. package/docs/defaults.md +64 -64
  34. package/docs/errors.md +196 -196
  35. package/docs/events.md +243 -243
  36. package/docs/limits-and-quotas.md +144 -144
  37. package/docs/limits.md +50 -50
  38. package/docs/mcp.md +44 -44
  39. package/docs/networking.md +163 -163
  40. package/docs/outputs.md +267 -267
  41. package/docs/public-surface.json +77 -77
  42. package/docs/quickstart.md +119 -119
  43. package/docs/release.md +38 -38
  44. package/docs/retries.md +129 -129
  45. package/docs/run-config.md +58 -58
  46. package/docs/run-record.md +55 -55
  47. package/docs/secrets.md +125 -125
  48. package/docs/testing.md +35 -35
  49. package/docs/vision-skills.md +91 -91
  50. package/docs/webhooks.md +127 -127
  51. package/examples/feature-tour.ts +282 -282
  52. package/examples/spike-settle-latency.ts +125 -125
  53. package/package.json +1 -1
package/docs/retries.md CHANGED
@@ -1,129 +1,129 @@
1
- ---
2
- title: Retries and throttling
3
- ---
4
-
5
- # Retries and throttling
6
-
7
- The SDK ships with built-in transport resilience. Every request it makes to the
8
- aex API is automatically retried on **transient** failures with bounded
9
- exponential backoff and jitter, honoring the server's `Retry-After` header. You
10
- get this by default — no wrapper code — and it is safe to leave on because the
11
- billable submits carry a stable idempotency key, so a retry never creates a
12
- duplicate run.
13
-
14
- ## What gets retried
15
-
16
- Retried automatically:
17
-
18
- - HTTP `429` (rate limited)
19
- - HTTP `500`, `502`, `503`, `504` (server hiccups)
20
- - HTTP `529` (upstream provider overloaded)
21
- - Network errors (connection reset, DNS failure, timeout)
22
-
23
- Never retried — these fail fast so you see the real problem immediately:
24
-
25
- - `400` / `422` (bad request), `401` / `403` (auth), `404` (not found),
26
- `409` (conflict), and every other non-transient `4xx`.
27
- - A request you aborted yourself (via an `AbortSignal`).
28
-
29
- ## Tuning or disabling
30
-
31
- Pass a `retry` option when you construct the client:
32
-
33
- ```ts
34
- import { Aex } from "@aexhq/sdk";
35
-
36
- const aex = new Aex({
37
- apiKey: process.env.AEX_API_KEY!,
38
- retry: {
39
- maxAttempts: 4, // total tries incl. the first (default 4)
40
- initialDelayMs: 500, // base backoff, doubles per retry (default 500)
41
- maxDelayMs: 20_000, // cap on any single wait (default 20s)
42
- maxElapsedMs: 120_000 // overall wall-clock budget (default 2m)
43
- }
44
- });
45
- ```
46
-
47
- Turn it off entirely with `retry: false`, or make a single attempt with
48
- `retry: { maxAttempts: 1 }`.
49
-
50
- ## Idempotent by construction
51
-
52
- Retries — whether the built-in transport retry or your own re-invocation of
53
- `run(...)` — never double-bill. The one-shot `run(...)` and `sessions.run(...)`
54
- derive the turn's idempotency key from the session-create key, so re-invoking
55
- either with the same `idempotencyKey` de-duplicates **both** the session create
56
- and the billable turn server-side:
57
-
58
- ```ts
59
- // A retried call with the same idempotencyKey resolves to the same run,
60
- // not a second billable one.
61
- const result = await aex.run({
62
- model: "claude-haiku-4-5",
63
- message: "Write a short report and save it as a file.",
64
- apiKeys: { anthropic: process.env.ANTHROPIC_API_KEY! },
65
- idempotencyKey: "report-2026-07-01"
66
- });
67
- ```
68
-
69
- ## Replaying a throttled turn
70
-
71
- When a turn on a live session is interrupted by a throttle, replay the last
72
- message with `session.replayLast()`. It reuses the previous message's idempotency
73
- key by default, so if the original turn actually landed it de-duplicates instead
74
- of billing twice:
75
-
76
- ```ts
77
- const session = await aex.openSession({
78
- model: "claude-haiku-4-5",
79
- apiKeys: { anthropic: process.env.ANTHROPIC_API_KEY! }
80
- });
81
-
82
- try {
83
- await session.send("Summarize the attached dataset.").done();
84
- } catch (err) {
85
- const { isRateLimited } = await import("@aexhq/sdk");
86
- if (isRateLimited(err)) {
87
- // Wait out the throttle, then replay the same message.
88
- await new Promise((r) => setTimeout(r, err.retryAfterMs ?? 2_000));
89
- await session.replayLast().done();
90
- } else {
91
- throw err;
92
- }
93
- }
94
- ```
95
-
96
- Pass a fresh key (`session.replayLast({ idempotencyKey: "..." })`) when you
97
- deliberately want a brand-new turn instead of a de-duplicated replay.
98
-
99
- ## The throttle error
100
-
101
- When retries are exhausted on a rate-limit / overloaded status, the SDK throws an
102
- `AexRateLimitError`. It extends `AexApiError`, so existing `catch` sites keep
103
- working, and it carries structured, non-leaky detail:
104
-
105
- ```ts
106
- import { isRateLimited } from "@aexhq/sdk";
107
-
108
- try {
109
- await aex.run({ /* … */ });
110
- } catch (err) {
111
- if (isRateLimited(err)) {
112
- err.status; // 429 | 503 | 529
113
- err.attempts; // how many tries were made
114
- err.retryAfterMs; // suggested wait, when the server supplied one
115
- err.source; // "api" (aex plane) or "provider" (upstream model)
116
- err.providerFault; // upstream fault detail, when the model provider throttled
117
- }
118
- }
119
- ```
120
-
121
- The `message` is a fixed summary (e.g. `aex API rate limit reached (HTTP 429)
122
- after 4 attempts; retry after ~2s`) — it never echoes the raw response body,
123
- which stays available, redacted, on `err.body`.
124
-
125
- When the throttle originated at the upstream model provider (rather than the aex
126
- API plane), `err.source` is `"provider"` and `err.providerFault` describes it:
127
- its `kind` (`rate_limit` / `overloaded` / `quota_exceeded` / `provider_error`),
128
- the upstream `status`, and a suggested `retryAfterMs`. Use `parseProviderFault`
129
- to read the same shape off a raw fault value yourself.
1
+ ---
2
+ title: Retries and throttling
3
+ ---
4
+
5
+ # Retries and throttling
6
+
7
+ The SDK ships with built-in transport resilience. Every request it makes to the
8
+ aex API is automatically retried on **transient** failures with bounded
9
+ exponential backoff and jitter, honoring the server's `Retry-After` header. You
10
+ get this by default — no wrapper code — and it is safe to leave on because the
11
+ billable submits carry a stable idempotency key, so a retry never creates a
12
+ duplicate run.
13
+
14
+ ## What gets retried
15
+
16
+ Retried automatically:
17
+
18
+ - HTTP `429` (rate limited)
19
+ - HTTP `500`, `502`, `503`, `504` (server hiccups)
20
+ - HTTP `529` (upstream provider overloaded)
21
+ - Network errors (connection reset, DNS failure, timeout)
22
+
23
+ Never retried — these fail fast so you see the real problem immediately:
24
+
25
+ - `400` / `422` (bad request), `401` / `403` (auth), `404` (not found),
26
+ `409` (conflict), and every other non-transient `4xx`.
27
+ - A request you aborted yourself (via an `AbortSignal`).
28
+
29
+ ## Tuning or disabling
30
+
31
+ Pass a `retry` option when you construct the client:
32
+
33
+ ```ts
34
+ import { Aex } from "@aexhq/sdk";
35
+
36
+ const aex = new Aex({
37
+ apiKey: process.env.AEX_API_KEY!,
38
+ retry: {
39
+ maxAttempts: 4, // total tries incl. the first (default 4)
40
+ initialDelayMs: 500, // base backoff, doubles per retry (default 500)
41
+ maxDelayMs: 20_000, // cap on any single wait (default 20s)
42
+ maxElapsedMs: 120_000 // overall wall-clock budget (default 2m)
43
+ }
44
+ });
45
+ ```
46
+
47
+ Turn it off entirely with `retry: false`, or make a single attempt with
48
+ `retry: { maxAttempts: 1 }`.
49
+
50
+ ## Idempotent by construction
51
+
52
+ Retries — whether the built-in transport retry or your own re-invocation of
53
+ `run(...)` — never double-bill. The one-shot `run(...)` and `sessions.run(...)`
54
+ derive the turn's idempotency key from the session-create key, so re-invoking
55
+ either with the same `idempotencyKey` de-duplicates **both** the session create
56
+ and the billable turn server-side:
57
+
58
+ ```ts
59
+ // A retried call with the same idempotencyKey resolves to the same run,
60
+ // not a second billable one.
61
+ const result = await aex.run({
62
+ model: "claude-haiku-4-5",
63
+ message: "Write a short report and save it as a file.",
64
+ apiKeys: { anthropic: process.env.ANTHROPIC_API_KEY! },
65
+ idempotencyKey: "report-2026-07-01"
66
+ });
67
+ ```
68
+
69
+ ## Replaying a throttled turn
70
+
71
+ When a turn on a live session is interrupted by a throttle, replay the last
72
+ message with `session.replayLast()`. It reuses the previous message's idempotency
73
+ key by default, so if the original turn actually landed it de-duplicates instead
74
+ of billing twice:
75
+
76
+ ```ts
77
+ const session = await aex.openSession({
78
+ model: "claude-haiku-4-5",
79
+ apiKeys: { anthropic: process.env.ANTHROPIC_API_KEY! }
80
+ });
81
+
82
+ try {
83
+ await session.send("Summarize the attached dataset.").done();
84
+ } catch (err) {
85
+ const { isRateLimited } = await import("@aexhq/sdk");
86
+ if (isRateLimited(err)) {
87
+ // Wait out the throttle, then replay the same message.
88
+ await new Promise((r) => setTimeout(r, err.retryAfterMs ?? 2_000));
89
+ await session.replayLast().done();
90
+ } else {
91
+ throw err;
92
+ }
93
+ }
94
+ ```
95
+
96
+ Pass a fresh key (`session.replayLast({ idempotencyKey: "..." })`) when you
97
+ deliberately want a brand-new turn instead of a de-duplicated replay.
98
+
99
+ ## The throttle error
100
+
101
+ When retries are exhausted on a rate-limit / overloaded status, the SDK throws an
102
+ `AexRateLimitError`. It extends `AexApiError`, so existing `catch` sites keep
103
+ working, and it carries structured, non-leaky detail:
104
+
105
+ ```ts
106
+ import { isRateLimited } from "@aexhq/sdk";
107
+
108
+ try {
109
+ await aex.run({ /* … */ });
110
+ } catch (err) {
111
+ if (isRateLimited(err)) {
112
+ err.status; // 429 | 503 | 529
113
+ err.attempts; // how many tries were made
114
+ err.retryAfterMs; // suggested wait, when the server supplied one
115
+ err.source; // "api" (aex plane) or "provider" (upstream model)
116
+ err.providerFault; // upstream fault detail, when the model provider throttled
117
+ }
118
+ }
119
+ ```
120
+
121
+ The `message` is a fixed summary (e.g. `aex API rate limit reached (HTTP 429)
122
+ after 4 attempts; retry after ~2s`) — it never echoes the raw response body,
123
+ which stays available, redacted, on `err.body`.
124
+
125
+ When the throttle originated at the upstream model provider (rather than the aex
126
+ API plane), `err.source` is `"provider"` and `err.providerFault` describes it:
127
+ its `kind` (`rate_limit` / `overloaded` / `quota_exceeded` / `provider_error`),
128
+ the upstream `status`, and a suggested `retryAfterMs`. Use `parseProviderFault`
129
+ to read the same shape off a raw fault value yourself.
@@ -1,58 +1,58 @@
1
- ---
2
- title: Run configuration
3
- ---
4
-
5
- # Run configuration
6
-
7
- A run config is the credential-free subset of the session options (`openSession` / `run`) that you can keep in code or load from a JSON file. It is not a platform object, saved definition, DSL, trigger, or persistent agent profile. aex only stores the immutable session record created when the session lands.
8
-
9
- Allowed fields:
10
-
11
- - `model` - required.
12
- - `system` - optional system message.
13
- - `mcpServers` - array of `McpServerRef`; headers are split into the vaulted secrets channel server-side.
14
- - `environment` - `{ networking?, packages?, variables? }`. Networking is open by default; set `networking.mode` to `limited` only when you want an allowlist. `variables` are merged into the in-container `RUNTIME.env` / `RUNTIME.json` mounts. (Run secrets go in `environment.secrets`, which carries live `Secret` instances and is not part of a shareable config.)
15
- - `runtime` - optional managed-runtime preset. Prefer `Sizes` in TypeScript.
16
- - `metadata` - non-secret structured metadata.
17
- - `overrides` - `{ idleTtl?, timeout?, maxSpendUsd?, maxTurns? }`. `timeout` is an optional session deadline (e.g. `"30m"`, `"2h"`); `maxSpendUsd` stops the session once its spend would exceed the cap; `maxTurns` caps the agent's iteration count for the turn (a positive integer — omit to accept the platform default of 20, clamped to the ceiling). See [Limits & quotas](limits-and-quotas.md).
18
-
19
- `message` (the one-shot `run` input), `agentsMd`, `files`, `outputs`, `tools`, `skills`, `includeBuiltinTools`, and `outputMode` are `openSession` / `run` options, not reusable run-config fields. They carry the turn input, bytes, capture behavior, or agent tool/output controls that belong on a concrete call. Skill bundles are `skills` entries built with `Skill.fromDir(...)`, `Skill.fromUrl(...)`, or the other `Skill.from*` factories, so they too are SDK-code options rather than config fields. Subagents are session-internal (the in-run `subagent` tool — see [Subagents](concepts/subagents.md)); there is no `parentRunId` option. The wire contract carries a per-run `limits` object (the exported `RunLimits` type: `maxConcurrentChildRuns`, `maxSubagentDepth`, `maxSpendUsd`, `maxTurns`), and the session surface exposes its spend and iteration dials via `overrides.maxSpendUsd` and `overrides.maxTurns`; the subagent depth/breadth dials are not settable per-session today and take the platform defaults.
20
-
21
- Secrets never live in run config. Pass provider keys through the top-level
22
- `apiKeys` map and runtime secrets through `environment.secrets` in the SDK, or
23
- the equivalent host-mode flags (`--anthropic-api-key`, `--mcp-auth`) in the CLI.
24
- See [Secrets](secrets.md) for secret lifecycles and [Credentials](credentials.md)
25
- for credential handling.
26
-
27
- ## Reuse in code
28
-
29
- Use an ordinary function when you want reusable typed parameters. aex does not store or execute this function; it only receives the parameters you pass.
30
-
31
- ```ts
32
- import { Models } from "@aexhq/sdk";
33
-
34
- function summarise(topic: string) {
35
- return {
36
- model: Models.CLAUDE_HAIKU_4_5,
37
- system: "You are a concise automation agent.",
38
- message: `Write a short answer about ${topic}.`
39
- };
40
- }
41
-
42
- await aex.run({
43
- ...summarise("agent-first SDK design"),
44
- apiKeys: { anthropic: apiKey }
45
- });
46
- ```
47
-
48
- ## CLI
49
-
50
- The `aex run` host subcommand accepts the same run config either as a JSON file:
51
-
52
- ```bash
53
- aex run --config ./run.json \
54
- --api-key "$AEX_API_KEY" \
55
- --anthropic-api-key "$ANTHROPIC_API_KEY"
56
- ```
57
-
58
- ...or as explicit flags (`--model`, `--system`, `--prompt`, `--mcp`, `--mcp-auth`, `--runtime-size`, `--run-timeout`, `--metadata`). The two modes are mutually exclusive. Composition inputs attach with repeatable flags that map onto the SDK's `Skill`/`Tool`/`AgentsMd`/`File` factories: `--skill @bundle` (workspace skill), `--tool @tool.js` (custom tool module), `--agents-md @AGENTS.md`, and `--file @path` (mount a file into `/workspace`).
1
+ ---
2
+ title: Run configuration
3
+ ---
4
+
5
+ # Run configuration
6
+
7
+ A run config is the credential-free subset of the session options (`openSession` / `run`) that you can keep in code or load from a JSON file. It is not a platform object, saved definition, DSL, trigger, or persistent agent profile. aex only stores the immutable session record created when the session lands.
8
+
9
+ Allowed fields:
10
+
11
+ - `model` - required.
12
+ - `system` - optional system message.
13
+ - `mcpServers` - array of `McpServerRef`; headers are split into the vaulted secrets channel server-side.
14
+ - `environment` - `{ networking?, packages?, variables? }`. Networking is open by default; set `networking.mode` to `limited` only when you want an allowlist. `variables` are merged into the in-container `RUNTIME.env` / `RUNTIME.json` mounts. (Run secrets go in `environment.secrets`, which carries live `Secret` instances and is not part of a shareable config.)
15
+ - `runtime` - optional managed-runtime preset. Prefer `Sizes` in TypeScript.
16
+ - `metadata` - non-secret structured metadata.
17
+ - `overrides` - `{ idleTtl?, timeout?, maxSpendUsd?, maxTurns? }`. `timeout` is an optional session deadline (e.g. `"30m"`, `"2h"`); `maxSpendUsd` stops the session once its spend would exceed the cap; `maxTurns` caps the agent's iteration count for the turn (a positive integer — omit to accept the platform default of 20, clamped to the ceiling). See [Limits & quotas](limits-and-quotas.md).
18
+
19
+ `message` (the one-shot `run` input), `agentsMd`, `files`, `outputs`, `tools`, `skills`, `includeBuiltinTools`, and `outputMode` are `openSession` / `run` options, not reusable run-config fields. They carry the turn input, bytes, capture behavior, or agent tool/output controls that belong on a concrete call. Skill bundles are `skills` entries built with `Skill.fromDir(...)`, `Skill.fromUrl(...)`, or the other `Skill.from*` factories, so they too are SDK-code options rather than config fields. Subagents are session-internal (the in-run `subagent` tool — see [Subagents](concepts/subagents.md)); there is no `parentRunId` option. The wire contract carries a per-run `limits` object (the exported `RunLimits` type: `maxConcurrentChildRuns`, `maxSubagentDepth`, `maxSpendUsd`, `maxTurns`), and the session surface exposes its spend and iteration dials via `overrides.maxSpendUsd` and `overrides.maxTurns`; the subagent depth/breadth dials are not settable per-session today and take the platform defaults.
20
+
21
+ Secrets never live in run config. Pass provider keys through the top-level
22
+ `apiKeys` map and runtime secrets through `environment.secrets` in the SDK, or
23
+ the equivalent host-mode flags (`--anthropic-api-key`, `--mcp-auth`) in the CLI.
24
+ See [Secrets](secrets.md) for secret lifecycles and [Credentials](credentials.md)
25
+ for credential handling.
26
+
27
+ ## Reuse in code
28
+
29
+ Use an ordinary function when you want reusable typed parameters. aex does not store or execute this function; it only receives the parameters you pass.
30
+
31
+ ```ts
32
+ import { Models } from "@aexhq/sdk";
33
+
34
+ function summarise(topic: string) {
35
+ return {
36
+ model: Models.CLAUDE_HAIKU_4_5,
37
+ system: "You are a concise automation agent.",
38
+ message: `Write a short answer about ${topic}.`
39
+ };
40
+ }
41
+
42
+ await aex.run({
43
+ ...summarise("agent-first SDK design"),
44
+ apiKeys: { anthropic: apiKey }
45
+ });
46
+ ```
47
+
48
+ ## CLI
49
+
50
+ The `aex run` host subcommand accepts the same run config either as a JSON file:
51
+
52
+ ```bash
53
+ aex run --config ./run.json \
54
+ --api-key "$AEX_API_KEY" \
55
+ --anthropic-api-key "$ANTHROPIC_API_KEY"
56
+ ```
57
+
58
+ ...or as explicit flags (`--model`, `--system`, `--prompt`, `--mcp`, `--mcp-auth`, `--runtime-size`, `--run-timeout`, `--metadata`). The two modes are mutually exclusive. Composition inputs attach with repeatable flags that map onto the SDK's `Skill`/`Tool`/`AgentsMd`/`File` factories: `--skill @bundle` (workspace skill), `--tool @tool.js` (custom tool module), `--agents-md @AGENTS.md`, and `--file @path` (mount a file into `/workspace`).
@@ -1,55 +1,55 @@
1
- ---
2
- title: Run record
3
- ---
4
-
5
- # Run record
6
-
7
- The run record is the durable product primitive for one session. It is the public-safe bundle of status metadata, the non-secret submission snapshot when available, typed events, captured outputs, and manifest entries for custody and cost telemetry.
8
-
9
- ## Listing sessions
10
-
11
- `aex.sessions.list(query?)` enumerates the sessions in this workspace, most-recent first, one page at a time. The workspace is derived server-side from the API key, so this only ever returns your own sessions. It is the workspace-wide discovery entry point: combine it with `aex.sessions.outputs(id).list()` / `.read(...)` (see [Outputs](outputs.md)) to reach any session's deliverables.
12
-
13
- ```ts
14
- let cursor: string | undefined;
15
- do {
16
- const page = await aex.sessions.list({ status: "idle", limit: 25, cursor });
17
- for (const session of page.sessions) {
18
- console.log(session.id, session.status, session.createdAt, session.costUsd);
19
- }
20
- cursor = page.nextCursor;
21
- } while (cursor);
22
- ```
23
-
24
- `query` fields are all optional: `status` (single session status, e.g. `"idle"`), `since` (ISO-8601 lower bound on `createdAt`), `limit` (defaults to 25, clamped to `[1, 100]`), and `cursor` (the opaque keyset cursor from a prior page's `nextCursor` — absent on the last page). Each page row is a public-safe `SessionSummary` (`id`, `status`, `createdAt`, `updatedAt`, and `costUsd` once settled); it deliberately omits the submission snapshot (model / system / env). Use `aex.sessions.get(id)` for status / timing / cost on one session, or `session.unit()` on a handle for the full self-contained record including the parsed submission.
25
-
26
- ## Downloading a run record
27
-
28
- `session.download()` and `aex download <session-id>` return a zip with this layout:
29
-
30
- ```text
31
- manifest.json
32
- metadata/run.json
33
- metadata/submission.json # when a public-safe submission snapshot is returned by the read API
34
- metadata/cost.json # when public cost telemetry is returned by the read API
35
- events/events.jsonl
36
- outputs/<captured deliverable files>
37
- ```
38
-
39
- `manifest.json` is versioned as `RunRecordManifestV1`:
40
-
41
- | Field | Meaning |
42
- | --- | --- |
43
- | `schemaVersion` | `aex.run-record.manifest.v1`. |
44
- | `runRecordSchemaVersion` | `aex.run-record.v1`. |
45
- | `runId` | The run the archive was assembled for. |
46
- | `namespaces[]` | The documented top-level namespaces: `metadata`, `events`, `outputs`. |
47
- | `files[]` | Inventory of expected and present files with `namespace`, `path`, `role`, and `status`. |
48
- | `outputs[]` | Compatibility alias for present captured output artifacts. |
49
- | `errors[]` | Per-artifact byte fetch failures during archive assembly. |
50
-
51
- Current v1 downloads always include `metadata/run.json` and `events/events.jsonl`. `events/events.jsonl` contains typed event-channel records only; internal diagnostics and full internal streams are not mixed into that file.
52
-
53
- `metadata/submission.json` is present only when the run read shape includes a public-safe submission snapshot. `metadata/cost.json` is present only when the run read shape includes public `costTelemetry`; otherwise cost stays `pending`. `metadata/custody.json` remains `pending` until the custody manifest writer and public read surface land. `events/manifest.json` remains `unavailable` in this client-side slice because there is no public coordinator-manifest download route.
54
-
55
- The record boundary is public-safe. It must not contain provider API keys, runner bearers, workspace tokens, signed URLs, raw provider response bodies, object-store keys, Vault ids, raw query strings, secret-shaped values, or internal diagnostic files. Credentials are supplied per run and vaulted separately for the run lifetime.
1
+ ---
2
+ title: Run record
3
+ ---
4
+
5
+ # Run record
6
+
7
+ The run record is the durable product primitive for one session. It is the public-safe bundle of status metadata, the non-secret submission snapshot when available, typed events, captured outputs, and manifest entries for custody and cost telemetry.
8
+
9
+ ## Listing sessions
10
+
11
+ `aex.sessions.list(query?)` enumerates the sessions in this workspace, most-recent first, one page at a time. The workspace is derived server-side from the API key, so this only ever returns your own sessions. It is the workspace-wide discovery entry point: combine it with `aex.sessions.outputs(id).list()` / `.read(...)` (see [Outputs](outputs.md)) to reach any session's deliverables.
12
+
13
+ ```ts
14
+ let cursor: string | undefined;
15
+ do {
16
+ const page = await aex.sessions.list({ status: "idle", limit: 25, cursor });
17
+ for (const session of page.sessions) {
18
+ console.log(session.id, session.status, session.createdAt, session.costUsd);
19
+ }
20
+ cursor = page.nextCursor;
21
+ } while (cursor);
22
+ ```
23
+
24
+ `query` fields are all optional: `status` (single session status, e.g. `"idle"`), `since` (ISO-8601 lower bound on `createdAt`), `limit` (defaults to 25, clamped to `[1, 100]`), and `cursor` (the opaque keyset cursor from a prior page's `nextCursor` — absent on the last page). Each page row is a public-safe `SessionSummary` (`id`, `status`, `createdAt`, `updatedAt`, and `costUsd` once settled); it deliberately omits the submission snapshot (model / system / env). Use `aex.sessions.get(id)` for status / timing / cost on one session, or `session.unit()` on a handle for the full self-contained record including the parsed submission.
25
+
26
+ ## Downloading a run record
27
+
28
+ `session.download()` and `aex download <session-id>` return a zip with this layout:
29
+
30
+ ```text
31
+ manifest.json
32
+ metadata/run.json
33
+ metadata/submission.json # when a public-safe submission snapshot is returned by the read API
34
+ metadata/cost.json # when public cost telemetry is returned by the read API
35
+ events/events.jsonl
36
+ outputs/<captured deliverable files>
37
+ ```
38
+
39
+ `manifest.json` is versioned as `RunRecordManifestV1`:
40
+
41
+ | Field | Meaning |
42
+ | --- | --- |
43
+ | `schemaVersion` | `aex.run-record.manifest.v1`. |
44
+ | `runRecordSchemaVersion` | `aex.run-record.v1`. |
45
+ | `runId` | The run the archive was assembled for. |
46
+ | `namespaces[]` | The documented top-level namespaces: `metadata`, `events`, `outputs`. |
47
+ | `files[]` | Inventory of expected and present files with `namespace`, `path`, `role`, and `status`. |
48
+ | `outputs[]` | Compatibility alias for present captured output artifacts. |
49
+ | `errors[]` | Per-artifact byte fetch failures during archive assembly. |
50
+
51
+ Current v1 downloads always include `metadata/run.json` and `events/events.jsonl`. `events/events.jsonl` contains typed event-channel records only; internal diagnostics and full internal streams are not mixed into that file.
52
+
53
+ `metadata/submission.json` is present only when the run read shape includes a public-safe submission snapshot. `metadata/cost.json` is present only when the run read shape includes public `costTelemetry`; otherwise cost stays `pending`. `metadata/custody.json` remains `pending` until the custody manifest writer and public read surface land. `events/manifest.json` remains `unavailable` in this client-side slice because there is no public coordinator-manifest download route.
54
+
55
+ The record boundary is public-safe. It must not contain provider API keys, runner bearers, workspace tokens, signed URLs, raw provider response bodies, object-store keys, Vault ids, raw query strings, secret-shaped values, or internal diagnostic files. Credentials are supplied per run and vaulted separately for the run lifetime.