Expand description
v0.6.0.0 — webhook subscriptions.
Subscribers register a URL + shared secret + event/namespace/agent
filters. When a matching event fires (e.g. memory_store), a
fire-and-forget thread POSTs an HMAC-SHA256-signed JSON payload.
SSRF hardening:
http://only to127.0.0.0/8orlocalhosthosts; everywhere else requireshttps://- RFC1918 / RFC4193 / link-local hosts are rejected unless
allow_private_networks = truein the daemon config
Signature:
- Header
X-Ai-Memory-Signature: sha256=<hex>over the raw JSON body - The secret stored in the DB is a SHA-256 of the plaintext shared secret; the plaintext is returned once at subscription time and never leaves the DB after.
Modules§
- webhook_
events - v0.7.0 multi-agent literal-sweep (scanner B finding F-B10.x) — canonical webhook-event-type slugs that are NOT MCP tool names.
Structs§
- Approval
Requested Event Details - v0.7.0 K4 —
approval_requestedevent details. - Consolidated
Event Details memory_consolidatedevent — fires afterdb::consolidatecommits. The outermemory_idcarries the new consolidated memory’s id;source_idsis the array of memories that were merged (and deleted by the consolidate op).- Delete
Event Details memory_deleteevent — fires after the row is removed frommemories.titleandtiercome from the pre-delete snapshot so subscribers can write meaningful audit entries without a roundtrip.- DlqEntry
- One row of the
subscription_dlqtable. Created when a delivery exhausts the [200ms, 1s, 5s] retry ladder. K7’s inspector tool surfaces these rows; K6 only ships the writer. - Link
Created Event Details memory_link_createdevent — fires afterdb::create_linkcommits. The outermemory_idcarries the source id (the link-author side);target_idis the destination of the directed link.- Link
Invalidated Event Details memory_link_invalidatedevent (v0.7 J4 / G14) — fires after a successfulmemory_kg_invalidatewritesvalid_untilon the(source_id, target_id, relation)link. The outermemory_idcarries the source id (the link-author side);target_id,relation, and the freshly-writtenvalid_untildescribe the supersession edge so consumers can replay the invalidation log without re-readingmemory_links.previous_valid_untildistinguishes the first supersession (None) from an idempotent retry (carries the prior stamp).- NewSubscription
- Parameters for creating a subscription.
- Promote
Event Details memory_promoteevent — fires after a tier or vertical promotion commits.to_namespaceisSomefor vertical (memory_promotewith ato_namespaceargument); for the default tier promotion it isNoneandtieris set to the new tier ("long").- Subscription
- Public-facing subscription record (no secret plaintext).
- Subscription
Event - One row of the
subscription_eventsper-delivery audit log. K6 writes one row before each network send; K7’smemory_subscription_replaytool reads the rows back ordered bydelivered_atfor replay-from-cursor support.
Constants§
- ACK_
TIMEOUT - v0.7.0 K6 — per-attempt ACK timeout. Receivers MUST return a JSON
body of the form
{"status":"ack","correlation_id":"..."}within this window for the delivery to count as successful. A non-2xx response, a timeout, or an ACK whosecorrelation_iddoesn’t match the dispatched id all count as failure and trigger the next retry. Exposed so the integration tests can pin the wall-clock expectations. - DEFAULT_
WEBHOOK_ DISPATCH_ CONCURRENCY - PERF-3 (fix campaign 2026-05-26, FX-10) — default upper bound on the number of webhook deliveries that may be in flight concurrently.
- MAX_
SUBSCRIPTION_ DLQ_ ROWS - #1253 (MED, 2026-05-25) — operator-disk-fill DoS guard. Hard cap
on the per-subscription
subscription_dlqdepth. Pre-#1253 the DLQ grew unboundedly when a hostile (or simply broken) webhook target failed every delivery — a single bad subscriber could exhaust the operator’s disk by sustaining a high failure rate. Past this caprecord_dlq_with_connrefuses the insert with the typeddlq_overflowerror, increments theai_memory_subscription_dlq_overflow_totalcounter, and emits atracing::warn!. Operators drain the queue viamemory_subscription_dlq_list+ the planned... dlq drainadmin tool before resetting. 10_000 rows is well above realistic transient-failure depths (a healthy peer recovers in minutes, not thousands of events) while still bounded enough that a hostile peer can write at most ~10 MB before being capped (the payload column is itself bounded by the webhook JSON-body cap upstream). - RETRY_
BACKOFFS - v0.7.0 K6 — exponential-backoff retry ladder for failed webhook
deliveries. The dispatcher attempts the initial POST, then up to
three retries spaced [200ms, 1s, 5s] apart. After the final retry
fails the row lands in
subscription_dlqfor K7’s inspector tool to surface. Exposed as a constant so tests can reason about the total wall-clock budget (≈ 6.2s + per-attempt timeout). - WEBHOOK_
EVENT_ TYPES - Canonical list of webhook lifecycle events surfaced to subscribers
and to
memory_capabilities(capabilities v2webhook_events). Keep stable: integrators pin against these strings.
Functions§
- delete
- Delete a subscription by id, optionally scoped to its owner.
- dispatch_
approval_ requested - v0.7.0 K4 — dispatch the
approval_requestedlifecycle event for a freshly-insertedpending_actionsrow. - dispatch_
event - Fire an event to all matching subscribers. Each dispatch is
fire-and-forget — the caller is NOT blocked on delivery. Errors are
logged and counted in the DB via
failure_count. - dispatch_
event_ to_ subs - #932 (v0.7.0 Track D, 2026-05-20) — backend-neutral per-sub
dispatch worker pool. Takes an already-resolved
Vec<(Subscription, Option<secret_hash>)>so the sqlite path (viadispatch_event_with_details) and the postgres path (viadispatch_event_postgres) share the same delivery + audit + DLQ + HMAC code while their subscription-source lookups remain adapter-specific. - dispatch_
event_ with_ details - P5 (G9): full lifecycle dispatch with optional event-specific
details. The details JSON is FLATTENED into the dispatch payload —
keys must not collide with the outer envelope (
event,memory_id,namespace,agent_id,delivered_at). The four new event types (memory_promote,memory_delete,memory_link_created,memory_consolidated) supply their*EventDetailsstruct serialised viaserde_json::to_value. - get_
owner - Look up a subscription’s owner (
created_by) by id. - insert
- Insert a subscription, hashing any secret before persisting.
- list
- List active subscriptions, optionally scoped to a single owner.
- list_
by_ event - P5 (G9): list subscriptions matching a specific event type. Returns rows where either:
- list_
dlq - v0.7.0 K6 — list
subscription_dlqrows. Used by the K7 inspector tool (not registered in MCP yet) and by the K6 integration test suite. - memory_
subscription_ replay - v0.7.0 K6 — handler for
memory_subscription_replay. K7 wired this into the MCP dispatch table behind the existingmemory_subscription_*family — seesrc/mcp/tools/subscribe.rs::handle_subscription_replayfor the thin MCP wrapper that delegates here. The K6-vintage “DO NOT add to MCP dispatch table” warning in the prior docstring was stale per the v0.7.0 multi-agent literal-sweep (scanner B finding F-B6.x); registration has been in tree since K7. - prewarm_
dispatch_ tls - v0.7.0 K6 — dispatcher driver. Issues the initial POST plus up to
three retries spaced [200ms, 1s, 5s] apart. Each attempt validates
the receiver’s ACK body — a 2xx response with no ACK or a
mismatched correlation_id counts as failure and triggers the next
retry. Returns the cumulative [
DeliveryOutcome]. One-time process-global warm-up of thereqwest::blockingTLS connector used by webhook delivery. - record_
dlq - v0.7.0 K6 — append a
subscription_dlqrow for a delivery that exhausted the [200ms, 1s, 5s] retry ladder. K7’s inspector tool surfaces these rows to operators; K6 only ships the writer. - record_
dlq_ with_ conn - v0.7.0 #1072 —
Connection-reuse variant ofrecord_dlq. - record_
subscription_ event - v0.7.0 K6 — append a
subscription_eventsaudit row for one outgoing delivery. Called from the dispatch worker BEFORE the network send so replay-from-cursor (K7) sees a stable record even if the dispatcher process crashes mid-retry. The row is created withdelivery_status = 'pending'; [update_event_status] transitions it to'ack'/'failed'once the retry ladder settles. - record_
subscription_ event_ with_ conn - v0.7.0 #1072 —
Connection-reuse variant. Used by the per- subscription dispatch worker so a single thread-local connection covers every sqlite write in the delivery path. - replay_
subscription_ events - v0.7.0 K6 — replay subscription events for a single subscription
since
since_rfc3339. Returns the audit rows ordered bydelivered_atascending (so cursor-by-time scans are stable). - validate_
hmac_ secret_ hex - v0.7.0 #1048 (Agent-5 #8) — boot-time hex validator for the
operator-supplied
hmac_secret. ReturnsOk(())when the value isNone(no secret configured) OR is valid hex; returnsErr(String)with a descriptive operator-facing message when the value is non-hex. Callers MUST surface the error before the daemon accepts traffic; the runtimehmac_sha256_hexwill otherwise silently degrade to a stable-but-WEAK key (raw bytes of the misconfigured secret) on every dispatch. - validate_
url - SSRF guard. Rejects URLs that would cause the daemon to connect to private-range addresses, link-local, loopback (except explicitly), or non-HTTPS remote hosts.
- validate_
url_ dns - SSRF guard with DNS resolution (#301 item 2). Resolves the host
via the stdlib resolver and rejects if ANY returned
SocketAddr’s IP is private / loopback / link-local. Guards against DNS-rebind attacks where an attacker-controlled hostname resolves to an internal IP at connect time.