Remote sync
knomit is a distributed, multi-machine knowledge base. Each machine writes only
its own agent branch (agent/<hostname>-<key-fingerprint8>); main is
consensus. Syncing with a git remote (“origin”) is how agent branches reach and
absorb that consensus. Configure it from the web UI (the gear icon in the top
bar) or with environment variables.
The consensus model
Section titled “The consensus model”- This machine is the sole writer of its agent branch.
main(or whatever the upstream branch is named) is never written directly by the MCP server.- Reconciliation is merge-based, not replay-based: in steady state the agent
branch merges
mainwith a local-wins strategy (the local agent’s edits win overlapping paths), producing a fast-forward, a no-op, or a single merge commit — O(1) regardless of how many commitsmainabsorbed. - A zero-diff merge is reported as a no-op and never creates a merge commit, which is what makes squash-merged upstreams fast-forward correctly.
Auth methods
Section titled “Auth methods”| Method | URL format | Credentials |
|---|---|---|
| SSH | git@github.com:user/repo.git | knomit’s SSH key (~/.knomit/id_ed25519); auto-detected for git@… / ssh:// URLs |
| Token (GitHub PAT, etc.) | https://github.com/user/repo.git | a PAT, sent as HTTP basic auth with username x-token; auto-detected for https:// URLs with a token present |
| Basic | https://github.com/user/repo.git | user:password, stored in one field and split on the first colon |
| None / anonymous | any public URL | no credentials (public repos) |
The auth_method is inferred from the URL and fields when left empty.
SSH host-key verification
Section titled “SSH host-key verification”SSH remotes verify the server’s host key against an OpenSSH known_hosts file,
on a trust-on-first-use model:
| Situation | Behaviour |
|---|---|
| Host not yet listed | Accepted, and its key is appended to known_hosts |
| Host listed under the same key | Accepted |
| Host listed under a different key | Rejected — the sync fails |
known_hosts unusable (bad path, unparseable) | Hard error; there is no insecure fallback |
The file is set by [remote] known_hosts / KNOMIT_REMOTE_KNOWN_HOSTS and
defaults to <KNOMIT_HOME>/known_hosts; point it at ~/.ssh/known_hosts to
share pins with your shell’s git. It is re-read on every connection, so an edit
takes effect without a restart. Each first-use pin is logged with the key’s
SHA-256 fingerprint — verify that fingerprint out-of-band if the remote is
untrusted. See Configuration.
Credential encryption
Section titled “Credential encryption”Auth tokens are never stored in plaintext. They are encrypted with
AES-256-GCM using a key derived (HKDF-SHA256) from the agent’s SSH private key
(id_ed25519), and stored base64-encoded in control.db’s repo_origins
table — one machine-local control plane for every repo, not a copy per repo
database. If the SSH key is unreadable at boot, the encryptor is unavailable and
setting a remote refuses to persist any auth token rather than writing it in
the clear. (Legacy plaintext values are still read, with a warning.)
This is one of several guarantees that hold at the sync boundary — the local origin gate and host-key pinning above are the others. See Security & privacy for how they fit together.
The origin record
Section titled “The origin record”An origin is attached at runtime — when the repo is created from a remote
(below), through the origin REST endpoints, or through the web UI’s “Connect
Remote” flow. There is no config key or environment variable for the URL: config
holds only the credentials used to reach it ([remote] in knomit.toml, or the
KNOMIT_REMOTE_* variables). The stored record carries:
| Field | Meaning |
|---|---|
URL | Git remote URL |
Branch | Upstream consensus branch — default main, configurable (e.g. master); changeable later via PATCH /origin/upstream |
Interval / PushInterval | Pull / push cadence (seconds); a single reconcile loop ticks at the shorter of the two and does both |
AuthMethod | token · basic · ssh · none (inferred if empty) |
AuthToken | Encrypted at rest (see above) |
LastSyncAt / LastStatus / LastError (+ push variants) | Last-outcome bookkeeping, surfaced through the gear-icon indicator |
On clone/init, knomit prefers main over a remote agent-branch HEAD as the
consensus upstream, and syncs both the agent branch and the upstream.
PATCH /origin/upstream takes a {"branch": "…"} body and changes only the
upstream branch — auth is untouched and the connect flow is not re-run. The
branch is validated against a conservative subset of git’s ref-name rules
(control characters, spaces, ~ ^ : ? * [ \, a leading - or /, a trailing
/, and the .. / @{ sequences are rejected with 400). The reconcile loop
re-reads the record each tick, so the change takes effect on the next cycle.
Local-path origins (security gate)
Section titled “Local-path origins (security gate)”Cloning from a local filesystem path is disabled by default. To allow it, set
local_origin_root / KNOMIT_LOCAL_ORIGIN_ROOT to an absolute directory; a local
origin is then accepted only if it resolves (symlinks evaluated) to a path
inside that root. Network origins (https://, ssh://, git://,
git@host:path) always pass. Every clone/fetch path is gated — there is no
trusted exemption.
Connecting a remote at runtime
Section titled “Connecting a remote at runtime”The “Connect Remote” flow is session-based and ephemeral (≈10-min idle expiry):
POST /repos/{repo}/origin-sessionswith the URL + credentials → a session.GET …/testchecks connectivity;GET …/previewshows one merge summary — counts of local-only / remote-only / shared-path / dead-ref facts. The conflict strategy is not a preview input; it is an argument to apply.POST …/applythen…/commitperforms the merge, takingconflict_strategyin the apply body.
…/test, …/preview, …/apply and …/commit are SSE streams
(text/event-stream), each ending in a done event carrying its result. The
session endpoints below serve application/hal+json, and errors as
application/problem+json:
| Endpoint | Response shape |
|---|---|
GET /repos/{repo}/origin-sessions | HAL collection — a count plus the sessions under _embedded.sessions, not a bare JSON array |
POST /repos/{repo}/origin-sessions | session_id plus _links |
GET …/origin-sessions/{sessionID} | Session detail (state, url, and any history / last_preview / last_apply) plus _links |
Disjoint histories (e.g. reconnecting after a token refresh against a repo with no shared history) are reconciled via a cursor replay with one-level dead-ref resolution.
Background reconcile
Section titled “Background reconcile”A per-repo reconcile loop re-reads the remote config fresh every tick, runs an
initial synchronous reconcile first (so credential errors surface immediately),
and escalates its log level WARN→ERROR after five consecutive failures. Sync uses
a compare-and-swap watermark on last_commit.
Pull and push share one loop rather than running on separate timers: every tick
syncs, then pushes (push is skipped in read-only mode). The loop interval is the
shorter of Interval and PushInterval — a non-positive PushInterval is
ignored, and if neither yields a positive value the interval falls back to 300s.
Serving git over HTTP
Section titled “Serving git over HTTP”Set KNOMIT_GIT_SERVE (default on, optionally with KNOMIT_GIT_PORT) to
expose a git smart-HTTP endpoint at /git, so other knomit instances or git
clients can clone and fetch directly from this server.
This endpoint is also what knomit-okf reads: the KB URL you hand
clone is <server>/git/<repo>, and it is unauthenticated, so an export against
your own server needs no credentials.
Creating a repo from a remote
Section titled “Creating a repo from a remote”Create a repo directly from a remote through the REST API, in one of two modes.
They are the two halves of a single question about the branch you name — does
it already carry .knomit/ontology.yaml? — which
POST /api/v1/repos:probe-initialized answers.
clone joins a branch that is already a knowledge base. Its ontology
governs, and passing ontology_preset or ontology_yaml is refused rather than
silently dropped:
curl -N -X POST http://localhost:19278/api/v1/repos \ -H 'Content-Type: application/json' \ -d '{"name":"work","mode":"clone", "origin":{"url":"git@github.com:you/kb.git","branch":"main"}}'initialize turns a branch that is not a knowledge base into one. It
clones the branch, cuts agent/<host> from its tip, commits the chosen
ontology there, and pushes that branch alone. An
ontology_preset or ontology_yaml is required — writing that file is the
act that makes the branch a knowledge base, so without one there is nothing to
write:
curl -N -X POST http://localhost:19278/api/v1/repos \ -H 'Content-Type: application/json' \ -d '{"name":"work","mode":"initialize","ontology_preset":"code", "origin":{"url":"git@github.com:you/project.git","branch":"main"}}'Neither mode mints a new identity: the repo id is the remote’s existing root commit, so two machines pointed at the same remote converge on one identity instead of splitting into two.
See the REST API reference for the full repo-management surface.