---
title: "ZeroDB Functions"
description: "Event-triggered serverless hooks for ZeroDB — automate workflows when your data changes"
canonical: "https://docs.ainative.studio/docs/zerodb/functions"
last-updated: "2026-10-22T17:48:46.689Z"
---

# ZeroDB Functions

Source: https://docs.ainative.studio/docs/zerodb/functions

> Event-triggered serverless hooks for ZeroDB — automate workflows when your data changes

## 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.

```mermaid
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:

```json
{
  "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).

```bash
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.

```bash
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.

```bash
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

```bash
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`):

```json
{
  "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

```bash
## 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

```bash
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

```bash
## 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

```bash
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:

```bash
## 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

```python
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

```python
## 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:

```sql
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:

1. **Write completes** — the database INSERT/UPSERT commits successfully
2. **Emitter fires** — `emit_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 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 `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:

| 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 |

Functions run in a sandboxed execution environment with the resource limits shown above.

---

## 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
