Stencila v2: a Rust rewrite where CRDTs became the document store
Documents with Scientific Intelligence
At a glance
- What is it?
- Stencila is a platform for programmable, data-driven documents, rewritten in Rust around conflict-free replicated data types as the storage layer for every document. The schema is typed, code in a document can be executable, and the project is candid that parts of the v2 line are alpha quality.
- Who is it for?
- Stencila fits teams that want documents to be data, where figures, prose and executable code live in one typed schema and need to travel between a CLI, a web client and a container. It does not fit a team that wants a stable editor today, since the project describes its own early iterations as limited and buggy, and the schema still marks some node types as under development.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 1 day ago.
- What is it written in?
- Mainly Rust, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A document platform where the document is a typed data structure
Stencila started in scientific communication and grew into a platform for creating and publishing dynamic, data-driven content. The pitch is documents that are not just text: they are structured, semantically rich objects that can also be applications, in the mould of tools such as Notion and Coda.
The v2 line is a rewrite in Rust, and the rewrite is the interesting part rather than an implementation detail. The project states its aim plainly: CRDTs will be the foundational synchronization and storage layer for Stencila documents, which requires fundamental changes to most other parts of the platform. Much of the code in the v1 branch is reused after tidy-ups, so it is not a complete rewrite, but the foundation moved.
Two things follow from that choice. First, collaborative editing is not a feature bolted on; it is what the data model is for. Second, version control of a document becomes a solved problem rather than a separate mechanism, since the same replication machinery that merges concurrent edits can hold history.
The scope is wide on purpose. The repository root holds rust/, node/, ts/, web/, python/, vscode/, claude/, workers/, site/, schema/, examples/, prompts/ and docs/ directories, and the Cargo workspace takes members from rust/*, node and python/stencila. A project that spans a CLI, a web client, an editor extension and a set of SDKs in three languages is a platform project, not a library.
The schema separates executable code from a plain code block
The Stencila Schema is the data model for documents, and its generated reference documentation lives in the repository. The categories are worth reading closely because the status of each one tells you how much you can rely on it.
Works types, such as Article, Figure and Review, are listed as stable and mostly based on schema.org. Prose types, such as Paragraph, List and Heading, are also stable and mostly based on HTML, JATS and Pandoc. Code types are at beta status and may change, and the reason is visible in the type names themselves: CodeChunk is the type for executable code, and CodeBlock is the type for non-executable code. That single split is what makes a Stencila document different from a markdown file. A document can hold a runnable snippet, not only a displayed one.
The status table is not uniform, and the project says so: most of the schema is well defined but some document node types are still marked as under development. Math types, with MathBlock for equations, fall in that unsettled region of the table.
For an adopter, that status column is the single most useful thing in the repository. It tells you which parts of the model you can build a durable integration on, and which parts will move. Choosing the schema deliberately, rather than accepting whatever a converter emits, is the difference between a document format that survives a version bump and one that does not.
LLM edits are recorded against the actor that made them
The second pillar of the v2 design is large language models, used for assisting with writing and editing, in prose and in code. What is unusual is not the assistance but the accounting. The project describes baking in mechanisms to mitigate the risks of using LLMs, and gives one example: recording the actor, whether human or LLM, that made a change to a document.
That decision has consequences for anyone storing a document over time. A document edited with model assistance is not a document with a single author history, and the audit trail has to carry both kinds of actor. It also fits the CRDT foundation rather than fighting it, because a CRDT operation already carries an actor identifier, so attributing a change to a model is a matter of naming that actor correctly rather than bolting a new log onto the side.
The prompts directory in the repository root, and the examples/prompts directory alongside it, suggest that prompt handling is a first class part of the system rather than a string in a config file. The repository also contains a claude/ directory and a .claude-plugin/ entry, so there is a Claude-specific integration in the tree, and the Makefile has a lint target for it alongside the other language components.
The honest caveat is that the v2 products are described as alpha and beta in places, and the project's stated strategy is to iterate horizontally across the feature set, expecting early iterations to have limited functionality and to be buggy. Model-assisted editing is exactly the sort of feature where that matters.
One Rust workspace with two crates deliberately excluded
The Rust side is a Cargo workspace whose members are the rust/* directory glob plus node and python/stencila, with the resolver set to 2. The workspace package version is 2.16.0 and the licence is Apache-2.0.
Two crates are excluded, and the reason is given in a comment: rust/codec-polars and rust/kernel-rhai are excluded to reduce lint and test compile times, and they are described as currently unmaintained crates. That comment is a small but useful signal about the project's priorities, since compilation time of the test suite is treated as a cost worth paying to remove.
The dependency list gives a picture of what the platform actually does. The CLI is built with clap 4.6.1, with derive, color, env and wrap_help features. Concurrency uses async-trait and futures. The keyring crate 3.6.3 is present with the apple-native, windows-native and sync-secret-service features, plus vendored, which points to credentials being stored in the operating system keychain rather than in plain files. Content negotiation uses mime and mime_guess, and there is a jsonschema crate 0.46.4, consistent with the typed schema being validated at runtime.
The rendering side shows up as well: headless_chrome with rustls for browser based rendering, image 0.25.10 with png, jpeg, gif and tiff features, lopdf 0.39.0 for PDF handling, lightningcss and brotli for styles and compression, and notify 8.2.0 with macos_kqueue for filesystem watching. Together these say the platform reads and writes documents, renders them, and watches directories for changes.
The Node side is a workspace of four packages
The JavaScript side of the project is a separate npm workspace with four members, declared in the root package.json.
"workspaces": [
"node",
"ts",
"web",
"workers/convert"
],The engines field asks for Node 18 or newer, and the type field is set to module, so the packages are ES modules rather than CommonJS. The dependency list is a development toolchain: vitest with coverage-v8 for tests, typescript 5.9.3 for the type system, eslint 10.2.1 with the typescript and lit plugins, prettier 3.8.3 with an XML plugin, vite 8.0.8 for bundling with a compression plugin, tsup for building packages, wrangler 4.83.0 for the Cloudflare Workers target, and publint for checking that published packages are well formed. better-npm-audit and the audit script sit alongside those.
The four members tell a story. The web directory is the browser client, workers/convert is a conversion worker deployed to Cloudflare, ts holds the TypeScript sources, and node holds the Node binding. The Dockerfile confirms the relationship between them, because the web bundle is built first and then copied into the Rust build stage so that it can be embedded in the CLI binary.
The vitest and eslint presence in the web and ts members, together with the per-language make targets in the Makefile, indicate that quality checks are run per component rather than once for the whole repository. The Makefile splits lint, test, cover, bench, audit and pubcheck into separate targets for rust, ts, node, python, web, vscode and claude.
The container ships one binary and exposes port 9000
The Dockerfile is a three stage build and it is worth reading as a deployment description rather than as a recipe. The first stage uses the official Node.js image to build the web clients and components for embedding in the CLI binary, running npm ci and then the build scripts in the ts and web directories. The second stage uses the official Rust image, installs cmake and libclang-dev, copies the web bundle into the expected location, and runs the release build of the binary.
FROM rust:1.95 AS cli
RUN cargo build --bin stencila --release
FROM ubuntu:24.04
COPY --from=cli /build/target/release/stencila /
EXPOSE 9000
ENTRYPOINT ["/stencila"]
CMD []The final image is Ubuntu 24.04 with the single binary copied to the root, port 9000 exposed, and the binary as the entry point. The default command is empty, so the container does nothing until you tell it what to do.
Two things follow. The Rust toolchain in the build stage is pinned to 1.95, and the release image does not carry a shell script or a runtime dependency tree, only the binary and the base operating system. That is a small deployment surface, which matters for a platform whose whole argument is that documents are portable artefacts.
There is also a separate Dockerfile.convert in the repository root, so conversion has its own image definition, separate from the main one. The distinction suggests conversion is treated as its own workload, which makes sense when conversion is where a document format meets an external tool.
Apache-2.0 across the workspace, with a REUSE file and a licence check
The licence for the workspace package is Apache-2.0, and the licence file sits at the repository root next to a LICENSES/ directory. The presence of REUSE.toml and a Makefile target that runs reuse lint show that the project uses the REUSE specification, where each file carries its licence and copyright information, rather than relying on a single root licence statement.
That is stricter than most projects do it, and it has a practical consequence. Because reuse lint is part of the lint target, the check runs alongside the Rust, TypeScript, Node, Python, web, VS Code and Claude linters, and a file that is missing or mislabelled metadata fails the same command as a lint error. For a repository with this many languages and asset types, that is the mechanism that keeps the licensing story consistent.
On maintenance, the repository is not archived and the last push was on 2026-10-19. The release list shows v2.16.0 on 2026-08-19, v2.15.0 on 2026-05-03 and v2.14.1 on 2026-03-26, and the workspace version in Cargo.toml matches the latest release at 2.16.0. The project publishes to several destinations at once, with badges pointing at GitHub releases, Docker Hub, PyPI and the npm packages @stencila/node and @stencila/types, so an upgrade means checking several registries rather than one.
The upgrade cost for a user is therefore mostly in the schema. A document written against a beta code type may need attention when that type changes, and the stable categories listed in the schema table are the safe ground to build on.
Editorial conclusion
Stencila fits teams that want documents to be data, where figures, prose and executable code live in one typed schema and need to travel between a CLI, a web client and a container. It does not fit a team that wants a stable editor today, since the project describes its own early iterations as limited and buggy, and the schema still marks some node types as under development. Before committing, check which of the schema categories you need are stable, and run the same readme install path the project points to on https://stencila.io rather than assuming a generic package install gives you the CLI.
Frequently asked questions
What is Stencila and who is it for?
Stencila is a platform for creating and publishing dynamic, data-driven content, with roots in scientific communication. It is for people who want documents to be structured, semantically rich and, in v2, synchronized through CRDTs, rather than plain text files.
How do I install the Stencila CLI?
The README points to an Install section on the project pages at https://stencila.io, and the repository ships an install.sh script at its root. Release binaries are published on GitHub Releases, and there is also a Docker image stencila/stencila on Docker Hub, a stencila package on PyPI, and the @stencila/node and @stencila/types packages on npm.
Can a Stencila document contain runnable code?
Yes. The schema defines CodeChunk for executable code and CodeBlock for non-executable code, which is what separates a Stencila document from a markdown file. The code types are still at beta status and may change, whereas the Works and Prose types are listed as stable.
How does Stencila handle collaboration and version control?
In v2 the storage and synchronization layer for documents is built on conflict-free replicated data types, so concurrent edits merge without a central authority. The project also records which actor, human or LLM, made each change to a document.
Is Stencila v2 ready for production use?
Treat it as early software. The project says its strategy is to iterate horizontally across the feature set, that it makes alpha and beta releases early and often, and that initial iterations have limited functionality and are buggy. The repository is not archived and the last push was on 2026-10-19.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/stencila-stencila)Was this useful?