
# KYB — Know Your Business
**The shared memory your AI agent fleet keeps forgetting it needs.**
A git-backed, searchable knowledge base and incident tracker for fleets of AI agents.
Which servers exist, how services are wired, what broke and how it ended — the operational
truth that survives between sessions and across machines.
[**Website**](https://kybmemory.com) · By [Alexander Panasenko](https://prod.codes/about/)
[](https://crates.io/crates/knowyourbusiness)
[](LICENSE)
[](#stack--tests)
[](#stack--tests)
[](Cargo.toml)
[](#search-quality)
[](https://skills.sh/alex09x/kyb/kyb)
[**Quick start**](#quick-start) · [**How it works**](#how-it-works) · [**Incidents**](#incident-reports) · [**Agent skill**](#the-agent-skill) · [**MCP**](#mcp) · [**Web UI**](#web-ui-and-control-room) · [**HTTP API**](#http-api) · [**Citation**](#citation)
---
## Why
> Agents forget everything between sessions. Multiple agents — Claude Code, Codex,
> Antigravity — on multiple machines rediscover the same infrastructure over and over,
> and every hard-won incident lesson evaporates the moment the context window closes.
KYB is the shared memory that survives: which servers exist and what runs on them, how
services are wired, how they deploy, which decisions were made and why — plus **incident
reports**: what broke, the impact, how to live with it, and how it ended.
---
## How it works
- **Git is the canon — there is no database.** One markdown file (YAML frontmatter + body)
per entry — under `knowledge/`, `incidents/` or `tasks/` by kind — one commit per change,
**a commit sha is a version id**. History, diff and rollback come for free; the canon can
be read and edited with any text editor. (Flat pre-v2 canons migrate themselves on start.)
- **The tree holds only what is live.** Closing an incident or a task *archives* it: the
file leaves the working tree, but the final version — resolution included — stays in the
**default search**, in the listings and in `GET` (marked `archived`). Deleting plain
knowledge is a *retraction* and drops it from the default search. Git keeps everything
either way.
- **Tantivy is the index** — a disposable cache rebuilt from git on every start. It covers
the latest version of every key *and every historical version*, so agents can search what
the knowledge said before it changed (`--history`: "what moved where").
- **Search is hybrid** — BM25 fused (reciprocal rank) with vector search over every entry
(multilingual-e5-small, int8 ONNX, runs locally on CPU in ~5 ms). Ask in one language about
a base written in another and it lands; exact technical terms still rank first. No model on
disk → the service runs lexical-only.
- **Writes are upserts by key** — a no-op when content did not change. Flat key space plus
tags; no projects, no namespaces.
- **Secrets never enter the base** — writes are rejected if the body looks like a token, a
private key or a password. Store pointers instead.
### Architecture
[](docs/assets/kyb-architecture.png)
[View the full-size diagram](docs/assets/kyb-architecture.png).
Agents use the `kyb` CLI over HTTP. The Rust/axum server stores Markdown and history in Git,
then builds search state from that history. Tantivy provides BM25 search; an optional
multilingual-e5-small ONNX model supplies in-memory vectors. Reciprocal rank fusion combines
lexical and semantic candidates. All search modules run inside the server. Without a model,
lexical search still works.
---
## Incident reports
Incidents are first-class entries (`kind: incident`, keys prefixed `inc-`) with a lifecycle
`open → mitigated → resolved` and a rule: **closing requires a resolution** — an incident
that ends with "it just went away" teaches nobody anything. A report is a *control panel, not
a story*:
| Field | What it gives an agent |
|---|---|
| `detection` | an executable "is it still happening?" check, with the expected healthy result |
| `affected` | machine-readable poisoned windows `[{scope, from, to}]` — a backtest excludes them programmatically |
| `knowledge` | links to the knowledge entries the incident concerns |
| `resolution` | how it ended — searchable, so "how did we fix this last time" has an answer |
| timeline | `started/detected/mitigated/resolved_at`; the server stamps status transitions |
| follow-ups | `- [ ]` checkboxes in the body; the server counts them and warns when closing over them |
The server teaches structure instead of gating on it: a bare report is accepted but the
reply carries `hints` naming the missing actionable parts. `kyb incident --template` prints
the canonical skeleton.
Resolving archives the report: the canon stays clean (open things only), the record stays
searchable forever.
---
## Tasks
The third kind (`kind: task`, keys prefixed `task-`): short actionable notes and ideas with
the same close-with-an-outcome discipline and none of the incident ceremony.
- **Lifecycle** — `open → in_progress → blocked → done | dropped`. Only `done` and `dropped`
are terminal: they require a resolution ("dropped: obsolete after the rewrite" is knowledge
too) and archive the task. `in_progress` and `blocked` are work in flight — the task stays
live in the canon, in `kyb tasks` and in `open_tasks`.
- **`priority`** — optional, `low | medium | high | critical`; empty means unranked and stays
unranked, nothing infers one. Exact filter on `GET /tasks` and `/search`.
- **`blocked_reason`** — optional, what the task waits on. It belongs to `status: blocked`
only: setting it on any other status is rejected, and moving off `blocked` clears it, so a
task never reports a block it is no longer in.
- **`assignee`** — optional, who holds the task right now: a short public label (≤80 chars,
single line, secret-scanned like every other field). Empty means unclaimed and stays
unclaimed. Exact filter on `GET /tasks` and `/search`.
- **`parent_task`** — optional, the `task-` key this one hangs under; empty means top-level.
Must be a valid `task-` key and cannot create a parent cycle. A parent that does not exist
*yet* is allowed — a child can be filed before its parent — and is reported back as
`unknown_parent`.
- **Partial transitions** — `POST /tasks/{key}/transition` (`kyb task-status`) moves a task
between the live statuses and optionally changes its owner or parent **without resending
title, body, tags, priority or links**. Leaving `blocked` clears the reason; the terminal
statuses are refused with a pointer to `kyb done`, because closing demands an outcome.
Closing archives the task; `kyb tasks` and the search keep the full record. Every write is a
commit, so `kyb history