Skip to main content
Understanding the performance profile of the Praxa SDK helps you design your application’s mission submission patterns and error handling strategy. The 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.
This page explains client behavior and tuning considerations. It does not report a production benchmark or promise a latency or throughput level. Read the benchmark and validation evidence and the preprint before interpreting measured results.

SDK client overhead

PraxaClient adds three identifiable sources of overhead to every outbound request. 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.
The following wrapper caches the token and refreshes it 5 seconds before expiry, eliminating the token round-trip for the lifetime of the cached value:
Contract version header. Every request — including the SSE stream — carries x-aura-contract-version: aura-integration-gateway-v8.1. The Praxa-named PRAXA_CONTRACT_VERSION export aliases this stable compatibility value. The header adds negligible serialization overhead; mismatches result in a non-retryable 4xx response rather than a silent behavioural difference. Body serialization. Request bodies are serialized with JSON.stringify. The cost depends on your payload and runtime. 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 run synchronously and throw for invalid inputs rather than producing a network error. Measure their cost in your runtime if it matters to your workload.

Retry and backoff behavior

The PraxaClient 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.
If you reuse the same idempotency key for logically distinct missions — for example, by using a static string in a loop — the gateway will return the original mission’s result for every call after the first. Generate a fresh UUID per mission submission.

SSE stream performance

missionEvents() returns an async generator that yields PraxaSseEvent 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:
Long-running missions. Missions run for up to 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:
Resumption and the 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 using Promise.all when your tasks are independent and you want to minimize total wall-clock time. Give each distinct logical mission a different idempotency key — sharing one key across independent submissions would conflict, while retries of the same mission must reuse its original key.
Fan-out can reduce submission wall-clock time for independent tasks. Actual throughput depends on your application, network path, and gateway deployment. At high concurrency, expect the following behaviours:
  • 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 with Promise.all on 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.
  • maximumParallelism per mission. This field in ResourceBudget governs internal step-level parallelism within a single mission, not the number of missions you can submit concurrently. Submitting 10 missions with maximumParallelism: 1 creates 10 independent missions each running one step at a time.

Optimizing resource budgets

Every mission carries a ResourceBudget 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.
Call client.getReferenceCoverage() against your gateway deployment before building production workflows. The response documents which endpoints and capability kinds your specific deployment supports. If a capability you depend on — such as browser or a2a_agent — is absent from the coverage response, submission may succeed but execution will fail when the agent attempts to invoke that capability. Checking coverage at startup saves you from mysterious mid-mission failures.
Last modified on August 14, 2026