How the ai-memory Hook Command Spools Events with Idempotency Keys
The ai-memory CLI generates a unique UUID-based ingest key for every lifecycle event and embeds it in the POST request URL, enabling the server to deduplicate observations via the ingest_keys table even during network retries.
The akitaonrails/ai-memory repository implements a fire-and-forget hook mechanism that captures agent lifecycle events and spools them to a remote server. To guarantee exactly-once ingestion despite transient network failures or timeouts, the native hook command implements client-side idempotency key generation coupled with server-side deduplication logic.
Generating Client-Side Idempotency Keys
The idempotency workflow begins in the CLI hook command, where each event receives a unique identifier before transmission.
UUID Construction in hook.rs
When the hook command executes, it generates a fresh ingest key using Rust's UUID library. This key serves as the idempotency token for the entire request lifecycle.
In crates/ai-memory-cli/src/commands/hook.rs, the implementation creates a simple, hyphen-free UUID:
// crates/ai-memory-cli/src/commands/hook.rs (lines 563-566)
let ingest_key = uuid::Uuid::new_v4().simple().to_string();
The .simple() method strips hyphens from the UUID, producing a 32-character alphanumeric string that functions as the canonical identifier for this specific event instance.
Constructing the Spool Request
Once generated, the ingest key is embedded directly into the request URL as a query parameter, ensuring it reaches the server for validation.
URL Assembly and Event Transmission
The CLI constructs the full endpoint URL by appending the ingest key alongside other context metadata such as event type, agent name, and session identifiers:
// crates/ai-memory-cli/src/commands/hook.rs
let ingest_key = uuid::Uuid::new_v4().simple().to_string();
let url = format!(
"{base}/hook?event={event}&agent={agent}{session_qs}{cwd_qs}{ingest_key}",
base = base_url,
event = event,
agent = agent,
session_qs = session_qs,
cwd_qs = cwd_qs,
ingest_key = ingest_key,
);
The actual network transmission occurs via the ai_memory_post_hook function defined in hooks/_lib.sh. This wrapper uses curl with a strict 500-millisecond timeout to ensure the hook operation never blocks the agent:
# hooks/_lib.sh (lines 70-81) – ai_memory_post_hook
if [ -n "${AI_MEMORY_AUTH_TOKEN:-}" ]; then
curl -s --max-time 0.5 -X POST "$1" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $AI_MEMORY_AUTH_TOKEN" \
--data-binary @-
else
curl -s --max-time 0.5 -X POST "$1" \
-H "Content-Type: application/json" \
--data-binary @-
fi
Server-Side Idempotency Validation
Upon receiving the POST request, the server extracts and validates the ingest key before persisting any observation data.
Validating the Ingest Key
In crates/ai-memory-hooks/src/payload.rs, the server parses the incoming query string and validates the key format using the valid_ingest_key function:
// crates/ai-memory-hooks/src/payload.rs (lines 55-62)
fn valid_ingest_key(key: &str) -> bool {
let len = key.len();
len >= 1 && len <= 64 && key.chars().all(|c| c.is_ascii_alphanumeric() || "_-".contains(c))
}
This validation ensures the key contains only safe ASCII characters and falls within acceptable length bounds before database operations proceed.
Database Deduplication Logic
The router in crates/ai-memory-hooks/src/router.rs (lines 103-110) extracts the validated key and passes it to the storage layer. The crates/ai-memory-store/src/writer.rs file handles the atomic insertion via insert_observation_keyed, which first checks for existing entries.
The underlying idempotency mechanism resides in crates/ai-memory-store/src/ops.rs:
// crates/ai-memory-store/src/ops.rs (lines 1116-1147)
tx.execute(
"INSERT INTO ingest_keys (project_id, key, seen_at, completed_at) VALUES (?1, ?2, ?3, NULL)",
params![project_id.as_bytes(), key, now],
)?;
The ingest_keys table enforces uniqueness at the database level. Before insertion, the system queries for existing records:
// crates/ai-memory-store/src/ops.rs
let completed: Option<i64> = tx.query_row(
"SELECT completed_at FROM ingest_keys WHERE project_id = ?1 AND key = ?2",
params![project_id.as_bytes(), key],
|row| row.get(0),
)?;
if completed.is_some() {
// already processed → skip
}
If the query returns a row, the observation is discarded as a duplicate, ensuring exactly-once processing semantics.
Retry Safety and Network Resilience
The idempotency architecture guarantees safety during network retries. If the curl request times out or fails, the CLI can retry using the same ingest_key value (cached in the splice state file). Because the server recognizes duplicate keys and ignores them, transient network hiccups cannot create duplicate observations.
This design pattern ensures that each logical lifecycle event results in at most one database entry, regardless of how many times the client attempts transmission.
Summary
- The ai-memory hook command generates a UUID-based ingest key for every event in
crates/ai-memory-cli/src/commands/hook.rs. - The key is embedded as a URL query parameter and transmitted via
curlwith a 0.5-second timeout defined inhooks/_lib.sh. - Server-side validation occurs in
crates/ai-memory-hooks/src/payload.rsusing thevalid_ingest_keyfunction. - The
ingest_keystable incrates/ai-memory-store/src/ops.rsprovides atomic deduplication, treating duplicate inserts as no-ops. - Retry operations reuse the same key, making the entire pipeline idempotent against network failures.
Frequently Asked Questions
What is an idempotency key in ai-memory?
An idempotency key (called an ingest key in the codebase) is a client-generated UUID that uniquely identifies a specific lifecycle event observation. According to the akitaonrails/ai-memory source code, this key ensures that retrying a failed network request does not result in duplicate database entries.
How does ai-memory prevent duplicate observations during network retries?
The CLI caches the generated ingest key in the splice state file, allowing retries to reuse the same identifier. When the server receives a request with a previously seen key, crates/ai-memory-store/src/ops.rs detects the existing entry in the ingest_keys table and skips the insertion, effectively discarding the duplicate.
What happens if the ingest key validation fails?
If the key fails the valid_ingest_key check in crates/ai-memory-hooks/src/payload.rs (lines 55-62), the server rejects the request early in the processing pipeline. The validation requires the key to be 1-64 characters containing only ASCII alphanumeric characters, underscores, or hyphens.
Where is the idempotency logic implemented in the ai-memory codebase?
The idempotency workflow spans multiple crates: crates/ai-memory-cli/src/commands/hook.rs generates the key, crates/ai-memory-hooks/src/payload.rs validates it, crates/ai-memory-hooks/src/router.rs routes it, and crates/ai-memory-store/src/ops.rs handles the database-level deduplication in the ingest_keys table.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →