The Venus server exposes a WebSocket-based API for real-time notebook interaction, plus REST endpoints for querying state. This API allows building custom frontends, IDE plugins, or programmatic notebook control.
Architecture: Venus provides a built-in web frontend for ease of use, but the server API is designed for custom frontends. Advanced users can build their own UIs using the documented protocol below.
Endpoints:
ws://localhost:8080/ws- WebSocket for notebook operationsws://localhost:8080/lsp- WebSocket for LSP (rust-analyzer) integrationGET /health- Health checkGET /api/state- Current notebook stateGET /api/graph- Dependency graph
Protocol: JSON messages over WebSocket. All messages are tagged with a type field for discrimination.
The Venus server API executes arbitrary Rust code received over WebSocket with NO sandboxing.
For custom frontend developers:
- Deploy Venus server in isolated environment (container/VM)
- Never expose publicly without authentication and isolation
- Treat all notebook code as potentially malicious
- Provider responsibility: YOU must secure the deployment - Venus provides no security
Venus cells have full system access:
- Can read/write/delete any file
- Can make network requests
- Can spawn processes
- Can execute any system calls
This is equivalent to exposing cargo run over the internet. See SECURITY.md for the complete security policy.
const ws = new WebSocket("ws://localhost:8080/ws");
ws.onopen = () => {
console.log("Connected to Venus server");
// Server automatically sends initial NotebookState on connection
};
ws.onmessage = (event) => {
const message = JSON.parse(event.data);
console.log("Server message:", message);
};// Execute a cell
ws.send(
JSON.stringify({
type: "execute_cell",
cell_id: 1,
})
);
// Get current state
ws.send(
JSON.stringify({
type: "get_state",
})
);The server broadcasts state changes to all connected clients:
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
switch (msg.type) {
case "notebook_state":
updateNotebookUI(msg.cells, msg.execution_order);
break;
case "cell_completed":
displayCellOutput(msg.cell_id, msg.output);
break;
case "cell_error":
showError(msg.cell_id, msg.error);
break;
}
};Health check endpoint.
Response:
{
"status": "ok",
"version": "0.1.0"
}Get current notebook state.
Response: Returns a NotebookState message (see Server Messages).
Get dependency graph information.
Response:
{
"execution_order": [1, 2, 3, 4]
}Messages sent from client to server. All messages must include a type field.
GetState
{ "type": "get_state" }Request the complete notebook state.
GetGraph
{ "type": "get_graph" }Request the dependency graph.
ExecuteCell
{
"type": "execute_cell",
"cell_id": 1
}Execute a specific cell.
ExecuteAll
{ "type": "execute_all" }Execute all cells in dependency order.
ExecuteDirty
{ "type": "execute_dirty" }Execute only cells marked as dirty (needing re-execution).
Interrupt
{ "type": "interrupt" }Abort currently running execution.
InsertCell
{
"type": "insert_cell",
"after_cell_id": 2 // Optional: null for end
}Insert a new code cell.
DeleteCell
{
"type": "delete_cell",
"cell_id": 3
}Delete a cell.
DuplicateCell
{
"type": "duplicate_cell",
"cell_id": 1
}Duplicate an existing cell.
MoveCell
{
"type": "move_cell",
"cell_id": 2,
"direction": "up" // "up" or "down"
}Move a cell up or down in source order.
RenameCell
{
"type": "rename_cell",
"cell_id": 1,
"new_display_name": "My Cell"
}Rename a cell's display name.
CellEdit
{
"type": "cell_edit",
"cell_id": 1,
"source": "pub fn new_code() -> String { ... }"
}Edit a cell's source code (marks cell as dirty).
InsertMarkdownCell
{
"type": "insert_markdown_cell",
"content": "# My Markdown\n\nSome content",
"after_cell_id": null // Optional
}EditMarkdownCell
{
"type": "edit_markdown_cell",
"cell_id": 5,
"new_content": "Updated markdown content"
}DeleteMarkdownCell
{
"type": "delete_markdown_cell",
"cell_id": 5
}MoveMarkdownCell
{
"type": "move_markdown_cell",
"cell_id": 5,
"direction": "down"
}Definition cells contain types, imports, and helper functions compiled into the universe.
InsertDefinitionCell
{
"type": "insert_definition_cell",
"content": "struct MyStruct { field: i32 }",
"definition_type": "struct", // "struct", "enum", "trait", "use", "impl", "const", "static", "type_alias", "fn"
"after_cell_id": null
}EditDefinitionCell
{
"type": "edit_definition_cell",
"cell_id": 10,
"new_content": "struct UpdatedStruct { field: String }"
}DeleteDefinitionCell
{
"type": "delete_definition_cell",
"cell_id": 10
}MoveDefinitionCell
{
"type": "move_definition_cell",
"cell_id": 10,
"direction": "up"
}WidgetUpdate
{
"type": "widget_update",
"cell_id": 2,
"widget_id": "my_slider",
"value": {
"Float": 0.75 // Or: {"Int": 42}, {"String": "hello"}, {"Bool": true}
}
}Update a widget value. Does NOT trigger re-execution automatically.
SelectHistory
{
"type": "select_history",
"cell_id": 1,
"index": 2 // 0 = oldest
}Select a previous output from cell history (for cells that have been executed multiple times).
Undo
{ "type": "undo" }Undo the last cell management operation.
Redo
{ "type": "redo" }Redo the last undone operation.
RestartKernel
{ "type": "restart_kernel" }Kill worker process pool, clear memory state, preserve source code.
ClearOutputs
{ "type": "clear_outputs" }Clear all cell outputs without restarting.
Sync
{ "type": "sync" }Export notebook to .ipynb format for GitHub preview.
Messages sent from server to clients. Server automatically broadcasts state changes to all connected clients.
NotebookState
{
"type": "notebook_state",
"path": "/path/to/notebook.rs",
"cells": [
{
"cell_type": "code",
"id": 1,
"name": "my_cell",
"display_name": "My Cell",
"source": "pub fn my_cell() -> String { ... }",
"description": "Doc comment",
"return_type": "String",
"dependencies": ["other_cell"],
"status": "idle", // "idle", "running", "completed", "error"
"output": {
/* CellOutput */
},
"dirty": false
},
{
"cell_type": "markdown",
"id": 2,
"content": "# My Markdown"
},
{
"cell_type": "definition",
"id": 3,
"content": "struct MyStruct { ... }",
"definition_type": "struct",
"doc_comment": "/// Documentation"
}
],
"source_order": [1, 2, 3],
"execution_order": [1, 3],
"workspace_root": "/path/to/workspace",
"cargo_toml_path": "/path/to/Cargo.toml"
}CellStarted
{
"type": "cell_started",
"cell_id": 1
}CellCompleted
{
"type": "cell_completed",
"cell_id": 1,
"duration_ms": 123,
"output": {
"display": "Result: 42",
"widgets": [
{
"id": "slider_1",
"widget_type": "slider",
"label": "Value",
"min": 0.0,
"max": 1.0,
"step": 0.01,
"value": { "Float": 0.5 }
}
]
}
}CellError
{
"type": "cell_error",
"cell_id": 1,
"error": "Division by zero",
"location": {
"file": "notebook.rs",
"line": 15,
"column": 20,
"snippet": "let x = 1 / 0;"
}
}CompileError
{
"type": "compile_error",
"cell_id": 1,
"errors": [
{
"message": "mismatched types",
"severity": "error",
"code": "E0308",
"line": 10,
"column": 5,
"snippet": "expected `String`, found `i32`"
}
]
}ExecutionAborted
{
"type": "execution_aborted",
"cell_id": 1 // Optional
}GraphUpdated
{
"type": "graph_updated",
"edges": [
{ "from": 2, "to": 1 },
{ "from": 3, "to": 1 }
],
"levels": [[2, 3], [1]] // Parallel execution levels
}FileChanged
{
"type": "file_changed",
"modified_cells": [1, 2],
"added_cells": [
{
/* CellState */
}
],
"removed_cells": [5]
}CellInserted / CellDeleted / CellDuplicated / CellMoved / CellRenamed
{
"type": "cell_inserted",
"cell_id": 10,
"error": null // or "Error message" if failed
}MarkdownCellInserted / MarkdownCellEdited / MarkdownCellDeleted / MarkdownCellMoved
{
"type": "markdown_cell_inserted",
"cell_id": 5,
"error": null
}DefinitionCellInserted / DefinitionCellEdited / DefinitionCellDeleted / DefinitionCellMoved
{
"type": "definition_cell_edited",
"cell_id": 3,
"error": null,
"dirty_cells": [1, 2] // Cells affected by definition change
}UndoResult / RedoResult
{
"type": "undo_result",
"success": true,
"error": null,
"description": "Deleted cell 'my_cell'"
}UndoRedoState
{
"type": "undo_redo_state",
"can_undo": true,
"can_redo": false,
"undo_description": "Delete cell 'foo'",
"redo_description": null
}HistorySelected
{
"type": "history_selected",
"cell_id": 1,
"index": 2,
"count": 5,
"output": {
/* CellOutput */
},
"dirty_cells": [2, 3] // Cells now needing re-execution
}KernelRestarted
{
"type": "kernel_restarted",
"error": null
}OutputsCleared
{
"type": "outputs_cleared",
"error": null
}SyncCompleted
{
"type": "sync_completed",
"ipynb_path": "/path/to/notebook.ipynb"
}Error
{
"type": "error",
"message": "Operation failed: ..."
}Integer identifier for cells. Unique within a notebook.
Enum: "idle", "running", "completed", "error"
Enum: "up", "down"
Enum: "struct", "enum", "trait", "use", "impl", "const", "static", "type_alias", "fn"
Tagged union:
{"Int": 42}{"Float": 3.14}{"String": "hello"}{"Bool": true}
{
display: string, // Formatted output for display
widgets?: WidgetDef[] // Interactive widget definitions
}{
id: string,
widget_type: "slider" | "text" | "dropdown" | "checkbox",
label: string,
// For slider:
min?: number,
max?: number,
step?: number,
// For dropdown:
options?: string[],
value: WidgetValue
}<!DOCTYPE html>
<html>
<head>
<title>Venus Client</title>
</head>
<body>
<h1>Custom Venus Frontend</h1>
<div id="cells"></div>
<button onclick="executeAll()">Execute All</button>
<script>
const ws = new WebSocket("ws://localhost:8080/ws");
let cells = [];
ws.onmessage = (e) => {
const msg = JSON.parse(e.data);
if (msg.type === "notebook_state") {
cells = msg.cells;
renderCells();
} else if (msg.type === "cell_completed") {
updateCellOutput(msg.cell_id, msg.output);
}
};
function renderCells() {
const container = document.getElementById("cells");
container.innerHTML = cells
.map((cell) => {
if (cell.cell_type === "code") {
return `
<div class="cell">
<h3>${cell.display_name}</h3>
<pre>${cell.source}</pre>
<button onclick="executeCell(${cell.id})">Execute</button>
<div id="output-${cell.id}"></div>
</div>
`;
}
return "";
})
.join("");
}
function executeCell(cellId) {
ws.send(JSON.stringify({ type: "execute_cell", cell_id: cellId }));
}
function executeAll() {
ws.send(JSON.stringify({ type: "execute_all" }));
}
function updateCellOutput(cellId, output) {
const div = document.getElementById(`output-${cellId}`);
if (div && output) {
div.textContent = output.display;
}
}
</script>
</body>
</html>- Subscribe to all server messages: The server broadcasts state changes to all clients
- Handle reconnection: WebSocket may disconnect; implement reconnect logic
- Track undo/redo state: Use
UndoRedoStateto enable/disable UI buttons - Show execution status: Display cell status (
running,completed,error) - Handle errors gracefully: All operations return
errorfield on failure - Respect dirty flags: UI should indicate which cells need re-execution
Current Status: Active Development (0.1.x)
The WebSocket protocol is stabilizing but may change before 1.0. Breaking changes will be noted in release notes.
Future Plans:
- Additional REST endpoints for stateless operations
- Batch execution API
- Authentication/authorization
- Multi-notebook support
- CLI Reference - Command-line interface
- Getting Started - Notebook basics
- Widgets - Interactive widget usage