Concepts
kutl is a sync protocol for teams of humans and agents, written in Rust. It syncs three things through one protocol: spaces (structure), documents (knowledge), and signals (coordination). The system consists of a daemon that each participant runs locally, a shared relay that coordinates sync, and a CRDT engine that makes merging deterministic.
Local-first by design
Participants who run the daemon (via the CLI or the desktop app) keep a full copy of every document in their space on their local filesystem. Edits happen locally and sync when a connection is available. The relay coordinates between participants rather than holding content on their behalf; if the relay goes away, daemon participants still have their copies and can bring up another relay to resume collaboration.
Browser-only participants (for example, users of the kutlhub web UI without a daemon attached) are a partial exception: their content lives on the server rather than on their own disk, so for them the relay is load-bearing in the way a conventional hosted service is. Anyone who wants the full local-first guarantee can attach a CLI or desktop daemon at any time and pull a copy.
What this design aims for:
- No lock-in for daemon participants. Documents sit on your filesystem as regular files. You can read them, back them up, or stop using kutl without an export step.
- Tool freedom. Any program that reads and writes files (your editor, a script, an AI agent) becomes a sync participant automatically, without plugins or bespoke integrations.
- Mixed environments. Vim, VS Code, a browser, and an agent runtime all work on the same documents through the same sync protocol, each through whatever interface suits them.
- Offline tolerance. For daemon participants, edits happen locally and sync when a connection returns. There's no “you must be online to edit” requirement in that mode.
Spaces
A space is a named sync context. On disk, it's a directory containing a .kutlspace config file and a .kutl/ directory for local state. Every participant who joins the same space sees the same set of documents. Spaces are identified by a UUID internally, but you refer to them by a human-readable name when joining.
The daemon
Every participant runs a daemon on their own machine. It is a background process that watches all of your registered spaces. When a file changes, the daemon translates the edit into CRDT operations and sends them to the relay. When the relay sends operations from other participants, the daemon applies them to local files. Any tool that reads and writes files (your editor, an AI agent, a script) becomes a sync participant automatically, with no plugins or integrations required.
The relay
The relay is the one shared piece of infrastructure. It is a WebSocket server that receives CRDT operations from each participant's daemon, reconciles them, and sends the merged result back. Only one person on your team needs to set it up, typically whoever is comfortable running a server process. Everyone else just points their daemon at it.
The relay understands the CRDT protocol rather than acting as a passive message broker. It handles catch-up for clients that reconnect after being offline, eviction of unresponsive clients, and document lifecycle tracking.
Using a relay rather than peer-to-peer connections is a deliberate choice. Peer-to-peer sync requires participants to discover and connect to each other directly, which means dealing with NAT traversal, firewall rules, and hole-punching. In practice, most teams work across networks where direct connections between machines are not possible or not desirable. The relay is an ordinary web server that listens on a single port. Participants connect outbound to it over standard WebSocket, which works through firewalls, proxies, and corporate networks without special configuration.
You can self-host a relay on your own infrastructure. The OSS relay persists space registrations, invites, and the signal/change log to SQLite (under KUTL_RELAY_DATA_DIR, defaulting to the platform data directory); live document content is held in memory and re-seeded by clients after a restart, since every client has the full history. See Self-Hosting a Relay for deployment options.
Documents and binary files
Text files stay ordinary files on disk. Concurrent edits to the same document merge automatically, character by character.
Binary files (images, PDFs, and other non-text content) also sync through kutl, but use a different strategy. Binaries are synced whole-file with last-write-wins semantics based on timestamp. The daemon tracks content hashes to avoid re-sending unchanged files.
All files, text and binary, are identified by UUID. Renaming or moving a file across machines doesn't create duplicates. The identity follows the file, not its path.
Signals
kutl signals are the protocol-level primitive for coordination. Where documents capture knowledge, signals capture the interactions around it: questions, reviews, decisions, and threaded conversations. Signals travel through the same relay as document edits, use the same identity model (DIDs), and participate in the same spaces.
Signal types:
- Flags are structured messages with a kind (info, question, review requested, blocked, completed, or an inline comment) and an audience. Flags are how participants get each other's attention.
- Chats are free-form messages in a space's stream.
- Decisions are extracted automatically from heading-annotated markers in documents (
## ?for open,## =for resolved). - Replies are threaded responses on any signal.
In the OSS relay, agents exchange flags and replies via MCP tools. Humans participate by working in documents and using the CLI. Managed relays like kutlhub add a web interface with tasks, feeds, chat, and emoji reactions on top of the same signal protocol.
What kutl keeps on disk
Documents and signals are both stored locally, in shapes that suit what each one is. For every text file, the daemon keeps a history sidecar under .kutl/docs/ (diamond-types format) holding the full edit history as a compact log of CRDT operations. The file is what you edit; the sidecar is the merge history that lets two people change the same paragraph offline. Binary files have no sidecar, since they sync whole-file rather than merging.
Signals are records rather than text, so a space keeps one append-only log of them under .kutl/signals/, written as binary segment files. The daemon appends each record it receives, starts a new segment as one fills, and compresses the ones it closes. A lock file keeps the daemon the only writer.
Signal records are immutable. Editing a signal or resolving it appends a new record rather than changing an earlier one, so reading is a fold: the CLI and the feed replay the log and collapse each signal's records into its current state. That local copy is what kutl signal list reads by default, and --fetch pulls anything newer from the relay first. The directory appears once signals reach the daemon.
CRDTs
CRDT stands for Conflict-free Replicated Data Type. It is a data structure where any two copies can be merged deterministically without coordination. Given the same set of operations in any order, every participant arrives at the same result. For text content that means no merge conflict to resolve by hand and no lost edits. (Binary files can't be merged: the most recent version wins, and a name collision keeps both files as a conflict copy.)
kutl uses CRDTs for the whole space: document content, subscriptions, document lifecycle (creation, renames, deletes), and coordination state are all modeled as convergent structures. File renames and deletes converge deterministically across participants, the same way text edits do.
The text CRDT engine is diamond-types, which implements the Eg-walker algorithm. It operates on character-level edits, so concurrent changes interleave cleanly: file edits sync each time the file is written, and the in-app editors sync as you type.
DIDs (Decentralized Identifiers)
Your identity in kutl is a DID, specifically a did:key derived from an ed25519 keypair generated locally and stored in ~/.kutl/identity.json. There is no account server or sign-up form, and no email address is required. Your DID is created automatically the first time you run kutl init or kutl join. For OSS use, DIDs are sufficient for identity. Managed relays like kutlhub layer OAuth and accounts on top.
Working with agents
kutl is designed for mixed teams of humans and AI agents. There are two ways an agent can participate in a space:
- File-based. The simplest path: point the agent at a directory inside a kutl space. The daemon handles sync, and the agent reads and writes files like any other tool, without any kutl-specific code.
- MCP (Model Context Protocol). For a tighter integration,
kutl mcp serveruns as an MCP server over stdio. It exposes tools for reading and editing documents, listing participants, sending and responding to flags, and polling for changes. The agent receives real-time notifications when someone flags it.
The MCP interface provides get_changes for checkpoint-based polling, edit_document for content updates, and signal tools: create_flag, create_reply, close_flag, and get_signal_detail.
An edit is a merge, not a replacement. The agent sends the document as it wants it, together with the version its read_document returned, and the relay applies the difference between those two as CRDT operations. Text a participant added since that read is not overwritten. If a region the agent changed was rewritten in the meantime, that region is refused and the rest of the edit lands; the agent reads again and reapplies it.
How they fit together
kutl syncs three things as one protocol. Spaces hold the structure: which documents exist, how they're organized, who participates. Documents hold the knowledge: CRDT-synced content that merges deterministically. Signals hold the coordination: flags, replies, and decisions that capture the interactions around the knowledge.
All three flow through the same relay, use the same identity model (DIDs), and work the same way for humans and agents. One person sets up a relay and everyone else points their daemon at it; edits, structure changes, and signals all sync through the same connection.