Build
Developer workspace
Python notebooks at /notebooks that execute on sandboxed server kernels — real CPython with pip and the agentic frameworks pre-installed. Start from read-only framework samples, or write your own and call models and knowledge bases through your account.
Framework samples
The workspace ships with four read-only sample notebooks under Learn by example:
- LangChain fundamentals — models, prompt templates, output parsers, LCEL chains, tools/agents, memory, and RAG.
- LangGraph fundamentals — state, nodes, edges, conditional routing, cycles, human-in-the-loop, and multi-agent supervisors.
- LlamaIndex fundamentals — documents, nodes, indexes, retrievers, and query engines.
- Mix of all — the agentic stack — using your knowledge base, building tools, composing skills, applying guardrails, reaching MCP servers, and wiring a small multi-agent RAG service.
langchain, langgraph and llama_index imports executing on a container kernel. Model calls go through agentswarms.chat_model(), a genuine LangChain BaseChatModel that routes via the platform, so your IAM model rules, budgets and Traces apply and no provider key ever exists inside the sandbox. All four samples are executed end-to-end in CI-style verification before release.Read-only & fork
Samples can be run cell-by-cell but not edited. Click Fork to my notebooks to copy a sample into an editable notebook owned by your account — then change anything and it autosaves. Your own notebooks live under My notebooks and are private to you.
How cells run
- Each code cell is a real editor (CodeMirror) — run it with the play button or Shift+Enter. stdout, the last expression's value, errors, and run duration appear under the cell.
- Cells execute on a sandboxed container kernel — non-root, read-only root filesystem, capabilities dropped, network egress restricted to an allowlist. The kernel starts on your first run and is reaped when idle.
- All cells share one interpreter, Jupyter-style: variables defined in one cell are visible in the next, and top-level
awaitis supported. - LangChain, LangGraph, LlamaIndex, pandas and numpy are pre-installed; anything else is a
!pip installaway (writes land in a per-session writable layer).
The agentswarms helper
An agentswarms module is injected into every notebook. It routes calls through your account — your provider keys, your IAM model rules, your budgets, logged in Traces:
reply = await agentswarms.chat("…", provider="openrouter", model="openai/gpt-4o-mini")— a chat completion through any OpenAI-compatible provider you've connected under Integrations (openrouter, openai, gemini, groq, grok, qwen, ollama, vllm, nvidia).hits = await agentswarms.kb_search("refund policy", top_k=5)— hybrid (vector + keyword) retrieval over your Knowledge Base. Access is enforced by the same rules as everywhere else: you only search KBs you own, were granted, or that are public samples.kbs = await agentswarms.list_knowledge_bases()— the knowledge bases your account can read.agentswarms.format_context(hits)— turn retrieval results into a numbered context block for a prompt.llm = agentswarms.chat_model(model="openai/gpt-4o-mini")— a real LangChainBaseChatModel. Use it anywhere a LangChain model is accepted:chain = prompt | llm | StrOutputParser(), LangGraph nodes, and so on. It supports tool-calling viallm.bind_tools([...]), so LangGraph'screate_react_agentandToolNodework too.agentswarms.llama_llm(...)andagentswarms.kb_retriever(top_k=4)— a LlamaIndex LLM and a retriever over your Knowledge Base (managed hybrid search, no embedding model to configure).
Where the workspace fits
The Developer workspace is the free-form counterpart to the rest of the platform: sketch a pattern here in Python with real model calls and retrieval, then rebuild it as a durable, deployable system on the Swarm Canvas and in Agent Builder.
There is one runtime, and it is off by default
Notebook cells execute on real container kernels — full CPython, working pip install, and the actual agentic frameworks. There is no in-browser fallback: until an administrator enables the server runtime, opening a notebook shows a "runtime required" panel with the command to run rather than a half-working editor.
docker compose --profile notebooks up -d --buildThen turn it on under Admin → Developer runtime. Enabling it mints the signing secret automatically.
Installing libraries
The kernel image ships build-essential and sets PIP_USER=1 against a writable per-session ~/.local, so packages with C extensions compile and install fine:
!pip install polars duckdb scikit-learnInstalls only reach hosts on the egress allow-list
pip install git+https://github.com/…, a model download from Hugging Face, a private index — fails until an administrator adds that host under Admin → Developer runtime → Egress allow-list. Saving there now writes the list and restarts the proxy, and the UI tells you if either step didn't happen.| Env var | Values | Purpose |
|---|---|---|
NOTEBOOK_RUNTIME_ENABLED | on / off | Master switch |
NOTEBOOK_RUNTIME_BACKEND | docker | k8s | e2b | Where kernels are launched |
NOTEBOOK_RUNTIME_IMAGE | image ref | Kernel image |
NOTEBOOK_GATEWAY_URL | url | Websocket gateway the browser connects to |
NOTEBOOK_RUNTIME_SECRET | secret | Session-token signing key; generated if omitted |
NOTEBOOK_CRON_TOKEN | token | Presented by the external cron that reaps idle sessions |
The docker backend is single-host
Kernel network isolation
Kernels sit on an internal network with no direct internet access. Their only route out is a default-deny egress proxy with an allow-list, so a notebook cannot exfiltrate data to an arbitrary host — and a pip install only works for hosts you permit.
Calling your deployed agents and swarms
The helper also reaches the things you have already built, so a notebook can orchestrate them rather than re-implement them:
| Call | Returns |
|---|---|
await agentswarms.chat(prompt, model=…) | A raw model completion |
await agentswarms.kb_search(query) | Knowledge base hits |
await agentswarms.list_knowledge_bases() | Collections you can read |
await agentswarms.run_swarm(swarm_id, input) | A saved swarm's final output |
await agentswarms.list_agents() | Your agents and swarms, with ids |
await agentswarms.run_agent(agent_id, prompt) | Not available — returns 501. See below. |
import agentswarms
catalog = await agentswarms.list_agents()
swarm_id = catalog["swarms"][0]["id"]
result = await agentswarms.run_swarm(swarm_id, "Summarise ticket 48213")
print(result)Why it works this way
run_swarm is not chat with extra steps. It runs what you built exactly as configured, so a notebook gets the same behaviour as the canvas instead of a Python re-implementation that drifts from it. Provider keys never enter the sandbox — the call is brokered by the platform and stays governed by IAM model rules, budgets and traces.run_agent returns 501, and always has
To run one saved agent from a notebook, wrap it in a single-node swarm. Importing an agent onto a swarm node copies its prompt, model, tools and knowledge base onto that node, so the run is faithful to the agent. The one difference worth knowing: the copy is a snapshot, so later edits to the agent do not reach a swarm built from it — re-import the node to pick them up.
Approval nodes are rejected, not awaited
Note
Publishing a notebook as an API
A notebook can be called by your own systems. Open it and click Publish to mint an API key; anything that can send an HTTP request can then run it.
- Key name — how you will recognise it later. Mint one per caller so you can revoke a single integration without breaking the others.
- Entrypoint function — the function called with the request body, defaults to
entrypoint. Leave it empty to run the notebook top to bottom and return the last expression instead.
Define the entrypoint in any cell. It receives the JSON you post under inputs and its return value becomes the response:
async def entrypoint(inputs):
date = inputs.get("date")
hits = await agentswarms.kb_search(f"incidents on {date}", top_k=5)
summary = await agentswarms.chat(
f"Summarise these incidents:\n{agentswarms.format_context(hits)}"
)
return {"date": date, "summary": summary}curl -X POST https://your-instance/api/notebook/run \
-H "Authorization: Bearer nbk_…" \
-H "Content-Type: application/json" \
-d '{"inputs": {"date": "2026-07-28"}}'| Field | Type | Meaning |
|---|---|---|
inputs | object | Passed to the entrypoint. Optional — omit for none. |
async | boolean | true returns 202 with a runId immediately instead of waiting |
A synchronous call waits up to 110 seconds. If the run is still going it returns 202 with a runId rather than holding the connection open — poll it the same way an async run is polled:
curl -X POST https://your-instance/api/notebook/run/status \
-H "Authorization: Bearer nbk_…" \
-H "Content-Type: application/json" \
-d '{"runId": "…"}'Status is queued, running, succeeded (with result) or error (with error).
Why it works this way
The key is shown once
Version control (Git)
Click Version in a notebook to commit it to your GitHub or GitLab repository. It is written as a plain Python file in the widely used percent format — the same # %% cell markers Jupytext, VS Code and PyCharm understand:
# %% [markdown]
# ## Load yesterday's incidents
# %%
hits = await agentswarms.kb_search("incidents", top_k=5)- Commit writes the notebook plus a small
.jsonmanifest recording how it is published — the entrypoint and the key prefixes, never a key itself. - History lists every commit with a link to it, and the header says Uncommitted changes or Up to date so you can tell at a glance whether the repo matches what is running.
- Restore reads the file back at that commit and replaces the notebook's cells. Anything not committed is lost, so it asks first.
Why it works this way
Note