@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
@@ -12,7 +12,7 @@ Managed runs inject the complete builtin tool set into the agent by default:
12
12
  - `head`, `tail` — read bounded file slices
13
13
  - `web_fetch`, `web_search` — fetch a URL / managed web search
14
14
  - `todo_write` — maintain a todo list
15
- - `subagent`, `subagent_result` — delegate to and read back from child runs
15
+ - `subagent`, `subagent_result` — delegate to and read back from child runs (see [Subagents](subagents.md))
16
16
  - `bash_output`, `bash_kill` — manage background bash jobs
17
17
  - `wait`, `git` — bounded idle-yield and first-class git
18
18
 
@@ -32,13 +32,13 @@ to pick a narrow subset alongside `includeBuiltinTools: false`.
32
32
  The final tool list is ordered: resolved builtin tools, then custom tools, then
33
33
  MCP tools.
34
34
 
35
- Networking is open by default: the agent may reach any public host, subject to a
36
- fixed SSRF deny-list. `web_fetch` and `web_search` reach the network over a
35
+ Networking is open by default within the platform's managed egress ceiling and
36
+ a fixed SSRF deny-list. `web_fetch` and `web_search` reach the network over a
37
37
  managed, SSRF-guarded path that is **not** governed by `environment.networking`,
38
38
  so their hosts never need to be listed in a `limited` allowlist. Setting
39
39
  `environment.networking.mode` to `limited` restricts only the agent's own
40
40
  arbitrary egress (e.g. a `curl` in `bash`); the built-in web tools keep working.
41
- See [Networking](../networking.md).
41
+ See [Networking](../networking.md) for the full two-layer model.
42
42
 
43
43
  ## Disable builtins
44
44
 
@@ -14,11 +14,11 @@ runtime before the first agent turn.
14
14
  | Agent instructions | `AgentsMd.fromPath`, `AgentsMd.fromContent` |
15
15
  | Reference files and folders | `File.fromPath`, `File.fromBytes` |
16
16
  | Remote tools | `McpServer.remote`, `McpServer.fromId` |
17
- | Credentialed HTTP APIs | `ProxyEndpoint.none`, `bearer`, `basic`, `header`, `query` |
18
17
  | Non-secret runtime settings | `environment.variables`, `environment.packages`, `environment.networking` |
18
+ | Runtime secrets for your code | `Secret.value`, `Secret.ref`, `environment.secrets` |
19
19
 
20
20
  ```ts
21
- import { AgentsMd, File, McpServer, Models, ProxyEndpoint, Tools } from "@aexhq/sdk";
21
+ import { AgentsMd, File, McpServer, Models, Secret, Tools } from "@aexhq/sdk";
22
22
 
23
23
  await aex.run({
24
24
  model: Models.CLAUDE_HAIKU_4_5,
@@ -27,20 +27,14 @@ await aex.run({
27
27
  files: [await File.fromPath("./input")],
28
28
  tools: [await Tools.fromSkillDir("./skills/report-writer", { name: "report-writer" })],
29
29
  mcpServers: [McpServer.remote({ name: "github", url: "https://example.com/mcp" })],
30
- proxyEndpoints: [
31
- ProxyEndpoint.bearer({
32
- name: "internal-api",
33
- baseUrl: "https://api.example.com",
34
- token: process.env.INTERNAL_API_TOKEN!,
35
- allowMethods: ["GET"],
36
- allowPathPrefixes: ["/v1/"]
37
- })
38
- ],
30
+ environment: {
31
+ secrets: { INTERNAL_API_TOKEN: Secret.value(process.env.INTERNAL_API_TOKEN!) },
32
+ networking: { mode: "limited", allowedHosts: ["api.example.com"] }
33
+ },
39
34
  apiKeys: { anthropic: process.env.ANTHROPIC_API_KEY! }
40
35
  });
41
36
  ```
42
37
 
43
38
  Secrets stay out of reusable configs. Provider keys go in the top-level `apiKeys`
44
- map; MCP auth rides on each `McpServer` instance and proxy auth on each
45
- `ProxyEndpoint` instance the SDK splits them into the vaulted secrets channel
46
- server-side, so they never live in a shareable config object.
39
+ map; reusable or per-run values for your own code go in `environment.secrets`.
40
+ Your code then makes normal HTTP calls with the standard client for that service.
@@ -17,7 +17,10 @@ aex exposes one submission shape across supported providers:
17
17
  | Doubao | `Providers.DOUBAO` |
18
18
  | Doubao China | `Providers.DOUBAO_CN` |
19
19
 
20
- All submissions run on the managed runtime. There is no public runtime selector; omit `runtime`.
20
+ All submissions run on the managed runtime. The optional `runtime` option picks
21
+ a managed machine-size preset — use `Sizes.*` in TypeScript (e.g.
22
+ `runtime: Sizes.SHARED_0_25X_1GB`) or `--runtime-size` in the CLI. Omit it for
23
+ the default size; there is no alternative runtime backend to select.
21
24
 
22
25
  ## Selection
23
26
 
@@ -38,7 +38,8 @@ The same durable record backs SDK and CLI reads. From the handle use `refresh`,
38
38
  and `events().stream()` / `events().streamEnvelopes()` / `outputs().read(...)` for
39
39
  streaming and byte-capped reads) — to inspect the session live or after it parks;
40
40
  from the client, `aex.sessions.list()` / `aex.sessions.get(id)` read across the
41
- workspace.
41
+ workspace (CLI mirrors: `aex sessions` and `aex runs` list the workspace's
42
+ sessions/runs newest-first).
42
43
 
43
44
  Use `idempotencyKey` when retrying `openSession` or `send` from your own
44
45
  workflow. aex hashes the normalized non-secret submission, so a retry with the
@@ -0,0 +1,85 @@
1
+ ---
2
+ title: Subagents
3
+ description: Delegate bounded sub-tasks to child agent runs with the subagent tool.
4
+ icon: GitFork
5
+ ---
6
+
7
+ A run can delegate bounded sub-tasks to **child agent runs** with the built-in
8
+ `subagent` tool. Delegation is agent-driven: the model decides to fan work out,
9
+ each child is a real run with its own record, and the parent collects results
10
+ as the children finish. There is no client-side parent/child API — lineage is
11
+ session-internal.
12
+
13
+ ## How the tool works
14
+
15
+ The agent calls `subagent` with a `prompt` and a `model` (both required), plus
16
+ optional `system`, `provider`, `runtimeSize`, `timeout`,
17
+ `includeBuiltinTools`, `tools` (builtin-tool names for the child), `skills`,
18
+ and `files`. The call is **always async**: on a successful spawn it returns
19
+ immediately with the child run id, and the parent keeps working while the child
20
+ runs. When a child settles, the parent is notified in its loop, and it reads
21
+ the child's status and captured outputs on demand with the companion
22
+ `subagent_result` tool.
23
+
24
+ Children inherit the parent's vaulted BYOK provider keys server-side — the
25
+ child submission carries no secrets. Include a provider key for every provider
26
+ your subagents may use when you open the parent session (a parent holding no
27
+ key for the child's provider gets a clear `parent_missing_provider_key` tool
28
+ error). See [Credentials](../credentials.md).
29
+
30
+ ## Depth and breadth limits
31
+
32
+ Delegation is bounded by two server-enforced lineage limits:
33
+
34
+ | Limit | Value | Behavior at the limit |
35
+ | --- | --- | --- |
36
+ | Max depth | **5** — the root run is depth 0 and may spawn down to depth 5; a depth-5 run may not spawn further | The spawn is rejected with a `depth_exceeded` tool error (the parent keeps running). |
37
+ | Concurrent children per lineage root | **1000** live (non-terminal) descendants by default; hard platform ceiling **4096** | Further spawns are refused until a child settles. |
38
+
39
+ The whole descendant subtree of one root shares a single depth and breadth
40
+ budget, enforced server-side at every level — a grandchild spawn counts against
41
+ the same root budget as a direct child. Values are mirrored in
42
+ [Limits & quotas](../limits-and-quotas.md).
43
+
44
+ ## Where children run: `in-process` vs `container`
45
+
46
+ By default a child runs **in-process**: it executes as a sibling agent process
47
+ inside the parent's own machine, sharing the parent's CPU, memory, and
48
+ lifetime. This is the platform default shipped today.
49
+
50
+ - **No extra runtime cost.** The parent's machine is the billable unit, so
51
+ in-process children bill **$0 of additional runtime** — fan-out is priced by
52
+ the parent box, however many children it hosts. (Model-token spend is still
53
+ whatever each child's provider calls cost on your BYOK key.)
54
+ - **Shared capacity.** N children share the parent's fixed CPU/memory. For
55
+ large fan-outs, size the parent up (`runtime`) rather than assuming each
56
+ child gets its own machine.
57
+ - **Joined lifecycle.** The parent's terminal waits for its in-process children,
58
+ and their results are folded into the parent's per-child accounting. Platform
59
+ recovery re-spawns in-process children exactly once if the parent's machine
60
+ is replaced mid-run — settled children are never re-run.
61
+
62
+ The escape valve is `host: "container"`: the child is dispatched to its **own
63
+ isolated machine** with its own runtime size and its own runtime billing, and
64
+ the parent does not host it. Use it when a child needs guaranteed capacity,
65
+ isolation from the parent's filesystem/CPU, or a different machine size than
66
+ the parent can share.
67
+
68
+ ## Lineage and observability
69
+
70
+ Every child — in-process or container — is a first-class run record:
71
+
72
+ - The parent's transcript logs each spawn with the child's run id.
73
+ - Each child has its own status, typed event timeline, and captured outputs,
74
+ readable by id like any other run (`aex.sessions.get(id)`, or the CLI's
75
+ `aex status` / `aex events` / `aex outputs` / `aex download`).
76
+ - The child's outputs are handed back to the parent via `subagent_result`, and
77
+ they remain independently downloadable after the lineage finishes.
78
+
79
+ ## Bounding delegation
80
+
81
+ - Turn delegation off for a run by cherry-picking builtins without `subagent`
82
+ (see [Agent tools](agent-tools.md)) or setting `includeBuiltinTools: false`.
83
+ - A per-session spend cap (`overrides.maxSpendUsd`) bounds the parent's spend.
84
+ - The depth/breadth limits above are platform defaults and are not settable
85
+ per-session today.
@@ -4,142 +4,124 @@ title: Credentials
4
4
 
5
5
  # Credentials
6
6
 
7
- aex treats provider keys, MCP credentials, and proxy endpoint auth as per-run
8
- credentials. Reusable env secrets are documented separately in
9
- [Secrets](secrets.md).
7
+ aex uses explicit, per-session credentials:
10
8
 
11
- The caller passes a workspace-scoped SDK token and the provider key inline on every `openSession` / `run` call. aex holds the bundle in run-scoped custody for the session lifecycle and attempts terminal cleanup/revocation for the aex-controlled references. MCP credentials and proxy endpoint auth values travel the same way.
9
+ - `AEX_API_TOKEN` authenticates the SDK or CLI to aex.
10
+ - `apiKeys` carries BYOK provider keys for the model provider.
11
+ - `McpServer.remote(..., { headers })` carries MCP auth when a remote MCP server needs it.
12
+ - `environment.secrets` carries runtime secrets for your own code.
12
13
 
13
- A session selects one upstream `provider` (default `anthropic`) and must carry a BYOK
14
- key for it. Keys are supplied per-provider so a session can also hold keys for the
15
- **other** providers its subagents may use:
14
+ Secrets never belong in reusable run config, files, prompts, or examples.
16
15
 
17
- | Field | Required secret |
18
- | --- | --- |
19
- | Provider API keys | `apiKeys` (top-level, keyed by provider) |
16
+ ## The client credential
20
17
 
21
- ```ts
22
- // The session's own provider key, plus extra keys its subagents can use.
23
- apiKeys: {
24
- anthropic: process.env.ANTHROPIC_API_KEY!, // the session's provider
25
- deepseek: process.env.DEEPSEEK_API_KEY! // for a cross-provider subagent
26
- }
27
- ```
18
+ Pass your aex API token directly to the constructor — `new Aex(apiKey)` — or as
19
+ the `apiKey` option. The older `apiToken` option remains accepted as a
20
+ compatibility alias, so existing code keeps working:
28
21
 
29
- A `subagent` spawned with a different-family model **inherits the parent's keys
30
- server-side** from the session's vaulted bundle — the keys never transit the
31
- container. If the parent holds no key for the child's provider, the child is
32
- rejected with `parent_missing_provider_key`.
22
+ ```ts
23
+ import { Aex } from "@aexhq/sdk";
33
24
 
34
- MCP credential types:
25
+ const aex = new Aex(process.env.AEX_API_TOKEN!); // preferred shorthand
26
+ // equivalently:
27
+ // const aex = new Aex({ apiKey: process.env.AEX_API_TOKEN! });
28
+ // const aex = new Aex({ apiToken: process.env.AEX_API_TOKEN! }); // alias
29
+ ```
35
30
 
36
- - `static_bearer`;
37
- - `oauth_access_token`.
31
+ See [Authentication](authentication.md) for how tokens are scoped, rotated, and
32
+ issued during the beta.
38
33
 
39
- Unsupported:
34
+ ## Provider keys
40
35
 
41
- - arbitrary headers;
42
- - OAuth refresh;
43
- - persisted aex vault.
36
+ A session selects one upstream provider and must carry a BYOK key for it. Include
37
+ additional provider keys only when subagents may use those providers.
44
38
 
45
- For managed-runtime runs, aex injects the matching BYOK provider key at the hosted provider-proxy. Provider-side sessions and data remain subject to the selected provider account's retention and deletion policies.
39
+ ```ts
40
+ const result = await aex.run({
41
+ model: Models.CLAUDE_HAIKU_4_5,
42
+ message: "Write a short report and save it as a file.",
43
+ apiKeys: {
44
+ anthropic: process.env.ANTHROPIC_API_KEY!
45
+ }
46
+ });
47
+ ```
46
48
 
47
- ## Proxy endpoints (per-run custom HTTP credentials)
49
+ Provider keys are used by the managed runtime for model calls. They are not saved
50
+ as client defaults.
48
51
 
49
- Some skills need to call non-MCP HTTP services (e.g. Stripe, internal APIs). Embedding the credential in the skill content puts the raw secret on disk in the agent container and in the model's context — both prompt-injection-readable.
52
+ ## Runtime secrets
50
53
 
51
- The platform's managed HTTP proxy is the agent-first alternative. Declare each endpoint with a `ProxyEndpoint.*` constructor: the instance carries the non-secret **policy** (hashed for idempotency) and its **auth token** together at the call site. The SDK splits the token into the vaulted secrets channel server-side (not hashed, so key rotation does not collapse onto a stale run), and the raw credential value never enters the container.
54
+ Use `environment.secrets` for credentials your code needs at runtime. The value
55
+ can be ephemeral with `Secret.value(...)` or a workspace secret reference with
56
+ `Secret.ref(...)`.
52
57
 
53
58
  ```ts
54
- import { Aex, Models, ProxyEndpoint } from "@aexhq/sdk";
59
+ import { Aex, Models, Secret } from "@aexhq/sdk";
55
60
 
56
- const aex = new Aex({
57
- apiToken: "ant_..."
58
- });
59
-
60
- const stripe = ProxyEndpoint.bearer({
61
- name: "stripe",
62
- baseUrl: "https://api.stripe.com",
63
- token: process.env.STRIPE_API_KEY!,
64
- allowMethods: ["GET", "POST"],
65
- allowPathPrefixes: ["/v1/charges", "/v1/refunds"],
66
- maxRequestBytes: 65_536,
67
- maxResponseBytes: 65_536,
68
- timeoutMs: 10_000,
69
- responseMode: "headers_only",
70
- retry: {
71
- maxAttempts: 3,
72
- initialDelayMs: 250,
73
- maxDelayMs: 5000,
74
- jitter: "full",
75
- retryOnStatuses: [408, 425, 429, 500, 502, 503, 504],
76
- retryOnMethods: ["GET", "HEAD"],
77
- respectRetryAfter: true
78
- }
79
- });
61
+ const aex = new Aex({ apiToken: process.env.AEX_API_TOKEN! });
80
62
 
81
63
  await aex.run({
82
64
  model: Models.CLAUDE_HAIKU_4_5,
83
- message: "",
84
- proxyEndpoints: [stripe],
65
+ message: "Call https://api.example.com/v1/status with INTERNAL_API_TOKEN and summarize it.",
66
+ environment: {
67
+ secrets: {
68
+ INTERNAL_API_TOKEN: Secret.value(process.env.INTERNAL_API_TOKEN!)
69
+ },
70
+ networking: {
71
+ mode: "limited",
72
+ allowedHosts: ["api.example.com"]
73
+ }
74
+ },
85
75
  apiKeys: { anthropic: process.env.ANTHROPIC_API_KEY! }
86
76
  });
87
77
  ```
88
78
 
89
- The five constructors — `ProxyEndpoint.none` / `bearer` / `header` / `basic` / `query` — put the auth secret on the same call as the policy, so any drift (wrong `responseMode`, misnamed auth field) is a TypeScript error at the call site instead of an HTTP 400 a round-trip later.
90
-
91
- Inside the run container, every session has the platform CLI mounted at `/mnt/session/uploads/aex/aex` (a Bun-compatible ESM bundle) and a manifest at `/mnt/session/uploads/aex/index.json` describing the declared endpoints. The skill invokes the CLI through `bun` (the mount has no execute permission so direct invocation fails with `bad interpreter: Permission denied`):
79
+ Inside the run, use normal HTTP code for the service:
92
80
 
93
81
  ```bash
94
- bun /mnt/session/uploads/aex/aex proxy stripe \
95
- --method GET \
96
- --path /v1/charges/ch_123 \
97
- --response-mode headers_only
82
+ curl -sS \
83
+ -H "Authorization: Bearer $INTERNAL_API_TOKEN" \
84
+ https://api.example.com/v1/status
98
85
  ```
99
86
 
100
- The CLI reads the per-run bearer from `/mnt/session/uploads/aex/run-token`, attaches the `X-Aex-Proxy-Protocol` header, and the hosted proxy injects the bearer/header/query/basic credential before dispatching the outbound call. Only the response (subject to `responseMode` and `maxResponseBytes`) reaches the container. `--response-mode` can only narrow below the policy ceiling.
87
+ ## Workspace secrets
101
88
 
102
- Retries are declaration-based. Add `retry` to the endpoint policy when safe for that upstream; runs without `retry` keep single-attempt behavior. `maxAttempts` counts the initial request, and defaults apply only when `retry` is present: `maxAttempts: 3`, `initialDelayMs: 250`, `maxDelayMs: 5000`, `jitter: "full"`, `retryOnStatuses: [408, 425, 429, 500, 502, 503, 504]`, `retryOnMethods: ["GET", "HEAD"]`, and `respectRetryAfter: true`. There are no per-call `aex proxy` retry flags.
89
+ > **Availability note:** workspace-secret `Secret.ref(...)` injection requires
90
+ > the next platform deploy — on the current hosted plane the referenced
91
+ > variable can resolve empty inside the run. Per-run `Secret.value(...)`
92
+ > secrets are unaffected.
103
93
 
104
- #### Keyless upstreams (`authShape: { type: "none" }`)
105
-
106
- For public APIs that take no credential (Wikimedia Commons, Internet Archive, Library of Congress, NASA Images, NARA, GDELT, etc.), declare the endpoint with `ProxyEndpoint.none(...)` — it produces only a declaration, no auth token:
94
+ Store reusable values once, then reference them by name:
107
95
 
108
96
  ```ts
109
- import { ProxyEndpoint } from "@aexhq/sdk";
110
-
111
- const wikimedia = ProxyEndpoint.none({
112
- name: "wikimedia",
113
- baseUrl: "https://commons.wikimedia.org",
114
- allowMethods: ["GET"],
115
- allowPathPrefixes: ["/wiki/", "/w/api.php"]
97
+ await aex.secrets.set({
98
+ name: "internal-api-token",
99
+ value: process.env.INTERNAL_API_TOKEN!
116
100
  });
117
101
 
118
102
  await aex.run({
119
103
  model: Models.CLAUDE_HAIKU_4_5,
120
- message: "",
121
- proxyEndpoints: [wikimedia],
104
+ message: "Use INTERNAL_API_TOKEN for the status request.",
105
+ environment: {
106
+ secrets: {
107
+ INTERNAL_API_TOKEN: Secret.ref("internal-api-token")
108
+ }
109
+ },
122
110
  apiKeys: { anthropic: process.env.ANTHROPIC_API_KEY! }
123
111
  });
124
112
  ```
125
113
 
126
- The keyless endpoint still routes through the aex managed proxy: every call is allow-listed, audited, and redacted. The hosted proxy injects no `Authorization` header and no query-string credential.
114
+ Secret reads return metadata only; they never return the stored value.
127
115
 
128
- `bun /mnt/session/uploads/aex/aex --help` reads endpoint details from `/mnt/session/uploads/aex/index.json`. Runs that do not declare any `proxyEndpoints` still have the CLI and an empty manifest mounted, so agents never need to introspect whether the surface exists.
116
+ ## Networking
129
117
 
130
- ### Networking
131
-
132
- Networking is open by default. When a run explicitly uses `limited` networking,
133
- the platform host must appear in `allowed_hosts`. aex injects it
134
- automatically; for advance validation use:
135
-
136
- ```ts
137
- const allowedHosts = buildPlatformAllowedHosts({
138
- baseUrl: "https://api.aex.dev",
139
- extraHosts: ["api.stripe.com"]
140
- });
141
- ```
118
+ Networking is open by default within the platform's managed egress ceiling. Use
119
+ `environment.networking.mode: "limited"` with `allowedHosts` when you want a
120
+ run's own code to reach only named hosts. See [Networking](networking.md) for
121
+ the two-layer enforcement model.
142
122
 
143
- ### Secrets are always explicit at the call site
123
+ ## Explicit call-site rule
144
124
 
145
- There is no `defaultSecrets` and no client-held secret state. Every `openSession` / `run` call carries its own credentials at the call site: the top-level `apiKeys` map (one provider key, plus any subagent keys), MCP auth on each `McpServer` instance, and proxy auth on each `ProxyEndpoint` instance. This is the agent-first invariant: the credentials being used on any given call are visible in the same code that opens the session.
125
+ There is no `defaultSecrets` and no client-held secret state. Each
126
+ `openSession(...)` or `run(...)` call should show the provider keys, MCP auth, and
127
+ runtime secrets needed for that call.
package/docs/defaults.md CHANGED
@@ -5,10 +5,9 @@ title: Defaults
5
5
  # Defaults
6
6
 
7
7
  These are the values aex applies when you **omit** the corresponding option on a
8
- run. 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
+ run. 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 value below is named by its source-of-truth constant. The runtime-size
14
13
  presets are defined in the public
@@ -21,7 +20,7 @@ For the hard ceilings and who can raise them, see
21
20
 
22
21
  | Option | Default | How to override | Source |
23
22
  | --- | --- | --- | --- |
24
- | `timeout` (run deadline) | 1 hour | Per-session via `overrides.timeout` (e.g. `"30m"`, `"2h"`), clamped to the run-timeout floor/ceiling. | `RUN_DEFAULT_TIMEOUT_MS` |
23
+ | `timeout` (run deadline) | 8 hours (also the ceiling) | Per-session via `overrides.timeout` (e.g. `"30m"`, `"2h"`), clamped to the run-timeout floor/ceiling. | `RUN_DEFAULT_TIMEOUT_MS` |
25
24
  | `runtime` (machine size) | `shared-0.25x-1gb` — 0.25 vCPU, 1 GB | Per-session via `runtime` (use `Sizes.*` in TypeScript). | `RUN_DEFAULT_RUNTIME_SIZE` |
26
25
  | `overrides.maxSpendUsd` (per-session spend cap) | None — no spend cap (the session is still bounded by its `timeout` and any workspace-level cap) | Per-session via `overrides.maxSpendUsd` (a positive USD amount); the session is stopped once its spend would exceed the cap. | — |
27
26
 
@@ -39,14 +38,6 @@ For the hard ceilings and who can raise them, see
39
38
  | MCP connect timeout (register + initialize + discover) | 30 seconds | Per-port via `connectTimeoutMs`. | `RUN_DEFAULT_MCP_CONNECT_TIMEOUT_MS` |
40
39
  | MCP `tools/call` timeout | 30 minutes | Per-port via `callTimeoutMs`. | `RUN_DEFAULT_MCP_CALL_TIMEOUT_MS` |
41
40
 
42
- ## Proxy endpoints
43
-
44
- | Option | Default | How to override | Source |
45
- | --- | --- | --- | --- |
46
- | `maxRequestBytes` | 10 MiB | Per-endpoint via the endpoint's `maxRequestBytes`. | `REQUEST_PROXY_DEFAULT_MAX_REQUEST_BYTES` |
47
- | `maxResponseBytes` | `0` (unlimited — the response is streamed unbuffered) | Per-endpoint via the endpoint's `maxResponseBytes`. | `REQUEST_PROXY_DEFAULT_MAX_RESPONSE_BYTES` |
48
- | `timeoutMs` (upstream) | 5 minutes | Per-endpoint via the endpoint's `timeoutMs`. | `REQUEST_PROXY_DEFAULT_TIMEOUT_MS` |
49
-
50
41
  ## Links (signed URLs and tickets)
51
42
 
52
43
  | Option | Default | How to override | Source |
@@ -65,5 +56,8 @@ For the hard ceilings and who can raise them, see
65
56
 
66
57
  | Option | Default | How to override | Source |
67
58
  | --- | --- | --- | --- |
68
- | Per-workspace mutation rate limits (per minute) | run submit 60, run cancel 30, run delete 30, signed link 120, API token create 10, API token delete 30 | Per-plane via the matching `AEX_RATE_LIMIT_<ACTION>_PER_MINUTE` env var. | `WORKSPACE_RATE_LIMIT_DEFAULTS` |
69
- | Workspace storage cap | 50 GiB | Per-plane via env `AEX_WORKSPACE_STORAGE_CAP_BYTES`; admin workspaces are uncapped (not a customer entitlement). | `WORKSPACE_DEFAULT_STORAGE_CAP_BYTES` |
59
+ | Run submit rate (per minute) | 120 (`0` = disabled); past it `POST /runs` fails with `429 workspace_submit_rate_exceeded` | Per-plane via env `AEX_WORKSPACE_SUBMIT_RATE_PER_MIN`; per-workspace via support. | |
60
+ | Max concurrent runs | 50 live root runs (hard ceiling 200) | Per-workspace override via support, clamped to the ceiling. | `WORKSPACE_DEFAULT_MAX_CONCURRENT_RUNS` |
61
+ | Monthly spend cap | $250 per UTC calendar month (`0` = unlimited) | Per-workspace override via support. | `WORKSPACE_DEFAULT_SPEND_CAP_USD` |
62
+ | Per-workspace mutation rate limits (per minute) | run cancel 30, run delete 30, signed link 120, API token create 10, API token delete 30 | Per-plane via the matching `AEX_RATE_LIMIT_<ACTION>_PER_MINUTE` env var. | `WORKSPACE_RATE_LIMIT_DEFAULTS` |
63
+ | Workspace storage cap | 500 GB (decimal) | Per-plane via env `AEX_WORKSPACE_STORAGE_CAP_BYTES`; admin workspaces are uncapped (not a customer entitlement). | `WORKSPACE_DEFAULT_STORAGE_CAP_BYTES` |
package/docs/errors.md ADDED
@@ -0,0 +1,132 @@
1
+ ---
2
+ title: Errors
3
+ ---
4
+
5
+ # Errors
6
+
7
+ Every API error is a JSON body with a machine-readable `error` code; most also
8
+ carry a human `message` and the self-describing fields named below. The SDK
9
+ surfaces non-2xx responses as `AexApiError` (with the parsed body attached) and
10
+ throttling as `AexRateLimitError`.
11
+
12
+ ## 401 — authentication
13
+
14
+ | Code | Meaning |
15
+ | --- | --- |
16
+ | `unauthorized` | Missing, invalid, or revoked bearer token. |
17
+
18
+ Check the token value and that it has not been deleted. `aex whoami` is the
19
+ cheapest way to validate a credential. See [Authentication](authentication.md).
20
+
21
+ ## 403 — authorization
22
+
23
+ | Code | Meaning |
24
+ | --- | --- |
25
+ | `insufficient_scope` | The token is valid but lacks the route's required scope. The body's `requiredScope` field names the missing scope. |
26
+ | `unknown_workspace` | The token does not route to a known workspace. |
27
+ | `forbidden` | The authenticated workspace does not own the addressed resource. |
28
+
29
+ ```json
30
+ { "error": "insufficient_scope", "requiredScope": "runs:write" }
31
+ ```
32
+
33
+ ## 400 — validation
34
+
35
+ | Code | Meaning |
36
+ | --- | --- |
37
+ | `bad_request` | Missing or unparseable request body. |
38
+ | `invalid_submission` | The submission failed shape validation; `message` names the offending field. |
39
+ | `missing_provider_key` | The submission names a provider but carries no BYOK key for it (`apiKeys[provider]`). |
40
+ | `malformed_token` | The bearer value is not a structurally valid aex token. |
41
+
42
+ 400s are permanent for that request — fix the input rather than retrying.
43
+ The SDK's client-side validation (`RunConfigValidationError`) catches most of
44
+ these before the request is sent.
45
+
46
+ ## 402 — payment required
47
+
48
+ Two distinct submit gates return 402; both bodies are self-describing.
49
+
50
+ **`insufficient_balance`** — the workspace prepaid balance is at or below the
51
+ effective submit floor. Top up the balance or bind a payment method.
52
+
53
+ ```json
54
+ {
55
+ "error": "insufficient_balance",
56
+ "message": "Workspace balance is depleted; top up your prepaid balance or bind a payment method to submit runs.",
57
+ "balanceUsd": 0,
58
+ "balanceGraceFloorUsd": 0,
59
+ "paymentMethodStatus": "none",
60
+ "planKey": "default"
61
+ }
62
+ ```
63
+
64
+ `balanceGraceFloorUsd` is the payment-method-aware floor the gate compared
65
+ against (`paymentMethodStatus: "active"` folds a bounded card overdraft into
66
+ it, so the floor can be negative).
67
+
68
+ **`workspace_spend_cap_exceeded`** — the workspace's monthly spend cap is
69
+ reached. The cap resets at the start of the next UTC month; contact support to
70
+ raise it.
71
+
72
+ ```json
73
+ {
74
+ "error": "workspace_spend_cap_exceeded",
75
+ "message": "Monthly spend cap of $250 reached ($251.13 accrued this month). The cap resets at the start of the next UTC month; contact support to raise it.",
76
+ "capUsd": 250,
77
+ "accruedUsd": 251.13
78
+ }
79
+ ```
80
+
81
+ ## 429 — rate limits
82
+
83
+ **`workspace_concurrency_exceeded`** — admitting one more live run would exceed
84
+ the workspace's concurrent-run cap. Wait for a run to finish, or contact
85
+ support to raise the cap.
86
+
87
+ ```json
88
+ {
89
+ "error": "workspace_concurrency_exceeded",
90
+ "message": "Workspace concurrency limit reached: 50 live runs at the cap of 50. Wait for a run to finish, or contact support to raise your workspace limit.",
91
+ "cap": 50,
92
+ "observed": 50
93
+ }
94
+ ```
95
+
96
+ **`workspace_submit_rate_exceeded`** — too many submits in the current
97
+ one-minute window. Retry shortly.
98
+
99
+ ```json
100
+ {
101
+ "error": "workspace_submit_rate_exceeded",
102
+ "message": "Submit rate limit of 120/minute exceeded. Retry shortly, or contact support to raise your workspace limit.",
103
+ "perMin": 120,
104
+ "observed": 121
105
+ }
106
+ ```
107
+
108
+ The `limit`-naming fields (`cap`/`perMin`) and the `observed` window value make
109
+ each deny self-describing, so a client can back off proportionally. To
110
+ anticipate both 429s and both 402s *before* submitting, read the effective caps
111
+ from `aex.whoami().limits` — the values come from the same resolution code the
112
+ gates enforce. See [Limits & quotas](limits-and-quotas.md).
113
+
114
+ ## 404 — not found
115
+
116
+ `not_found`: the id does not exist **or** belongs to another workspace (aex
117
+ does not distinguish the two).
118
+
119
+ ## 5xx — server errors
120
+
121
+ | Code | Meaning |
122
+ | --- | --- |
123
+ | `internal_error` (500) | Unexpected server fault. Retry with backoff; report persistent cases. |
124
+ | `db_resuming` (503) | The database tier is resuming from idle. Transient — retry. |
125
+
126
+ The SDK retries transient failures automatically: HTTP `429`, `5xx`, `529`, and
127
+ network errors get bounded exponential backoff with full jitter, honoring any
128
+ `Retry-After` header. Tune or disable this with the client `retry` option; use
129
+ `isRateLimited(err)` / `AexRateLimitError` to handle persistent throttling
130
+ without parsing raw bodies. Idempotent submit retries are safe — the SDK
131
+ attaches a stable idempotency key to billable session create/send requests, so
132
+ a retried request never double-submits.