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:
| Event | Channel | Emitted When |
|---|---|---|
zerodb.vector.stored | zerodb | A single vector is upserted |
zerodb.vector.batch_stored | zerodb | A batch of vectors completes |
zerodb.memory.stored | zerodb | A memory record is stored via ZeroDB API |
zerodb.file.uploaded | zerodb | A file is uploaded to ZeroDB storage |
zerodb.table.row_inserted | zerodb | A row is inserted into a NoSQL table |
zerodb.event.published | zerodb | An event is published to the event stream |
zeromemory.remembered | zeromemory | A 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:
| Event | Data Fields |
|---|---|
zerodb.vector.stored | vector_id, project_id, namespace, source, user_id |
zerodb.vector.batch_stored | project_id, count, user_id |
zerodb.memory.stored | memory_id, project_id, agent_id, user_id |
zerodb.file.uploaded | file_id, project_id, file_name, content_type, size_bytes, user_id |
zerodb.table.row_inserted | table_name, row_id, project_id, user_id |
zerodb.event.published | event_id, project_id, topic, user_id |
zeromemory.remembered | memory_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:
| Field | Type | Required | Description |
|---|---|---|---|
event_type | string | Yes | Event type to trigger on (see table above) |
hook_name | string | Yes | Handler name: auto_embed, auto_summarize, or auto_graph |
project_id | UUID | No | Scope to a specific project. null = all projects |
hook_config | object | No | Handler-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:
| Status | Condition |
|---|---|
409 Conflict | Hook already exists for this user_id + project_id + event_type + hook_name combination |
422 Unprocessable Entity | Invalid 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:
| Parameter | Type | Default | Description |
|---|---|---|---|
event_type | string | - | Filter by event type |
project_id | UUID | - | Filter by project |
active_only | bool | true | Only 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):
| Field | Type | Description |
|---|---|---|
hook_config | object | New handler configuration |
is_active | bool | Enable 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:
| Index | Column(s) | Notes |
|---|---|---|
| Primary key | id | UUID |
idx_zerodb_hooks_user_id | user_id | Fast lookup by owner |
idx_zerodb_hooks_event_type | event_type | Fast event dispatch |
idx_zerodb_hooks_active | is_active | Partial 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:
- Write completes — the database INSERT/UPSERT commits successfully
- Emitter fires —
emit_vector_stored(),emit_memory_stored(), etc. - Event bus publishes — event goes to in-memory
asyncio.Queueon thezerodborzeromemorychannel - Hook executor polls — background
asyncio.Taskpolls both channels every 50ms
Events are emitted from these write paths:
| Write Path | Emitter Function | Source File |
|---|---|---|
| Vector upsert | emit_vector_stored() | zerodb/services/database_service.py |
| Batch vector upsert | emit_vector_batch_stored() | zerodb/services/database_service.py |
| Memory store | emit_memory_stored() | zerodb/services/database_service.py |
| File upload | emit_file_uploaded() | zerodb/services/database_service.py |
| Table row insert | emit_table_row_inserted() | zerodb/services/database_service.py |
| Event publish | emit_zerodb_event_published() | zerodb/services/database_service.py |
| ZeroMemory remember | emit_zeromemory_remembered() | services/memory/zeromemory.py |
Hook Executor
The HookExecutor runs as a background asyncio.Task inside the FastAPI process:
- Subscribes to
zerodbandzeromemorychannels on the in-memoryEventBus - Polls both queues every 50ms
- For each event, queries
zerodb_hooksfor matchingevent_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:
| Property | Value |
|---|---|
| Runtimes | Python 3.11, Node.js 20 |
| Execution P50 | 18ms |
| Throughput | 112 RPS |
| Max concurrent | 20 per instance |
| Isolation | Firecracker microVM (e2b.dev) with env variable shadowing |
| Timeout | Configurable (1-120s) |
| Output limit | 50KB |
See the Sandbox Code Execution Guide for details.
Roadmap
| Phase | Status | Description |
|---|---|---|
| Phase 1 | Live | First-party hooks (auto_embed, auto_summarize, auto_graph) with event emission on all ZeroDB write paths |
| Phase 2 | Planned | User-defined functions deployed via API, triggered by events, cron, or HTTP. Executed in sandboxed Celery workers |
| Phase 3 | Planned | Deployed functions auto-register as MCP tools, callable from Claude and other MCP clients |
Refs #3962, #3963, #3964