Skip to main content

HandlerLimits

Struct HandlerLimits 

Source
pub struct HandlerLimits {
    pub max_id_length: usize,
    pub max_metadata_size: usize,
    pub max_cancellation_tokens: usize,
    pub max_token_age: Duration,
    pub push_delivery_timeout: Duration,
    pub max_artifacts_per_task: usize,
    pub max_context_locks: usize,
    pub max_push_configs_per_task: usize,
    pub max_parts_per_artifact: usize,
    pub max_total_push_configs: usize,
    pub subscribe_reattach_interval: Duration,
    pub subscribe_max_idle: Duration,
}
Expand description

Configurable limits for the request handler.

All fields have sensible defaults. Create with HandlerLimits::default() and override individual values as needed.

§Example

use a2a_protocol_server::handler::HandlerLimits;

let limits = HandlerLimits::default()
    .with_max_id_length(2048)
    .with_max_metadata_size(2 * 1024 * 1024);

Fields§

§max_id_length: usize

Maximum allowed length for task/context IDs. Default: 1024.

§max_metadata_size: usize

Maximum allowed serialized size for metadata fields in bytes. Default: 1 MiB.

§max_cancellation_tokens: usize

Maximum cancellation token map entries before cleanup sweep. Default: 10,000.

A sweep threshold, not a hard bound: the sweep only evicts cancelled or aged-out entries whose executor is gone — a token belonging to a live task is never removed, so with more than this many tasks genuinely in flight the map tracks the in-flight count instead.

§max_token_age: Duration

Maximum age for cancellation tokens. Default: 1 hour.

§push_delivery_timeout: Duration

Timeout for individual push webhook deliveries. Default: 5 seconds.

Bounds how long the handler waits for a single push notification delivery to complete, preventing one slow webhook from blocking all subsequent deliveries.

§This is a total, and the sender’s retries have to fit inside it

The bound covers the whole PushSender::send call, retries included — not one HTTP request. A sender whose own schedule is longer never finishes it, and the attempts it advertises simply do not happen.

The shipped defaults contradict each other. HttpPushSender::new() is three attempts at a 30-second request timeout with [1s, 2s] backoff — 93 seconds — against this 5-second bound. Measured 2026-08-19 against a real socket: one of the three attempts reaches the webhook, and the bound fires at 5.001s. So max_attempts and backoff are, at the defaults, configuration that cannot take effect.

The two numbers pull in opposite directions and neither is obviously wrong. Raising this bound to fit the retries makes the 30-second per-event budget in deliver_push_bg reachable by a single config, which is the amplification ceiling that budget exists to hold. Shrinking the sender’s schedule to fit gives real webhooks less time than a slow one legitimately needs. Choosing between them is a deployment decision, so this documents the arithmetic rather than picking:

attempts_that_run == 1 + how many whole (request_timeout + backoff)
                         cycles fit in push_delivery_timeout

A sender that reports PushSender::max_delivery_duration gets the truncation counted rather than mistaken for a slow endpoint — see push_outcome::TIMEOUT_TRUNCATED.

§max_artifacts_per_task: usize

Maximum number of artifacts per task. Default: 1000.

Prevents unbounded memory growth and O(n²) serialization cost when executors emit many artifacts. Once the limit is reached, new artifact updates are rejected.

§max_context_locks: usize

Maximum number of per-context locks before cleanup. Default: 10,000.

Context locks serialize concurrent SendMessage requests for the same context_id. Stale entries (where no other reference is held) are pruned when this limit is reached. Like max_cancellation_tokens this is a prune threshold, not a hard bound — entries currently held by in-flight requests are never pruned.

§max_push_configs_per_task: usize

Maximum number of push notification configs per task. Default: 100.

Enforced by the handler on CreateTaskPushNotificationConfig so the cap applies uniformly across all store backends. Without it, the SQL stores (which do not self-enforce) let a client mint unbounded configs for a single task — a disk-exhaustion vector, and a delivery-amplification vector since every stream event fans out to all of a task’s configs. Updating an existing config (same id) does not count against the cap.

§max_parts_per_artifact: usize

Maximum number of parts a single artifact may accumulate. Default: 10,000.

max_artifacts_per_task bounds the artifact count, but a stream of TaskArtifactUpdateEvents with append: true grows one artifact’s parts without bound. Since executors routinely stream model output derived from attacker-influenced prompts, this bounds the cumulative per-artifact (and thus per-task) size. Appends that would exceed the cap are dropped.

§max_total_push_configs: usize

Global ceiling on the total number of push configs a store may hold (per-tenant for tenant-scoped stores). Default: 100,000.

Complements max_push_configs_per_task: the per-task cap alone lets a client mint configs for unboundedly many distinct task ids (100 each), growing a SQL-backed table without limit. Enforced whenever the store reports a count (see PushConfigStore::count); stores that do not report one are unaffected.

§subscribe_reattach_interval: Duration

How often a SubscribeToTask stream re-checks whether its task has finished, once the current turn’s event queue has closed. Default: 250ms.

A task’s queue lives only as long as one executor invocation, so an agent that parks a task in input_required closes the queue at every turn boundary. Spec §3.1.6 requires the stream to run until a terminal state, so it waits here for the next turn rather than ending. Only an idle stream pays this cost — a live queue delivers events immediately.

§subscribe_max_idle: Duration

How long a SubscribeToTask stream waits for a parked task to make progress before ending. Default: 5 minutes.

Without a bound, a task left in input_required forever would pin a connection forever. Ending the stream is safe: §3.5.2 makes reconnection an expected flow, and the client gets a fresh snapshot when it resubscribes.

Implementations§

Source§

impl HandlerLimits

Source

pub const fn with_subscribe_reattach_interval(self, interval: Duration) -> Self

Sets how often an idle SubscribeToTask stream re-checks its task.

Source

pub const fn with_subscribe_max_idle(self, max_idle: Duration) -> Self

Sets how long a SubscribeToTask stream waits on a parked task.

Source

pub const fn with_max_id_length(self, length: usize) -> Self

Sets the maximum allowed length for task/context IDs.

Source

pub const fn with_max_metadata_size(self, size: usize) -> Self

Sets the maximum serialized size for metadata fields in bytes.

Source

pub const fn with_max_cancellation_tokens(self, max: usize) -> Self

Sets the maximum cancellation token map entries before cleanup.

Source

pub const fn with_max_token_age(self, age: Duration) -> Self

Sets the maximum age for cancellation tokens.

Source

pub const fn with_push_delivery_timeout(self, timeout: Duration) -> Self

Sets the timeout for individual push webhook deliveries.

Source

pub const fn with_max_artifacts_per_task(self, max: usize) -> Self

Sets the maximum number of artifacts per task.

Source

pub const fn with_max_push_configs_per_task(self, max: usize) -> Self

Sets the maximum number of push notification configs per task.

Source

pub const fn with_max_total_push_configs(self, max: usize) -> Self

Sets the global (per-tenant for tenant stores) ceiling on total push notification configs. Enforced only when the store reports a count.

Source

pub const fn with_max_parts_per_artifact(self, max: usize) -> Self

Sets the maximum number of parts a single artifact may accumulate.

Source

pub const fn with_max_context_locks(self, max: usize) -> Self

Sets the maximum number of per-context locks before cleanup.

Trait Implementations§

Source§

impl Clone for HandlerLimits

Source§

fn clone(&self) -> HandlerLimits

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for HandlerLimits

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for HandlerLimits

Source§

fn default() -> Self

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> FromRef<T> for T
where T: Clone,

Source§

fn from_ref(input: &T) -> T

Converts to this type from a reference to the input type.
Source§

impl<T> FutureExt for T

Source§

fn with_context(self, otel_cx: Context) -> WithContext<Self>

Attaches the provided Context to this type, returning a WithContext wrapper. Read more
Source§

fn with_current_context(self) -> WithContext<Self>

Attaches the current Context to this type, returning a WithContext wrapper. Read more
Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<T> IntoRequest<T> for T

Source§

fn into_request(self) -> Request<T>

Wrap the input message T in a tonic::Request
Source§

impl<L> LayerExt<L> for L

Source§

fn named_layer<S>(&self, service: S) -> Layered<<L as Layer<S>>::Service, S>
where L: Layer<S>,

Applies the layer to a service and wraps it in Layered.
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more