@aexhq/sdk 0.35.0 → 0.37.0

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 (72) hide show
  1. package/README.md +17 -16
  2. package/dist/_contracts/event-envelope.d.ts +22 -1
  3. package/dist/_contracts/event-envelope.js +26 -2
  4. package/dist/_contracts/event-stream-client.js +7 -1
  5. package/dist/_contracts/index.d.ts +3 -4
  6. package/dist/_contracts/index.js +1 -4
  7. package/dist/_contracts/operations.d.ts +31 -1
  8. package/dist/_contracts/operations.js +64 -1
  9. package/dist/_contracts/run-config.d.ts +2 -4
  10. package/dist/_contracts/run-config.js +2 -7
  11. package/dist/_contracts/run-trace.d.ts +0 -86
  12. package/dist/_contracts/run-trace.js +1 -184
  13. package/dist/_contracts/run-unit.d.ts +14 -25
  14. package/dist/_contracts/run-unit.js +56 -2
  15. package/dist/_contracts/runtime-manifest.d.ts +1 -1
  16. package/dist/_contracts/runtime-security-profile.d.ts +0 -2
  17. package/dist/_contracts/runtime-security-profile.js +0 -9
  18. package/dist/_contracts/runtime-sizes.d.ts +2 -2
  19. package/dist/_contracts/runtime-sizes.js +5 -5
  20. package/dist/_contracts/runtime-types.d.ts +123 -4
  21. package/dist/_contracts/stable.d.ts +1 -1
  22. package/dist/_contracts/stable.js +1 -1
  23. package/dist/_contracts/submission.d.ts +8 -76
  24. package/dist/_contracts/submission.js +5 -472
  25. package/dist/cli.mjs +574 -511
  26. package/dist/cli.mjs.sha256 +1 -1
  27. package/dist/client.d.ts +69 -25
  28. package/dist/client.js +338 -68
  29. package/dist/client.js.map +1 -1
  30. package/dist/index.d.ts +8 -16
  31. package/dist/index.js +5 -17
  32. package/dist/index.js.map +1 -1
  33. package/dist/secret.d.ts +2 -2
  34. package/dist/secret.js +1 -1
  35. package/dist/version.d.ts +1 -1
  36. package/dist/version.js +1 -1
  37. package/docs/authentication.md +92 -0
  38. package/docs/billing.md +112 -0
  39. package/docs/concepts/agent-tools.md +4 -4
  40. package/docs/concepts/composition.md +8 -14
  41. package/docs/concepts/providers-and-runtimes.md +4 -1
  42. package/docs/concepts/runs.md +2 -1
  43. package/docs/concepts/subagents.md +85 -0
  44. package/docs/credentials.md +78 -96
  45. package/docs/defaults.md +9 -15
  46. package/docs/errors.md +132 -0
  47. package/docs/events.md +44 -32
  48. package/docs/limits-and-quotas.md +30 -17
  49. package/docs/limits.md +4 -8
  50. package/docs/mcp.md +5 -6
  51. package/docs/networking.md +75 -59
  52. package/docs/outputs.md +4 -7
  53. package/docs/public-surface.json +4 -4
  54. package/docs/quickstart.md +12 -13
  55. package/docs/run-config.md +7 -4
  56. package/docs/secrets.md +6 -1
  57. package/docs/skills.md +3 -3
  58. package/docs/vision-skills.md +52 -101
  59. package/docs/webhooks.md +132 -0
  60. package/examples/feature-tour.ts +4 -21
  61. package/package.json +1 -1
  62. package/dist/_contracts/proxy-protocol.d.ts +0 -305
  63. package/dist/_contracts/proxy-protocol.js +0 -297
  64. package/dist/_contracts/proxy-validation.d.ts +0 -19
  65. package/dist/_contracts/proxy-validation.js +0 -51
  66. package/dist/data-tools.d.ts +0 -82
  67. package/dist/data-tools.js +0 -251
  68. package/dist/data-tools.js.map +0 -1
  69. package/dist/proxy-endpoint.d.ts +0 -131
  70. package/dist/proxy-endpoint.js +0 -144
  71. package/dist/proxy-endpoint.js.map +0 -1
  72. package/examples/chat-corpus.ts +0 -84
package/docs/events.md CHANGED
@@ -53,9 +53,9 @@ for the string:
53
53
  const lastText = (await session.messages().last())?.text;
54
54
  ```
55
55
 
56
- `decodeAssistantText`, `textOf`, and `summarizeRunTrace` remain exported as the
57
- power-user escape hatch over a raw `RunEvent` list, but "get the last message"
58
- is now `await session.messages().last()`.
56
+ Prefer `session.messages().list()` or the collected `result.messages` /
57
+ `result.text` fields for assistant text. Low-level event helpers remain exported
58
+ for callers that build custom collectors.
59
59
 
60
60
  The CLI mirrors the same surface:
61
61
 
@@ -110,22 +110,32 @@ collected session turn. The returned `runId` is the session id.
110
110
 
111
111
  ## Terminal events vs. the run record
112
112
 
113
- A session turn emits a terminal **event** `RUN_FINISHED`
114
- (success) or `RUN_ERROR` when
115
- the agent's stream ends. This is an AG-UI *render-complete* signal: the runner
116
- emits it **before** aex commits the authoritative session record, so an
117
- `aex.sessions.get(id)` issued the instant you observe `RUN_FINISHED` can still
118
- read a non-parked status for a moment. Treat the terminal event as the
119
- lowest-latency "stop the spinner" signal **not** a read-consistency barrier.
120
-
121
- Two facts make this easy to work with:
122
-
123
- - **Outputs are already durable at the terminal event.** The runner uploads every
124
- output before it emits the terminal event, and `session.outputs().list()` / downloads
125
- read object storage directly so the moment you see `RUN_FINISHED` the outputs
126
- are complete and readable.
127
- - **The session _record_ settles a beat later.** To read the authoritative status
128
- consistently, don't key off the terminal event — use one of:
113
+ Two families of events can end a turn's stream, and which one you see depends
114
+ on how the turn ends:
115
+
116
+ - **AG-UI terminals** `RUN_FINISHED` / `RUN_ERROR`. These are *render-complete*
117
+ signals emitted by the agent stream itself. On the managed plane a normal
118
+ session turn usually does **not** emit `RUN_FINISHED`: the session *parks*
119
+ instead (see below). Expect `RUN_ERROR` on stream-level failures, and treat
120
+ `RUN_FINISHED` — when it does appear — as a low-latency "stop the spinner"
121
+ hint, not a read-consistency barrier.
122
+ - **`aex.session.*` park terminals** — `CUSTOM` events named `aex.session.idle`,
123
+ `aex.session.suspended`, or `aex.session.error`. On the managed plane these
124
+ are what actually end a turn: the session parks with the matching status, and
125
+ by the time the park event is broadcast the session record has already
126
+ reached that status. This is the terminal you should expect from a managed
127
+ run's event stream.
128
+
129
+ The SDK's helpers cover both families so you never have to switch on the plane:
130
+
131
+ - `isRunTerminal(event)` — true for the AG-UI `RUN_FINISHED` / `RUN_ERROR` pair.
132
+ - `isRunSettled(event)` — true for the `aex.run.settled` settle barrier **and**
133
+ for any `aex.session.*` park terminal. The managed plane does not broadcast a
134
+ separate `aex.run.settled` barrier — the park event plays that role — so
135
+ `isRunSettled` is the one guard that reliably means "this stream is done and
136
+ the record is authoritative".
137
+
138
+ To read the authoritative status consistently, use one of:
129
139
 
130
140
  ```ts
131
141
  // Session record path: send a turn, then wait for the session to park.
@@ -135,18 +145,21 @@ const record = await session.wait(); // the parked session record
135
145
  ```
136
146
 
137
147
  ```ts
138
- // Live events AND a settle-consistent end: the iterator keeps reading past
139
- // RUN_FINISHED until the post-mirror barrier, so the record is terminal when it ends.
148
+ // Live events AND a settle-consistent end: the iterator ends on the settle
149
+ // barrier OR the aex.session.* park terminal, whichever the plane emits
150
+ // so when it ends, the session record is already parked/terminal.
140
151
  for await (const event of session.events().streamEnvelopes({ settleConsistent: true })) {
141
152
  // render events live…
142
153
  }
143
- const settled = await aex.sessions.get(session.id); // guaranteed terminal here
154
+ const settled = await aex.sessions.get(session.id); // parked/terminal here
144
155
  ```
145
156
 
146
- Under the hood the coordinator broadcasts one `aex.run.settled` CUSTOM event as a
147
- run's last stream event, immediately after the durable record commits.
148
- `settleConsistent` ends the stream on it; on a raw stream, detect it with
149
- `isRunSettled(event)`.
157
+ `settleConsistent: true` makes the iterator end exactly when `isRunSettled(event)`
158
+ first fires; on a raw stream, apply `isRunSettled(event)` yourself. What it
159
+ guarantees: when the stream ends, a subsequent `aex.sessions.get(id)` reads a
160
+ parked/terminal status and `session.outputs().list()` is complete. Outputs are
161
+ uploaded before the terminal is broadcast, so they are readable the moment the
162
+ stream ends.
150
163
 
151
164
  ## Temporary event archive links
152
165
 
@@ -162,7 +175,7 @@ const jsonl = await response.text();
162
175
 
163
176
  ## Event shape
164
177
 
165
- Events are typed as the discriminated `RunEvent` union for compatibility and as the versioned coordinator envelope for live consumers. aex records raw runtime/provider payloads **after** secret redaction and structural sanitization, so the bytes you see never contain the provider key, MCP credentials, or proxy bearer that were supplied when the session was opened.
178
+ Events are typed as the discriminated `RunEvent` union for compatibility and as the versioned coordinator envelope for live consumers. aex records raw runtime/provider payloads **after** secret redaction and structural sanitization, so the bytes you see never contain provider keys, MCP credentials, or runtime secrets supplied when the session was opened.
166
179
 
167
180
  ## Typed helpers
168
181
 
@@ -180,8 +193,7 @@ import {
180
193
  isToolCallResult,
181
194
  isCustom,
182
195
  isLog,
183
- isEventChannel,
184
- textOf
196
+ isEventChannel
185
197
  } from "@aexhq/sdk";
186
198
  ```
187
199
 
@@ -191,6 +203,6 @@ All guards test the `type` discriminant at runtime. `isTextMessage`,
191
203
  `event.data` to the fields that event type carries — e.g. inside
192
204
  `if (isTextMessage(e))`, `e.data.text` is typed `string`. The lifecycle/channel
193
205
  guards (`isRunStarted`, `isRunError`, `isCustom`, `isLog`, …) operate on the
194
- coordinator envelope and narrow only the discriminant. `textOf(events)` returns
195
- the run's final assistant text concatenated from the `TEXT_MESSAGE_CONTENT`
196
- blocks.
206
+ coordinator envelope and narrow only the discriminant. Use `result.text` or
207
+ `session.messages.all()` when you need assistant text without inspecting the
208
+ event stream directly.
@@ -5,10 +5,9 @@ title: Limits & quotas
5
5
  # Limits & quotas
6
6
 
7
7
  These are the hard ceilings and caps that bound a run, a workspace, and a single
8
- request. Every value is mirrored from a single source-of-truth constant; the
9
- constant file is authoritative and this page is generated documentation, not a
10
- second source of truth. If a value here ever disagrees with that constant,
11
- the constant wins.
8
+ request. Every value is mirrored from a single source-of-truth constant in the
9
+ platform's limits module; this page is hand-maintained against those constants.
10
+ If a value here ever disagrees with the constant, the constant wins.
12
11
 
13
12
  Each row is named by its source-of-truth constant. For the values that apply
14
13
  when you omit an option, see
@@ -26,7 +25,7 @@ And whether you can **raise** it: per-run option, per-plan, or no.
26
25
 
27
26
  | Limit | Value | Source | Raisable? | Constant |
28
27
  | --- | --- | --- | --- | --- |
29
- | Maximum run timeout | 6 hours | aex policy | Per plan (billing-driven) | `RUN_MAX_TIMEOUT_MS` |
28
+ | Maximum run timeout | 8 hours (also the default when `timeout` is omitted) | aex policy | Per plan (billing-driven) | `RUN_MAX_TIMEOUT_MS` |
30
29
  | Minimum run timeout | 1 minute | aex policy | No (floor) | `RUN_MIN_TIMEOUT_MS` |
31
30
  | Per-call exec timeout (default) | 30 minutes | aex policy | Per-call via the tool call's `timeoutMs` | `RUN_DEFAULT_EXEC_TIMEOUT_MS` |
32
31
  | MCP connect timeout (default) | 30 seconds | aex policy | Per-port via `connectTimeoutMs` | `RUN_DEFAULT_MCP_CONNECT_TIMEOUT_MS` |
@@ -43,8 +42,8 @@ silently lost.
43
42
  | --- | --- | --- | --- | --- |
44
43
  | Capture wall-clock budget | 1 hour | aex policy | No (hard ceiling) | `RUN_CAPTURE_DEFAULT_TIMEOUT_MS` |
45
44
  | Max files captured | 50,000 | aex policy | No (hard ceiling) | `RUN_CAPTURE_MAX_FILES` |
46
- | Max bytes per captured file | 1 TB | aex policy | No (hard ceiling) | `RUN_CAPTURE_MAX_FILE_BYTES` |
47
- | Max total captured bytes | 1 TB | aex policy | No (hard ceiling) | `RUN_CAPTURE_MAX_TOTAL_BYTES` |
45
+ | Max bytes per captured file | 500 GB (decimal) | aex policy | No (hard ceiling) | `RUN_CAPTURE_MAX_FILE_BYTES` |
46
+ | Max total captured bytes | 500 GB (decimal) | aex policy | No (hard ceiling) | `RUN_CAPTURE_MAX_TOTAL_BYTES` |
48
47
 
49
48
  ### Tool output caps (per run)
50
49
 
@@ -74,9 +73,11 @@ silently lost.
74
73
 
75
74
  | Limit | Value | Source | Raisable? | Constant |
76
75
  | --- | --- | --- | --- | --- |
77
- | Workspace storage cap | 50 GiB (admins uncapped — not a customer entitlement) | Workspace default | Per-plane via env `AEX_WORKSPACE_STORAGE_CAP_BYTES` | `WORKSPACE_DEFAULT_STORAGE_CAP_BYTES` |
78
- | Max concurrent runs per workspace | Advisory there is no hard per-workspace concurrent-run cap constant; concurrency is bounded by plan, the subagent child-run cap, and provider/platform throughput rather than a fixed number. | aex policy | n/a | |
79
- | Skill bundle max compressed size (`.zip`) | 100 GB | Workspace default | Per-workspace (plan/env) | `WORKSPACE_SKILL_BUNDLE_MAX_COMPRESSED_BYTES` |
76
+ | Workspace storage cap | 500 GB (decimal; admins uncapped — not a customer entitlement) | Workspace default | Per-plane via env `AEX_WORKSPACE_STORAGE_CAP_BYTES` | `WORKSPACE_DEFAULT_STORAGE_CAP_BYTES` |
77
+ | Max concurrent runs per workspace | **50** live (non-terminal) root runs by default; hard platform ceiling **200**. One more submit past the cap fails with `429 workspace_concurrency_exceeded` (see [Errors](errors.md)). Subagent children are governed separately by the per-lineage caps below. | Workspace default | Per-workspace override (contact support), clamped to the 200 ceiling | `WORKSPACE_DEFAULT_MAX_CONCURRENT_RUNS` / `WORKSPACE_MAX_CONCURRENT_RUNS_CEILING` |
78
+ | Monthly workspace spend cap | **$250** per rolling UTC calendar month by default; `0` = unlimited. A submit past the cap fails with `402 workspace_spend_cap_exceeded` (see [Errors](errors.md)). | Workspace default | Per-workspace override (contact support) | `WORKSPACE_DEFAULT_SPEND_CAP_USD` |
79
+ | Skill bundle max compressed size (`.zip`) | 10 GiB (enforced at upload by the SDK and re-enforced server-side) | aex policy | No (hard ceiling) | `SKILL_BUNDLE_LIMITS.maxCompressedBytes` |
80
+ | Skill bundle max decompressed size (sum of uncompressed file sizes) | 50 MB | aex policy | No (hard ceiling) | `SKILL_BUNDLE_LIMITS.maxDecompressedBytes` |
80
81
  | Skill bundle max file entries | 1,000 | Workspace default | Per-workspace (plan/env) | `WORKSPACE_SKILL_BUNDLE_MAX_FILES` |
81
82
  | Skill bundle max directory depth (`a/b/c/d` = 4) | 16 | Workspace default | Per-workspace (plan/env) | `WORKSPACE_SKILL_BUNDLE_MAX_DEPTH` |
82
83
  | Skill bundle max entry path length | 512 characters | Workspace default | No (hard ceiling) | `WORKSPACE_SKILL_BUNDLE_MAX_PATH_LENGTH` |
@@ -84,24 +85,36 @@ silently lost.
84
85
 
85
86
  ### Rate limits (per workspace, per minute)
86
87
 
87
- Default values; each is overridable per-plane via the matching
88
- `AEX_RATE_LIMIT_<ACTION>_PER_MINUTE` env var.
88
+ Run submission has its own platform-enforced velocity cap: **120 submits per
89
+ minute** per workspace by default (`0` = disabled). Past it, `POST /runs` fails
90
+ with `429 workspace_submit_rate_exceeded` (see [Errors](errors.md)). It is
91
+ overridable per-plane via `AEX_WORKSPACE_SUBMIT_RATE_PER_MIN` or per-workspace
92
+ via support.
93
+
94
+ The dashboard mutation actions below default as listed; each is overridable
95
+ per-plane via the matching `AEX_RATE_LIMIT_<ACTION>_PER_MINUTE` env var.
89
96
 
90
97
  | Action | Default per minute | Source | Constant |
91
98
  | --- | --- | --- | --- |
92
- | Run submit | 60 | Workspace default | `WORKSPACE_RATE_LIMIT_DEFAULTS` |
93
99
  | Run cancel | 30 | Workspace default | `WORKSPACE_RATE_LIMIT_DEFAULTS` |
94
100
  | Run delete | 30 | Workspace default | `WORKSPACE_RATE_LIMIT_DEFAULTS` |
95
101
  | Signed output link | 120 | Workspace default | `WORKSPACE_RATE_LIMIT_DEFAULTS` |
96
102
  | API token create | 10 | Workspace default | `WORKSPACE_RATE_LIMIT_DEFAULTS` |
97
103
  | API token delete | 30 | Workspace default | `WORKSPACE_RATE_LIMIT_DEFAULTS` |
98
104
 
99
- ## Request scope (proxy and egress)
105
+ ### Introspecting your effective caps
106
+
107
+ `aex.whoami()` (CLI: `aex whoami`) returns a `limits` object carrying the
108
+ workspace's *effective* values for the caps above — `maxConcurrentRuns`,
109
+ `submitRatePerMinute`, `spendCapUsd`, plus the live `monthSpendUsd`,
110
+ `balanceUsd`, `balanceGraceFloorUsd`, and `paymentMethodStatus` — resolved by
111
+ the same code the admission gates use, so you can anticipate a `429`/`402`
112
+ before submitting. See [Authentication](authentication.md) and
113
+ [Errors](errors.md).
114
+
115
+ ## Request Scope
100
116
 
101
117
  | Limit | Value | Source | Raisable? | Constant |
102
118
  | --- | --- | --- | --- | --- |
103
- | Proxy request body | 10 MiB | aex policy | Per-endpoint via `maxRequestBytes` | `REQUEST_PROXY_DEFAULT_MAX_REQUEST_BYTES` |
104
- | Proxy response body | `0` = unlimited (streamed unbuffered) | aex policy | Per-endpoint via `maxResponseBytes` | `REQUEST_PROXY_DEFAULT_MAX_RESPONSE_BYTES` |
105
- | Proxy upstream timeout | 5 minutes | aex policy | Per-endpoint via `timeoutMs` | `REQUEST_PROXY_DEFAULT_TIMEOUT_MS` |
106
119
  | Signed output URL TTL | 300 seconds | aex policy | Per-call via `expiresSeconds` | `REQUEST_PRESIGN_URL_DEFAULT_TTL_SECONDS` |
107
120
  | Event-stream connection ticket TTL | 60 seconds | aex policy | Per-mint via `ttlMs` | `REQUEST_TICKET_DEFAULT_TTL_MS` |
package/docs/limits.md CHANGED
@@ -16,25 +16,21 @@ For the current provider/model set, see the generated
16
16
 
17
17
  | Area | Default |
18
18
  | --- | --- |
19
- | Workspace storage | 50 GiB per workspace for captured outputs and workspace artifacts. aex-maintainer admin workspaces may be unlimited for internal dogfooding; this is not a customer entitlement. |
20
- | Proxy request body | 10 MiB per proxy endpoint unless the endpoint declares a different `maxRequestBytes`. |
21
- | Proxy timeout | 5 minutes per proxy endpoint unless the endpoint declares a different `timeoutMs`. |
22
- | Proxy telemetry | Proxy calls emit report-only usage telemetry for call count, failed calls, request bytes, response bytes when known, and duration. Public proxy pricing is not shipped unless documented later. |
19
+ | Workspace storage | 500 GB per workspace for captured outputs and workspace artifacts. aex-maintainer admin workspaces may be unlimited for internal dogfooding; this is not a customer entitlement. |
23
20
 
24
21
  ## Product Boundaries
25
22
 
26
23
  | Area | Boundary |
27
24
  | --- | --- |
28
- | Runtime | New submissions run on the managed runtime. There is no public runtime selector. |
25
+ | Runtime | New submissions run on the managed runtime. The `runtime` option selects a managed machine-size preset (`Sizes.*`); there is no alternative runtime backend. |
29
26
  | Provider policy | Provider retention, training exclusion, HIPAA/BAA, data residency, abuse policy, and pricing belong to the selected provider account, endpoint, and contract. |
30
- | Secrets | Provider keys, MCP credentials, proxy auth, and env secrets are caller-owned. aex excludes secret values from idempotency and uses the explicit secret surfaces described in [Secrets](secrets.md). |
27
+ | Secrets | Provider keys, MCP credentials, and env secrets are caller-owned. aex excludes secret values from idempotency and uses the explicit secret surfaces described in [Secrets](secrets.md). |
31
28
  | MCP servers | Remote MCP servers are customer-trusted systems. aex validates declarations and routes credentials; it does not make an untrusted MCP server safe. |
32
- | Proxy endpoints | The proxy enforces declared host/path/method/auth policy for calls routed through it. Upstream side effects and data handling remain with the upstream service and customer. |
33
29
  | Outputs | Captured outputs, events, and metadata are stored under the run record and downloaded through auth-gated routes. Output content is customer content. |
34
30
  | Human review | Runs execute after submission. Cancellation is available, but aex does not pause a run for platform-mediated approval or interactive clarification. |
35
31
  | Sessions | The durable product primitive is the session/run record. Sessions can be resumed by id and auto-suspend after the configured idle window; persistent named agent profiles and saved agent definitions are out of scope. |
36
32
  | Deployment | The supported product is the hosted aex service plus the SDK and CLI. Alternate `baseUrl` values are for local, staging, or hosted aex API planes, not a self-host product promise. |
37
- | Cost | BYOK provider-token charges accrue to the customer's provider account. aex records report-only telemetry for runtime, storage, and proxy usage; free trials, billing-grade invoices, and public pricing documents are not shipped unless documented later. |
33
+ | Cost | BYOK provider-token charges accrue to the customer's provider account. aex records report-only telemetry for runtime and storage usage; free trials, billing-grade invoices, and public pricing documents are not shipped unless documented later. |
38
34
 
39
35
  ## Provider Policy Links
40
36
 
package/docs/mcp.md CHANGED
@@ -20,19 +20,18 @@ Use allowlists for sensitive servers whenever possible.
20
20
  ## Large-payload responses
21
21
 
22
22
  aex is a session dispatcher, not an MCP runtime. We intentionally do
23
- **not** interpose on the transport between Claude and an upstream MCP
23
+ **not** interpose on the transport between the model and an upstream MCP
24
24
  server, so we cannot elide MCP responses or write them to the session
25
25
  filesystem on the user's behalf. Anything an MCP tool returns lands
26
26
  directly in the model's context.
27
27
 
28
- For ingestion-style tools that return large JSON blobs (search results,
29
- catalogue dumps, bulk reads), use the **CLI-as-skill + managed proxy**
30
- pattern instead of MCP:
28
+ For ingestion-style MCP servers that return large JSON blobs (search results,
29
+ catalogue dumps, bulk reads), prefer a skill that writes files instead of
30
+ putting the whole response in model context:
31
31
 
32
32
  1. Package the upstream as a skill-tool (`Tools.fromSkillDir` /
33
33
  `Tools.fromSkillUrl`) — a CLI binary the agent invokes with its bash tool.
34
- 2. Route every upstream HTTPS call through a per-run `ProxyEndpoint`
35
- (audit, byte caps, budget enforcement).
34
+ 2. Keep any upstream HTTPS credentials in `environment.secrets`.
36
35
  3. Have the CLI write the full payload to the session filesystem. By default,
37
36
  files it creates or modifies are captured automatically; pass
38
37
  `outputs.allowedDirs` only when you want to narrow capture to specific roots.
@@ -4,45 +4,64 @@ title: Networking
4
4
 
5
5
  # Networking
6
6
 
7
- A run executes your agent's code in a sandbox that has **no unmediated route to
8
- the internet**. Every outbound connection whether it comes from the model, a
9
- built-in tool, or a `curl` your code runs in the shell — passes through the
10
- platform's egress boundary, which enforces the run's networking policy and a
11
- fixed SSRF deny-list (loopback, link-local, cloud-metadata, and other private
12
- ranges are always blocked, including hostnames that resolve to those ranges).
13
-
14
- **Networking is open by default.** A run that does not set
15
- `environment.networking` may reach any public host still subject to the SSRF
16
- deny-list above with no allowlist required. You use the `environment.networking`
17
- field to *narrow* that surface when you want a tighter, auditable egress posture.
18
- Code cannot widen the policy from inside the container: the boundary is the
19
- platform's, not the agent's.
7
+ A run executes your agent's code in a sandbox with **no direct route to the
8
+ internet**. Outbound traffic is governed by **two layers**:
9
+
10
+ 1. **The per-run policy (`environment.networking`)** enforced by the agent
11
+ runtime *inside the run*. It applies to the standard proxy path every normal
12
+ HTTP client uses (see below) and can only *narrow* what the run may reach.
13
+ 2. **The platform ceiling** — a fixed, platform-managed egress boundary every
14
+ connection ultimately traverses. It allows the hosts aex itself manages
15
+ (model providers, built-in tool endpoints, package registries, and related
16
+ well-known development hosts such as `github.com`) and enforces a fixed SSRF
17
+ deny-list: loopback, link-local, cloud-metadata, and other private ranges are
18
+ always blocked, including hostnames that resolve to those ranges.
19
+
20
+ Honest boundary statement: the per-run `allowedHosts` policy is enforced by the
21
+ run's own runtime on the standard proxy path — it is **not** yet enforced at
22
+ the platform proxy layer. A subprocess that deliberately bypasses the standard
23
+ proxy environment (a raw socket / raw CONNECT) is bounded by the **platform
24
+ ceiling** rather than by the per-run list. Per-run enforcement at the platform
25
+ proxy layer is planned; until it ships, treat `allowedHosts` as a strong
26
+ default-path control and an auditable statement of intent, not a hard isolation
27
+ boundary against adversarial code inside the run.
28
+
29
+ **Default posture.** A run that does not set `environment.networking` runs in
30
+ `open` mode: its own code may reach anything within the platform ceiling with
31
+ no allowlist required. Use `environment.networking` to *narrow* that surface
32
+ when you want a tighter, auditable egress posture. Code cannot widen the
33
+ ceiling from inside the container.
20
34
 
21
35
  ## Paths that always work
22
36
 
23
- These reach the network over managed paths and are **not** subject to
37
+ These reach the network over managed platform paths and are **not** subject to
24
38
  `environment.networking`, so you never list their hosts:
25
39
 
26
40
  - The model / provider call for the run (and its subagents).
27
- - The built-in `web_search` and `web_fetch` tools (still SSRF-guarded).
28
- - Any remote MCP servers you declare in `mcpServers` see [MCP](mcp.md).
29
- - Any `proxyEndpoints` you declare see [Credentials](credentials.md).
41
+ - The built-in `web_search` and `web_fetch` tools. They run over a managed,
42
+ SSRF-guarded server-side path, which is why they can reach arbitrary public
43
+ URLs even though your own code is bounded by the ceiling.
44
+ - Remote MCP servers you declare in `mcpServers` — MCP traffic rides a managed
45
+ path; see [MCP](mcp.md).
30
46
  - The package registries for any `environment.packages` you declare (pip → PyPI,
31
47
  apt → the distribution mirrors). Declaring a package implicitly allows the
32
48
  registry it installs from.
33
49
 
34
- `environment.networking` governs the **other** case: arbitrary outbound that
35
- your own code makes to a host the platform doesn't already manage — a `curl` in
36
- the `bash` tool, a `requests`/`urllib` call in Python, a `fetch` in
37
- `code_execution`, or a third-party SDK.
50
+ `environment.networking` governs the **other** case: outbound that your own
51
+ code makes a `curl` in the `bash` tool, a `requests`/`urllib` call in Python,
52
+ a `fetch` in `code_execution`, or a third-party SDK.
38
53
 
39
54
  ## Restrict a run to an allowlist
40
55
 
41
56
  Set `mode: "limited"` and list exactly the hosts your code is allowed to reach.
42
- Anything not on the list (and not one of the always-allowed paths above) is
43
- blocked, and the call fails. The platform automatically appends the
44
- infrastructure hosts aex itself needs, so the model and tool paths keep working
45
- without you listing them.
57
+ The run's runtime enforces the list on the standard proxy path: a connection to
58
+ a host that is neither on the list nor one of the always-allowed paths above is
59
+ refused before it leaves the run. Package-registry hosts implied by
60
+ `environment.packages` are appended automatically so installs keep working.
61
+
62
+ Note that `allowedHosts` narrows *within* the platform ceiling — listing a host
63
+ does not by itself make it reachable if the platform ceiling does not carry it.
64
+ If your run needs a host the ceiling blocks, contact support.
46
65
 
47
66
  ### TypeScript
48
67
 
@@ -70,26 +89,17 @@ non-default port when you need one (`api.example.com:8443`); a bare host name
70
89
  covers HTTPS on 443. Matching is exact per host — it is not a wildcard or suffix
71
90
  match, so list each host you need.
72
91
 
73
- To validate your allowlist before submitting, `buildPlatformAllowedHosts` returns
74
- the host set the platform will enforce given a base URL plus your extra hosts:
75
-
76
- ```ts
77
- import { buildPlatformAllowedHosts } from "@aexhq/sdk";
78
-
79
- const allowedHosts = buildPlatformAllowedHosts({
80
- baseUrl: "https://api.aex.dev",
81
- extraHosts: ["api.example.com"]
82
- });
83
- ```
92
+ Keep the allowlist in your session options so the submitted network policy is
93
+ visible at the same call site as the code that needs it.
84
94
 
85
95
  ## Open mode
86
96
 
87
97
  `open` is the default: a run that omits `environment.networking` already runs in
88
- open mode. Set `mode: "open"` explicitly when you want to be unambiguous, or when
89
- a run needs to reach hosts you can't enumerate ahead of time. The run may then
90
- reach any public host, still subject to the SSRF deny-list. Prefer `limited`
91
- whenever you can name the hosts — it gives the run a stable, auditable, least-
92
- privilege egress surface (it is the tighter posture, not the default).
98
+ open mode. Set `mode: "open"` explicitly when you want to be unambiguous. Open
99
+ mode applies no per-run allowlist the run's own code may reach anything the
100
+ platform ceiling allows, still subject to the SSRF deny-list. Prefer `limited`
101
+ whenever you can name the hosts — it gives the run a stable, auditable,
102
+ least-privilege egress surface (it is the tighter posture, not the default).
93
103
 
94
104
  ```ts
95
105
  await aex.run({
@@ -100,6 +110,9 @@ await aex.run({
100
110
  });
101
111
  ```
102
112
 
113
+ (Web research like the example above flows through the managed `web_search` /
114
+ `web_fetch` path, which is not ceiling-bounded.)
115
+
103
116
  ## Transparent for normal HTTP clients
104
117
 
105
118
  You write ordinary code — there is no per-request proxy configuration and no
@@ -111,7 +124,7 @@ that honors it — `curl`, Python `requests` / `urllib`, `pip`, `npm`, Node
111
124
  ```bash
112
125
  # In the agent's shell, against a limited run that allows api.example.com:
113
126
  curl -sS https://api.example.com/v1/status # works
114
- curl -sS https://other-host.example # blocked (not in allowlist)
127
+ curl -sS https://other-host.example # refused (not in allowlist)
115
128
  ```
116
129
 
117
130
  You also do **not** need to install any certificate. The platform manages the
@@ -120,22 +133,25 @@ your client succeeds without extra setup.
120
133
 
121
134
  ## Limitations and gotchas
122
135
 
123
- - **Enforcement is at the platform boundary, not in your code.** A tool can't
124
- bypass the policy by ignoring proxy settings or opening a raw socket — the
125
- sandbox has no other route out, so a disallowed host simply fails. This is the
126
- intended fail-closed behavior.
136
+ - **The per-run policy is enforced by the run's runtime, not at the platform
137
+ proxy.** Clients that honor the standard proxy environment (almost all HTTP
138
+ tooling) are held to the `allowedHosts` list. A subprocess that deliberately
139
+ ignores the proxy environment and opens a raw connection is **not** held to
140
+ the per-run list — it is bounded by the platform ceiling (the aex-managed
141
+ provider/tool/registry host set) and the SSRF deny-list instead. Per-run
142
+ enforcement at the platform proxy layer is planned.
127
143
  - **A client that hard-bypasses the standard environment may fail to connect.**
128
- This is rare almost all HTTP tooling honors `HTTP_PROXY` / `HTTPS_PROXY` and
129
- the system trust store. But a client that is explicitly told to ignore the
130
- proxy environment, pins or replaces its certificate trust store, or speaks a
131
- non-HTTP protocol over a raw socket can hit a wall even for an allowed host.
132
- The fix is to let the client use the standard proxy and certificate
133
- environment the runtime provides (most libraries do by default), rather than
134
- overriding it.
144
+ A client that ignores the proxy environment, pins or replaces its certificate
145
+ trust store, or speaks a non-HTTP protocol over a raw socket can hit a wall
146
+ even for a host you allowed. The fix is to let the client use the standard
147
+ proxy and certificate environment the runtime provides (most libraries do by
148
+ default), rather than overriding it.
135
149
  - **`allowedHosts` only applies in `limited` mode.** It is ignored in `open`
136
- mode, where the SSRF deny-list is the only gate.
137
-
138
- For routing credentialed HTTP calls through the managed proxy without putting the
139
- secret in the container, use proxy endpoints — see
140
- [Credentials](credentials.md). For remote tool servers, see [MCP](mcp.md). For
141
- the full set of run-config fields, see [Run configuration](run-config.md).
150
+ mode, where the platform ceiling and the SSRF deny-list are the gates.
151
+ - **`allowedHosts` cannot exceed the platform ceiling.** It narrows; it never
152
+ widens. A listed host outside the ceiling still fails.
153
+
154
+ For credentialed HTTP calls, pass the credential as an `environment.secrets`
155
+ entry and let your code use its normal HTTP client. For remote tool servers, see
156
+ [MCP](mcp.md). For the full set of run-config fields, see
157
+ [Run configuration](run-config.md).
package/docs/outputs.md CHANGED
@@ -100,10 +100,6 @@ if (truncated) {
100
100
 
101
101
  Check `truncated` before treating `text` as complete. Pass `options.grep` (a substring or `RegExp`) to keep only matching lines of the capped text. The returned `output` is the matched `Output` record, and `totalBytes` is the file's full size when the server reports it.
102
102
 
103
- ### Chatting over a workspace's outputs
104
-
105
- `createDataTools(client)` packages the read surface (`sessions.list` + `sessions.outputs(id).list` + `sessions.outputs(id).read`) as a vendor-neutral LLM tool set (`{ tools, instructions, execute }`) so you can build a search-then-fetch chat over your sessions and their outputs in a few lines on top of the public SDK. The `tools` are plain JSON-Schema definitions (the shape every major LLM tool API accepts); `execute(name, input)` dispatches a tool call against the workspace-scoped client. See the runnable `examples/data-chat/` example.
106
-
107
103
  ## Finding outputs
108
104
 
109
105
  `session.outputs().list(query?)` can filter the captured output list client-side. Use `session.outputs().find(query)` when you want discovery to be explicit, or `session.outputs().findOne(query)` when exactly one file is expected:
@@ -167,9 +163,10 @@ const stream = response.body;
167
163
 
168
164
  | Run state | Behaviour |
169
165
  | --- | --- |
170
- | `pending` / `queued` / `provisioning` | `metadata/run.json` reflects the early state; `events/` and `outputs/` are typically empty. |
171
- | `provider_running`, mid-session / `cleaning_up` | Whatever events + outputs have been captured so far. Call again after terminal for the complete set. |
172
- | `succeeded` / `failed` / `cancelled` / `terminated` | The complete typed event archive + all captured outputs. |
166
+ | `queued` / `claiming` / `provisioning` | `metadata/run.json` reflects the early state; `events/` and `outputs/` are typically empty. |
167
+ | `provider_running`, mid-session / `capturing_outputs` / `cleaning_up` | Whatever events + outputs have been captured so far. Call again after the session parks for the complete set. |
168
+ | `idle` / `suspended` (parked between turns) | The complete archive for every turn sent so far; a later turn appends to it. |
169
+ | `succeeded` / `failed` / `timed_out` / `cancelled` | The complete typed event archive + all captured outputs. |
173
170
 
174
171
  ## `outputs.allowedDirs` — override capture roots
175
172
 
@@ -2,12 +2,12 @@
2
2
  "brand": "aex",
3
3
  "productName": "Agent Executor",
4
4
  "oneLine": "aex is an agent execution platform for launching autonomous agents from a simple TypeScript SDK and CLI.",
5
- "description": "Open durable agent sessions, send turns, stream events, capture outputs, and compose agents with skills, files, MCP, proxy endpoints, and subagents across the managed runtime.",
5
+ "description": "Open durable agent sessions, send turns, stream events, capture outputs, and compose agents with skills, files, MCP, secrets, networking controls, and subagents across the managed runtime.",
6
6
  "alpha": {
7
7
  "label": "Alpha testing",
8
8
  "description": "Access is limited to invited testers while we harden the hosted runtime, dashboard, and SDK workflows."
9
9
  },
10
- "installCommand": "bun add @aexhq/sdk",
10
+ "installCommand": "npm i @aexhq/sdk",
11
11
  "examples": {
12
12
  "typescriptLines": [
13
13
  "import { Aex, Models, Sizes } from \"@aexhq/sdk\";",
@@ -61,7 +61,7 @@
61
61
  "slug": "agent-composition",
62
62
  "href": "/docs/features/#agent-composition",
63
63
  "title": "Agent composition",
64
- "description": "Skills, files, AGENTS.md, remote MCP servers, proxy endpoints, environment variables, packages, and networking controls."
64
+ "description": "Skills, files, AGENTS.md, remote MCP servers, environment variables, packages, secrets, and networking controls."
65
65
  },
66
66
  {
67
67
  "slug": "subagents",
@@ -79,7 +79,7 @@
79
79
  "slug": "typed-control-surface",
80
80
  "href": "/docs/features/#typed-control-surface",
81
81
  "title": "Typed control surface",
82
- "description": "Strongly typed SDK inputs, CLI parity, BYOK secrets, scoped proxy auth, redaction, and output modes."
82
+ "description": "Strongly typed SDK inputs, CLI parity, BYOK provider keys, workspace secrets, redaction, and output modes."
83
83
  }
84
84
  ]
85
85
  }
@@ -7,16 +7,18 @@ title: Quickstart
7
7
  ## 1. Install
8
8
 
9
9
  ```bash
10
- bun add @aexhq/sdk
10
+ npm i @aexhq/sdk
11
11
  ```
12
12
 
13
13
  This installs the TypeScript SDK exports and the bundled `aex` CLI.
14
14
 
15
15
  ## 2. Set credentials
16
16
 
17
- In the dashboard, create a quickstart SDK token with `runs:read`, `runs:write`,
18
- and `outputs:read`. The examples also need your BYOK provider key for the model
19
- you choose. For the Claude examples below:
17
+ aex is currently in **invite-only beta**: workspaces and API tokens are issued
18
+ by the aex team contact <support@aex.dev> for beta access. Once you have
19
+ access, create a quickstart SDK token with `runs:read`, `runs:write`, and
20
+ `outputs:read` in the dashboard at <https://aex.dev>. The examples also need
21
+ your BYOK provider key for the model you choose. For the Claude examples below:
20
22
 
21
23
  ```bash
22
24
  export AEX_API_TOKEN="<your-aex-token>"
@@ -28,7 +30,7 @@ export ANTHROPIC_API_KEY="<your-anthropic-api-key>"
28
30
  ```ts
29
31
  import { Aex, Models, Sizes } from "@aexhq/sdk";
30
32
 
31
- const aex = new Aex({ apiToken: process.env.AEX_API_TOKEN! });
33
+ const aex = new Aex(process.env.AEX_API_TOKEN!);
32
34
 
33
35
  const session = await aex.openSession({
34
36
  model: Models.CLAUDE_HAIKU_4_5,
@@ -83,11 +85,8 @@ for await (const event of turn) {
83
85
  }
84
86
  await turn.done();
85
87
 
86
- // Reads/streams/downloads are grouped into accessor sub-resources:
87
- // session.messages() / events() / outputs() / webhooks(). Grab the last
88
- // assistant message (an AssistantTextEntry; use ?.text for the string).
89
- const lastText = (await session.messages().last())?.text;
90
- console.log(lastText);
88
+ const messages = await session.messages().list();
89
+ console.log(messages.at(-1)?.text);
91
90
 
92
91
  // Poll the record until the session parks (idle / suspended / error).
93
92
  const record = await session.wait();
@@ -110,8 +109,8 @@ aex run \
110
109
 
111
110
  ## Add capabilities
112
111
 
113
- - Add files, skills, AGENTS.md, MCP servers, proxy endpoints, packages, and networking controls with [Composition](concepts/composition.md).
114
- - Inspect runtime tools with [Agent tools](concepts/agent-tools.md).
115
- - Use parent/child run delegation from the [Features](https://aex.dev/docs/features/#subagents) page.
112
+ - Add files, skills, AGENTS.md, MCP servers, packages, and networking controls with [Composition](concepts/composition.md).
113
+ - Delegate bounded sub-tasks to child runs with [Subagents](concepts/subagents.md).
114
+ - Get notified when a run finishes with [Webhooks](webhooks.md).
116
115
  - Narrow output capture or download individual files with [Outputs](outputs.md).
117
116
  - Check supported providers and models in the [provider/runtime capability matrix](provider-runtime-capabilities.md).
@@ -13,13 +13,16 @@ Allowed fields:
13
13
  - `mcpServers` - array of `McpServerRef`; headers are split into the vaulted secrets channel server-side.
14
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
15
  - `runtime` - optional managed-runtime preset. Prefer `Sizes` in TypeScript.
16
- - `proxyEndpoints` - array of `ProxyEndpoint` instances; endpoint-level `retry` is allowed here and remains declaration-based.
17
16
  - `metadata` - non-secret structured metadata.
18
17
  - `overrides` - `{ idleTtl?, timeout?, maxSpendUsd? }`. `timeout` is an optional session deadline (e.g. `"30m"`, `"2h"`); `maxSpendUsd` stops the session once its spend would exceed the cap (see [Limits & quotas](limits-and-quotas.md)).
19
18
 
20
- `message` (the one-shot `run` input), `agentsMd`, `files`, `outputs`, `tools`, `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 `tools` entries built with `Tools.fromSkillDir(...)` / `Tools.fromSkillUrl(...)`, so they too are SDK-code options rather than config fields. Subagents run in-process; there is no `limits` / `parentRunId` option.
19
+ `message` (the one-shot `run` input), `agentsMd`, `files`, `outputs`, `tools`, `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 `tools` entries built with `Tools.fromSkillDir(...)` / `Tools.fromSkillUrl(...)`, 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`), but the session surface exposes only its spend dial — set it with `overrides.maxSpendUsd`; the subagent depth/breadth dials are not settable per-session today and take the platform defaults.
21
20
 
22
- Secrets never live in run config. Pass provider keys through the top-level `apiKeys` map (and run secrets through `environment.secrets`) in the SDK, or the equivalent host-mode flags (`--anthropic-api-key`, `--mcp-auth`, `--proxy-auth`) in the CLI. See [Secrets](secrets.md) for secret lifecycles and [Credentials](credentials.md) for the proxy endpoint policy/auth split and retry fields.
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.
23
26
 
24
27
  ## Reuse in code
25
28
 
@@ -52,4 +55,4 @@ aex run --config ./run.json \
52
55
  --anthropic-api-key "$ANTHROPIC_API_KEY"
53
56
  ```
54
57
 
55
- ...or as explicit flags (`--model`, `--system`, `--prompt`, `--mcp`, `--mcp-auth`, `--runtime-size`, `--run-timeout`, `--proxy-endpoint`, `--proxy-auth`, `--metadata`). The two modes are mutually exclusive.
58
+ ...or as explicit flags (`--model`, `--system`, `--prompt`, `--mcp`, `--mcp-auth`, `--runtime-size`, `--run-timeout`, `--metadata`). The two modes are mutually exclusive.