Skip to content
alex09xPublic

About

The shared memory your AI agent fleet keeps forgetting it needs. Git-backed knowledge base and incident tracker.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

KYB

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 · By Alexander Panasenko


crates.io license tests build rust search skills.sh


Quick start · How it works · Incidents · Agent skill · MCP · Web UI · HTTP API · 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

KYB architecture: AI agents, Rust server, Git history and derived BM25 plus optional vector search

View the full-size diagram.

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 <key> + kyb get <key> --at <sha> reconstruct who held a task, in which status, at any point in time.


Quick start

# install the server binary from crates.io
cargo install knowyourbusiness --locked

# run natively — reindexes from git on start, listens on 127.0.0.1:9310
kyb-server

Note: cargo install knowyourbusiness --locked installs the kyb-server binary. The separate kyb client CLI (skills/kyb/bin/kyb via bash skills/install.sh) and model setup for hybrid search remain separate steps (see CLI, The agent skill, and Search quality).

From repository checkout

# build and run from source
cargo run --release

# docker — data (git canon + index) lives in ./data
docker compose up -d

# remote private-network clients: bind one exact interface, never every NIC
KYB_PUBLISH_ADDR=10.0.0.10 docker compose up -d
Configuration — env, all optional
Variable Default Notes
KYB_DATA ./kyb-data git canon directory
KYB_INDEX ./index Tantivy cache (safe to delete)
KYB_ADDR 127.0.0.1:9310 no auth by design — run on a private network
KYB_PUBLISH_ADDR 127.0.0.1 Docker Compose host interface; set one exact private IP for remote clients
KYB_MODEL (unset) dir with model.onnx + tokenizer.json; absent = lexical-only
KYB_AUDIT audit.jsonl JSONL request log

CLI

skills/kyb/bin/kyb (installed to ~/.local/bin/kyb; point it anywhere with KYB_ADDR=host:port):

kyb query "nats streams" [--tag infra] [--history] [--recent] [--kind incident] [--status open] [--service X]
kyb query "nats streams" --as-of 2026-08-01      # the base as it stood then: one version per key,
                                                 # the value that was current, not today's
kyb query "" --changed-between 2026-08-01,2026-08-07   # what moved while you were away
kyb diff nats-streams [--from <rev>] [--to <rev>]      # what changed in it (defaults to the two newest)

# The three above ask a server-version-dependent question, so the CLI checks
# /healthz first and refuses rather than let an older server answer a question
# about the past with today's data.  Plain queries are unaffected.
kyb tags                                    # which topics the base covers
kyb add --key nats-streams --title "..." --tags nats,infra <<< "body"    # upsert by key
kyb get nats-streams [--at <sha>]           # current or any historical version
kyb history nats-streams                    # the whole chain of changes

kyb incident --template                     # print the report skeleton
kyb incident --key inc-2026-07-22-orders-api-oom --title "..." --service orders_api \
             --severity high [--hosts host-a] [--knowledge orders-api-architecture] \
             [--detection "check + healthy result"] \
             [--affected '[{"scope":"...","from":"...","to":"..."}]'] <<< "body"
kyb incidents [--status X] [--service X] [--open-followups] [--all]   # live by default, --all adds archived
kyb resolve inc-2026-07-22-orders-api-oom <<< "what fixed it"   # resolution is mandatory; closing archives

kyb task --key task-raise-log-retention --title "..." [--tags idea] \
         [--priority high] [--status in_progress] [--assignee agent-a] <<< "body"
kyb task --key task-swap-disk --title "..." --status blocked \
         --blocked-reason "waiting on the replacement disk" [--parent task-migrate-logs] <<< "body"
kyb task-status task-raise-log-retention --status in_progress --assignee agent-a   # partial: nothing resent
kyb task-status task-swap-disk --status blocked --blocked-reason "waiting on the disk"
kyb tasks [--status X] [--priority P] [--assignee A] [--parent K] [--open-followups] [--all]
kyb done task-raise-log-retention <<< "what came of it"         # or --status dropped + why

The agent skill

bash skills/install.sh installs, for every agent found on the machine:

  • the CLI and the MCP bridge (one copy of each on PATH),
  • the manual SKILL.md,
  • a pointer section in each agent's always-loaded global instructions — a skill an agent never opens is a skill it never uses.
Agent Skill path Pointer
Claude Code ~/.claude/skills/kyb CLAUDE.md
Codex ~/.codex/skills/kyb AGENTS.md
Antigravity ~/.gemini/config/skills/kyb GEMINI.md

Idempotent: sections are delimited by markers and updated in place. The skill encodes the governance that keeps a shared base alive — always query before adding, overwrite the same key instead of inventing synonyms, only verified facts, English entries, file incidents when something breaks and fold the lesson back into knowledge after resolving.

Alternatively, install the skill into any supported agent workspace using the open skills CLI:

npx skills add alex09x/kyb

MCP

The server speaks MCP itself, at POST /mcp. Nothing is installed on the client:

claude mcp add --transport http kyb http://<host>:9310/mcp

That is the whole setup. The tool list comes from the server, so upgrading the server is how every connected agent gets new tools — there is no client to update, and no version of a client that can disagree with the server about what exists.

A tool call is replayed through the same router that serves the public API, so there is one implementation of what POST /knowledge means, the audit log records MCP-driven writes like any other write, and a route that changes changes here with it.

It is stateless: no sessions, no server-initiated stream, GET and DELETE answer 405 and say so. Batches work. ?readonly=1 on the URL drops the six writing tools for that registration — a guard rail for an agent, not a security boundary, since nothing stops a caller from omitting it.

rm and reindex are not exposed over MCP at all: retracting an entry and rebuilding the index should not be something an agent reaches for mid-thought.

This adds no reach that the API did not already have. POST /knowledge and DELETE /knowledge/{key} already answer unauthenticated on this port; /mcp is exactly as open as they are and no more. Bind accordingly — see the note in src/config.rs.

It does not replace the CLI

A deploy script, an ssh one-liner on a fleet node, cron and a person at a terminal all need the CLI, and MCP reaches none of them. Anything that cannot speak MCP uses kyb.


Web UI and Control Room

kyb-server embeds a zero-dependency, dark-mode Web UI served directly at http://<host>:9310/:

  • Live Fleet Feed: real-time activity stream of agent reads, writes, and tool executions from the audit log.
  • Interactive Topology Graph: 2D force-directed map visualizing services, hosts, dependencies, and linked incidents.
  • Incident Control Center: status board (Open / Mitigated / Resolved), executable detection check commands, and mandatory post-mortem resolutions.
  • Agent Task Kanban: columns for Backlog, In Progress, Blocked (with reasons highlighted), and Closed tasks.
  • Knowledge Explorer: instant search, full Markdown document rendering, revision histories, and line-by-line visual git diffs.

HTTP API

Method Path What it does
GET / Web UI & Control Room (zero-dependency single-page application)
GET /api/audit ?limit=50 — recent operations from the JSONL audit log, newest first
GET /knowledge/{key}/diff ?from=<sha>&to=<sha> — unified git diff between two revisions
POST /knowledge upsert by key. Body: {key, title, body, tags?, refs?} → {key, sha, changed, action}. Identical content = changed:false, no commit
GET /knowledge/{key} the entry (kind-specific fields included); archived incidents/tasks come back with archived:true; ?at=<sha> returns a version from history
GET /knowledge/{key}/history {key, versions:[{sha, committed_at, message, change}]}, newest first
POST /incidents upsert a report; reply carries unknown_knowledge for dangling links and hints for missing structure. status:resolved requires resolution and archives
GET /incidents ?status=&service=&followups=open&all=true&limit= — live reports by default, open first, freshest on top; all=true, an explicit status= or followups=open include archived ones
POST /incidents/{key}/resolve {resolution, status?=resolved} — flips status, records the outcome, stamps the timeline, archives on close
POST /tasks upsert a task: {key, title, body, status?, priority?, blocked_reason?, assignee?, parent_task?, knowledge?, resolution?, tags?, refs?}; status is open|in_progress|blocked|done|dropped, priority is ""|low|medium|high|critical, blocked_reason requires status:blocked (400 otherwise), parent_task must be "" or another task- key and cannot create a cycle; the terminal statuses require resolution and archive
GET /tasks ?status=&priority=&assignee=&parent_task=&followups=open&all=true&limit= — live tasks (open, in_progress, blocked) by default, freshest on top; same archive rules as /incidents
POST /tasks/{key}/transition partial update: {status, assignee?, parent_task?, blocked_reason?} — status is a live one (open|in_progress|blocked); omitted fields keep their stored value, leaving blocked clears blocked_reason, and a terminal status is refused with a pointer to /tasks/{key}/resolve
POST /tasks/{key}/resolve {resolution, status?=done} — flips status, records the outcome; a non-blocked status clears blocked_reason; done/dropped archive
GET /search ?q=&tag=&history=&limit=&sort=recent&kind=&status=&service=&priority=&assignee=&parent_task= → ranked hits with full bodies; an empty q lists newest first
GET /tags which topics the base covers, most used first
DELETE /knowledge/{key} knowledge: retract (drops from the default search); incident/task: archive (stays searchable)
POST /reindex full index rebuild from git
GET /healthz {ok, entries, open_incidents, open_tasks, index_docs, last_commit} — open_tasks counts every live status (open, in_progress, blocked)

Every request except /healthz is appended to a JSONL audit log: timestamp, client ip, method, path, query, status, duration.


Search quality

scripts/eval-search.sh [addr] [--lexical] measures retrieval: paraphrase questions that share few or no tokens with the entries they should land on, scored top-1 / top-3.

Measured 2026-09-22 against a 643-entry base, e5-small int8, round trip over LAN:

setup top-1 top-3 latency
lexical only 1/14 2/14 ~7 ms
+ e5-small int8 (118 MB, shipped) 3/14 4/14 ~9 ms

Read the absolute numbers with care and the comparison as the point. The fourteen questions that ship in the script are examples written against a different base — three of the keys they expect do not exist in the base measured above, so those cases cannot be hit by either mode and the ceiling is well under 14/14. What the run does establish, on identical cases against an identical base, is that the vector side roughly doubles the hit rate over BM25 alone.

Replace CASES in the script with questions and keys from your own base before drawing any conclusion about your own setup. An earlier edition of this table reported 9/14 and 12/14 for the hybrid row; those numbers came from the base the example questions were written for and do not reproduce elsewhere, which is exactly the trap this paragraph exists to flag.

e5-small ships in the Docker image (MODEL_REPO build arg to swap). Without a model on disk the service degrades to lexical-only by design.


Stack & tests

Rust: axum + tantivy 0.22 + git2 + ort (ONNX Runtime). A single write mutex (Tantivy allows one IndexWriter and git commits are sequential anyway); reads are lock-free.

cargo test        # 321 cases

CI builds the image and smoke-tests that the container starts and answers /healthz.


Citation

For academic or archival reference, cite the published v0.2.3 release:

@misc{panasenko2026kyb,
  doi = {10.5281/zenodo.23027682},
  url = {https://zenodo.org/records/23027682},
  author = {Panasenko, Alexander},
  title = {KYB (Know Your Business): Git-backed knowledge base and incident tracker for AI agents},
  version = {0.2.3},
  publisher = {Zenodo},
  year = {2026}
}

License

MIT

About

The shared memory your AI agent fleet keeps forgetting it needs. Git-backed knowledge base and incident tracker.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages