# Understanding Lifecycle Hooks in ai-memory: How Agent Events Are Captured

> Explore ai-memory lifecycle hooks to capture agent events via HTTP endpoints. Learn about policy enforcement, rate limiting, and async storage in SQLite and wiki pages.

- Repository: [Fabio Akita/ai-memory](https://github.com/akitaonrails/ai-memory)
- Tags: deep-dive
- Published: 2026-09-06

---

**Lifecycle hooks in ai-memory are HTTP endpoints that capture every observable AI agent event through a validated pipeline of capture-policy enforcement, rate-limiting, and asynchronous storage into SQLite and wiki pages.**

The ai-memory project, developed by akitaonrails, implements a disciplined event-ingestion system through specialized lifecycle hooks. These hooks serve as the exclusive entry point for recording agent activities, transforming raw tool usage and session data into searchable, immutable observations. Understanding how these lifecycle hooks in ai-memory capture agent events reveals the architecture behind reliable AI session tracking and privacy-aware data retention.

## Core Lifecycle Hook Endpoints

Three HTTP endpoints in [`crates/ai-memory-hooks/src/router.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-hooks/src/router.rs) expose the hook interface:

**`POST /hook`** – Captures single events like tool use or session transitions. Returns **202 Accepted** immediately while processing occurs asynchronously.

**`POST /hook/batch`** – Ingests multiple spooled events in one request. Each item in the batch undergoes the same validation and rate-limiting checks as individual requests.

**`GET /handoff`** – Retrieves pending session handoffs for newly started agents. Returns markdown content if a handoff exists, or an empty response, atomically marking the handoff as accepted to ensure one-to-one transfer semantics.

## The Event-Capture Pipeline

When an agent emits an event, the system executes a rigorous nine-stage pipeline before persistence:

1. **Raw Request Parsing** – `HookEnvelope::from_query_and_body` constructs a typed envelope from query parameters (`?event=…&agent=…`) and the JSON payload, defined in [`crates/ai-memory-hooks/src/payload.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-hooks/src/payload.rs).

2. **Assistant Message Stripping** – The system removes any `_ai_memory_assistant` field through [`crates/ai-memory-hooks/src/assistant_capture.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-hooks/src/assistant_capture.rs) before storage processing begins.

3. **Capture Policy Enforcement** – `inspect_capture_envelope` evaluates the `_ai_memory_capture` marker against project-specific [`.ai-memory.toml`](https://github.com/akitaonrails/ai-memory/blob/main/.ai-memory.toml) policies. This step can drop events entirely or reduce them to metadata-only for privacy protection, particularly for sensitive file-tool arguments in [`crates/ai-memory-hooks/src/capture_policy.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-hooks/src/capture_policy.rs).

4. **Sub-agent Handling** – For projects with `drop_subagent` enabled, the router uses a bounded LRU set (`SubagentSessionSet`) to accept-but-drop tail events from sub-agent sessions, preventing pollution of parent session logs.

5. **Global Ingest Semaphore** – A `tokio::Semaphore` named `DEFAULT_HOOK_INGEST_MAX_IN_FLIGHT` caps concurrent ingest tasks. When saturated, the endpoint returns **429 Too Many Requests**.

6. **Per-Source Rate Limiting** – The `IngestRateLimiter` implements a token-bucket algorithm to limit events per logical source (user + session + project). Exceeding the limit triggers another **429** response.

7. **Ingest Gates** – `IngestGates` serialize overlapping retries for identical `(project_id, ingest_key)` pairs, eliminating race conditions between duplicate deliveries.

8. **Asynchronous Processing** – After clearing semaphores and rate limits, the hook spawns async tasks calling `process_envelope` (or `process_authorized` for batch items). These functions write observations to the store, update session pages, and optionally invoke LLM-driven consolidation. Success triggers `ingest_metrics.record_persisted`.

9. **Metrics and Telemetry** – The `HookState` maintains `Arc<IngestMetrics>` tracking accepted, dropped, rate-limited, and persisted counts, exposed through the `ai-memory status` CLI.

## Lifecycle Hook Semantics

The system captures distinct event categories through these hooks:

**Session Lifecycle Events**

- `SessionStart`, `SessionEnd`, `SubagentStart`, `SubagentStop`, and `UserPromptSubmit` mark session boundaries.
- `SessionEnd` triggers `synthesize_session_page` to generate wiki summaries and optionally invokes the LLM consolidator when `AI_MEMORY_CONSOLIDATE_ON_SESSION_END` is enabled.

**Tool Usage Tracking**

- `PreToolUse` and `PostToolUse` events capture tool invocations. The capture policy may reduce file-tool arguments to metadata-only, protecting sensitive path information while preserving operational context.

**Handoff Mechanism**

- New agents call `GET /handoff` to retrieve context from previous sessions. The atomic acceptance ensures exactly-once handoff semantics, preventing duplicate context injection.

## Practical Code Examples

Capture a single tool-use event:

```bash
curl -X POST "http://127.0.0.1:49374/hook?event=PreToolUse&agent=Claude" \
     -H "Content-Type: application/json" \
     -d '{
           "session_id":"s-123",
           "cwd":"/home/user/project",
           "tool_family":"file",
           "tool_name":"open",
           "path":"/home/user/project/main.rs",
           "_ai_memory_capture":"{...}"
         }'

```

*The server replies `202 Accepted`. Processing occurs asynchronously.*

Batch ingestion for spooled events:

```bash
curl -X POST "http://127.0.0.1:49374/hook/batch" \
     -H "Content-Type: application/json" \
     -d '[
           {"url":"http://127.0.0.1:49374/hook?event=PreToolUse&agent=Claude","body":{"session_id":"s-123","tool_family":"file","path":"/a.txt"}},
           {"url":"http://127.0.0.1:49374/hook?event=PostToolUse&agent=Claude","body":{"session_id":"s-123","tool_family":"file","path":"/a.txt","outcome":"success"}}
         ]'

```

*Returns a `HookBatchAck` JSON object indicating committed items.*

Retrieve a session handoff:

```bash
curl "http://127.0.0.1:49374/handoff?agent=Claude&session_id=s-123"

```

*Returns markdown content if available, empty otherwise.*

## Summary

- **Lifecycle hooks in ai-memory** provide the exclusive HTTP interface for agent event ingestion through `POST /hook`, `POST /hook/batch`, and `GET /handoff`.
- The **capture pipeline** enforces validation, privacy policies via [`.ai-memory.toml`](https://github.com/akitaonrails/ai-memory/blob/main/.ai-memory.toml), and multi-layered rate limiting before asynchronous storage.
- **Global and per-source limits** prevent resource exhaustion using semaphores and token-bucket algorithms, returning **429** when exceeded.
- **Session semantics** include atomic handoffs, sub-agent isolation via `SubagentSessionSet`, and automatic wiki page generation through `synthesize_session_page`.
- All events ultimately persist to SQLite through the single-writer architecture in [`crates/ai-memory-store/src/writer.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-store/src/writer.rs).

## Frequently Asked Questions

### What happens when the lifecycle hook rate limit is exceeded?

When the `IngestRateLimiter` token bucket depletes or the global `tokio::Semaphore` saturates, the endpoint immediately returns **429 Too Many Requests**. The agent should implement exponential backoff and retry logic, as the hook does not queue requests beyond the concurrent task limit defined in `DEFAULT_HOOK_INGEST_MAX_IN_FLIGHT`.

### How does ai-memory protect sensitive data in tool arguments?

The `inspect_capture_envelope` function in [`crates/ai-memory-hooks/src/capture_policy.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-hooks/src/capture_policy.rs) checks the `_ai_memory_capture` marker against project-specific policies. File-tool arguments can be configured to `Drop` entirely or persist as `MetadataOnly`, stripping actual content while preserving operation records. This protects sensitive paths and data while maintaining observability.

### Can lifecycle hooks handle high-throughput agent sessions?

Yes, through the batch endpoint `POST /hook/batch` and the `SubagentSessionSet` LRU cache. The batch endpoint allows agents to spool events during offline operation, while the bounded LRU set prevents sub-agent spam from overwhelming parent session logs. The `IngestGates` mechanism also deduplicates retries to reduce load on the SQLite writer.

### Where are captured events ultimately stored?

After processing through `process_envelope`, events persist to SQLite via [`crates/ai-memory-store/src/writer.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-store/src/writer.rs), which maintains a single-writer invariant ensuring atomic indexing within transactions. Additionally, session summaries generate markdown wiki pages for human-readable context retrieval and handoff operations through `synthesize_session_page`.