How ai-memory Handles Hook Event Timeouts: Per-Webhook Configuration and Failure Policies
ai-memory handles hook event timeouts through per-webhook timeout_ms configuration with a 2,000 ms default, a hard 30,000 ms upper bound enforced by the webhook_timeout helper, and FailurePolicy—based propagation or suppression of timeout errors in the AdmissionChain defined in crates/ai-memory-wiki/src/admission.rs.
The ai-memory project implements admission webhooks for page write, delete, and purge operations through a robust timeout mechanism that balances operator flexibility with system stability. This article examines the timeout handling implementation in the Rust-based wiki engine, tracing how configuration values flow through the AdmissionChain to reqwest client requests and how failures are handled according to operator-defined policies.
Per-Hook Timeout Configuration in WebhookConfig
Every webhook registered in ai-memory is described by a WebhookConfig struct that includes explicit timeout control. The timeout_ms field allows operators to specify how long the AdmissionChain should wait for a webhook response before treating it as failed.
If an operator omits the timeout value, the system falls back to default_timeout_ms(), which returns 2,000 ms as a safe default. This prevents unconfigured webhooks from causing unpredictable behavior while still providing a reasonable window for typical webhook processing.
// From crates/ai-memory-wiki/src/admission.rs (lines 33-35)
fn default_timeout_ms() -> u64 {
2000 // 2 seconds default
}
When constructing webhook configurations, operators can override this default for individual hooks based on expected latency:
use ai_memory_wiki::{AdmissionChain, WebhookConfig, FailurePolicy, AdmissionOp};
/// Build a chain with a custom timeout (5 seconds) that aborts on failure.
let hook = WebhookConfig {
name: "my_hook".into(),
url: "http://example.com/hook".into(),
timeout_ms: 5_000, // custom per-hook timeout
failure_policy: FailurePolicy::Reject,
events: vec![AdmissionOp::WritePage],
blocking: true,
};
let chain = AdmissionChain::new(vec![hook])?;
Hard Upper Bound with webhook_timeout Helper
To prevent misconfigured webhooks from hanging the write path indefinitely, ai-memory enforces a 30,000 ms maximum timeout through the webhook_timeout helper function. This clamping mechanism ensures that even an operator-specified timeout of one hour would be reduced to the safe maximum.
// From crates/ai-memory-wiki/src/admission.rs (lines 76-78)
fn webhook_timeout(timeout_ms: u64) -> std::time::Duration {
// Minimum 1 ms, maximum 30,000 ms.
std::time::Duration::from_millis(timeout_ms.clamp(1, 30_000))
}
The helper applies both a floor (1 ms) and ceiling (MAX_WEBHOOK_TIMEOUT_MS = 30_000) to every timeout value. This two-sided clamping prevents edge cases like zero-duration timeouts while capping worst-case latency impact on the write path.
Applying Timeouts to Blocking and Async Webhooks
The AdmissionChain applies the clamped timeout to webhook requests through the reqwest HTTP client. For blocking webhooks, the timeout is set directly on the request builder:
// From crates/ai-memory-wiki/src/admission.rs (lines 61-63)
let response = self
.client
.post(&hook.url)
.timeout(webhook_timeout(hook.timeout_ms))
.json(&payload)
.send()
.await;
For fire-and-forget async webhooks, the same webhook_timeout helper ensures consistent timeout enforcement:
// From crates/ai-memory-wiki/src/admission.rs (lines 136-138)
let request = self
.client
.post(&hook.url)
.timeout(webhook_timeout(hook.timeout_ms))
.json(&payload)
.build()?;
This unified approach guarantees that all webhook invocations—regardless of blocking semantics—respect the same timeout boundaries.
Timeout Error Handling and FailurePolicy
When a webhook request exceeds its configured timeout, reqwest returns an Err. The AdmissionChain handles this through its error classification system, which respects the webhook's failure_policy:
FailurePolicy::Reject– The timeout error propagates, causing the entire page write/delete/purge operation to abort with anIoerror.FailurePolicy::Ignore– The timeout is logged as a warning, but the operation continues as if the webhook had succeeded.
This behavior applies uniformly to all request failures, including timeouts, connection errors, and DNS resolution failures:
// Error handling pattern from crates/ai-memory-wiki/src/admission.rs (lines 24-33)
match result {
Ok(response) => { /* process 2xx or 204 responses */ }
Err(e) => {
warn!("Webhook {} failed: {}", hook.name, e);
if hook.failure_policy == FailurePolicy::Reject {
return Err(Error::Io(e.into()));
}
// Policy is Ignore: continue operation
}
}
Async Webhook Concurrency Limits
For non-blocking webhooks, ai-memory implements additional protection through a semaphore-capped queue. The MAX_ASYNC_ADMISSION_IN_FLIGHT = 256 constant limits concurrent in-flight async webhook requests:
- If the queue has capacity, the async webhook spawns immediately with its configured timeout.
- If the queue is saturated, the webhook is dropped and a warning is emitted.
This mechanism prevents memory exhaustion during webhook storms while preserving latency bounds for operations that do proceed. Blocking webhooks bypass this semaphore—they execute synchronously with timeout enforcement directly on the request.
Summary
- Default timeout: 2,000 ms via
default_timeout_ms()whentimeout_msis unspecified. - Maximum timeout: 30,000 ms hard cap enforced by
webhook_timeout()clamping helper. - Request application: Timeout passed to
reqwestvia.timeout()for both blocking and async webhooks. - Failure handling:
FailurePolicy::Rejectaborts the operation;FailurePolicy::Ignorelogs and continues. - Async limits: 256 concurrent in-flight requests capped by semaphore; excess dropped with warning.
Frequently Asked Questions
What happens if a webhook times out in ai-memory?
If a webhook exceeds its configured timeout_ms, reqwest returns an error that the AdmissionChain catches and logs as a warning. The consequence depends on the webhook's failure_policy: with Reject, the error propagates and aborts the page operation; with Ignore, the operation continues unaffected. This logic is implemented in crates/ai-memory-wiki/src/admission.rs.
What is the maximum webhook timeout allowed in ai-memory?
The maximum webhook timeout is 30,000 ms (30 seconds), defined by MAX_WEBHOOK_TIMEOUT_MS and enforced through the webhook_timeout helper function. Any operator-specified timeout exceeding this value is clamped to 30 seconds, preventing indefinite hangs on the write path.
How do I configure a custom timeout for a specific webhook?
Set the timeout_ms field in your WebhookConfig struct. Values are automatically clamped between 1 ms and 30,000 ms. For example: timeout_ms: 5_000 for a 5-second timeout. If omitted, the system uses the 2,000 ms default from default_timeout_ms().
Does ai-memory handle async webhooks differently for timeouts?
Async webhooks use the same webhook_timeout helper for request timeouts, but they execute through a semaphore-limited queue (MAX_ASYNC_ADMISSION_IN_FLIGHT = 256). If the queue is full, the webhook is dropped rather than queued. Blocking webhooks execute synchronously with timeout enforcement and are not subject to this concurrency limit.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →