Admission Webhook System and Content Filtering in ai-memory: A Complete Guide

The admission webhook system in ai-memory is a configurable chain of HTTP callbacks that intercepts every durable wiki mutation, enabling operators to filter content, enrich metadata, or reject writes before they reach the SQLite store or on-disk markdown.

The akitaonrails/ai-memory engine implements an admission webhook system to give operators fine-grained control over what enters the permanent wiki. By invoking a sequential chain of HTTP callbacks immediately before persistence, the system supports content filtering, front-matter enrichment, and policy enforcement without allowing direct access to the underlying storage layers.

How the Admission Chain Works

Trigger Points and Supported Operations

The webhook chain fires for specific durable mutation operations. According to the source code, the engine invokes the admission chain for the following op values: write_page, consolidate, delete, purge_project, purge_workspace, move_project, move_session, handoff_begin, handoff_accept, and handoff_cancel.

These trigger points are wired into the wiki write entry point in crates/ai-memory-wiki/src/wiki.rs. This ensures that every state-changing operation passes through the configured hooks before the engine commits data to disk or updates the SQLite index.

Execution Order and Blocking Behavior

Webhooks execute sequentially in the order they appear in the configuration. Each webhook receives the page payload as it may have been mutated by the previous hook, creating a deterministic pipeline.

The engine supports two execution modes:

  • Blocking (blocking = true, the default): The hook runs synchronously inside the write path. It may mutate the page payload or reject the operation entirely.
  • Non-blocking (blocking = false): The hook is fire-and-forget, dispatched after the durable write completes. It can only observe the final state and cannot mutate or abort.

Failure Policies and Timeout Limits

When a webhook cannot be reached or returns a non-2xx status, the engine follows the hook's configured failure_policy:

  • ignore — logs a warning and continues processing the chain (the default).
  • reject — aborts the write immediately and propagates an error to the caller.

Each webhook also respects a per-request timeout_ms (default 2000 ms). Because blocking hooks run sequentially, the worst-case write latency is the sum of all blocking hook timeouts. The system enforces hard limits: the chain length is capped at MAX_ADMISSION_WEBHOOKS = 16, and response bodies larger than MAX_RESPONSE_BYTES = 1 MiB are discarded.

Content Filtering Mechanism

Mutating Page Payloads Across the Chain

For write_page and consolidate operations, a webhook can return a partial page object to modify the incoming content. The engine applies these changes before passing the payload to the next hook or committing the final atomic write.

The expected response format is:

{
  "page": {
    "frontmatter": { },
    "body": "…"
  }
}

Missing fields are left unchanged. This is the core content filtering mechanism in akitaonrails/ai-memory. A webhook may strip secrets, enforce front-matter schemas, add contributor metadata, or rewrite the page body. The mutated payload flows directly into the next webhook or to the final persistence layer in crates/ai-memory-wiki/src/wiki.rs.

Content Filtering Example

The following TOML configures two webhooks: one mutating and one observational.

[[admission_webhooks]]
name = "contributors"
url  = "http://contributors.svc/enrich"
timeout_ms = 2000
failure_policy = "ignore"
events = ["write_page", "consolidate"]
blocking = true

[[admission_webhooks]]
name = "git-mirror"
url  = "http://git-mirror.svc/sync"
timeout_ms = 2000
failure_policy = "ignore"
events = ["write_page", "delete", "purge_project", "move_project"]
blocking = false

A mutating webhook can enrich front-matter before persistence. For example, returning this JSON:

{
  "page": {
    "frontmatter": {
      "contributors": [
        {
          "agent": "opencode",
          "user": "alice",
          "first_seen": "2024-01-01T00:00:00Z",
          "writes": 1
        }
      ]
    }
  }
}

The engine merges the contributors field into the page's front-matter, leaving all other fields intact.

You can trigger the chain through the CLI or MCP:

ai-memory write-page --workspace default --project notes \
  --path "ideas/new.md" --title "New Idea"

Loop Prevention and Safety Guarantees

Preventing Infinite Recursion

If a webhook writes back to the engine—for example, by calling /admin/write-page to update the same page—it must include the header:


X-Memory-Skip-Admission-Chain: <hook-name>

This instructs the engine to skip the originating hook on the re-entrant call, preventing infinite recursion through the admission chain. Without this safeguard, a mutating webhook that triggers additional writes could loop indefinitely.

Storage Isolation

The admission webhook system enforces strict isolation. Hooks can add or modify front-matter, rewrite the page body, mirror writes to external systems, or reject policy violations. However, they cannot directly read from or write to the SQLite store or access on-disk markdown outside the single-page payload they receive. This boundary ensures that content filtering happens through the well-defined admission contract rather than ad-hoc storage manipulation.

Source Code Implementation

Core Data Structures and Chain Runner

The authoritative definitions for the admission system live in crates/ai-memory-wiki/src/admission.rs. This file declares the AdmissionContext and ActorContext structs, which carry the page payload and operation metadata into the chain. The AdmissionChain::run method implements the sequential runner that applies timeouts, enforces MAX_ADMISSION_WEBHOOKS, and handles blocking versus non-blocking dispatch.

End-to-end tests validating the admission contract are located in crates/ai-memory-wiki/tests/admission.rs.

Integration Points

The CLI configuration schema for webhooks is parsed in crates/ai-memory-cli/src/config.rs, where the TOML arrays are deserialized into typed hook definitions. The HTTP server wiring that attaches the admission chain to incoming write requests is implemented in crates/ai-memory-cli/src/commands/serve.rs. For a human-readable specification of the protocol, see docs/admission-webhooks.md in the repository root.

Summary

  • The admission webhook system in akitaonrails/ai-memory intercepts every durable mutation through a sequential chain of HTTP callbacks invoked before persistence.
  • Content filtering works by returning partial page objects from blocking webhooks, allowing hooks to mutate front-matter or body content while leaving unspecified fields unchanged.
  • Hooks run in declared order, support blocking (synchronous, mutating) and non-blocking (asynchronous, observational) modes, and respect per-hook failure_policy and timeout_ms settings.
  • Hard limits cap the chain at MAX_ADMISSION_WEBHOOKS = 16 and responses at MAX_RESPONSE_BYTES = 1 MiB, with a default timeout of 2000 ms.
  • The X-Memory-Skip-Admission-Chain header prevents recursive loops when webhooks write back into the engine.

Frequently Asked Questions

What operations trigger the admission webhook chain?

The admission chain runs for write_page, consolidate, delete, purge_project, purge_workspace, move_project, move_session, handoff_begin, handoff_accept, and handoff_cancel. These trigger points cover every durable mutation path in the wiki engine.

How does a webhook filter or modify page content?

For write_page and consolidate operations, a blocking webhook returns a JSON object containing a partial page with optional frontmatter and body fields. The engine merges these fields into the current payload before the next hook or the final atomic write, enabling content filtering, secret stripping, and metadata enrichment.

What happens if an admission webhook fails or times out?

The engine applies the hook's configured failure_policy. If set to ignore (the default), the engine logs a warning and continues. If set to reject, the engine aborts the entire write and returns an error to the caller. Each hook also has a timeout_ms budget (default 2000 ms) that caps its execution time.

Can a webhook write back to the wiki without causing an infinite loop?

Yes, but the re-entrant request must include the X-Memory-Skip-Admission-Chain header set to the originating hook's name. This tells the engine to skip that specific hook for the recursive call, breaking the cycle and preventing infinite recursion through the admission chain.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →