Skip to main content

ZeroDB Functions

ZeroDB Functions lets you run code automatically when data changes in your database. Store a vector, upload a file, or write a memory — and your hooks fire.

How It Works

Your app writes data → ZeroDB emits an event → Hook executor finds matching hooks → Handler runs

Every ZeroDB write operation emits a fire-and-forget event into an in-memory event bus. The hook executor polls events from two channels (zerodb and zeromemory), looks up matching hooks in the zerodb_hooks table, and dispatches to the registered handler. Hooks are non-blocking — a failed hook never blocks the original write.

sequenceDiagram
participant App as Your App
participant API as ZeroDB API
participant DB as PostgreSQL
participant EE as Event Emitter
participant HE as Hook Executor
participant H as Handler (auto_embed, etc.)

App->>API: POST /remember, /vectors, /files, etc.
API->>DB: INSERT / UPSERT
API->>EE: emit_*(fire-and-forget)
EE->>HE: Publish to event bus channel
HE->>DB: Query zerodb_hooks for matching event_type
HE->>H: Call handler(event, hook_config)
H-->>DB: Side-effect (embed, summarize, graph)

Event Types

Every ZeroDB write path emits a typed event. These are the events you can hook into:

EventChannelEmitted When
zerodb.vector.storedzerodbA single vector is upserted
zerodb.vector.batch_storedzerodbA batch of vectors completes
zerodb.memory.storedzerodbA memory record is stored via ZeroDB API
zerodb.file.uploadedzerodbA file is uploaded to ZeroDB storage
zerodb.table.row_insertedzerodbA row is inserted into a NoSQL table
zerodb.event.publishedzerodbAn event is published to the event stream
zeromemory.rememberedzeromemoryA memory is stored via the ZeroMemory /remember endpoint

Event Payload Structure

Every event follows this structure:

{
"id": "evt_a1b2c3d4",
"event": "zerodb.vector.stored",
"channel": "zerodb",
"data": {
"vector_id": "vec_123",
"project_id": "proj_456",
"namespace": "default",
"source": "api",
"user_id": "user_789"
},
"timestamp": "2026-06-10T08:00:00Z"
}

Event-Specific Data Fields

Each event type includes different fields in its data payload:

EventData Fields
zerodb.vector.storedvector_id, project_id, namespace, source, user_id
zerodb.vector.batch_storedproject_id, count, user_id
zerodb.memory.storedmemory_id, project_id, agent_id, user_id
zerodb.file.uploadedfile_id, project_id, file_name, content_type, size_bytes, user_id
zerodb.table.row_insertedtable_name, row_id, project_id, user_id
zerodb.event.publishedevent_id, project_id, topic, user_id
zeromemory.rememberedmemory_id, entity_id, memory_type, namespace, user_id

First-Party Hooks

Three built-in hooks ship with Phase 1. These run in-process as trusted Python functions — no sandbox overhead.

auto_embed

Automatically generate embeddings when a vector or memory is stored without one. Uses DigitalOcean GenAI inference with the bge-m3 model (1024-dimensional vectors).

curl -X POST https://api.ainative.studio/api/v1/hooks \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"event_type": "zerodb.vector.stored",
"hook_name": "auto_embed",
"hook_config": {
"model": "bge-m3",
"dimensions": 1024
}
}'

auto_summarize

Automatically generate a summary when a document or file is uploaded. Useful for building searchable indices over uploaded content.

curl -X POST https://api.ainative.studio/api/v1/hooks \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"event_type": "zerodb.file.uploaded",
"hook_name": "auto_summarize",
"hook_config": {
"max_length": 500
}
}'

auto_graph

Automatically extract entities and relationships from stored memories and insert them into the Context Graph. Turns unstructured memory into a queryable knowledge graph.

curl -X POST https://api.ainative.studio/api/v1/hooks \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"event_type": "zeromemory.remembered",
"hook_name": "auto_graph",
"hook_config": {
"extract_entities": true,
"max_relations": 10
}
}'

Hook Management API

All endpoints require authentication via Bearer token or API key.

Base path: https://api.ainative.studio/api/v1/hooks

Create a Hook

curl -X POST https://api.ainative.studio/api/v1/hooks \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"event_type": "zerodb.vector.stored",
"hook_name": "auto_embed",
"hook_config": {"model": "bge-m3"}
}'

Request Body:

FieldTypeRequiredDescription
event_typestringYesEvent type to trigger on (see table above)
hook_namestringYesHandler name: auto_embed, auto_summarize, or auto_graph
project_idUUIDNoScope to a specific project. null = all projects
hook_configobjectNoHandler-specific configuration (defaults to {})

Response (201 Created):

{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"user_id": "u1234-5678-90ab-cdef",
"project_id": null,
"event_type": "zerodb.vector.stored",
"hook_type": "first_party",
"hook_name": "auto_embed",
"hook_config": {"model": "bge-m3"},
"is_active": true,
"created_at": "2026-06-10T08:00:00Z",
"updated_at": "2026-06-10T08:00:00Z"
}

Error responses:

StatusCondition
409 ConflictHook already exists for this user_id + project_id + event_type + hook_name combination
422 Unprocessable EntityInvalid hook_name (must be one of the first-party hooks) or invalid hook_type

List Hooks

# All active hooks
curl "https://api.ainative.studio/api/v1/hooks" \
-H "Authorization: Bearer $TOKEN"

# Filter by event type
curl "https://api.ainative.studio/api/v1/hooks?event_type=zerodb.vector.stored" \
-H "Authorization: Bearer $TOKEN"

# Filter by project
curl "https://api.ainative.studio/api/v1/hooks?project_id=YOUR_PROJECT_UUID" \
-H "Authorization: Bearer $TOKEN"

# Include inactive hooks
curl "https://api.ainative.studio/api/v1/hooks?active_only=false" \
-H "Authorization: Bearer $TOKEN"

Query Parameters:

ParameterTypeDefaultDescription
event_typestring-Filter by event type
project_idUUID-Filter by project
active_onlybooltrueOnly return active hooks

Get a Hook

curl "https://api.ainative.studio/api/v1/hooks/{hook_id}" \
-H "Authorization: Bearer $TOKEN"

Returns 404 if the hook does not exist or belongs to a different user.

Update a Hook

# Change configuration
curl -X PATCH "https://api.ainative.studio/api/v1/hooks/{hook_id}" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"hook_config": {"model": "text-embedding-3-small", "dimensions": 1536}}'

# Disable a hook (stop it from firing)
curl -X PATCH "https://api.ainative.studio/api/v1/hooks/{hook_id}" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"is_active": false}'

# Re-enable a hook
curl -X PATCH "https://api.ainative.studio/api/v1/hooks/{hook_id}" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"is_active": true}'

Request Body (all fields optional):

FieldTypeDescription
hook_configobjectNew handler configuration
is_activeboolEnable or disable the hook

Delete a Hook

curl -X DELETE "https://api.ainative.studio/api/v1/hooks/{hook_id}" \
-H "Authorization: Bearer $TOKEN"

Returns 204 No Content on success, 404 if not found.


Project Scoping

Hooks can target a specific project or apply globally to all your projects:

# Scoped to a single project — only fires for events in that project
curl -X POST https://api.ainative.studio/api/v1/hooks \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"event_type": "zerodb.vector.stored",
"hook_name": "auto_embed",
"project_id": "your-project-uuid"
}'

# Global — fires for events in ALL your projects
curl -X POST https://api.ainative.studio/api/v1/hooks \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"event_type": "zerodb.vector.stored",
"hook_name": "auto_embed"
}'

When an event fires, the executor matches hooks where project_id equals the event's project OR project_id IS NULL (global hooks).


Python Examples

Register hooks and store data

import requests

BASE = "https://api.ainative.studio/api/v1"
HEADERS = {"Authorization": "Bearer YOUR_API_KEY"}

# 1. Register an auto_graph hook on zeromemory.remembered
resp = requests.post(f"{BASE}/hooks", headers=HEADERS, json={
"event_type": "zeromemory.remembered",
"hook_name": "auto_graph",
"hook_config": {"extract_entities": True, "max_relations": 10}
})
hook = resp.json()
print(f"Hook created: {hook['id']}")

# 2. Store a memory — auto_graph fires automatically
resp = requests.post(
f"{BASE}/public/memory/v2/remember",
headers=HEADERS,
json={
"content": "Alice met Bob at the AI conference in Berlin",
"entity_id": "alice",
"memory_type": "episodic",
}
)
print(f"Memory stored: {resp.json()}")

# 3. Check the graph — entities were auto-extracted
resp = requests.get(
f"{BASE}/public/memory/v2/graph/neighbors/alice",
headers=HEADERS
)
print(f"Graph neighbors: {resp.json()}")

List and manage hooks

# List all active hooks
hooks = requests.get(f"{BASE}/hooks", headers=HEADERS).json()
for h in hooks:
print(f" {h['hook_name']} on {h['event_type']} (active={h['is_active']})")

# Disable a hook
requests.patch(
f"{BASE}/hooks/{hook['id']}",
headers=HEADERS,
json={"is_active": False}
)

# Delete a hook
requests.delete(f"{BASE}/hooks/{hook['id']}", headers=HEADERS)

Database Schema

Hooks are stored in the zerodb_hooks table:

CREATE TABLE zerodb_hooks (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL,
project_id UUID,
event_type VARCHAR(100) NOT NULL,
hook_type VARCHAR(50) NOT NULL DEFAULT 'first_party',
hook_name VARCHAR(100) NOT NULL,
hook_config JSONB NOT NULL DEFAULT '{}',
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT uix_zerodb_hooks_user_event_name
UNIQUE (user_id, project_id, event_type, hook_name)
);

Indexes:

IndexColumn(s)Notes
Primary keyidUUID
idx_zerodb_hooks_user_iduser_idFast lookup by owner
idx_zerodb_hooks_event_typeevent_typeFast event dispatch
idx_zerodb_hooks_activeis_activePartial index (WHERE is_active = TRUE)

Unique constraint: One hook per (user_id, project_id, event_type, hook_name) combination. You cannot register the same hook twice for the same event in the same project.


Architecture Details

Event Emission

Events are emitted using fire-and-forget semantics. The emitter catches all exceptions so a failed event never blocks a write operation:

  1. Write completes — the database INSERT/UPSERT commits successfully
  2. Emitter firesemit_vector_stored(), emit_memory_stored(), etc.
  3. Event bus publishes — event goes to in-memory asyncio.Queue on the zerodb or zeromemory channel
  4. Hook executor polls — background asyncio.Task polls both channels every 50ms

Events are emitted from these write paths:

Write PathEmitter FunctionSource File
Vector upsertemit_vector_stored()zerodb/services/database_service.py
Batch vector upsertemit_vector_batch_stored()zerodb/services/database_service.py
Memory storeemit_memory_stored()zerodb/services/database_service.py
File uploademit_file_uploaded()zerodb/services/database_service.py
Table row insertemit_table_row_inserted()zerodb/services/database_service.py
Event publishemit_zerodb_event_published()zerodb/services/database_service.py
ZeroMemory rememberemit_zeromemory_remembered()services/memory/zeromemory.py

Hook Executor

The HookExecutor runs as a background asyncio.Task inside the FastAPI process:

  • Subscribes to zerodb and zeromemory channels on the in-memory EventBus
  • Polls both queues every 50ms
  • For each event, queries zerodb_hooks for matching event_type + user_id + project_id
  • Dispatches to the registered Python handler
  • Errors in handlers are logged but never propagated — one bad hook cannot take down the system

Sandbox Executor (Phase 2)

For user-defined functions (Phase 2), code executes in a sandboxed environment:

PropertyValue
RuntimesPython 3.11, Node.js 20
Execution P5018ms
Throughput112 RPS
Max concurrent20 per instance
IsolationFirecracker microVM (e2b.dev) with env variable shadowing
TimeoutConfigurable (1-120s)
Output limit50KB

See the Sandbox Code Execution Guide for details.


Roadmap

PhaseStatusDescription
Phase 1LiveFirst-party hooks (auto_embed, auto_summarize, auto_graph) with event emission on all ZeroDB write paths
Phase 2PlannedUser-defined functions deployed via API, triggered by events, cron, or HTTP. Executed in sandboxed Celery workers
Phase 3PlannedDeployed functions auto-register as MCP tools, callable from Claude and other MCP clients

Refs #3962, #3963, #3964