How ai-memory Captures Automatic Lifecycle Hooks from Agent CLIs

ai-memory records AI-coding sessions automatically through a fire-and-forget pipeline where agent CLIs emit JSON events via generated shell scripts, the server sanitizes payloads through a typed privacy boundary, and a single-writer SQLite actor persists observations atomically without blocking the CLI.

The ai-memory project eliminates manual instrumentation by implementing a zero-configuration lifecycle capture system for agent CLIs including Claude Code, Codex, and OpenCode. By intercepting events such as SessionStart, UserPrompt, and ToolUse through thin shell wrappers, the system builds a complete, searchable history of development sessions while enforcing strict data privacy guarantees.

The Three-Stage Capture Architecture

The automatic capture mechanism operates as a tightly-coupled pipeline across three distinct stages, ensuring that no user intervention is required after the initial hook installation.

Stage 1: CLI Emission via Generated Shell Hooks

Each supported agent CLI ships with a thin shell script that the CLI invokes on specific lifecycle milestones. When events like SessionStart, UserPrompt, ToolUse, or SessionEnd occur, the script reads a JSON payload from stdin and forwards it via a fire-and-forget HTTP request.

The scripts are generated by the install-hooks command implemented in [crates/ai-memory-cli/src/main.rs](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-cli/src/main.rs). For example, running ai-memory install-hooks --agent open-code produces a bash wrapper that posts to http://127.0.0.1:49374/hook:

#!/usr/bin/env bash

# Auto-generated by `ai-memory install-hooks --agent open-code --apply`.

# Sends a SessionStart event to the local ai-memory server.

AI_MEMORY_HOOK_URL=http://127.0.0.1:49374/hook
exec curl -sS -X POST "$AI_MEMORY_HOOK_URL" \
  -H "Content-Type: application/json" \
  -d "$(cat -)"   # reads the JSON payload from stdin

These requests use a short client-side timeout, ensuring the agent CLI never blocks on network I/O.

Stage 2: Server-Side Routing and Sanitization

The hook_router function defined in [crates/ai-memory-hooks/src/router.rs](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-hooks/src/router.rs) exposes the POST /hook and POST /hook/batch endpoints. Upon receiving a payload, the server:

  1. Parses the HookEnvelope (defined in [crates/ai-memory-hooks/src/payload.rs](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-hooks/src/payload.rs)) to identify the specific HookEvent variant.
  2. Passes the raw observation through the Sanitizer (ai_memory_core::Sanitizer), which enforces a typed privacy boundary by transforming the data into Sanitized<NewObservation>. This step guarantees that raw secrets are redacted before reaching storage.
  3. Guards against overload by checking DEFAULT_HOOK_INGEST_MAX_IN_FLIGHT; if exceeded, the router returns HTTP 429 (Too Many Requests).
pub async fn hook_router(
    State(state): State<AppState>,
    Json(envelopes): Json<Vec<HookEnvelope>>,
) -> impl IntoResponse {
    // 1️⃣  Guard against overload
    if state.ingest_gates.current_load() > DEFAULT_HOOK_INGEST_MAX_IN_FLIGHT {
        return (StatusCode::TOO_MANY_REQUESTS, "overloaded");
    }

    // 2️⃣  Process each envelope
    for env in envelopes {
        let (event, raw_body) = (env.event, env.body);
        let mut observation = NewObservation::from_event(event, raw_body);
        // 3️⃣  Sanitise – typed privacy boundary
        let sanitized = Sanitizer::new(&state.sanitize_cfg)
            .sanitize(&mut observation)
            .expect("sanitisation must succeed");
        // 4️⃣  Write to the single‑writer store
        state.writer_handle.submit(sanitized).await?;
    }

    // 5️⃣  Immediately respond (fire‑and‑forget)
    (StatusCode::ACCEPTED, "queued")
}

Stage 3: Atomic Persistence and Async Processing

Once sanitized, observations are handed to the WriterHandle, a single-writer SQLite actor that ensures all writes occur in a single database transaction. This satisfies the invariant that "indexes commit in the same transaction as the data," preventing inconsistent states.

For SessionEnd events, the system triggers wiki synthesis via synthesize_session_page in [crates/ai-memory-hooks/src/synth.rs](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-hooks/src/synth.rs). This function aggregates session observations into a concise markdown summary:

pub async fn synthesize_session_page(
    writer: &WriterHandle,
    session_id: SessionId,
) -> Result<(), StoreError> {
    // Gather all observations for the session, build a markdown summary,
    // then write the page atomically via the Wiki component.
    let page = WikiPage::from_observations(writer.fetch_session(session_id).await?);
    writer.wiki.write_page(page).await?;
    Ok(())
}

Heavy downstream work—such as indexing, handoff updates, and optional LLM-driven consolidation—executes asynchronously after the server returns HTTP 202 to the CLI, preserving the non-blocking contract.

Privacy-First Data Flow

The sanitization layer in [crates/ai-memory-core/src/sanitize.rs](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-core/src/sanitize.rs) guarantees that every observation crossing into storage is wrapped in the Sanitized<T> type. This architectural enforcement ensures that private credentials, environment variables, and secrets are redacted at the boundary, never persisting to disk. The Sanitizer configuration is shared across the hook_router and core storage layers, maintaining consistent privacy rules throughout the pipeline.

Key Implementation Files

The automatic lifecycle capture system spans several critical components in the ai-memory repository:

Summary

  • ai-memory captures agent CLI lifecycle events through auto-generated shell scripts that POST JSON payloads to a local server endpoint.
  • The fire-and-forget design uses short timeouts and immediate HTTP 202 responses, ensuring agent CLIs never block on telemetry collection.
  • A typed privacy boundary enforced by the Sanitizer type guarantees that all Sanitized<NewObservation> instances are redacted before persistence.
  • Atomic transactions in the single-writer SQLite actor ensure that data and indexes commit together, maintaining consistency.
  • Wiki synthesis triggers automatically on SessionEnd, generating markdown summaries without manual intervention.

Frequently Asked Questions

Which agent CLIs are compatible with ai-memory lifecycle hooks?

ai-memory supports any CLI that can invoke external scripts on lifecycle events, including Claude Code, Codex, and OpenCode. The install-hooks command generates agent-specific shell wrappers for each supported platform, ensuring the correct JSON schema and endpoint configuration for that particular tool.

How does ai-memory prevent the CLI from hanging during capture?

The system implements a fire-and-forget protocol where generated shell scripts use curl with short timeouts to POST data to http://127.0.0.1:49374/hook. The server immediately returns HTTP 202 after queueing the observation, while heavy processing occurs asynchronously. If the server is overloaded beyond DEFAULT_HOOK_INGEST_MAX_IN_FLIGHT, it returns HTTP 429, instructing the CLI to skip that event rather than blocking.

What ensures that sensitive data like API keys aren't stored in ai-memory?

Every observation passes through the Sanitizer implemented in ai-memory-core before reaching storage. This component enforces a typed privacy boundary by transforming raw input into Sanitized<NewObservation>, redacting secrets, environment variables, and personally identifiable information. Raw payloads never touch the database; only sanitized versions persist.

When does ai-memory generate the session wiki summary?

Wiki synthesis occurs automatically when the system receives a SessionEnd hook event. The synthesize_session_page function in [synth.rs](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-hooks/src/synth.rs) queries all observations for that session ID, compiles them into a markdown document via the WikiPage builder, and atomically writes the summary to storage using the same WriterHandle transaction used for the final observation.

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 →