# Understanding the Relationship Between Notebooks, Sources, and Notes in Open Notebook

> Explore the relationship between Notebooks, Sources, and Notes in Open Notebook. Learn how these domain models connect via SurrealDB graph edges for efficient data management.

- Repository: [Luis Novo/open-notebook](https://github.com/lfnovo/open-notebook)
- Tags: deep-dive
- Published: 2026-06-24

---

**In Open Notebook, Notebooks act as parent containers that link to Sources and Notes through explicit graph edges in SurrealDB, using `reference` edges for sources and `artifact` edges for notes.**

Open Notebook implements a graph-based domain model where three core entities—**Notebooks**, **Sources**, and **Notes**—are stored in SurrealDB and connected through directed relationships. This architecture enables efficient querying and contextual assembly for LLM workflows while maintaining strict data integrity through cascade deletes.

## The Graph-Based Architecture

The relationship between Notebooks, Sources, and Notes relies on SurrealDB's graph capabilities rather than traditional foreign keys. Notebooks serve as the parent node, with directed edges pointing to child Sources and Notes.

### Notebook-to-Source Relationships via Reference Edges

Sources connect to Notebooks through the `reference` edge table. In [`open_notebook/domain/notebook.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/domain/notebook.py), the `Notebook.get_sources()` method queries these edges to fetch all sources belonging to a specific notebook.

The edge direction follows `out → notebook`, `in → source`, meaning the notebook is the origin and the source is the target. When adding a source to a notebook, `Source.add_to_notebook(notebook_id)` creates this edge relationship (lines 72-76).

```python

# From open_notebook/domain/notebook.py, lines 29-41

notebook = await Notebook.get(notebook_id)
sources = await notebook.get_sources()  # Returns List[Source]

```

### Notebook-to-Note Relationships via Artifact Edges

Notes link to Notebooks through the `artifact` edge table. The `Notebook.get_notes()` method retrieves these connections (lines 47-63), while `Note.add_to_notebook(notebook_id)` establishes the link (lines 61-65).

```python

# Retrieve notes with optional content loading

notes = await notebook.get_notes(include_content=True)

```

## Working with Domain Relationships in Code

The [`open_notebook/domain/notebook.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/domain/notebook.py) file contains the implementation for managing these graph relationships. Here's how to create and query these connections:

```python

# Fetch a notebook instance

notebook = await Notebook.get(notebook_id)

# Get sources without loading full text (efficient)

sources = await notebook.get_sources()

# Get notes with full content

notes = await notebook.get_notes(include_content=True)

# Create and link a new source

new_source = Source(title="Research Article", full_text="Content...")
await new_source.save()
await new_source.add_to_notebook(notebook.id)

# Create and link a new note

new_note = Note(title="Summary", content="Key points...")
await new_note.save()
await new_note.add_to_notebook(notebook.id)

```

## Operational Benefits of the Graph Structure

This directed graph architecture provides specific advantages for the Open Notebook platform.

### Efficient Retrieval with Selective Loading

The relationship design supports lazy loading through flags like `include_full_text` and `include_content`. When calling `get_sources()` or `get_notes()`, the system queries only the edge tables and metadata unless explicitly requested to load the full content.

### Context Assembly for LLM Workflows

The `Notebook.get_context()` method (lines 70-78) leverages these relationships to build unified context strings. It iterates through connected sources and notes, calling their respective `get_context` methods to assemble a long-form text suitable for LLM processing.

### Cascade Delete Integrity

When deleting a notebook, the system handles cleanup through a specific sequence. The code first removes associated notes using `note.delete()`, optionally removes exclusive sources, then deletes the `reference` and `artifact` edges before finally removing the notebook record itself (lines 24-30).

## Summary

- **Notebooks** serve as parent containers in the graph, with outgoing edges to **Sources** and **Notes**.
- **Sources** connect via `reference` edges, accessed through `Notebook.get_sources()` and linked via `Source.add_to_notebook()`.
- **Notes** connect via `artifact` edges, accessed through `Notebook.get_notes()` and linked via `Note.add_to_notebook()`.
- The directed graph structure in SurrealDB enables efficient querying, context building for LLMs, and proper cascade deletion.
- All relationship logic is implemented in [`open_notebook/domain/notebook.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/domain/notebook.py) with support from [`open_notebook/domain/base.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/domain/base.py) for generic CRUD operations.

## Frequently Asked Questions

### How do Notebooks, Sources, and Notes relate in Open Notebook?

Notebooks act as parent containers that own Sources and Notes through explicit graph edges in SurrealDB. Notebooks point to Sources via `reference` edges and to Notes via `artifact` edges, creating a directed relationship where the notebook is always the `out` side and the child entities are the `in` side.

### What is the difference between reference and artifact edges?

The `reference` edge table links Notebooks to Sources, while the `artifact` edge table links Notebooks to Notes. This separation allows the system to distinguish between source material (references) and synthesized content (artifacts) when querying relationships.

### How does the code retrieve all Sources for a Notebook?

The `Notebook.get_sources()` method in [`open_notebook/domain/notebook.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/domain/notebook.py) (lines 29-41) queries the `reference` edge table to fetch all sources where the notebook is the `out` node. This method supports an `include_full_text` parameter to control whether the source content is loaded or just metadata.

### What happens to related Sources and Notes when a Notebook is deleted?

The deletion process follows a cascade pattern. First, the system deletes all associated Notes using `note.delete()`, then optionally removes exclusive Sources, followed by the removal of all `reference` and `artifact` edges, and finally deletes the Notebook record itself. This prevents orphaned records and maintains database integrity.