kyb

mcp
Guvenlik Denetimi
Basarisiz
Health Gecti
  • License — License: MIT
  • Description — Repository has a description
  • Active repo — Last push 0 days ago
  • Community trust — 80 GitHub stars
Code Basarisiz
  • rm -rf — Recursive force deletion command in .github/workflows/update-homebrew.yml
  • rm -rf — Recursive force deletion command in scripts/deploy.sh
  • network request — Outbound network request in scripts/dev.py
  • rm -rf — Recursive force deletion command in scripts/package-macos-arm64.sh
Permissions Gecti
  • Permissions — No dangerous permissions requested

Bu listing icin henuz AI raporu yok.

SUMMARY

Git-backed shared memory for AI agent fleets: searchable knowledge base, incident and task tracking, every write versioned and attributed. CLI, HTTP API and MCP server.

README.md
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 where to listen; with KYB_AUTH=off anyone who can reach it can write
KYB_AUTH off off trusts the network, compat honours a token but does not ask for one (for rollout, on a trusted bind — it protects nothing), required refuses callers without one
KYB_AUTH_FILE ./auth.json (Docker: /data/auth.json) the token book; keep it on persistent storage beside the data, never inside kyb-data
KYB_ANON (unset) read: with KYB_AUTH=required, callers without a token may read (a public demo)
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. The
                                                 # snapshot is chosen first and searched second: a
                                                 # superseded version never answers for its key,
                                                 # and knowledge deleted by then is absent
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   # Claude Code
codex mcp add kyb --url http://<host>:9310/mcp              # Codex
agy mcp add -t http kyb http://<host>:9310/mcp              # Antigravity
gemini mcp add -t http kyb http://<host>:9310/mcp           # Gemini CLI

Cursor, Windsurf, VS Code and any other client that speaks MCP over HTTP take the same URL in
their own configuration file, in that client's schema (VS Code, for instance, lists it under
servers with type: http). That is the whole setup on the server side; attribution of writes
made over MCP depends on what the client sends - see Attribution. 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. The boundary is the token: /mcp passes the caller's
Authorization header to every tool call it replays, so a tool can do exactly what the same
token can do over REST.

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.


Access: tokens and roles

Out of the box KYB asks nobody for anything (KYB_AUTH=off) and relies on the network it
listens on. To know who wrote what, and to let some callers only read, turn tokens on.

Tokens tell callers apart; they do not hide traffic. KYB itself speaks plain HTTP, and a
token sent over plain HTTP can be read and replayed by anyone who can watch the network. On a
network you trust against eavesdropping (loopback, a private LAN, a VPN) that is enough. To
serve anyone beyond it, put a TLS-terminating proxy in front and point clients at it with
KYB_URL=https://kyb.example.

With Docker Compose the three settings are passed through from .env (KYB_AUTH, KYB_ANON,
KYB_AUTH_FILE), and the book defaults to /data/auth.json - on the persistent volume, outside
the git canon (data/ and auth.json are in .gitignore; the server refuses to start with a
book inside KYB_DATA). Issue tokens with docker compose exec kyb kyb-server auth add .... The server
prints the mode in force when it starts (kyb: auth required - 3 principals in ...); check that
line, or kyb whoami, rather than assuming.

# on the server, next to the data (the book is the root of trust; it is not in git)
kyb-server auth add --id alex    --name "Alex"    --role admin
kyb-server auth add --id agent-a --name "Agent A" --role write --label build-box
kyb-server auth list                 # ids, roles, token ids — never tokens
kyb-server auth token  --id agent-a  # a second token: rotate with an overlap
kyb-server auth revoke --token t_1a2b3c4d      # or --id agent-a for the whole principal

# on each client
echo "$TOKEN" | kyb login            # checked against the server, then kept in ~/.config/kyb/token (0600)
kyb whoami

A token is 256 random bits, shown once; the book keeps its SHA-256. Changes to the book take
effect without a restart. Three roles, each including the one before:

Role May
read search, read, history, diffs, relations
write add and edit entries; file, resolve and archive incidents and tasks
admin retract a knowledge entry (kyb rm), reindex, read the raw request log (/api/audit: client addresses and everyone's queries)

The git commit a write produces is authored by the token's principal, so kyb history and the
Versions tab say who changed what. A commit made without a token (before tokens, or under
off/compat) names nobody. This is attribution, not proof: anyone with direct access to the
repository can write any author. Rolling out: start with KYB_AUTH=compat on the trusted bind,
give every agent a token, watch the actor field of the audit log until no - remains, then
switch to required. Never leave compat on an address reachable from outside: it lets a
caller through by leaving the token out. A public read-only demo is its own instance, with its
own synthetic data, started with KYB_AUTH=required KYB_ANON=read. The web UI asks for a token when the server does and hides what the
token's role would be refused.

Attribution: which agent wrote what

Every write is committed with trailers (Kyb-Agent, Kyb-Session, Kyb-Workspace, Kyb-Via)
naming the agent, its session and the workspace it ran in, as far as the client could tell; the
history and activity endpoints return them as by, and the console shows them on each version.
The CLI reads them from the environment the agent runs it in; for the MCP endpoint a Claude Code
hook adds them to each call as _ctx (taken out of the arguments before the API sees them), and
Antigravity's and Codex's own call metadata is read when there is no hook. Headers for any
client: X-Kyb-Agent, X-Kyb-Session, X-Kyb-Workspace, X-Kyb-Via. Workspaces are a
repository name and a path inside it, never an absolute path. Attribution is forensics, not
identity: it grants nothing (tokens do that) and GET /whoami echoes what a request claims.

Web UI and Control Room

kyb-server serves a zero-dependency, dark-mode web console at http://<host>:9310/, assembled
from src/web/css and src/web/js at compile time and driven end to end by a browser test in CI:

  • Queues, not boards: incidents and tasks are attention queues — what needs a person now, with
    the reason in the row (blocked and why, open with no detection check, stale for how long).
  • Versions: an entry's own ledger — every version, newest first, with the whole of what it
    changed and, when the client supplied it, who wrote it (by: agent, session, workspace); any
    version readable as it was first written.
  • Ledger: activity as a change ledger showing the whole change; each row names the author when
    tokens identify one, otherwise the agent and workspace when supplied, otherwise unknown.
  • Services and hosts: for each service or host an incident names, what is open on it right
    now, the notes those incidents cite and the open tasks pointing at them. Every row says why it
    is listed.
  • Related entries: each card lists what it refers to and what refers to it, taken only from
    typed fields (knowledge, parent_task) and internal links in the text ([[key]],
    [text](key)). Shared tags are a filter, not a relation.
  • Knowledge explorer: instant search, #tag filters, deep links, full Markdown rendering.
  • Tokens: when the server asks for one, the console asks you and hides what your role cannot do.

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
GET /knowledge/{key}/related what the entry refers to and what refers to it, each with how (knowledge, parent_task, link)
GET /places services and hosts named by incidents, with open and total counts
GET /impact ?service=<name> or ?host=<name> — incidents on it, the notes they cite, the open tasks pointing at them
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        # 421 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.3.0 release:

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

License

MIT

Yorumlar (0)

Sonuc bulunamadi