Skip to main content

Module subscriptions

Module subscriptions 

Source
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 to 127.0.0.0/8 or localhost hosts; everywhere else requires https://
  • RFC1918 / RFC4193 / link-local hosts are rejected unless allow_private_networks = true in 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§

ApprovalRequestedEventDetails
v0.7.0 K4 — approval_requested event details.
ConsolidatedEventDetails
memory_consolidated event — fires after db::consolidate commits. The outer memory_id carries the new consolidated memory’s id; source_ids is the array of memories that were merged (and deleted by the consolidate op).
DeleteEventDetails
memory_delete event — fires after the row is removed from memories. title and tier come from the pre-delete snapshot so subscribers can write meaningful audit entries without a roundtrip.
DlqEntry
One row of the subscription_dlq table. Created when a delivery exhausts the [200ms, 1s, 5s] retry ladder. K7’s inspector tool surfaces these rows; K6 only ships the writer.
LinkCreatedEventDetails
memory_link_created event — fires after db::create_link commits. The outer memory_id carries the source id (the link-author side); target_id is the destination of the directed link.
LinkInvalidatedEventDetails
memory_link_invalidated event (v0.7 J4 / G14) — fires after a successful memory_kg_invalidate writes valid_until on the (source_id, target_id, relation) link. The outer memory_id carries the source id (the link-author side); target_id, relation, and the freshly-written valid_until describe the supersession edge so consumers can replay the invalidation log without re-reading memory_links. previous_valid_until distinguishes the first supersession (None) from an idempotent retry (carries the prior stamp).
NewSubscription
Parameters for creating a subscription.
PromoteEventDetails
memory_promote event — fires after a tier or vertical promotion commits. to_namespace is Some for vertical (memory_promote with a to_namespace argument); for the default tier promotion it is None and tier is set to the new tier ("long").
Subscription
Public-facing subscription record (no secret plaintext).
SubscriptionEvent
One row of the subscription_events per-delivery audit log. K6 writes one row before each network send; K7’s memory_subscription_replay tool reads the rows back ordered by delivered_at for 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 whose correlation_id doesn’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_dlq depth. 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 cap record_dlq_with_conn refuses the insert with the typed dlq_overflow error, increments the ai_memory_subscription_dlq_overflow_total counter, and emits a tracing::warn!. Operators drain the queue via memory_subscription_dlq_list + the planned ... dlq drain admin 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_dlq for 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 v2 webhook_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_requested lifecycle event for a freshly-inserted pending_actions row.
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 (via dispatch_event_with_details) and the postgres path (via dispatch_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 *EventDetails struct serialised via serde_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_dlq rows. 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 existing memory_subscription_* family — see src/mcp/tools/subscribe.rs::handle_subscription_replay for 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 the reqwest::blocking TLS connector used by webhook delivery.
record_dlq
v0.7.0 K6 — append a subscription_dlq row 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 of record_dlq.
record_subscription_event
v0.7.0 K6 — append a subscription_events audit 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 with delivery_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 by delivered_at ascending (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. Returns Ok(()) when the value is None (no secret configured) OR is valid hex; returns Err(String) with a descriptive operator-facing message when the value is non-hex. Callers MUST surface the error before the daemon accepts traffic; the runtime hmac_sha256_hex will 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.