# How ai-memory Handles Agent Lifecycle Events and Observations: A Technical Deep Dive

> Explore how ai-memory manages agent lifecycle events and observations. Learn about its unique approach to capturing every execution step for complete session reconstruction.

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

---

**ai-memory captures every agent execution step—from session start to tool use—by converting hook scripts into sanitized observations stored in SQLite, enabling full session reconstruction via a single-writer architecture.**

The `ai-memory` project (akitaonrails/ai-memory) provides a Rust-based persistence layer for AI agents. It records every significant step of an agent's execution as immutable observations, allowing developers to reconstruct session timelines, debug failures, and enable auto-improvement loops. This article examines the complete lifecycle handling pipeline, from shell hook invocation to durable SQLite storage.

## Hook-Based Event Capture Architecture

The lifecycle event handling begins with shell scripts invoked by the agent runtime. These hooks convert execution milestones into structured HTTP payloads sent to the ai-memory daemon.

### Shell Script Entry Points

Located in `hooks/*/session-start.sh`, `hooks/*/pre-tool-use.sh`, `hooks/*/post-tool-use.sh`, and `hooks/*/session-end.sh`, these scripts construct JSON payloads describing events. For example, [`hooks/opencode/post-tool-use.sh`](https://github.com/akitaonrails/ai-memory/blob/main/hooks/opencode/post-tool-use.sh) captures tool execution details and POSTs them to the `/hook` endpoint.

```bash

# Simplified example based on hooks/opencode/post-tool-use.sh

curl -X POST http://127.0.0.1:49374/hook \
  -H "Content-Type: application/json" \
  -d '{
    "kind": "PostToolUse",
    "title": "run cargo test",
    "body": "Executed `cargo test --workspace`",
    "session_id": "'"$AI_MEMORY_SESSION_ID"'",
    "project_id": "'"$AI_MEMORY_PROJECT_ID"'",
    "workspace_id": "'"$AI_MEMORY_WORKSPACE_ID"'"
  }'

```

## Payload Processing Pipeline

Once the HTTP request reaches the `ai-memory-hooks` crate, the system deserializes, sanitizes, and routes the payload through a type-safe pipeline.

### Router and Deserialization

In [`crates/ai-memory-hooks/src/router.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-hooks/src/router.rs), the router deserializes incoming JSON into a `Payload` struct defined in [`crates/ai-memory-hooks/src/payload.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-hooks/src/payload.rs). The router then hands the structured data to the workstream orchestrator in [`crates/ai-memory-hooks/src/workstream.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-hooks/src/workstream.rs).

```rust
// crates/ai-memory-hooks/src/router.rs (excerpt)
async fn handle(payload: Payload) -> Result<()> {
    let sanitized = Sanitizer::sanitize(payload.into_new_observation())?;
    workstream::process(sanitized).await
}

```

### Privacy Sanitization

Before storage, the `Sanitizer` in [`crates/ai-memory-hooks/src/lib.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-hooks/src/lib.rs) strips private data from the payload. This enforces privacy boundaries by returning a `Sanitized<NewObservation>` type that guarantees sensitive data has been removed before reaching the database layer.

## Observation Storage and Indexing

The storage layer ensures durable, searchable records of all lifecycle events while maintaining strict write consistency.

### Lifecycle-Only Observations

Not all events produce user-visible pages. Events like `SessionStart`, `PreToolUse`, and `Stop` create *lifecycle-only* observations signaled by `LifecycleOnlyEndOutcome`. These rows remain indexed and searchable in SQLite, enabling the UI and API to reconstruct complete session timelines without cluttering the primary observation feed.

### The Single-Writer SQLite Actor

The store writer in [`crates/ai-memory-store/src/writer.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-store/src/writer.rs) handles all database operations through a single-writer actor pattern. This ensures transaction safety and prevents write conflicts. The `InsertObservation` command writes to the `observations` table, while `InsertObservationIngest` creates temporary records for the auto-improve pipeline.

```rust
// crates/ai-memory-store/src/writer.rs (excerpt)
WriteCmd::InsertObservation {
    obs: sanitized,
    reply: tx,
} => {
    let id = tx
        .send(self.db.insert_observation(obs)?)
        .await?;
}

```

## Querying and Retrieving Observations

The web API in [`crates/ai-memory-web/src/routes/api.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-web/src/routes/api.rs) exposes endpoints for retrieving observation streams. The `/api/v1/sessions/:id/observations` endpoint accepts filters for `ObservationKind` (such as `UserPrompt`, `PostToolUse`, or `Stop`) and ordering parameters (`asc` or `desc`).

```bash

# Fetch observations for a specific session

curl "http://127.0.0.1:49374/api/v1/sessions/42/observations?kind=UserPrompt&order=asc"

```

Example response:

```json
{
  "observations": [
    {
      "id": "obs-0001",
      "kind": "UserPrompt",
      "title": "Implement feature X",
      "body": "Detailed request content...",
      "timestamp": "2026-08-26T12:34:56Z"
    },
    {
      "id": "obs-0002",
      "kind": "PostToolUse",
      "title": "run cargo test",
      "body": "Test output summary...",
      "timestamp": "2026-08-26T12:35:10Z"
    }
  ],
  "order": "asc"
}

```

## Auto-Improvement and Consolidation

After session completion, the consolidation pipeline in [`crates/ai-memory-consolidate/src/lib.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-consolidate/src/lib.rs) processes the observation stream. It may invoke LLM providers to generate summary observations (such as additional `PostToolUse` entries) and inserts them back through the standard ingestion path. This maintains the single-writer invariant while enriching the session history with derived insights.

## Summary

- Hook scripts in `hooks/*/` capture agent lifecycle events as JSON payloads posted to `/hook`
- The `ai-memory-hooks` crate processes payloads through [`router.rs`](https://github.com/akitaonrails/ai-memory/blob/main/router.rs), [`payload.rs`](https://github.com/akitaonrails/ai-memory/blob/main/payload.rs), and [`workstream.rs`](https://github.com/akitaonrails/ai-memory/blob/main/workstream.rs)
- `Sanitizer` in [`lib.rs`](https://github.com/akitaonrails/ai-memory/blob/main/lib.rs) enforces privacy boundaries before storage
- `ai-memory-store` uses a single-writer SQLite actor in [`writer.rs`](https://github.com/akitaonrails/ai-memory/blob/main/writer.rs) for transaction-safe `InsertObservation` operations
- Lifecycle-only observations index events like `SessionStart` and `PreToolUse` without creating visible pages
- The web API in [`routes/api.rs`](https://github.com/akitaonrails/ai-memory/blob/main/routes/api.rs) enables filtered querying by `ObservationKind` and temporal ordering
- The `consolidate` pipeline auto-generates derived observations while preserving the single-writer workflow

## Frequently Asked Questions

### How does ai-memory ensure privacy when storing agent observations?

The system uses the `Sanitizer` struct in [`crates/ai-memory-hooks/src/lib.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-hooks/src/lib.rs) to strip sensitive data before storage. By converting raw payloads into `Sanitized<NewObservation>` types, the architecture enforces privacy boundaries at the ingestion layer, ensuring private information never reaches the SQLite store.

### What is the difference between regular observations and lifecycle-only observations?

Lifecycle-only observations (marked with `LifecycleOnlyEndOutcome`) capture internal agent events like `SessionStart`, `PreToolUse`, and `Stop` that don't produce user-visible pages. While they remain fully indexed and searchable for timeline reconstruction, they don't appear in the main observation feed, keeping the UI focused on substantive interactions.

### How can I query specific types of agent lifecycle events?

Use the web API endpoint `/api/v1/sessions/:id/observations` with the `kind` parameter to filter by `ObservationKind` enum values such as `UserPrompt`, `PostToolUse`, or `SessionStart`. Combine this with `order=asc` or `desc` to retrieve events in chronological or reverse-chronological sequence.

### What prevents write conflicts when multiple hooks fire simultaneously?

The `ai-memory-store` crate implements a single-writer SQLite actor pattern where all write operations serialize through [`writer.rs`](https://github.com/akitaonrails/ai-memory/blob/main/writer.rs). This design ensures that concurrent `InsertObservation` commands from multiple lifecycle events execute transaction-safe without race conditions, even during high-frequency tool use sequences.