alterlab-open-notebook
Runs and scripts Open Notebook, a self-hosted open-source alternative to Google NotebookLM with a full REST API, for AI-powered research and document analysis. Use when organizing research materials into notebooks, ingesting diverse content sources (PDFs, videos, audio, web pages, Office documents), generating AI-powered notes and summaries, creating multi-speaker podcasts from research, chatting with documents using context-aware AI, searching across materials with full-text and vector search, or running custom content transformations. Supports 18+ AI providers including OpenAI, Anthropic, Google, Ollama, LM Studio, Groq, and Mistral, with data kept on your own infrastructure through self-hosting. For a one-shot file-to-Markdown conversion (no notebook, chat, or search), use alterlab-markitdown instead. Part of the AlterLab Academic Skills suite.
How do I install this agent skill?
npx skills add https://github.com/alterlab-ieu/alterlab-academic-skills --skill alterlab-open-notebookIs this agent skill safe to install?
- Gen Agent Trust Hubpass
The skill provides a secure interface for 'Open Notebook', a self-hosted academic research platform. It enables researchers to manage materials, generate AI insights, and create podcasts via a local API. Security analysis confirms that external downloads originate from legitimate project sources on GitHub, and sensitive configuration fields use standard secure management practices. The skill includes a standard vulnerability surface for indirect prompt injection common to research tools processing external documents, which is documented and handled via the system's retrieval-augmented generation architecture.
- Socketwarn
1 alert: gptSecurity
- Snykwarn
Risk: MEDIUM · 1 issue
What does this agent skill do?
Open Notebook
Overview
Open Notebook is an open-source (MIT), self-hosted alternative to Google's NotebookLM that lets researchers organize materials, generate AI-powered insights, create podcasts, and hold context-aware conversations with their documents. It exposes a full REST API, works with 18+ AI providers through the Esperanto library, and runs entirely on your own infrastructure. This skill targets the 1.x line (v1.14 as of 2026-09; Docker image lfnovo/open_notebook:v1-latest).
Key advantages over NotebookLM:
- Full REST API for programmatic access and automation
- Choice of 18+ AI providers (not locked to Google models), including fully local Ollama / LM Studio
- Multi-speaker podcast generation with 1-4 customizable speakers
- Storage stays on your own machine or server
- Open source and extensible (MIT license)
Data privacy: self-hosting keeps notebooks and files local, but source text is still sent to whichever cloud model provider you configure. For sensitive or IRB-restricted material, use a local provider (Ollama, LM Studio) or one covered by your data agreements.
Repository: https://github.com/lfnovo/open-notebook
When to Use This Skill
Use this skill when the user wants to:
- Stand up a private, self-hosted NotebookLM-style research workspace
- Ingest PDFs, URLs, audio/video, or text into notebooks and chat with them using source context
- Search a personal research corpus with full-text or vector search
- Generate multi-speaker podcasts or custom transformations (summaries, method extraction) from sources
- Automate any of the above through the REST API
Does NOT Trigger
| Scenario | Use Instead |
|---|---|
| One-shot conversion of a file to Markdown (no notebook, chat, or search) | alterlab-markitdown |
| Located, cross-section Q&A inside a single PDF | alterlab-pdf-explore |
| A one-row-per-paper evidence table across many PDFs | alterlab-pdf-extract |
| Managing references, DOIs, or BibTeX in a Zotero library | alterlab-pyzotero |
| Systematic searching and screening of new literature | alterlab-literature-review |
Quick Start
Prerequisites
- Docker with Docker Compose v2 (OrbStack or Docker Desktop both work;
docker composemust be available) - API key for at least one AI provider (or local Ollama / LM Studio for free local inference)
Installation
# 1. Download the official compose file (SurrealDB v2 + lfnovo/open_notebook:v1-latest)
curl -o docker-compose.yml https://raw.githubusercontent.com/lfnovo/open-notebook/main/docker-compose.yml
# 2. Set the encryption key: the compose file hard-codes
# OPEN_NOTEBOOK_ENCRYPTION_KEY=change-me-to-a-secret-string, so edit that line
# (exporting a shell variable has no effect on this file)
sed -i.bak "s/change-me-to-a-secret-string/$(openssl rand -hex 32)/" docker-compose.yml
# 3. Launch the services
docker compose up -d
Access the application (allow 15-20 seconds for startup):
- Frontend UI: http://localhost:8502
- REST API: http://localhost:5055
- API Documentation: http://localhost:5055/docs (the authoritative schema)
SurrealDB defaults to root:root and is bound to 127.0.0.1 only. Before exposing the instance beyond your machine, set SURREAL_USER / SURREAL_PASSWORD in a .env file next to the compose file, set OPEN_NOTEBOOK_PASSWORD, and put the UI/API behind HTTPS.
Configure AI Provider
In the UI, open Models, choose a provider, click + Add Configuration, paste the key, Test the connection, Sync Models, then use Auto-Assign Defaults under Default Model Assignments.
Or configure via the REST API:
import os
import requests
BASE_URL = "http://localhost:5055/api"
# Only needed when OPEN_NOTEBOOK_PASSWORD is set on the server
HEADERS = {"Authorization": f"Bearer {os.environ['OPEN_NOTEBOOK_PASSWORD']}"} if os.environ.get("OPEN_NOTEBOOK_PASSWORD") else {}
# Add a credential for an AI provider (stored encrypted with OPEN_NOTEBOOK_ENCRYPTION_KEY)
credential = requests.post(f"{BASE_URL}/credentials", headers=HEADERS, json={
"provider": "openai",
"name": "My OpenAI Key",
"api_key": os.environ["OPENAI_API_KEY"],
}).json()
# Discover the provider's models: {"credential_id", "provider", "discovered": [{name, provider, model_type}, ...]}
discovered = requests.post(
f"{BASE_URL}/credentials/{credential['id']}/discover", headers=HEADERS
).json()["discovered"]
# Register them (model_type: language, embedding, text_to_speech, speech_to_text)
requests.post(
f"{BASE_URL}/credentials/{credential['id']}/register-models",
headers=HEADERS,
json={"models": [
{"name": m["name"], "provider": m["provider"], "model_type": m["model_type"]}
for m in discovered if m.get("model_type")
]},
).raise_for_status()
# Fill empty default slots (chat, transformation, large-context, embedding, TTS, STT, tools)
requests.post(f"{BASE_URL}/models/auto-assign", headers=HEADERS)
Core Features
The snippets below reuse BASE_URL and HEADERS from above.
Notebooks
Organize research into separate notebooks, each containing sources, notes, and chat sessions.
notebook = requests.post(f"{BASE_URL}/notebooks", headers=HEADERS, json={
"name": "Cancer Genomics Research",
"description": "Literature review on tumor mutational burden",
}).json()
notebook_id = notebook["id"]
Sources
Ingest PDFs, videos, audio, web pages, Office documents, or raw text. POST /api/sources takes multipart form fields; type is required (link, upload, or text), and embed="true" makes the source available to vector search.
# Add a web URL source (processed in the background)
source = requests.post(f"{BASE_URL}/sources", headers=HEADERS, data={
"type": "link",
"url": "https://arxiv.org/abs/2301.00001",
"notebook_id": notebook_id,
"embed": "true",
"async_processing": "true",
}).json()
# Upload a PDF file
with open("paper.pdf", "rb") as f:
upload = requests.post(
f"{BASE_URL}/sources",
headers=HEADERS,
data={"type": "upload", "notebook_id": notebook_id, "embed": "true"},
files={"file": ("paper.pdf", f, "application/pdf")},
).json()
# Poll background processing: status is queued / running / completed / failed
status = requests.get(f"{BASE_URL}/sources/{source['id']}/status", headers=HEADERS).json()
Notes
Create and manage notes (human or AI-generated) associated with notebooks.
requests.post(f"{BASE_URL}/notes", headers=HEADERS, json={
"title": "Key Findings",
"content": "TMB correlates with immunotherapy response in NSCLC...",
"note_type": "human",
"notebook_id": notebook_id,
})
Context-Aware Chat
Chat answers are grounded in the context you pass. Build it with /api/chat/context (an empty context_config includes every source and note in the notebook as short context; per-item values "full content", "insights", or "not in context" refine it), then send it with the message.
session = requests.post(f"{BASE_URL}/chat/sessions", headers=HEADERS, json={
"notebook_id": notebook_id,
"title": "TMB Discussion",
}).json()
ctx = requests.post(f"{BASE_URL}/chat/context", headers=HEADERS, json={
"notebook_id": notebook_id,
"context_config": {}, # or {"sources": {source_id: "full content"}, "notes": {...}}
}).json()
reply = requests.post(f"{BASE_URL}/chat/execute", headers=HEADERS, json={
"session_id": session["id"],
"message": "What are the key biomarkers for immunotherapy response?",
"context": ctx["context"],
}).json()
print(reply["messages"][-1]["content"]) # messages: [{id, type: human|ai, content}, ...]
Search
Search across all materials using full-text ("text", default) or semantic ("vector", needs an embedding model) search, optionally scoped with notebook_id / notebook_ids.
results = requests.post(f"{BASE_URL}/search", headers=HEADERS, json={
"query": "tumor mutational burden immunotherapy",
"type": "vector",
"limit": 10,
"notebook_id": notebook_id,
}).json() # {"results": [...], "total_count": n, "search_type": "vector"}
# Ask a question: the three model IDs are required (see GET /api/models or /api/models/defaults)
defaults = requests.get(f"{BASE_URL}/models/defaults", headers=HEADERS).json()
chat_model = defaults["default_chat_model"]
answer = requests.post(f"{BASE_URL}/search/ask/simple", headers=HEADERS, json={
"question": "How does TMB predict checkpoint inhibitor response?",
"strategy_model": chat_model,
"answer_model": chat_model,
"final_answer_model": chat_model,
"notebook_id": notebook_id,
}).json()["answer"]
POST /api/search/ask takes the same body and streams Server-Sent Events.
Podcast Generation
Generate multi-speaker podcasts (1-4 speakers) from a notebook. Profiles are referenced by name: an episode profile (format, models, length) and a speaker profile (voices and personas).
episode_profile = requests.get(f"{BASE_URL}/episode-profiles", headers=HEADERS).json()[0]
job = requests.post(f"{BASE_URL}/podcasts/generate", headers=HEADERS, json={
"episode_profile": episode_profile["name"],
"speaker_profile": episode_profile["speaker_config_name"], # or any name from /api/speaker-profiles
"episode_name": "TMB and immunotherapy",
"notebook_id": notebook_id,
}).json()
# Poll until the job reports "completed" (or "failed")
status = requests.get(f"{BASE_URL}/podcasts/jobs/{job['job_id']}", headers=HEADERS).json()["status"]
# Then locate the episode and download its audio
episodes = requests.get(f"{BASE_URL}/podcasts/episodes", headers=HEADERS).json()
episode = next(e for e in episodes if e["name"] == "TMB and immunotherapy")
audio = requests.get(f"{BASE_URL}/podcasts/episodes/{episode['id']}/audio", headers=HEADERS)
Content Transformations
Apply custom AI-powered transformations to content for summarization, extraction, and analysis.
transform = requests.post(f"{BASE_URL}/transformations", headers=HEADERS, json={
"name": "extract_methods",
"title": "Extract Methods",
"description": "Extract methodology details from papers",
"prompt": "Extract and summarize the methodology section...",
"apply_default": False,
}).json()
result = requests.post(f"{BASE_URL}/transformations/execute", headers=HEADERS, json={
"transformation_id": transform["id"],
"input_text": "...",
# "model_id": "model:...", # optional; defaults to the transformation model
}).json()["output"]
Supported AI Providers
Open Notebook supports 18+ AI providers through the Esperanto library. A representative subset:
| Provider | LLM | Embedding | Speech-to-Text | Text-to-Speech |
|---|---|---|---|---|
| OpenAI | Yes | Yes | Yes | Yes |
| Anthropic | Yes | No | No | No |
| Google GenAI | Yes | Yes | Yes | Yes |
| Vertex AI | Yes | Yes | No | Yes |
| Ollama | Yes | Yes | No | No |
| Groq | Yes | No | Yes | No |
| Mistral | Yes | Yes | Yes | Yes |
| Azure OpenAI | Yes | Yes | Yes | Yes |
| DeepSeek | Yes | No | No | No |
| xAI | Yes | No | No | Yes |
| OpenRouter | Yes | Yes | Yes | Yes |
| ElevenLabs | No | No | Yes | Yes |
| Voyage | No | Yes | No | No |
| OpenAI-compatible (LM Studio, vLLM, ...) | Yes | Yes | Yes | Yes |
For the full, current matrix (including oMLX, Perplexity, Deepgram, Cohere, DashScope, MiniMax, Novita), see the upstream README.
Environment Variables
Key configuration variables for Docker deployment:
| Variable | Description | Default |
|---|---|---|
OPEN_NOTEBOOK_ENCRYPTION_KEY | Required. Encrypts stored provider credentials; keep it stable (changing or losing it makes saved credentials unreadable) | None |
OPEN_NOTEBOOK_PASSWORD | Optional password; when set, API calls need Authorization: Bearer <password> | None |
SURREAL_URL | SurrealDB connection URL | ws://surrealdb:8000/rpc |
SURREAL_USER / SURREAL_PASSWORD | SurrealDB credentials (override via .env before exposing the instance) | root / root |
SURREAL_NAMESPACE | Database namespace | open_notebook |
SURREAL_DATABASE | Database name | open_notebook |
OPEN_NOTEBOOK_WORKER_MAX_TASKS | Concurrent background jobs; set 1 for a single local GPU | 5 |
API Reference
The REST API is available at http://localhost:5055/api, with interactive documentation at /docs.
Core endpoint groups:
/api/notebooks- Notebook CRUD, delete preview, and source association/api/sources- Source ingestion, status, retry, insights, download/api/notes- Note management/api/chat/sessions,/api/chat/context,/api/chat/execute- Chat sessions, context building, messages/api/search,/api/search/ask,/api/search/ask/simple- Full-text/vector search and question answering/api/podcasts,/api/episode-profiles,/api/speaker-profiles- Podcast generation and profiles/api/transformations- Content transformation pipelines/api/models- Model registration, defaults, sync, auto-assign/api/credentials- Provider credential management, discovery, registration
For request/response details see references/api_reference.md; runnable examples live in scripts/ and references/examples.md.
Architecture
Open Notebook uses a modern stack:
- Backend: Python with FastAPI
- Database: SurrealDB (document + relational)
- AI Integration: LangChain / LangGraph with the Esperanto multi-provider library
- Frontend: Next.js with React
- Deployment: Docker Compose with persistent volumes (
./notebook_data,./surreal_data)
Important Notes
- Open Notebook is deployed with Docker (a from-source setup exists for development)
- At least one AI provider must be configured for AI features; vector search and Ask also need an embedding model
- For free local inference without API costs, use Ollama or LM Studio
- The
OPEN_NOTEBOOK_ENCRYPTION_KEYmust be set before first launch and kept consistent across restarts - Data lives in the bind-mounted
notebook_data/andsurreal_data/folders; back them up - MCP clients (Claude Desktop, VS Code) can reach a running instance through the community
open-notebook-mcppackage that the upstream docs reference (uvx open-notebook-mcpwithOPEN_NOTEBOOK_URL/OPEN_NOTEBOOK_PASSWORD)
Part of the AlterLab Academic Skills suite.
How can the creator link this skill?
Add the canonical catalog link to the repository README so users can inspect current installs and available audits. The publishing guide covers the complete discovery path.
<a href="https://skillzs.dev/skills/alterlab-ieu/alterlab-academic-skills/alterlab-open-notebook">View alterlab-open-notebook on skillZs</a>