PraxaClient is a thin, stateless HTTP and SSE client — it adds no local queuing, no background threads, and no persistent connection pool of its own. Every performance characteristic described here flows directly from how your application configures the client, how quickly your accessToken function resolves, and the network path between your runtime and the Integration Gateway.
SDK Client Overhead
PraxaClient adds three sources of overhead to every outbound request, all of which are deterministic and measurable.
Token retrieval. Each call to createMission(), getMission(), missionEvents(), or any other SDK method invokes your accessToken function once to obtain a bearer token. If your function performs a network call to a token endpoint on every invocation, that round-trip adds directly to your observed request latency. The SDK does not cache the returned token — caching is your responsibility, and it is the single most effective optimization you can make for high-frequency mission submission.
x-aura-contract-version: aura-integration-gateway-v8.1. This header is set from the compiled AURA_CONTRACT_VERSION constant and adds negligible serialization overhead. The gateway uses it to route requests to the correct contract handler; mismatches result in a non-retryable 4xx response rather than a silent behavioural difference.
Body serialization. Request bodies are serialized with JSON.stringify. For typical CreateMissionRequest payloads — a goalSpec object and a ResourceBudget with four numeric fields — this is sub-millisecond. If your goalSpec carries large embedded context objects, measure serialization time separately and consider moving large payloads to a memory compartment queryable via queryMemory() instead.
The SDK validates your
baseUrl as an HTTPS origin with no embedded credentials and validates the accessToken string length and character set before each request. Both checks are synchronous and complete in microseconds, but they will throw synchronously for invalid inputs rather than producing a network error.Retry and Backoff Behavior
ThePraxaClient retry mechanism activates automatically on transient failures, preserving the idempotency key and request body unchanged across all attempts. This means you do not need to implement your own retry loop for createMission() calls — the SDK handles transient gateway unavailability for you.
When retries activate. Retries only fire when the request is safe to repeat: either the request used an idempotency key (all createMission(), signalMission(), and cancelMission() calls) or the request is a safe read (getMission(), getTrace(), searchCapabilities(), and similar GET/POST reads). Requests without either property attempt exactly once, regardless of the maximumAttempts setting.
Retryable status codes. The SDK retries on the following HTTP status codes:
Non-retryable 4xx errors (401, 403, 404, 409, 422, etc.) are thrown immediately as
PraxaClientError without consuming retry budget.
Attempt count. The maximum number of attempts is controlled by maximumAttempts at construction time, clamped to the range 1–4. The default is 3. Setting maximumAttempts: 1 disables retries entirely.
Backoff formula. Between attempts, the client sleeps for retryBaseDelayMs × 2^(attempt − 1) milliseconds. retryBaseDelayMs defaults to 100 ms and is clamped to the range 0–30,000 ms. The progression for the default configuration:
The sleep respects the
AbortSignal you pass to the method call — if the signal fires during a backoff sleep, the delay is cancelled and the abort reason is thrown immediately.
Idempotency key preservation. The SDK sets the Idempotency-Key header to the same value on every attempt. The Integration Gateway uses this key to deduplicate accepted missions: if the first attempt actually succeeded but the response was lost in transit, the retry receives the original MissionProjection rather than creating a duplicate mission. Always supply a crypto.randomUUID() value unless you intentionally want idempotent re-submission of the same logical mission.
SSE Stream Performance
missionEvents() returns an async generator that yields AuraSseEvent objects as they arrive from the Integration Gateway. The implementation uses a buffered UTF-8 line reader directly over the response body stream, with no intermediate buffering layer beyond what the underlying fetch implementation provides.
Memory footprint. The reader holds at most one unfinished line in memory at a time. Event data accumulates until the blank-line dispatch boundary, after which it is parsed and the buffer is cleared. Memory usage scales with the size of the largest single event, not with the total number of events emitted or the duration of the mission. The SDK enforces a 1 MiB ceiling per line and per event; events exceeding this limit cause the generator to throw.
Early cancellation. If you break out of the for await loop before the stream ends, the generator’s finally block calls reader.cancel() to release the response body. This immediately frees the connection and associated buffers. You can also pass an AbortSignal in the options to cancel the stream from outside the loop:
maximumElapsedMs milliseconds without requiring reconnection. The SSE stream remains open for the full duration. If your runtime or a proxy closes idle connections, use the lastEventId option on reconnection to resume the stream from the last received event:
Last-Event-ID header. The SDK validates lastEventId against the pattern /^[\x21-\x7e]{1,256}$/ — printable ASCII, 1–256 characters — before sending the Last-Event-ID request header. Values that fail this check throw synchronously before opening the stream.
The SSE stream does not retry automatically on connection drops the way a browser
EventSource does. If you need reconnection logic, wrap missionEvents() in a loop that catches network errors and resumes with the last received event.id as lastEventId.Concurrent Mission Patterns
You can submit missions sequentially or in parallel. The right pattern depends on whether your tasks are independent and whether your token budget and gateway rate limits can accommodate burst submission.Sequential Missions
Submit one mission at a time when tasks have ordering dependencies or when you want predictable, metered resource consumption. Sequential submission is simple to reason about and produces a clear per-mission latency profile.Parallel Mission Fan-Out
Submit multiple missions simultaneously usingPromise.all when your tasks are independent and you want to minimize total wall-clock time. Each mission must carry a unique idempotency key — sharing a key across parallel submissions would cause the gateway to deduplicate them to a single mission.
- 429 Too Many Requests — the gateway may rate-limit burst submissions. The SDK will back off and retry automatically (up to
maximumAttempts). If your tasks array is large, consider submitting in batches withPromise.allon a fixed-size window. - Token acquisition. With caching in place, all parallel
createMission()calls share the same cached token and incur no additional token fetches during their concurrent window. maximumParallelismper mission. This field inResourceBudgetgoverns internal step-level parallelism within a single mission, not the number of missions you can submit concurrently. Submitting 10 missions withmaximumParallelism: 1creates 10 independent missions each running one step at a time.
Optimizing Resource Budgets
Every mission carries aResourceBudget that caps four dimensions of resource consumption: maximumSteps, maximumToolCalls, maximumElapsedMs, and maximumParallelism. Tight budgets reduce costs, limit blast radius from runaway missions, and surface unexpected mission behaviour earlier in development.
Start small and expand from trace data. When you first integrate a new goalSpec template, set conservative budgets — for example, maximumSteps: 5, maximumToolCalls: 4, maximumElapsedMs: 30_000. Retrieve the execution trace with getTrace(traceId) after the mission completes and examine actual step and tool call counts. Increase the budget to give a reasonable headroom above the p95 observed values for that task type.
maximumElapsedMs is a hard ceiling. If a mission reaches this limit before completing, it transitions to failed or cancelled. This is the primary mechanism for preventing runaway agent loops. Set it generously enough that normal execution has room to complete, but tightly enough that an unexpected infinite loop is caught within a predictable time window.
maximumToolCalls limits external effects. Each tool call represents a potential external side effect. Capping this value limits the number of consequential actions an agent can take in a single mission, which is the primary budget lever for safety-sensitive workflows.
maximumParallelism affects throughput, not submission. Higher values allow the execution plane to run more steps concurrently within a mission, which can reduce maximumElapsedMs consumption for parallelisable tasks — but it also increases peak resource usage. For latency-sensitive missions where predictable serialized execution matters more than speed, maximumParallelism: 1 gives the clearest execution trace.