Quick Start

Ten minutes from nothing to a value that explains itself. Use the xkv command first to see the idea, then the Go package to build it into a program.

1. Get the Command

Download the archive for your system from the download page, unpack it, and put xkv on your PATH. Check it runs:

xkv version

2. Write a Chain

Three services each write one value. Each names what it acted on with --cause.

xkv put limits.xkv chargeback:cb_991 "card dispute filed" \
    --reason "bank notice" --ref BANK-7731 --actor billing-worker

xkv put limits.xkv risk:42:score 0.82 \
    --reason "new chargeback" --cause chargeback:cb_991 --actor risk-model

xkv put limits.xkv account:42:limit 50 \
    --reason "risk policy v3" --cause risk:42:score --actor limits-service

--cause KEY points at the key’s current revision. --cause KEY@REV points at one revision by number. Leave out --reason and the write is refused: that’s strict mode, on by default.

3. Ask Why

xkv why limits.xkv account:42:limit
account:42:limit = 50   r3 · 2026-10-19 09:20:41
│ by limits-service · risk policy v3
└─ risk:42:score = 0.82   r2 · 2026-10-19 09:20:41
   │ by risk-model · new chargeback
   └─ chargeback:cb_991 = card dispute filed   r1 · 2026-10-19 09:20:41
        by billing-worker · bank notice · ref BANK-7731

Add --json for the same tree as JSON. xkv log limits.xkv risk:42:score lists a key’s revisions, newest first, and xkv get limits.xkv account:42:limit --at 2 shows a value as it stood at revision 2.

4. Use It From Go

go get github.com/explainable-kv/xkv
package main

import (
	"context"
	"log"
	"os"

	"github.com/explainable-kv/xkv"
)

func main() {
	db, err := xkv.Open("limits.xkv", nil)
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	// Name the actor once; every write made with ctx records it.
	ctx := xkv.WithActor(context.Background(), "limits-service")

	_, err = db.Put(ctx, "account:42:limit", []byte("40"),
		xkv.Reason("second chargeback this quarter"),
		xkv.CauseLatest("risk:42:score"))
	if err != nil {
		log.Fatal(err)
	}

	tree, err := db.Why(ctx, "account:42:limit", 0)
	if err != nil {
		log.Fatal(err)
	}
	tree.WriteText(os.Stdout)
}

Close the command-line session’s file first, or open it read-only from Go with &xkv.Options{ReadOnly: true}: one handle writes to a file at a time.

5. Write Several Keys at Once

Update runs a transaction. Every write inside commits, or none does, and the options given to Update apply to each write.

err := db.Update(ctx, func(tx *xkv.Tx) error {
	if _, err := tx.Put("price:basic", []byte("9")); err != nil {
		return err
	}
	_, err := tx.Put("price:pro", []byte("33"))
	return err
}, xkv.Reason("Q4 price list"), xkv.Ref("PRICE-12"))

6. From Other Languages

Run the store as a small HTTP service on localhost:

xkv serve limits.xkv
curl -X PUT localhost:7070/v1/keys/account:42:limit \
  -d '{"value": "45", "reason": "manual review", "causes": [{"key": "risk:42:score"}]}'
curl localhost:7070/v1/why/account:42:limit

Where Next