Guide · Linux
A durable record of shared work, on your own machine.
Kovee is a daemon and a small set of clients. You build it, start it, and it keeps an immutable, typed record of what people and agents contributed to a shared space — with the context given to an assistant recorded exactly, and every committed change on one event ledger you can page through.
Kovee is under active development and has not been audited. It runs, and
everything on this page was executed against a live koveed, but
the assurance profile is developer — same-UID, no
confinement, no team mode. Read
security & limits before you put anything you
care about in it.
What kovee is
One daemon, koveed, holding a SQLite database and listening on two
Unix domain sockets — one for clients, one for assistant workers. Everything
else talks to it: the kovee CLI, an MCP server your agent harness
can launch, and a Python SDK an assistant runs inside.
The records are ordinary and inspectable:
- A space is the unit of shared work and visibility.
- A contribution is one immutable, typed entry appended to a branch of that space — a question, a claim, evidence, a synthesis.
- A relation connects two exact revisions, attributed to whoever asserted it.
- A frontier pins an exact point in the space so a later read means the same thing it meant then.
- A ContextAssembly records exactly what an assistant was given at such a point, item by item.
- Every committed change lands on a per-project event ledger with dense sequence numbers and an authenticated cursor.
Concepts takes each of these apart. The full operation set is in the reference.
What it is not
Kovee's design describes considerably more than kovee ships. This table is the short version; each "not shipped" row is checked against the operation registry on every build of this site.
| You might expect | Today |
|---|---|
| Fork a branch, merge by adoption | Not shipped. Branching is a
record model: every space has a main branch and every contribution
carries an origin branch and sequence. There is
no branch_* operation in
the registry — nothing forks and nothing merges. |
| An attention contract that wakes an assistant | Not shipped. There is
no attention_* operation;
crates/kovee-attention is a two-line stub. An assistant is
invoked explicitly, by a client that says so. |
| Needs, offers, formation proposals, commitments | Not shipped. No
need_*,
offer_* or commitment_* operation exists. |
| Team mode — several people, a server, sign-in | Does not exist. No PostgreSQL, no
OIDC, no realtime, no NATS. Kovee binds Unix sockets and authenticates its
peer by SO_PEERCRED same-UID. There is no network listener at
all. |
| A sandbox that contains an agent | Not claimed. The assurance profile is
developer: no UID separation, no attested process identity, no
asymmetric endpoint identity. A same-UID process can do anything you can. |
| A gateway that stops an agent reaching a model directly | Not claimed. The broker's own calls are disclosed, permitted and metered. Nothing stops a different process on your machine from calling a provider itself. See provider bypass. |
| A chat app, a UI, a hosted account | None of those. There is no web interface in this repository. The clients are a CLI, an MCP server, and a Python SDK. |
Requirements
| You need | Why |
|---|---|
| Linux | Both sockets are Unix domain sockets, and the daemon authenticates its peer per connection by reading a ucred through SO_PEERCRED — a Linux interface. Nothing here has been built or tested on another platform. |
| A Rust toolchain | The tree pins one in rust-toolchain.toml; rustup will fetch it. This guide was run on cargo 1.95.0 / rustc 1.95.0. |
| A C compiler | SQLite is compiled in through rusqlite's bundled feature, so there is no system SQLite to install — but there is C to build. |
| Python 3 (optional) | Only for the assistant SDK and for mcp/check.py. The SDK is stdlib-only. |
| A short runtime directory | A Unix socket path has a hard length limit. See troubleshooting. |
Build it
git clone https://github.com/zarbafian/kovee
cd kovee
cargo build --workspace --locked
That produces three binaries under target/debug/:
koveed (the daemon), kovee (the CLI) and
kovee-mcp (the MCP server). Put target/debug on your
PATH, or call them by path — this guide writes them as
koveed and kovee.
To validate the tree, run ./run-checks.sh: formatting, clippy under
-D warnings, the whole test suite, the MCP tool-bundle check, the
check that keeps this website honest, and two independent re-derivers of the
golden vectors — one in Python, one in TypeScript, sharing no code with the
Rust. CI runs most of those; the MCP and docs checks are local-only today.
Quickstart
Six verbs, in order, from an empty store. Every block below is the real output of the command above it, captured from one run on 2026-07-28; only the identifiers will differ on your machine, because they are minted per installation.
1. Start the daemon
koveed takes one optional flag, --data-dir. Anything
else is a hard error. Here it is pointed at a scratch directory with two
environment variables so nothing lands in your real profile:
export KOVEE_DATA_DIR=/tmp/kvqs/data
export KOVEE_RUNTIME_DIR=/tmp/kvqs/run
mkdir -p "$KOVEE_DATA_DIR" "$KOVEE_RUNTIME_DIR"
koveed &
koveed: personal profile; store /tmp/kvqs/data/kovee.db; listening on /tmp/kvqs/run/kovee.sock (external) and /tmp/kvqs/run/kovee-worker.sock (worker)
Without those variables the store goes to $XDG_DATA_HOME/kovee
(falling back to ~/.local/share/kovee) and the sockets to
$XDG_RUNTIME_DIR/kovee (falling back to
/tmp/kovee-<uid>). The directory is created 0700
and each socket file 0600.
2. Say hello
Every client starts here. hello negotiates a protocol
version and reports which feature bundles this installation actually serves.
kovee hello
{
"features": [
"core_v1",
"shared_space_v1",
"developer_assistant_v1"
],
"implementation": "koveed",
"implementation_version": "0.0.1",
"installation_id": "inst-bffac71619de79b979c4a9c9263fa393",
"limits_digest": "3ee3bd3169b8f32fad61b1e3a8bec77a9065c8052e8cfbda8df80e1e0441ada9",
"selected_version": "0.1",
"server_time": "2026-07-28T03:00:40Z"
}
Three bundles, not four. A bundle is atomic — a client may assume all of it or
none of it — so the fourth bundle,
governed_work_binding_v1, is deliberately not advertised while it
is built out at 9 of its fourteen
operations. Its operations do dispatch over the socket; they are simply not
something a client should discover and depend on yet. Capability discovery is
this list, never a version number.
3. Initialise
kovee init is three operations in one verb:
hello, realm_show,
and a project_create under a fixed
idempotency key, so running it again returns the same project instead of
minting a second one.
kovee init
installation: inst-bffac71619de79b979c4a9c9263fa393
realm: realm-personal
project: proj-b9e7413a81a4596bd34ac8c374b8c44b (name "personal")
The personal profile has exactly one realm, realm-personal, minted
at first start. Keep that project id — every later command needs it.
4. Create a space
kovee space create --project proj-b9e7413a81a4596bd34ac8c374b8c44b \
--title "Flaky test triage"
{
"created_at": "2026-07-28T03:00:40Z",
"created_by": "prin-owner",
"default_classification_ref": "class-default",
"main_branch_id": "branch-9b988617a2fb9bfe1cce041219052847",
"next_space_sequence": 1,
"policy_set_ref": "policy-default",
"project_id": "proj-b9e7413a81a4596bd34ac8c374b8c44b",
"realm_id": "realm-personal",
"revision": 1,
"space_id": "space-88b7a591ca55b98e0a2c59507bf380b8",
"status": "open",
"title": "Flaky test triage",
"visibility": "project"
}
event_cursor: kc1.7b2265706f…2c2276223a317d.73e93652bb…7f136ef63e (on stderr, elided)
Note three things. The space came with a main branch already —
branches are a record model, not something you create. Its
visibility defaulted to project; the other accepted
value is restricted, and widening it later is a separate prepared
path rather than an edit. And the result printed to stdout while the
event_cursor went to stderr, which is what lets you
pipe the JSON somewhere without losing the cursor.
5. Append contributions
A contribution is typed. --kind takes one value from a closed
enum and defaults to utterance; here
a question and then the evidence that answers it.
kovee space contribute --project proj-b9e7413a81a4596bd34ac8c374b8c44b \
--space space-88b7a591ca55b98e0a2c59507bf380b8 \
--kind question --text "Why does k1_crash_matrix flake only on CI?"
{
"author_actor_ref": "prin-owner",
"body_parts": [
{
"media_type": "text/plain",
"text": "Why does k1_crash_matrix flake only on CI?"
}
],
"classification_ref": "class-default",
"content_digest": "2aecf827087cb2267118e469914ba56f0795fc88c44f8c7c431eb520dcdee51c",
"contribution_id": "contrib-08ef618663d43b7416a0c6b5c5ee0f12",
"created_at": "2026-07-28T03:00:40Z",
"kind": "question",
"origin_branch_id": "branch-9b988617a2fb9bfe1cce041219052847",
"origin_branch_sequence": 1,
"project_id": "proj-b9e7413a81a4596bd34ac8c374b8c44b",
"realm_id": "realm-personal",
"retention_policy_ref": "ret-default",
"revision": 1,
"schema_ref": "schema:contribution-body-v1",
"source_refs": [],
"space_id": "space-88b7a591ca55b98e0a2c59507bf380b8",
"space_sequence": 1,
"subject_refs": []
}
kovee space contribute --project proj-b9e7413a81a4596bd34ac8c374b8c44b \
--space space-88b7a591ca55b98e0a2c59507bf380b8 \
--kind evidence --text "Ten reruns: three failures, all under the WAL checkpoint path."
{ …
"content_digest": "f5a3c0d75fbc91ea29f58b02201c363ad5ccb1e9d6beab79cae65d33617c886c",
"contribution_id": "contrib-a48910a8f8117f458031ae6ca880e7e9",
"kind": "evidence",
"origin_branch_sequence": 2,
"space_sequence": 2,
… }
A wrong kind is refused rather than coerced, and the daemon says which rule it broke:
kovee space contribute --project $P --space $S --kind banana --text hi
kovee: problem urn:kovee:error:invalid: invalid operation arguments
kind is not in the closed ContributionKind enum
exit status 1
6. Read the space, then the ledger
kovee space show --project proj-b9e7413a81a4596bd34ac8c374b8c44b \
space-88b7a591ca55b98e0a2c59507bf380b8
{ …
"next_space_sequence": 3,
"revision": 3,
"status": "open",
"title": "Flaky test triage",
"visibility": "project" }
The space is at revision 3 after two appends: creating it was revision 1, and each contribution moved it. Now the ledger:
kovee events --project proj-b9e7413a81a4596bd34ac8c374b8c44b --limit 4
1 2026-07-28T03:00:40Z dev.kovee.project.created.v1 proj-b9e7413a81a4596bd34ac8c374b8c44b
2 2026-07-28T03:00:40Z dev.kovee.space.created.v1 space-88b7a591ca55b98e0a2c59507bf380b8
3 2026-07-28T03:00:40Z dev.kovee.space.contribution-appended.v1 contrib-08ef618663d43b7416a0c6b5c5ee0f12
4 2026-07-28T03:00:40Z dev.kovee.space.contribution-appended.v1 contrib-a48910a8f8117f458031ae6ca880e7e9
next_cursor: kc1.7b2265706f…2c2276223a317d.eee9e1a3c8…931a38343b7e (on stderr, elided)
Paging is by cursor, not by offset. Feed the cursor back with
--after and you get everything committed since — the same events
from 3 onwards here, because the cursor above was taken after event 2:
kovee events --project proj-b9e7413a81a4596bd34ac8c374b8c44b \
--after "kc1.7b2265706f…2c2276223a317d.73e93652bb…7f136ef63e" --limit 5
3 2026-07-28T03:00:40Z dev.kovee.space.contribution-appended.v1 contrib-08ef618663d43b7416a0c6b5c5ee0f12
4 2026-07-28T03:00:40Z dev.kovee.space.contribution-appended.v1 contrib-a48910a8f8117f458031ae6ca880e7e9
That is the whole shipped CLI. Everything else is reached over the socket.
Reading the output
| Field | What it tells you |
|---|---|
revision | Optimistic-concurrency counter for that record. Send it back as expected_revision and a mutation that raced you is refused as stale rather than silently applied. |
space_sequence, origin_branch_sequence | Dense per-space and per-branch positions. Dense means no gaps — a rolled-back transaction consumes no number. |
content_digest | The address of that contribution's body. Relations and assemblies bind to it, so they name an exact revision rather than "the latest". |
event_cursor / next_cursor | An opaque, authenticated position on the project ledger: kc1.<hex payload>.<hex MAC>. It is minted per installation and per source, so a cursor from elsewhere is refused as invalid. Possession grants nothing; authorization is rechecked on every read. |
| Event types | Reverse-domain and versioned, e.g. dev.kovee.space.contribution-appended.v1. Reads may filter by type prefix. |
limits_digest | A digest over the protocol limits this installation enforces, so a client can tell two installations apart without probing them. |
Running it twice
Every mutation carries an idempotency key. The CLI mints a random one per
invocation, except for init, which uses a fixed key — that is why
running init twice prints the same project id rather than making a
second project. Both mutating verbs accept
--idempotency-key <key>, which is not in the usage block but is
real:
kovee space create --project $P --title "Once" --idempotency-key mine-1 | grep space_id
kovee space create --project $P --title "Once" --idempotency-key mine-1 | grep space_id
"space_id": "space-94f631c6e948f614832cbb57dd2f10e6",
"space_id": "space-94f631c6e948f614832cbb57dd2f10e6",
# the second call returned the first result — one space, not two
Reusing the key with different arguments is an error, not a silent overwrite. The daemon compares a canonical digest of the request — one that deliberately excludes the request id and trace headers, so a genuine retry still matches — and refuses a mismatch:
kovee space create --project $P --title "Twice" --idempotency-key mine-1
kovee: problem urn:kovee:error:idempotency-mismatch: same scoped idempotency key, different canonical request
reusing an idempotency key with changed arguments is refused
exit status 1
This is the same mechanism that makes a crashed client safe to retry — see the command transaction.
The other three surfaces
The CLI covers six verbs of 96 operations. The rest are reached by writing a JSON command line to the socket yourself, or through the MCP server or the SDK. All four surfaces speak the same protocol: one JSON object per line in, one reply line out, connection closed.
python3 - "$P" <<'PY'
import json, os, socket, sys
cmd = {"version": "0.1", "op": "space_list", "realm_id": "realm-personal",
"project_id": sys.argv[1], "args": {"limit": 3}}
s = socket.socket(socket.AF_UNIX)
s.connect(os.path.join(os.environ["KOVEE_RUNTIME_DIR"], "kovee.sock"))
s.sendall((json.dumps(cmd) + "\n").encode())
s.shutdown(socket.SHUT_WR)
print(json.dumps(json.loads(s.makefile().readline()), indent=2))
PY
{
"outcome": "ok",
"result": {
"boundary_event_cursor": "kc1.7b2265706f…2c2276223a317d.ab68d0a5c8…36371e9d45",
"items": [
{
"created_at": "2026-07-28T02:57:21Z",
"created_by": "prin-owner",
…
"space_id": "space-442e84b453eb9aa04ccd7a32ba556c5f",
"status": "open",
"title": "Flaky test triage",
"visibility": "project"
},
…
A reply is either {"outcome":"ok", "result": …} or
{"outcome":"problem", "problem": …}. Problems are typed —
urn:kovee:error:invalid,
urn:kovee:error:idempotency-mismatch and so on — with a human
detail line, which is what the CLI prints. Note the
boundary_event_cursor: a list read hands back the ledger position
its answer was true at, so a client can follow the ledger from there rather than
polling the list.
MCP server
kovee-mcp is a stdio JSON-RPC MCP server exposing
14 tools to an agent harness. It
parses no command-line arguments at all: the harness launches the binary and
speaks MCP on its stdin and stdout. The one configuration line in the tree is
claude mcp add kovee -- kovee-mcp
It needs a running koveed, finds the socket the same way the CLI
does, and scopes itself to one project: set KOVEE_PROJECT, or leave
it unset and let it use the single project if there is exactly one.
Each tool is derived from a registry operation, not hand-written — the tool
kovee_contribution_append is the operation
contribution_append minus the fields
the channel already fixes. Read-only tools are marked safe to allow; mutations
and the one credential-bearing read are marked gated, for your harness to prompt
on. The tool table lists all fourteen with the
operation each one binds; concepts explains how
they are derived.
Assistant SDK
An assistant is a Python class with a run method. The SDK
(sdk/python/kovee_sdk.py, stdlib only, no package to install — put
the directory on sys.path) does the rest: it creates the context
assembly, creates the invocation on the client socket, claims the attempt on the
worker socket, runs you, and completes.
from kovee_sdk import Assistant, run_one_shot
class DeterministicReviewer(Assistant):
def run(self, ctx):
question = ctx.trigger_contribution
synthesis = ctx.contribute(
kind="synthesis",
parts=[{"media_type": "text/plain", "text": "…"}],
operation_key="synthesis-v1",
)
ctx.relate("addresses", synthesis.ref, question["contribution_id"],
operation_key="addresses-v1")
ctx.result_ref = synthesis.ref
run_one_shot(DeterministicReviewer(), project_id=…, space_id=…, branch_id=…,
question_ref=…, invocation_key="review-1",
deadline="2027-01-01T00:00:00Z")
That is condensed from assistants/deterministic_reviewer.py, which
is not an illustration: the acceptance test runs that exact file against a real
daemon, kills the daemon mid-flow, and asserts the ledger ends with exactly one
synthesis and exactly one relation. The mandatory
operation_key on every mutating call is what makes that true — it
becomes the durable idempotency key, so a retry after a crash replays instead of
duplicating.
run_one_shot is a direct invocation: your code decides to
run the assistant and names the contribution it should look at. Nothing in
kovee wakes an assistant on its own — the attention machinery that would is
not shipped.
Troubleshooting
The CLI cannot reach the daemon
kovee: cannot reach koveed at /tmp/kvdoc/nope/kovee.sock (No such file or directory (os error 2)); is the daemon running?
The CLI and the daemon resolve the socket by the same rules, so this almost
always means the two disagree about KOVEE_RUNTIME_DIR. Check what
the daemon printed when it started — it names both socket paths.
The daemon will not bind
koveed: bind socket: socket io: path must be shorter than SUN_LEN
A Unix socket path is limited to about a hundred bytes by the kernel, and a deep
KOVEE_RUNTIME_DIR blows past it. Use a short one.
A cursor is refused
kovee: problem urn:kovee:error:invalid: invalid cursor
not a cursor this installation minted for this source
Cursors are authenticated against an installation secret and bound to the source
they were minted for, so one from another store — or a truncated copy-paste —
is indistinguishable from a forgery and is refused as invalid. In
zsh, note that 2>&1 >/dev/null does
not mean what it means in bash: with MULTIOS
on you capture the event lines too. Capture the cursor with
grep -o 'kc1\.[^ ]*' instead.
An unknown verb, or a missing flag
There is no --help. Any unrecognised verb or missing required flag
prints the usage block on stderr and exits 2; a rejected operation
exits 1.
Where next
- Concepts — what a space, a frontier, a lens, an assembly and an artifact digest actually are.
- Reference — every operation, its authority and its arguments, generated from the registry; and the CLI grammar.
- Internals — the one atomic command transaction, the store, and the sealed consumption authority.
- Security & limits — read this before quoting any capability.