data-olympus
Memory serverOfficial Knaisoma project
Created by Knaisoma
Published in knaisoma/data-olympus. Publisher page
30 GitHub stars
What it is
Governance-grade, OKF-compatible knowledge-base format with a single-writer MCP server and CLI. Pre-release.
Summary from the brain’s metadata
Governance-grade, OKF-compatible knowledge-base format with a single-writer MCP server and CLI. Pre-release. New here? Start with WHY.md. It is the story behind the project: the problem we kept hitting with coding agents, what data-olympus does differently, how it relates to Google's Open Knowledge Format, and where our benchmarks say it is strong and where it is not. The rest of this README is the technical reference.
How it works
Memory model: Markdown vault
AutoGineer overview, from the brain’s own files
Memory model: Markdown vault. Folders: .github (agent config, 20 files); bin (scripts, 7 files); docs (docs, 47 files); scripts (scripts, 29 files); src (source code, 81 files); tests (tests, 286 files). The agent reads .rules/versioning.md, example-bundle.
| Folder | Role | Files |
|---|---|---|
| .github | agent-config | 20 |
| bin | scripts | 7 |
| docs | docs | 47 |
| scripts | scripts | 29 |
| src | source-code | 81 |
| tests | tests | 286 |
| .rules | folder | 5 |
| benchmarks | folder | 328 |
| deploy | folder | 19 |
| example-bundle | folder | 27 |
| release | folder | 2 |
The agent reads
- .rules/versioning.md evidence: AGENTS.md
- example-bundle evidence: README.md
Requirements
Runtimes: Python >=3.13
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Google Antigravity, Kiro, OpenCode, Amp, Roo Code, Cline, JetBrains Junie, Devin (ex-Windsurf)
Set it up
- Not scanned by AutoGineerIts dependencies are installed by pip or uv when you run it and were not scanned by AutoGineer (5 declared). https://pypi.org/project/data-olympus/
- Not scanned by AutoGineerAutoGineer could not scan everything it runs. https://pypi.org/project/data-olympus/
Created by Knaisoma. Published in knaisoma/data-olympus. Licence: Apache License 2.0.
Usage
From the brain’s own files
- Requires Python 3.13+ and uv.
- Run the stable CLI directly from PyPI:
- Install it persistently when you are ready to create a bundle and run the server:
- An announced candidate remains opt in through its exact PyPI version.
- Replace X.Y.ZrcN with the candidate named on the releases page, if any:
- See release channels for what each channel means, how to verify a candidate before adopting it, and how to roll back.
- See docs/quickstart.md for bundle initialization, server startup, readiness, agent registration, and the contributor source installation.
- See docs/adoption.md for the full bundle authoring guide.
Documentation by Knaisoma
data-olympus
Image: knaisoma/data-olympus MCP server Image: MCP Marketplace
<!-- mcp-name: io.github.knaisoma/data-olympus -->
New here? Start with WHY.md. It is the story behind the project: the problem we kept hitting with coding agents, what data-olympus does differently, how it relates to Google's Open Knowledge Format, and where our benchmarks say it is strong and where it is not. The rest of this README is the technical reference.
data-olympus is a governance-grade knowledge-base format and server for agent workforces. It is readable by Open Knowledge Format (OKF) v0.2 consumers: it inherits OKF's directory structure, frontmatter conventions, reserved filenames, and link model, then layers governance extensions on top (stable id, controlled type/status/tier fields, supersedes chains) plus a single-writer MCP server and a CLI. CI proves two concrete directions against official Google OKF v0.2 commit ad30107c31c06aec8a7d5636e0d1058118604e6f: its reference visualization consumer reads every concept in example-bundle, and data-olympus imports, lints, indexes, searches, and retrieves the pinned official Bitcoin sample. This is fixture-scoped interoperability evidence, not a blanket guarantee for every OKF bundle or future upstream revision. The result is a git-native, version-controlled document graph of engineering standards, architectural decisions, and project knowledge that agents and humans can read, search, and extend without any proprietary service.
It governs decisions, not code. When an agent is about to make a choice (a library, a pattern, a migration), data-olympus surfaces the established standard or decision that should govern that choice. It is deliberately not a code-search, reference-finding, or "where is X used" tool: LSP, grep, and Sourcegraph already do that well. The retrieval task it targets is coding-intent to governing-rule, and it helps where current model interaction during vibe-coding is weakest: keeping the model aligned to patterns the team has already established as correct.
Status: pre-1.0 beta. Stable releases are distributed through PyPI and GHCR.
Why
Portable, no lock-in. The entire KB is a directory of markdown files in git. No database, no proprietary schema, no vendor.
Git-native diffs and review. Every change is a commit. Proposed edits go through a pending queue before commit; history is plain git log.
Agent and human readable. Plain markdown with YAML frontmatter. No SDK required to read or author a document.
Governed multi-agent writes. The single-writer MCP pipeline (advisory locks, per-session worktrees, durable push queue) prevents concurrent write races without requiring distributed locking infrastructure.
Queryable by status, tier, and type. Filter by
status: accepted,tier: T1, ortype: decisionwithout post-processing. Thesupersedeschain makes it possible to trace decision history across the graph.Tested with official OKF tooling. CI pins an exact Google OKF revision and proves both consumption directions over committed fixtures. The pin, fixture checksum, and Apache 2.0 license provenance live in
tests/okf/reference.json.
Quickstart
Requires Python 3.13+ and uv. Run the stable CLI
directly from PyPI:
uvx --from data-olympus data-olympus --helpInstall it persistently when you are ready to create a bundle and run the server:
uv tool install data-olympus
data-olympus init my-kb
data-olympus-mcp --helpAn announced candidate remains opt in through its exact PyPI version. Replace
X.Y.ZrcN with the candidate named on the
releases page, if any:
uvx --from 'data-olympus==X.Y.ZrcN' data-olympus --helpSee release channels for what each channel means, how to verify a candidate before adopting it, and how to roll back.
See docs/quickstart.md for bundle initialization, server startup, readiness,
agent registration, and the contributor source installation.
See docs/adoption.md for the full bundle authoring guide.
Documentation
SPEC.md: format specification (bundle layout, frontmatter schema, serving contracts).docs/quickstart.md: verified local-run procedure.docs/adoption.md: bring-your-own-KB guide (author, lint, index, serve, wire an agent).docs/serving.md: single-replica serving model, read-only replicas, git pull loop, health/readiness/liveness split, proxy headers, audit-log rotation.docs/operations.md: production runbook: backup, upgrade, recovery playbooks (degraded/fetch-failed, history rewrite, frozen/demoted push entries, orphaned locks), the health/alerting model, and release channels (stable and candidate, verification, rollback).docs/comparison.md: how data-olympus relates to OKF, enterprise catalogs, markdown KB tools, agent-context conventions, RAG, and ADR tooling.docs/okf-profile.md: field-by-field OKF profile: which governance extensions are stable, which are runtime-only serving fields, and which are experimental candidates.docs/glama.md: Glama registry claim, release, and score-maintenance notes.docs/mcp-registry.md: official MCP Registry notes, whatserver.jsondeclares, and the checklist for publishing.docs/enforcement.md: turning the KB into a mandatory consultation gate (hooks,kb enforce).benchmarks/README.md: retrieval benchmark methodology and how to reproduce the numbers indocs/comparison.md.SECURITY.md: supported versions and how to report a vulnerability.
License
docs/serving.md
Serving model
This document describes the normative serving model for data-olympus. It
summarizes SPEC.md section 8; for the authoritative text, see that section.
Single replica, streamable HTTP
A write-enabled data-olympus server runs as a single replica and exposes MCP over streamable HTTP.
The write path is single-writer by design:
Per-path advisory locks prevent two concurrent write operations from racing on the same concept file.
Per-session git worktrees isolate in-flight proposed edits from the live index until they are committed.
A durable push queue serializes outbound git pushes so no write is lost if the remote is briefly unavailable.
A single shared HTTP surface, rather than N independent stdio processes, gives every agent one synchronized conversation with the server. When multiple agents each run their own stdio MCP process against the same git working tree, they race each other's worktrees and lock state. Streamable HTTP eliminates that race.
The long-lived SSE stream (proxy and session tuning)
Each MCP client holds a long-lived GET /mcp Server-Sent-Events stream to
receive server-to-client messages. Two things must be configured so that stream
does not churn:
Ingress/proxy read timeout. A proxy that closes the idle stream (nginx's default is 60s) forces the client to reconnect on that cadence. Each reconnect creates a new transport server-side and, in a persistent client like
opencode serve, leaks event listeners. Set a long read timeout and disable response buffering on the/mcppath (seedeploy/k8s/ingress.yamlfor the nginx annotations).Session reaping. The MCP SDK only expires a transport a client leaves behind after 30 minutes without a request. The server runs its own reaper (
session_metrics): the activity middleware keeps a session non-idle for as long as its SSE stream is open, andKB_SESSION_IDLE_TIMEOUT_SEC(default300) terminates a session only after its stream has actually closed.KB_SESSION_TOUCH_INTERVAL_SEC(default30, clamped to a third of the idle window) sets how often an in-flight stream is re-stamped.kb_health//api/v1/healthand a periodic log surface the live session count.
Core configuration reference
A few server-wide settings, beyond the feature-specific env vars documented in their own sections below (search ranking, synonyms, embeddings, co-occurrence, trigram, auth, audit rotation):
KB_MAIN_PATH: the knowledge-base corpus to index and serve (default/kb-main).KB_INDEX_PATH: where the SQLite index is written (default/index/kb.db). Both treat a blank value as unset. A variable that is set but empty, or whitespace-only, -- what an unsubstituted compose/Helm/CI variable produces -- gets the documented default, not the server's working directory. A non-blank value is used verbatim and never rewritten. IfKB_MAIN_PATHdoes not resolve to a directory, startup fails with a message naming the setting, the path it resolved to, and whether that path came from the environment or the built-in default. An existing but empty corpus is not an error: the server starts, builds the schema, and reportsdegradeduntil content arrives.KB_GIT_BRANCH: the branch of the knowledge-base repository the server syncs (defaultmain). It is the branch fetched and fast-forwarded by the pull loop, the branch a session worktree is rebased onto before a write, the branch writes are pushed to, and the branch a worktree's commits must be reachable from before that worktree can be garbage collected. Set it tomaster, or to whatever your repository actually calls its trunk, and check that same branch out inKB_MAIN_PATH: the fast-forward merges by ancestry rather than by name, so a checkout left on an unrelated branch is the case that fails and leaves health degraded. A blank value means unset and gets the default. Accepted names are ASCII letters, digits,.,_,-and/, subject to git's structural rules (no component beginning with.or ending.lock, no..or//, no trailing/or.);HEADand a fully qualified ref such asrefs/heads/masterare refused, as is anything else outside that set. A rejected value fails startup with the setting and the reason named, rather than being passed to git.KB_HTTP_PORT: TCP port the MCP HTTP server binds (default8080).KB_CONFIDENCE_THRESHOLD: the auto-commit confidence cutoff, in[0, 1](default0.85). A proposal at or above it from a principal holdingauto_commitis committed; below it (or from a principal without that capability) it is parked as pending. An out-of-range value fails startup loudly.KB_PENDING_TIMEOUT_SEC: age after which an unresolved pending proposal is auto-expired by the pending GC loop (default86400, i.e. 24h). Each expiry emits an audit event.KB_WRITING_RULES_MODE: how the writing-rule gate treats the lines a write adds (defaultwarn, which commits and reports the findings).enforcerejects such a write asrejected_writing_rule, andoffskips the check. Any other value fails startup with the accepted values named. See "Write serialization and integrity gates" below.KB_WRITING_RULES_EXCLUDE_PATHS: comma-separatedfnmatchglobs of target paths the writing-rule gate skips, matched likeKB_WRITE_BLOCK_PATHS(default empty).KB_SECRET_SCAN_EXTRA_PATTERNS: additional regexes the secret-scanning gate (issue #71) checks alongside its built-in pattern set (see "Write serialization and integrity gates" above). Write it either as a JSON array of strings (["ACME_[A-Z0-9]{20,40}", "INTERNAL-[0-9]{6}"]) or comma-separated. In the comma form a comma inside{...}or[...]does not separate patterns, soACME_[A-Z0-9]{20,40}stays one pattern, and a backslash-escaped comma (\,) never separates. A value that parses as a JSON array is read as JSON. Each entry is scanned as its own named pattern (custom_1,custom_2, ...). The setting is parsed once at startup: an entry that is not a non-empty string, is not a valid regex, or has the classic nested-quantifier ReDoS shape fails startup with an error naming the setting and the entry number (not the pattern text). Every accepted pattern runs with a hard 1-second match timeout. Empty by default (no extra patterns).KB_DISABLE_VERSION_CHECK:1,true,yes, orondisables the public PyPI/GitHub version check completely. Use this for air-gapped deployments.0,false,no, oroffkeeps it enabled, as does leaving the setting unset or empty (the default). Values are case-insensitive and surrounding whitespace is ignored; any other value stops startup with an error naming the setting, so a typo cannot leave the outbound check running.KB_VERSION_CHECK_INTERVAL_SEC: how often the background task refreshes the cached latest-version result (default86400, i.e. 24h). The health request path never performs the outbound lookup.KB_GOVERNED_LANE_PROTECTION: governed-lane write protection (issue #112), defaulton; setoffto restore the exact pre-#112 behavior (see "Governed-lane write protection" below).KB_GATE_CLEARANCE(defaultintent): what a fresh explicit consult clears at the enforcement gate.intentclears only the governed actions the consult's intent covers;pairrestores the earlier rule, under which any fresh explicit consult for the session and workspace clears every governed action. Any other value fails startup naming the setting. Seedocs/enforcement.mdfor the coverage rule and the family mapping.KB_TOOL_DISCOVERY_MODE: MCP catalog exposure mode.searchis the default;allrestores the complete native catalog intools/list. Any other value fails startup.
MCP tool discovery
The default KB_TOOL_DISCOVERY_MODE=search keeps the initial MCP catalog small
without removing any capability. tools/list exposes these native tools:
kb_consultkb_searchkb_getkb_healthkb_gate_checkkb_record_eventkb_session_recap
It also exposes the synthetic tool_search and call_tool tools. Use
tool_search to find a native tool omitted from the initial list, then invoke
it with call_tool. A client that already knows the native name may still call
it directly. Search mode filters discovery only; it does not unregister tools.
Visibility is not an authorization boundary. A hidden write tool retains its
annotations and passes through the same principal capability middleware whether
it is called directly or through call_tool. For clients that require the
complete catalog during initialization, set KB_TOOL_DISCOVERY_MODE=all and
restart the server.
Read only replicas expose the subset of the default catalog that is actually
registered in read only mode, plus tool_search and call_tool. Discovery
cannot make a write tool available when the replica did not register it.
Read-only mirrors may scale horizontally
Set KB_READ_ONLY=true to run an instance as a read-only replica. In this mode
the server registers only the read tools (kb_search, kb_get, kb_list,
kb_outline, kb_curate, kb_health) and the read REST routes; the write and
enforcement-write tools and routes (propose, resolve, bootstrap,
consult, gate, record-event, and their observability mirrors) are not
registered at all and return 404. The write pipeline (worktrees, push queue,
pending) is never initialised, so no replica is a git writer.
Crucially, the git_pull_loop still runs, so each replica keeps KB_REMOTE_URL
set and refreshes its own index snapshot from the same git remote as the single
writer. Run as many replicas as you need for read throughput; the single
write-enabled instance remains the only owner of the git remote.
KB_READ_ONLY accepts 1, true, yes, or on to enable read-only mode,
and 0, false, no, or off for the default read-write behaviour, which
also applies when the setting is unset or empty. Values are case-insensitive
and surrounding whitespace is ignored. Any other value, such as ture or
enabled, stops startup with an error naming the setting, so a mistyped value
cannot start a replica with the write pipeline running.
See deploy/k8s/read-replica/ for a ready-to-apply Deployment (not the
StatefulSet writer) that runs N read replicas with per-pod ephemeral clone +
index scratch. Apply the base stack first, then the overlay:
kubectl apply -k deploy/k8s # namespace + secret + writer StatefulSet
kubectl apply -k deploy/k8s/read-replica # N read replicas + read Service
kubectl -n data-olympus scale deployment/data-olympus-mcp-read --replicas=5Git pull loop
On startup, and at the interval set by KB_SYNC_INTERVAL_SEC (default 60s),
the server calls git pull on KB_MAIN_PATH, against the branch named by
KB_GIT_BRANCH (default main). If KB_REMOTE_URL is empty,
the pull loop runs but exits cleanly with no action. The health endpoint
reports degraded: true only when the index has not been rebuilt within
KB_STALENESS_DEGRADED_SEC (default 600s).
For a local read-only demo with no remote, set KB_REMOTE_URL="". The server
stays healthy as long as the index builds successfully on startup.
Health, readiness, and liveness (three distinct signals)
The server exposes three endpoints that answer three different questions. They are deliberately split so a data-freshness problem does not eject a pod that can still serve reads:
| Endpoint | Question | 200 when | 503 when |
|---|---|---|---|
GET /api/v1/health | Is the data fresh? | index built, fresh, last build ok | degraded: staleness > KB_STALENESS_DEGRADED_SEC, no successful pull, empty index, or last build failed |
GET /readyz | Can this pod serve reads now? | process up and index loaded and last build ok | no index built yet, or the last index build failed |
GET /livez | Is the process responsive? | always (if the handler runs, the loop is alive) | never |
Key point: /readyz is independent of data staleness. A git-remote outage
makes the KB stale (so /api/v1/health returns 503 with degraded: true), but
the last-good index still answers reads perfectly, so /readyz stays 200 and the
pod stays in the Service. The Kubernetes readiness probe targets /readyz
(see deploy/k8s/statefulset.yaml); if it targeted /api/v1/health a stale
single-replica pod would be ejected from the Service and a "reads are a bit old"
condition would become a hard 503 with zero endpoints.
/api/v1/health keeps its 503-on-degraded contract because the bin/kb CLI
--no-stale flag relies on it (exit 2 when the endpoint reports degraded: true,
whether over HTTP 200 or 503). Alert on /api/v1/health degraded (and on the
last_git_fetch_status / staleness_seconds fields it carries) rather than
wiring it to a probe. The liveness probe is a tcpSocket check by default; the
HTTP /livez route is offered for operators who prefer an explicit route. See
docs/operations.md for the full alerting and recovery model.
The health payload also carries malformed_frontmatter: the count of docs whose
front-matter was present but malformed at the last index build. A non-zero value
means a doc silently lost its governance metadata (type/status/tier), so it
will not be governed or filtered correctly. It is a warning signal and does
not flip degraded (that would 503 every read for an authoring mistake);
alert on malformed_frontmatter > 0 separately.
When the background version check sees a newer published package than the
running server, health also carries latest_version and sets
update_available: true. latest_version is omitted from compact health output
until a check has found a published version; verbose health shows it as null
before then. This signal is informational only and does not affect degraded,
readyz, or write behavior.
Write serialization and integrity gates
Every write (kb_propose_memory, kb_propose_edit, kb_resolve_pending, and
onboarding bootstrap, which commits its whole file bundle through the same
serialized/validated multi-file path) goes through one serialized critical
section so concurrent writes cannot corrupt each other:
A process-wide write serializer wraps the write →
git add→ commit → enqueue sequence, so one thread's commit can never sweep another thread's staged file. Write volume is rate-limited, so a single global mutex is cheap.A per-path advisory lock, shared between the auto-commit path and the pending queue, prevents two writes to the same file from racing and prevents an auto-commit from landing on a path with a pending proposal in flight (whose later approval would clobber it). A write blocked by the lock returns
rejected_path_lock_busy. An orphaned lock (a crash between lock acquisition and the pending entry write) is reclaimed by the pending GC loop.A content-validation gate runs on the postimage before commit. It rejects
rejected_invalid_documentwhen the frontmatter is malformed YAML, atype/status/tierenum value is out of vocabulary, or theidis a forged/duplicate of one already used by a different path (which would break every subsequent index rebuild: one bad write, persistent degraded state). The responsereasoncarries the machine-readable errors.Supersession targets (issue #259). On commit, a
supersedesorsuperseded_byvalue that the write newly introduces must name a document present in the commit being made (any status; a pending proposal does not count, and an id the same write removes does not either). Otherwise the write is refused asrejected_invalid_documentwithunresolved_supersedes_targetorunresolved_superseded_by_targetat the start of the reason, ormalformed_supersedes/malformed_superseded_byfor a wrong shape. Aderived_fromtarget follows the same rule (unresolved_derived_from_target,malformed_derived_from); a source that has retired never causes a refusal. Target strings are compared exactly as written, so" TARGET "does not resolve toTARGET, and a blank or whitespace-only target is refused as malformed (lint ignores such a value rather than reporting it). A target left unchanged from the committed version of the same file, and a malformed value left exactly as it was (type-exact:1changed totrueis a change), keep passing, so a legacy document can still be edited, and removing either field is always accepted. A credential-shaped target is redacted in the reason. Validation reads the actual local commit the write lands on, with three git processes for the whole write; if that commit cannot be read the write is refused asunresolved_target_unverifiable. Writes without an enforceable base marker, and bootstrap, may proceed after a failed base refresh, so a target deleted upstream in that window is caught bydata-olympus lint, not by this check. A pending proposal parked by an earlier release that introduces such a target is refused at approval and stays pending with its lock. Two pending proposals that name each other (A.supersedes: B,B.superseded_by: A) cannot both be approved as written: approve A withedited_textthat omits the edge to B, approve B, then propose the edge on A again.Bootstrap scope. Onboarding bootstrap creates new files under the workspace or component it onboards, and nothing else. Every target path must lie under
projects/<workspace>/, or underprojects/<workspace>/components/<component>/when a component is given, whatever the onboarding state; a bundle with any other path is refused asrejected_path_not_indexable_or_blocked, listing those paths, before any pending entry or commit is made. A bootstrap of apartialworkspace is still narrowed to the missing canonical files first. At commit time a target that already exists in the session worktree refuses the whole bundle asrejected_already_onboarded, so a bootstrap never replaces a file. To change an existing document, usekb_propose_edit. The commit subject names every path the bootstrap writes.Bootstrap rejection order (issue #259). A bundle with an empty
fileslist is refused first, asrejected_empty_bundle(HTTP 400), before any claim, rate-limit slot, commit or pending entry (issue #311). After the earlier path, size and rate-limit refusals (which are unchanged and carry noreason), a bundle's commit-time checks run in a fixed order and refuse at the first failure: every file's path and postimage are secret-scanned first (so no later diagnostic can echo credential-shaped content), then duplicate ids inside the bundle, then each file's containment, existing-target and content validation in file order. Supersession targets resolve against the committed tree with the whole bundle applied, so a successor and its predecessor created in the same bundle are accepted. A secret-scan, duplicate-id or content-validation refusal carriesreasonin its response, redacted wholesale if it would contain credential-shaped content. A containment refusal (rejected_symlink_escape) carries noreason.Compare-and-swap (optimistic concurrency). When a caller supplies a base marker (
base_commitnaming a specific commit,base_blob_sha, ortarget_file_hash) onkb_propose_edit(or it is carried on the pending entry intokb_resolve_pending), the server refreshes the session worktree's base ontoorigin/mainand compares the marker against the current target content. A mismatch returnsrejected_stale_baseand nothing is committed. If the base cannot be refreshed at all (the remote is unreachable, or the rebase conflicts) while an enforceable marker was supplied, the write is also rejectedrejected_stale_baserather than committed against a possibly-stale base; the marker cannot be verified, and the push-path rebase recovery is not an equivalent safety net (a compatible rebase would still publish the stale write).base_commitmust beHEADor a commit id of 7 to 64 lowercase hex characters (abbreviated or full, SHA-1 or SHA-256); any other value, such as a branch name or a revision expression, is refused asrejected_invalid_basewhen the proposal is made, with a reason that does not repeat the value. A barebase_commitofHEADis advisory (no per-file expectation), and when no marker is supplied the pre-0.3.0 behavior is preserved (a refresh failure is non-fatal; the push path's non-FF recovery publishes the commit). Both hash markers are validated when the proposal is made.base_blob_shamust be the file's git blob id (40 lowercase hex characters) andtarget_file_hashthe sha256 of its current bytes (64 lowercase hex characters); either may be omitted. Any other form is refused asrejected_invalid_base, naming the field. A proposal parked by an earlier release with a malformed marker cannot pass the check, so every approval of it returnsrejected_stale_baseeven when the file has not changed. Recover by rejecting it and proposing the same content again with correct markers.Ordering of side effects: the commit message and all gates are evaluated BEFORE the file is written and
git add-ed, and on any failure after the add the worktree is hard-reset, so a rejected write never leaves a staged leftover for the session's next commit to sweep in.A secret-scanning gate (issue #71) runs on the postimage BEFORE the content-validation gate, on every commit path (auto-commit propose, resolve approve, including a resolved
edited_text, and onboarding bootstrap). A resolvededited_textis additionally scanned before the entry is claimed, so a flagged edit is refused with the entry left untouched. The claim records a sha256 digest of the approved content, never the content itself, so an edit that is refused, or committed under the explicit override, does not remain on the state volume. A claim written by 0.8.0 still holds the text until the first reconciliation pass after it is older thanKB_PENDING_CLAIM_TTL_SECreplaces it with the digest. It runs first so that whenever the gate matches, a postimage that is both malformed AND carries a credential-shaped value is rejected via the redactedrejected_secret_detectedpath rather thanrejected_invalid_document(which echoes the offending value verbatim in its message). Detection is shape-based, so this ordering guarantee is only as good as the pattern set: a credential the patterns do not recognise is not redacted by it either. The gate checks a built-in pattern set (PEM private-key blocks; GitHubghp_/gho_/ghs_/ghr_/ghu_/github_pat_tokens; AWSAKIA.../ASIA...access key ids, the second being the temporary form issued by STS; Slackxox[bpars]-tokens; LLM provider API keys as onellm_provider_api_keyclass (Anthropicsk-ant-, OpenAIsk-proj-/sk-svcacct-/legacysk-, OpenRoutersk-or-v1-, Hugging Facehf_, Groqgsk_, xAIxai-, and OpenAI-compatible gateways minting the same shape); GoogleAIza...API keys, which authenticate Gemini and the other Google APIs that accept API keys, and so are their own class rather than an LLM-specific one; genericpassword=/passwd=/secret=assignments, including env-style prefixed keys likeDB_PASSWORD=, with a non-placeholder value; andscheme://user:pass@hostconnection strings) plus any operator-suppliedKB_SECRET_SCAN_EXTRA_PATTERNS. Every class is a shape, not a validation: the gate recognises credential-shaped text and never contacts a provider, so it can both miss a real credential whose format it does not model and flag an identifier that merely looks like one. Treat it as a backstop against accidental paste, not as proof a postimage is clean. A match on an auto-commit or bootstrap path rejects the writerejected_secret_detectedbefore anything is written to disk. Only the pattern name and an approximate line number are ever surfaced in the response, the audit event, or a log line, never the matched value. A low-confidence proposal containing a secret still enters pending (removing it would defeat the operator-override workflow below), but the scan runs at propose time too: the response never echoes the raw text when flagged (only the pattern name), the pending entry is taggedsecret_scan_flagged/matching_pattern(names only) sokb pendingsurfaces the warning, and a flagged memory proposal's filename falls back to a neutral slug instead of embedding the flagged text. The gate also covers the fields AROUND the postimage: a credential-shapedtarget_path(filename) is rejected on edit, bootstrap, and resolve without ever echoing the path back (it would otherwise land in responses, audit events, commit subjects, and the git tree); a flagged editreasonis replaced with a redacted note before it reaches pending meta, push metadata, or audit events; and a flagged memory tag is stored redacted in pending meta.kb_resolve_pendingaccepts an operator-onlyoverride_secret_scanboolean (kb resolve --override-secret-scanon the CLI) to consciously commit a false positive anyway (recorded in the audit event); no auto-commit or bootstrap path exposes this override, so an agent can never self-authorize past a flagged write. An extra pattern with the classic nested-quantifier ReDoS shape is rejected at load time alongside an invalid regex, and every accepted custom pattern executes through theregexengine with a hard 1-second match timeout, so a catastrophic pattern the load-time check misses is bounded at scan time (logged and skipped) instead of hanging the single-writer write path.A writing-rule gate (issue #283) runs after the secret scan and before content validation, on the same commit paths. It applies the project's own raw writing rules (no em-dash, no en-dash used as one, no agent authorship credit; the rules CI enforces through
scripts/prose_lint.py) to the lines the write ADDS, so an edit never fails on text it did not write. A line counts as added when it occurs more often in the postimage than in the target's current content on the refreshed base (curly quotes folded first), so moving an existing line is not adding it, while a new offending line is always caught. A line ending in<!-- prose-lint: allow -->is exempt and keeps the marker in the committed text. Rules are checked on raw lines, including inside code fences. Inenforcemode a finding rejects the writerejected_writing_rule(HTTP 422 on propose and resolve) withwriting_rule_findingsentries of the formline <n>: <rule>: <excerpt>; in the defaultwarnmode the write commits, the findings are returned in the same field, the audit event records the rule names and line numbers (never the excerpts), and a WARNING with the finding count is logged (never the path or the text). Because it runs after the secret scan, a postimage carrying both a credential and a finding is still reported as the redactedrejected_secret_detected. A failure of the check itself, including an existing target that is not valid UTF-8, rejects inenforceand is reported as a warning inwarn; it is never silently skipped. A rejected resolve puts the pending entry back, as other gate rejections do. The machine-rendered maintenance ledger is exempt (it quotes corpus ids and paths no agent wrote there), recorded in its audit event asskipped:machine_rendered; that exemption is reachable from no MCP or REST call. Configure it withKB_WRITING_RULES_MODE(defaultwarn) andKB_WRITING_RULES_EXCLUDE_PATHS(see "Core configuration reference").
Governed-lane write protection (KB_GOVERNED_LANE_PROTECTION, issue #112)
KB_GOVERNED_LANE_PROTECTION (default on; case-insensitive off/0/
false/no disables it and restores the exact pre-#112 behavior) gates a
demotion-only feature on top of the write path above: "agents can propose,
only humans can promote." No write is ever REJECTED by this feature; an
affected write is DEMOTED to the pending queue instead of auto-committing.
See SECURITY.md's "Governed-lane write protection" section for the full
threat-model rationale; this section covers the mechanics and the demotion
semantics an operator needs at serving time.
Status clamp. Checked on the auto-commit path of
kb_propose_memory/kb_propose_edit, and on everykb_bootstrap_projectfile. If the postimage'sstatusfield is being set/changed to a value in the in-force class (active/accepted/approved--format.validate.IN_FORCE_STATUSES, single-sourced, never a local copy), the write is demoted withdemotion_reason: "status_promotion"instead of committed. Unconditional on the PRIOR status: a brand-new file, a promotion from a non-in-force status, and an unreviewed re-claim of an already in-force status all clamp the same way.Governed-target edit demotion. Checked only on
kb_propose_edit. If the edit'starget_pathcurrently resolves to an IN-FORCE document in the live index (the full composed predicate: status class AND validity window AND not-inbox AND not-graph-excluded -- the same predicateIndex.search(in_force=True)andkb_get's computedin_forcefield use), the write is demoted withdemotion_reason: "governed_target", REGARDLESS of confidence. A target that is expired or has been superseded out of force is not currently governing anything and so is not protected by this rule. The lookup FAILS CLOSED: when the target's in-force state cannot be verified (no index wired, or an index read fails), the edit is demoted with the distinctdemotion_reason: "governed_target_unverified"rather than auto-committed on the strength of a broken lookup -- an unhealthy index can never be used to slip an edit past this rule. A target with no entry in a HEALTHY index is definitive at the tool layer (a brand-new file cannot already be in force), and an authoritative IN-WORKTREE BACKSTOP closes the residual index-lag window: inside the serialized commit section, after every hard gate passes and after the worktree base is refreshed ontoorigin/main, the target's CURRENT bytes on that refreshed base are re-judged (status+ validity window from its own frontmatter,is_inboxfrom the path). The backstop deliberately consults NOTHING from the index -- in particular not graph exclusion, whose stale edges could otherwise REMOVE protection the base's own bytes assert (a target still carrying an in-forcestatusunder a live supersedes edge is over-demoted for operator review rather than auto-committed; a target properly flipped tosupersededis not in force by its own bytes and stays unprotected). A doc pushed as in-force but not yet re-indexed is therefore still demoted (demotion_reason: "governed_target") -- the decision is made against the same content the commit would replace, so index staleness cannot bypass the rule in either direction.Injection-pattern annotation. Every postimage evaluated by the two rules above is also scanned for agent-directed injection patterns ("ignore previous instructions", "disregard ... policy", an imperative paired with an http(s) URL, a long base64-looking run, "do not tell the operator"). A match sets
injection_suspect: trueand populatesinjection_patterns(a list of"pattern_name:line"strings, mirroring the issue #71 secret-scan redaction discipline -- never the matched text) on the pending entry. This is ADVISORY ONLY: it never blocks a write and never causes a demotion by itself; it exists so the human reviewer sees the signal alongside the demoted content.Ordering. The issue #71 secret-scanning gate and the content- validation / duplicate-id gate are evaluated FIRST for any write that would otherwise auto-commit. A postimage that would be rejected outright by either of those gates IS rejected (
rejected_secret_detected/rejected_invalid_document), never silently demoted -- a demotion cannot be used as a side door around a hard rejection. The exceptions are the checks that can only be decided against the committed tree (missing_statusand the issue #259 supersession codes): the index-only prediction ignores them, so such a write stays demoted and the commit path decides at approval.Unaffected paths. The maintenance-ledger system write (
maintenance.maybe_update_ledger, which stamps its own docstatus: active) commits through the shared multi-file write primitive DIRECTLY, bypassing the tool-function layer these checks live in, so it is never gated by this feature. The operator resolve path (kb_resolve_pending/kb resolve) is likewise never gated: an operator-confirmed resolve IS the promotion this feature exists to require.Per-write feedback. A demoted response carries the same
pending_idshape as a plain low-confidence park, plusdemotion_reasonand anoperator_promptthat explicitly instructs the calling model to inform the operator that the write awaits review rather than proceeding silently.Per-session feedback. See
docs/operations.mdforkb_session_recap/kb session-recap/GET /api/v1/session-recap, thekb_consultpending_actionssurfacing, and thebin/kb-session-recap-hookSessionEnd/Stop hook wiring.
Push queue and write-path visibility
Committed writes are published to origin/main through a durable push queue: a
commit that returns "committed" has a queue entry on disk, and a background loop
drains the queue with retry. Health surfaces the queue state so a stuck write
path is diagnosable:
push_queue_sizeis the live count of queued (not-yet-pushed) entries.pending_countis the live count of pending (awaiting-approval) entries.push_queue_frozenis the count of entries that hit the retry cap and were frozen.path_locks_heldis how many paths are locked, andpath_lockssays WHICH, each withtarget_path,owner_kind,pending_id,acquired_atandage_seconds. A leaked lock blocks every write to one path, so the count on its own was not diagnosable without reading the state volume by hand. A lock file that exists but cannot be read, or is not a JSON object, is listed withowner_kind: "unreadable"and null fields: it still holds a path, and it is never allowed to fail health, readiness or the REST reads that build on it. The orphan collector and the stale auto-commit reclaim leave such a file in place, since its owner cannot be established; remove it by hand once no write to that path is in flight.
Both counts are computed at health-report time from the queues themselves, so they cannot drift.
An interrupted resolve, and how it recovers
Approving a pending entry claims it, holds its path lock, commits, and then releases both. If the process dies in between, the entry is left in a claimed state on the state volume, still holding its path lock. Two things follow, and both are handled explicitly:
The entry is visible.
kb_list_pendingreports every entry with astateofpending,claimedoruncertain. A claimed entry is not awaiting a decision, so it does not count towardspending_count, but it is never hidden: an empty queue must mean an empty queue, not a decision stuck halfway.The outcome is reconciled from evidence, never from age. A background pass looks at claims older than
KB_PENDING_CLAIM_TTL_SEC(default 900) and decides from what the write actually recorded:Evidence Outcome A recorded commit sha Committed. The entry is consumed and never offered again. A recorded failure Not committed. The entry returns to pendingfor the operator to re-resolve, keeping its path lock.Nothing recorded The commit is searched for by its KB-Pending-Idtrailer on the recorded session ref. Found means committed. Otherwiseuncertain.
An uncertain entry keeps its path lock deliberately and is re-checked on every
later pass, so a transient unreadable repository resolves itself. A negative
search is not treated as proof that nothing committed: a rebase can drop a
commit that upstream already acquired as an equivalent patch, and a squash can
rewrite its trailer away, so absence of the commit is not absence of the write.
Restoring on absence would offer an already-applied decision for approval a
second time.
For the same reason, operations that rewrite session history reconcile outstanding claims first and then defer while a claim on that branch has begun its write: the base rebase, the non-fast-forward push recovery that calls it, and the worktree GC that removes a session worktree and deletes its branch. The GC teardown holds the write serializer across its whole sequence, so it either completes or leaves the worktree and the branch both intact. A deferred push is not a publication failure and does not consume its retry budget. These guards fail closed: an unreadable claim record, or a branch that cannot be identified, defers the rewrite rather than permitting it. Each guard holds the write serializer across its own check AND the history mutation it authorises, so the guard protects the history actually being rewritten rather than an earlier snapshot of it. The guard reconciles at the same claim TTL as the periodic sweep rather than from zero age, so a claim whose own resolver is still finishing is left alone and the rewrite simply defers. A successful resolve also consumes its own claim inside the same serializer acquisition that made the commit, immediately after recording the outcome, so no reconciliation can ever observe a committed-but-unconsumed claim and finish it on the owner's behalf.
If an entry stays uncertain, check whether the change is present on
origin/main and then resolve it by hand on the state volume: delete
/state/pending/<pending_id>.claimed and
/state/pending/locks/<sha256-of-target-path>.lock to release the path. Confirm
the content first; that pair is the only remaining record of the decision.
Reading back a parked proposal
A session that parks a proposal can read its own draft back:
GET /api/v1/pending/<pending_id>and the kb_get_pending MCP tool takes the same single pending_id. It returns
the postimage with in_force: false and a note saying so, because the content is
a pending proposal: it governs nothing, and it stays out of kb_consult and
every in_force retrieval exactly as before.
Recovery identifies a claim's commit by a KB-Pending-Id trailer, and it asks
git interpret-trailers to read that trailer rather than parsing the message
itself. That parse runs with global, system and environment-injected git
configuration disabled and repository discovery ceilinged, because git's own
trailer.<alias>.key configuration can RENAME an ordinary key into the claim
key and turn an unrelated commit into evidence. A duplicated key is treated as
ambiguous and yields nothing. Commit text is agent-controlled, so a parser that disagreed with git in
the accepting direction would let an ordinary write close somebody's decision;
delegating removes that whole class rather than chasing git's message-boundary
rules one at a time. A commit body that does not end in a newline is treated as
having no trailers, conservatively, because the CLI would otherwise answer for a
normalised body rather than the one recovery holds.
Ownership is the authenticated principal recorded when the proposal was made:
a principal holding
resolvemay read any entry, since it can already see the content by approving it;otherwise the caller's principal must be the one that made the proposal;
an entry with no recorded proposer predates this field, so its ownership cannot be established and it is resolver-only.
source_session deliberately plays no part. The listing publishes it for every
entry to any authenticated caller, so scoping a content read on it would be no
boundary at all: a reader could list the queue, copy somebody else's id and
session, and ask for their draft. With no principals configured every caller can
already resolve any entry, so the readback is open there too and matches the
posture of every other write surface.
Principal names are ownership identities, so they must be unique. The registry refuses a configuration with two credentials of the same name, including two unnamed ones, because they would otherwise share ownership of each other's drafts.
What this boundary does not cover. Only the POSTIMAGE is owner-scoped.
GET /api/v1/pending and kb_list_pending remain visible to every
authenticated principal and carry each entry's target_path, reason,
evidence, capture and identities. Those are content-derived: a target path can name
what a draft is about, and an evidence string can quote it. The secret scanner
redacts credential-shaped values, not ordinary confidential text. So the pending
queue's metadata is shared within a deployment, deliberately and as it always
has been, and this change does not narrow it. If that is not the boundary you
want, do not put confidential material in a proposal's path or evidence.
A postimage the secret scanner flagged is withheld from everyone except a
resolve principal. The proposer already held that content, but handing it back
would turn the queue into a place to retrieve a credential from. The response
still names the matched pattern, so the caller learns why.
Capture provenance on proposed memories
A memory distilled from a passive capture, such as a hook event or an external
session transcript, can say so. kb_propose_memory and
POST /api/v1/propose/memory accept an optional capture object that labels the
proposal as evidence-derived, binds it to the exact source event and proposal
bytes, and names the transformation that produced it. The captured raw event
stays outside data-olympus: only identifiers, hashes and enumerated values enter
the store.
The envelope is provenance, not authority. It adds no status, it does not force
review, and it does not change what in_force returns. A capture-derived memory
is rendered with status proposed under the memory inbox like any other memory,
so it is never in force and never returned by kb_consult, whether it parks or
auto-commits at high confidence.
| field | required | format |
|---|---|---|
capture_source | yes | [a-z0-9][a-z0-9_.-]{0,63}, for example claude_code.hook |
capture_event_id | yes | [A-Za-z0-9][A-Za-z0-9._:-]{0,127}, which fits UUID, ULID and urn: ids |
source_event_hash | yes | sha256: followed by 64 lowercase hex characters |
transformation | yes | [A-Za-z0-9][A-Za-z0-9._:@/+-]{0,127}, naming the transformer and its version, for example automem.distill/1.4.2 |
raw_retention | yes | one of discarded, redacted, retained |
capture_session | no | same format as capture_event_id |
classification | no | one of fact, decision, preference, task_state, noise |
derived_memory_hash | no | sha256: followed by 64 lowercase hex characters |
Every value is an ASCII token matched in full, so no value can hold whitespace,
a newline, a quote or prose. capture_session is the session that produced the
captured event; the top-level source_session is still the session making the
proposal. An explicit null on an optional field reads as absent, and a null
capture means no envelope.
The server always computes derived_memory_hash as sha256: of the UTF-8 bytes
of text exactly as submitted, with no normalization. A caller that supplies the
field must supply that same value, which binds the proposal bytes in transit.
Matching hashes show that neither side changed; they do not prove that the
memory was derived from the event, which is why transformation is required:
the reviewer checks the derivation claim against the named step.
Validation runs before the path, blocklist and rate-limit checks, so a refused
envelope costs no rate-limit token. A credential-shaped value anywhere in the
envelope, including under an unknown key, is refused as
rejected_secret_detected with the pattern name only, and nothing is parked or
committed. Every other problem (a non-object, {}, an unknown key, a missing
required field, a value of the wrong type, format or enumeration, or a
derived_memory_hash that does not match text) is refused as
rejected_invalid_capture. Neither reason ever repeats a submitted value. Over
REST, rejected_invalid_capture is HTTP 400 and rejected_secret_detected is
422. POST /api/v1/propose/edit refuses a capture key with
rejected_invalid_capture and HTTP 400, because an edit labels no new memory.
An accepted envelope, with the computed derived_memory_hash, is written:
into the memory's frontmatter as a nested
capturemapping, afterevidence, through the same YAML serializer as every other server-rendered key, so it survives an auto-commit and counts againstKB_MAX_TEXT_BYTES;into the pending entry, where
kb_list_pending,GET /api/v1/pending,kb_get_pendingandGET /api/v1/pending/<pending_id>return it ascapture(nullwhen the proposal had none, or when a stored value is malformed);onto the
committedandpending_confirmationaudit events for the proposal. A refused envelope's audit event carries the status and reason only.
kb_session_recap and GET /api/v1/session-recap count the session's committed
or parked memory proposals that carried an envelope as capture_derived.
An operator edit keeps the label. On resolve, edited_text replaces the
whole proposed document, so the server re-attaches the stored envelope to what
it commits, on approve and on edit. An edit that supplies its own frontmatter
keeps it, with capture set to the stored envelope; an edit that supplies a body
only, which is what kb resolve hands the operator, keeps the original
proposal's frontmatter. The re-attached derived_memory_hash is still the one
computed over the submitted text, so when it no longer matches the committed
body, that mismatch is the record that a human changed it. The size cap and the
secret scan on edited_text judge the bytes after re-attachment.
Passive hook ingestion. A capture hook records a tool event, hashes it, discards the raw payload, and proposes the distilled memory at low confidence so a person reviews it:
{
"text": "Integration tests in this repository need the staging database.",
"tags": ["testing"],
"source_session": "distiller-7",
"agent_identity": "automem",
"confidence": 0.4,
"capture": {
"capture_source": "claude_code.hook",
"capture_event_id": "01J9ZK3Q7R8S9T0V1W2X3Y4Z5A",
"source_event_hash": "sha256:3f9c0e5d1b7a2c4e6f8091a2b3c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6",
"transformation": "automem.distill/1.4.2",
"raw_retention": "discarded",
"classification": "fact"
}
}External-session transcript ingestion. An importer reads a transcript from another tool, keeps a redacted copy in its own store, and names the session the transcript came from:
{
"text": "The team chose weekly release trains over continuous deployment.",
"tags": ["release"],
"source_session": "importer-run-12",
"agent_identity": "transcript-importer",
"confidence": 0.5,
"capture": {
"capture_source": "codex.transcript",
"capture_event_id": "urn:transcript:2026-10-01:message:42",
"capture_session": "codex-session-8c1f",
"source_event_hash": "sha256:9a8b7c6d5e4f30211203f4e5d6c7b8a99a8b7c6d5e4f30211203f4e5d6c7b8a9",
"transformation": "transcript-import/0.3.0",
"raw_retention": "redacted",
"classification": "decision"
}
}Non-fast-forward push recovery
When a second overlapping session moves origin/main between a session's commit
and its push, the push is rejected non-fast-forward. The push loop classifies
this distinctly from a network failure: it fetches origin/main, rebases the
session branch onto it, and retries the push once. Most non-FF cases (the two
sessions touched different files) publish cleanly on the retry.
If the rebase conflicts (the two sessions edited the same lines
incompatibly), the commit cannot be auto-published. Instead of retrying forever,
the push loop demotes the commit to a pending entry for operator resolution:
the postimage is re-proposed as a pending edit (visible via kb_list_pending),
the queue entry is removed, and a push_conflict_demoted audit event is recorded.
The operator resolves the pending entry (approving re-applies it on the current
base, subject to the CAS/validation gates) to publish the change.
Pure contention (the retry push is itself non-FF because origin/main moved
again mid-rebase) is retried in-line for a bounded number of passes; if it never
wins the race it is demoted the same way rather than counted toward the freeze
cap, so persistent contention never becomes a silently-frozen queue item.
Startup recovery
A crash in the narrow window between git commit and the push-queue enqueue
would otherwise orphan a commit on a session worktree branch that nothing pushes.
On boot the server scans every session worktree and re-enqueues any commit
reachable from its HEAD but not from origin/main. Entries already in the
queue are left untouched, so recovery never double-enqueues. The recovery result
is logged (startup push recovery re-enqueued N orphaned commit(s) or no orphaned commits found).
Frozen entries and how to unfreeze
After max_attempts consecutive push failures a queue entry is frozen: the
retry loop stops retrying it (retrying a permanently failing push every interval
only spams the remote and hides the problem). The freeze is logged once at WARN
with the sha, worktree, and last error, and the entry is counted in
push_queue_frozen. A nonzero push_queue_frozen means writes are stuck and
need operator attention. Non-fast-forward pushes against a moved origin/main
are now recovered automatically (see Non-fast-forward push recovery above), so
a frozen entry now signals a persistent failure the rebase path could not
handle, typically an authentication, network, or remote-side rejection.
To unfreeze, an operator resolves the underlying push failure, then clears the
entry file directly on the push-queue volume (KB_PUSH_QUEUE_ROOT, default
/state/push-queue):
Requeue (retry from scratch): delete the
"frozen": trueand reset"attempts"to0in the entry's JSON file (or delete and let startup recovery re-enqueue it), then the retry loop picks it up again on its next pass.Drop (abandon the write): delete the entry's
<sha>.jsonfile. The commit stays on its session worktree branch but is never published.
There is no unfreeze API in this release; the file-level procedure above is the supported path.
Per-session worktree GC
Each writing session gets its own git worktree (and a kb-session/<safe_id>
branch) so in-flight edits are isolated. A background GC task, running every 5
minutes, removes worktrees idle beyond KB_WORKTREE_IDLE_SEC (default 3600s) so
KB checkouts do not accumulate one-per-session forever.
GC is conservative and coupled to the push queue:
A worktree with commits not yet reachable from
origin/mainis deferred (never removed) until the push queue drains those commits; the next GC pass cleans it up once they land upstream.When a worktree is removed, its
kb-session/<safe_id>branch is deleted in the same step. This matters because a returning session recreates its worktree withgit worktree add -b kb-session/<safe_id>, which would fail if the branch still existed. Deleting the branch keeps a GC'd session able to write again.
Streamable-http session lifecycle and reaping
Each session-less POST /mcp handshake makes the underlying MCP SDK create a
transport, register it in an in-memory session table, and start a task that
blocks waiting for further messages on that session. The SDK removes a session
on an explicit client DELETE, on the session task crashing, on server
shutdown, and, since MCP SDK 1.30, once the session has had no request in flight
for 30 minutes. A request being served or an open GET stream holds the
session. FastMCP builds the SDK's session manager without overriding that
window, so it applies to this server as a backstop.
Consequence: a client that handshakes and drops the connection without sending
DELETE (the common cause of repeated "Created new transport with session ID"
log lines) leaves its transport resident for at least half an hour, and a burst
of such handshakes can fill the session table. The SDK also caps the table at
10000 concurrent sessions and answers a request that would open another one
with HTTP 503 until room frees up.
data-olympus bounds this more tightly in two ways:
Observability:
healthreportslive_sessions, the current live transport count (ornullbefore the HTTP app has started serving). The server also logs the live count each reaper pass. Alive_sessionsvalue that only ever climbs is the signal of a leak.Bound: a background reaper terminates sessions idle beyond
KB_SESSION_IDLE_TIMEOUT_SEC(default300, five minutes). It scans everyKB_SESSION_REAP_INTERVAL_SEC(default60). SetKB_SESSION_IDLE_TIMEOUT_SEC=0to disable this reaper and keep observability only. Termination uses the SDK's ownterminate()path, so the session leaves the table and a client that reconnects simply gets a fresh session.
The SDK's 30 minute expiry stays active whatever these settings are, so
disabling the reaper, or setting a window longer than 1800 seconds, leaves the
SDK expiring sessions that have had nothing in flight for 30 minutes. Both
mechanisms treat a session with an open GET stream as live, so neither ends a
connected client's session while the other keeps it. With the default five
minute window and one minute scan the reaper acts long before the SDK would,
and an abandoned session stays in the table for at most six minutes, so
reaching the 10000 session cap takes clients abandoning about 28 sessions a
second, sustained.
The reaper works on elapsed time since a session's last activity stamp, not on
whether its connection is open. The clock advances on every request carrying
that session's mcp-session-id header, so a client whose calls arrive more
often than KB_SESSION_IDLE_TIMEOUT_SEC is never reaped. A client that polls
less often than that is reaped between calls and gets a fresh session on its
next handshake, so pick a keep-alive interval shorter than the idle window.
A session whose long-lived GET SSE stream is open does not need one. The
activity middleware runs a keep-alive task for the duration of that request and
re-stamps the session every KB_SESSION_TOUCH_INTERVAL_SEC, and the server
clamps that interval to at most a third of the idle window, so the stamps are
scheduled at least three times per window however short the window is. No
accepted combination of the two settings schedules them further apart than the
window, so a quiet but connected client is not reaped.
What the reaper is there to clear is the session nothing is stamping any more:
the abandoned handshake, the client that disconnected without sending DELETE.
That is the leak it bounds, and it is why a five minute default window does not
disturb a connected client.
Search ranking
kb_search orders hits by BM25 relevance and then applies a status-aware
rerank on top of that score, enabled by default. The rerank exists so a
retired document does not outrank the live one that replaced it: for a query that
matches both, the in-force doc is promoted and the retired one demoted.
The built-in status-to-weight map is:
In-force (boosted):
active,accepted,approved.Retired (penalized):
superseded,deprecated,rejected.Not yet in force (penalized):
draft,proposed.
A negative weight boosts (moves a hit earlier), a positive weight penalizes
(moves it later). Status matching is case-insensitive, so Active in frontmatter
ranks like active. Any status not in the map (including an empty status) is
neutral and is never dropped from results.
Because this reorders results, a query that previously surfaced a superseded
doc first may now surface the active one first. This is the intended behavior
change.
Override the map at deploy time, with no code change:
KB_STATUS_WEIGHTS: a JSON object of{status: weight}that replaces the built-in map (it is not merged). Keys are statuses (matched case-insensitively), values are numeric deltas: negative boosts an in-force status, positive penalizes a retired one. A malformed value fails startup loudly rather than silently shipping default ranking. Unset (the default) uses the built-in map above.Example:
KB_STATUS_WEIGHTS='{"active": -1.0, "approved": -1.0, "superseded": 2.0, "draft": 1.0}'.
in_force: hard in-force filter
kb_search accepts an in_force: bool = False parameter (MCP tool and
GET /api/v1/search?in_force=true). When set, results are HARD-filtered
BEFORE ranking to docs that are currently in force: the in-force status class
(active, accepted, approved) AND the validity window
(validity.valid_from not in the future, validity.valid_until not in the
past; both boundary days inclusive). So a superseded, deprecated,
draft, proposed, expired, or upcoming doc is excluded entirely rather
than merely soft-downranked by the status rerank above.
Use it when you want only guidance that currently applies and a demoted-but-
present retired doc is not acceptable. It differs from the status rerank: the
rerank always keeps every hit and only reorders; in_force drops out-of-force
hits. The in-force predicate is defined once
(format.validate.IN_FORCE_STATUSES for the status class,
format.validate.is_in_force for the combined status-AND-window predicate)
and shared by the rerank boosts and this filter.
in_force composes with the single-status status filter: passing both
requires a doc match status AND be in-force (so status=superseded with
in_force=true yields nothing). The filter also applies to the dense
(embedding) candidate source, so a hybrid deployment never leaks an out-of-force
doc through the semantic path.
The predicate also applies a memory-inbox floor (issue #109): a document
under the memory-inbox prefix (KB_MEMORY_INBOX_PREFIX, default
memory/inbox/) is never in force regardless of claimed status, so a legacy
inbox file or forged frontmatter on an agent-written memory cannot satisfy
in_force=true no matter what status it declares. This is a plain is_inbox
column derived once at index build time, not a per-query prefix scan. Together
with the supersession-graph exclusion below, the full in-force predicate is:
status class AND validity window AND not-inbox AND not-graph-excluded.
kb_consult passes in_force=true on its internal retrieval unconditionally
(not a caller-facing parameter): the enforcement surface must never present an
unreviewed, retired, expired/upcoming, memory-inbox, or graph-excluded
document as a governing rule for a code/architectural decision. See
docs/enforcement.md.
Computed in_force on served responses. A verbose (verbose=true)
kb_get response and each verbose kb_search hit carry a computed
in_force: bool: the same single-sourced predicate evaluated against the
doc's actual status/validity/inbox-membership/graph-exclusion, independent of
whether the query itself passed in_force=true. This lets a caller retrieve
a doc via a default (unfiltered) kb_get/kb_search and still tell whether
it currently governs, without a second in_force=true round trip. It is a
serving-layer derivation only, never written to frontmatter (see SPEC.md's
"runtime envelope" note). Compact responses emit it deviation-only:
in_force: false appears ONLY when the doc is not in force, and an in-force
doc's compact shape is byte-for-byte unchanged. The deviation emission is
required because the compact status/freshness fields key off the RAW
frontmatter status: a memory-inbox doc with a forged status: active would
otherwise render as an ordinary current rule with no signal that the in-force
floor disqualified it.
Supersession-graph exclusion (issue #110 slice 2)
in_force=true ALSO excludes any document that is the TARGET of a
supersedes edge whose SOURCE document is itself in force (the full
status-class AND validity-window AND not-memory-inbox predicate above, not
merely status: superseded). This is the in-force-source guard: a draft,
expired, already-retired, or memory-inbox document can never retire another
document just by naming it in supersedes. It closes the "forgotten status flip" gap -- A (active)
supersedes B, but B's own status was never updated to superseded -- B is
excluded from in_force=true results even though its own status class would
otherwise qualify it. Like the upcoming half of the validity window, graph
exclusion is scoped to in_force=true only: a plain (default) search still
returns the excluded document.
A mutually-supersessive in-force cycle (already a kb lint ERROR at parse
time) is NOT special-cased at retrieval time: every member independently
satisfies the exclusion rule (each is the target of an in-force source's
supersedes edge), so in_force=true excludes ALL of them. A dangling edge
(source or target id with no corresponding document) excludes nothing; the
exclusion query joins edges to docs on both ends.
kb_consult runs its search with in_force=true (see "Enforcement
endpoints" below), so it never returns a graph-excluded, not-yet-in-force, or
retired document -- the same guarantee the unconditional expired-doc exclusion
already gave it, made explicit for the graph rule since that rule is scoped to
in_force=true rather than applying unconditionally.
A graph_excluded_docs health counter (in kb_health / /api/v1/health,
alongside malformed_frontmatter and malformed_validity) reports the count
of documents currently excluded by this rule. It is evaluated LIVE at
health-read time against the current date, from the same SQL definition the
retrieval-time filter uses (format.validate.graph_excluded_ids_sql), so
the counter can never drift from the filter it reports on -- including
across a date boundary where an in-force source's validity window opens or
closes between index rebuilds (retrieval evaluates the window per query, so
a counter frozen at build time would keep reporting a stale value). The
short health cache (health_ttl_sec, default 5s) bounds its staleness. It
is a WARNING signal only and does not flip degraded.
Retirement is explainable
kb_get by id always resolves regardless of in-force or graph-exclusion
status (same as it already ignores expiry) and returns:
superseded_by: the sorted, deduped UNION of the document's own frontmattersuperseded_byclaim and any reversesupersedesedge naming it. This ONE consistent computed shape covers both the honest self-declared case and the forgotten-status-flip case above, where only the superseding document's ownsupersedeslist names this one.contradicts: the document's own frontmatter list (unchanged from the frontmatter, never affects filtering or ranking anywhere in the retrieval path).contradicted_by: the computed reverse -- every other document whosecontradictsnames this one.
All three are omitted from the compact kb_get response when empty (same
deviation-only pattern as freshness). Compact kb_search hits carry a
deviation-only superseded_by (the same computed union above), omitted when
the document is not superseded; it is computed and attached to EVERY hit
regardless of in_force, so a plain search result still explains why a hit is
historically superseded. kb_search hits do NOT carry contradicts /
contradicted_by; those are kb_get-only.
Derivation: derived_from is surfaced, never acted on
A document may declare derived_from (SPEC.md section 4.2, format 0.5):
the documents its guidance was drawn from. A document has left force
(is retired) when it is graph-excluded, its status is deprecated,
superseded or rejected, or it is expired. rejected counts deliberately:
deriving from an explicitly rejected document is itself worth surfacing.
draft, proposed, upcoming and memory-inbox documents are not retired,
because they never governed. kb_get returns three lists of ids, sorted,
deduped and dangling-safe:
derived_from: the document's ownderived_fromtargets that exist.derived_from_retired: the subset ofderived_fromthat is retired. Non-empty only while this document is itself in force.dependents_to_review: the documents in force (the full computedin_forcepredicate) whosederived_fromnames this one. Non-empty only while this document is retired.
Verbose kb_get always carries all three; compact kb_get omits each when
empty, so a document without derivation relationships keeps its exact compact
shape. MCP kb_get and REST GET /api/v1/get/{id} share one implementation.
Nothing is filtered or demoted: derived_from never changes any document's
in_force, kb_search or kb_consult results, ranking, or the
graph_excluded_docs counter, and kb_search hits do not carry these lists.
A dependent of a retired source can therefore still be returned by
kb_consult with no cue; kb lint (a warning on both ends) and kb_get are
where a person sees it. data-olympus lint reports the same condition as a
warning, never an error, because a source can retire by expiry.
Validity: expired docs leave default results
Independent of in_force, a doc past its validity.valid_until date is
excluded from EVERY default kb_search result (SPEC.md section 4.2): an
expired doc has no named successor to outrank it, so left visible it could be
the top hit and would govern. Three related kb_search parameters (MCP tool
and GET /api/v1/search):
include_expired: bool = falserestores expired docs to the result set; each carriesfreshness: "expired"in its hit.validity_stateis an audit facet:"expired","stale"(pastrecheck_by), or"expiring_within:N"(docs whosevalid_untilfalls within N days). Filtering for"expired"implies including expired docs. A malformed value is rejected (HTTP 400 on REST)."stale"here is recheck_by-only, narrower than the per-hitfreshness: "stale"a hit can also carry from verification age (issue #142);kb_curatelists every document currently showingfreshness: "stale"for either reason.Compact hits carry a deviation-only
freshnessfield (stale/expired/upcoming), omitted when fresh, plus afreshness_reasonnaming the field and date/day count behind it (omitted exactly whenfreshnessis). A doc with novalidityblock is omitted only whileKB_REVIEW_DUE_AFTER_DAYSis unset; with the threshold enabled such a doc has nolast_verifiedeither, so it reportsstalewith reasonlast_verified is not set. A doc with a futurevalid_fromstays in default results flaggedupcoming; onlyin_force=trueexcludes it. A stale doc (pastrecheck_by, or -- withKB_REVIEW_DUE_AFTER_DAYSset -- an old or absentlast_verifiedand norecheck_byoverride, issue #142) stays in force and visible: review-due is always advisory, never a filter.
kb_get by id always resolves regardless of expiry, returning the full
validity object plus the computed freshness indicator. kb_consult never
returns an expired (or upcoming, proposed/retired, memory-inbox, or
graph-excluded) doc, via its unconditional in_force=true retrieval (see the
in_force and "Supersession-graph exclusion" sections above), not merely by
reusing the default search path's own expired-exclusion.
The data-olympus validity-report CLI subcommand lists expired and
soon-to-expire docs from a bundle directory.
kb_curate: which in-force documents are review-due (issue #142)
kb_curate (MCP tool, GET /api/v1/curate) aggregates the same
freshness/freshness_reason signal kb_search and kb_get already expose
per-document into one list: every in-force document currently reported
stale, most-overdue first, each with id, path, title, and reason.
It is the answer to "what should I look at", where the search/get fields are
the answer to "is this one doc stale" -- both come from the SAME
compute_freshness call, so they cannot disagree.
Advisory and read-only, with no write-pipeline dependency at all: kb_curate
recommends, it never proposes, edits, promotes, or demotes anything.
Registered in read-only replicas (issue #44) alongside kb_search/kb_get/
kb_list/kb_outline, and reachable without a token the same way those are
-- it is not gated like kb_list_pending, since it surfaces document
metadata already retrievable through ordinary reads, not pending-queue state.
Candidates come from the exact same in-force predicate kb_search(in_force= true) uses (status class AND validity window AND not-inbox AND
not-graph-excluded), so an expired, upcoming, draft, retired, or
memory-inbox document never appears regardless of how overdue its
last_verified looks. An empty corpus or a corpus with nothing review-due
returns a valid empty list, never an error. With KB_REVIEW_DUE_AFTER_DAYS
unset (the default), only recheck_by-based staleness is ever reported,
matching every other read tool's pre-#142 behaviour.
Ordering: among verification-age entries, a document with NO last_verified
at all sorts first (nobody has ever checked it, the most urgent case), ahead
of one that was at least verified once however long ago. recheck_by is
classified first, so a document carrying an explicit past recheck_by is a
recheck_by-driven entry even when it has never been verified, and orders by
days past that date rather than sorting into the never-verified group; a
verification-age entry orders by days past the KB_REVIEW_DUE_AFTER_DAYS
threshold. limit clamps to 1..100 like kb_search's, defaulting to 50.
kb_curate serves issue #142's operator-visibility criterion: it makes the
review-due signal answerable in one call instead of requiring the operator to
know to filter for it. The tool NAME is reserved on issue #31, which avoids a
collision with the existing kb_audit event-log tool, but #31's requested
capability -- detecting repeated patterns across the corpus and proposing to
hoist them up the tier chain via kb_propose_edit -- is not implemented here
and #31 stays open. That work needs its own detection design.
Retention, freshness, and authority are three separate things
Issue #142 adds a freshness signal and nothing else. The three concepts are deliberately independent, and no tool in this release couples them:
Retention is whether a document still exists. Nothing in the freshness path deletes, archives, or prunes anything. A document that has gone unverified for a decade is still served, still retrievable by
kb_get, and still akb_searchcandidate. There is no automatic deletion for long-term governed knowledge, by design.Freshness is how long it has been since somebody checked the document, derived from
recheck_byor fromlast_verifiedage againstKB_REVIEW_DUE_AFTER_DAYS. It is advisory: a report about the review process, not a claim about the content.Authority is whether the document governs, and it is
statusplus the validity window plus the inbox/graph exclusions, single-sourced throughis_in_force. Only those inputs decide it.
Concretely: a stale document with status: approved inside its validity
window is still in_force: true, still ranks normally, and is still returned
by default. freshness never feeds in_force, never reorders default
retrieval, and never changes a status. kb_curate reports on the in-force
set; it does not shrink it. There is no authority_state enum parallel to
status, and an operator acting on a kb_curate entry is re-verifying a
document, not demoting it.
abstain: signal-gated abstention
kb_search accepts an abstain: bool = False parameter (MCP tool and
GET /api/v1/search?abstain=true). When set, the query is first run restricted
to the discriminating columns (title, tags, applies_when). If it matches
none of them, the query is treated as out-of-scope and the search returns NO
hits with abstained: true and abstain_reason: "no_signal_match", instead of
surfacing a weak match on generic body prose. A query with a real signal
retrieves normally over all columns (recall is preserved).
The response distinguishes a deliberate abstention from an ordinary empty
result: a normal search that simply finds nothing returns abstained: false.
The gate logic is single-sourced in data_olympus.search_gate; the benchmark
ablation imports it rather than reimplementing it.
Taxonomy and writable paths
The server maps each document's path to a (tier, category) pair. The built-in
default is deployment-neutral and covers universal/, tech-stacks/<stack>/,
decisions/, workflows/, memory/ (with memory/inbox/ and
memory/accepted/), tooling/, templates/, and projects/<name>/
(with components/<component>/ for T4).
Every path under universal/ is T1. The subdirectories foundation/,
quality/, security/, infrastructure/, database/, api/, and
services/ take their own name as the category; any other path under
universal/, such as universal/README.md or universal/process/x.md, has
the category universal. A path outside every prefix in the table gets the
tier and category meta.
A bundle that uses a different directory layout overrides the defaults at deploy time, with no code change:
KB_TAXONOMY_PATH: path to a JSON file holding a list of[prefix, tier, category]triples that replaces the default table. Thetech-stacks/andprojects/prefixes keep their dynamicstack:<name>/project:<name>behavior if present.KB_INDEXED_PREFIXES: comma-separated list of writable top-level prefixes that replaces the default writable set. Writes outside these prefixes are rejected by the structural rule.KB_MEMORY_INBOX_PREFIX: directory new memory proposals are written under (defaultmemory/inbox/).
A document's frontmatter tier or category, when present, overrides the
path-derived value in the index, so search facets and listings use it. The
write blocklist (KB_WRITE_BLOCK_TIERS) ignores that override and classifies
the target by path, so an author cannot declare a document out of a blocked
tier. data-olympus lint <bundle> reports each disagreement as a warning that
names the path-implied value, using the same taxonomy (KB_TAXONOMY_PATH when
set). The warning never changes the exit code. Because the frontmatter
vocabulary has no spelling for the meta path tiers (decisions, workflows,
memory, tooling, templates, or an unmatched path), a declared meta
satisfies them. If KB_TAXONOMY_PATH cannot be loaded, lint prints a notice
on stderr and skips this check.
Synonym / acronym query expansion
Before building the FTS MATCH, the server rewrites the query term list through a
curated, bidirectional synonym map. A short-form query (k8s, rls) also reaches
documents that only use the long form (kubernetes, row level security), and
vice versa; adjacent query tokens are scanned as n-grams so multi-word keys match
from a long-form query. Expansion is bounded (32 terms) and de-duplicated, and the
terms the user actually typed are ranked first. This is curated lexical expansion,
not semantic (embedding) retrieval.
The default map ships a small set of high-value, unambiguous technical acronyms
(k8s/kubernetes, authn/authz, rls, adr, rbac, sso, pii, iac,
kb). Ambiguous or generic short tokens are intentionally left out of the
defaults to avoid recall noise; add them per deployment via KB_SYNONYMS.
Two environment variables tune it:
KB_SYNONYMS: extra or override groups inkey=variant,variant;key2=variantform. Groups are;-separated; within a group thekeyis the canonical form and the comma-separated tail are its variants. Whitespace is trimmed and empty entries are dropped. Multi-word keys and variants are allowed (e.g.row level security=rls). Example:KB_SYNONYMS="feature flag=ff,flag;service level objective=slo".KB_SYNONYMS_MODE: howKB_SYNONYMScombines with the built-in defaults.merge(default): layerKB_SYNONYMSon top of the curated defaults; a key present in both is overridden byKB_SYNONYMS.replace: use onlyKB_SYNONYMS; the curated defaults are dropped.off: disable expansion entirely (the expander becomes a passthrough, so only the terms the user typed are matched).
Optional local-embedding hybrid ranking
For real paraphrase / synonymy handling (a query that shares no tokens with the
relevant doc, e.g. car for a doc about automobile), the server can blend BM25
with LOCAL embedding similarity. This is optional and OFF by default: with
the feature off and the embeddings extra uninstalled, nothing imports the
embedding stack and the default lexical product is unchanged. There is no
external API and no network call at query time; a small ONNX model runs
in-process.
When enabled, each document is embedded at index-build time and its vector is
stored in a doc_vectors table (schema v8) written into the same tmp database
and swapped atomically with the rest of the index, so vectors rebuild
atomically. At query time a hybrid reranker embeds the query once and blends
normalised BM25 with cosine similarity over the candidate hits' stored
vectors, then re-sorts. It composes UNDER the exact-id / exact-tag short-circuit
(so an exact id or tag still ranks first) and wraps the status-aware rerank (so
an active doc still outranks the superseded one it replaced). When disabled the
reranker stack is exactly the status + id/tag behaviour described under Search
ranking.
Enable and tune it with:
KB_EMBEDDINGS_MODE:onto enable; any other value (including unset) leaves it off.KB_EMBEDDINGS_MODEL: the local model name (defaultBAAI/bge-small-en-v1.5, a 384-dimensional MiniLM-class model). Anyfastembed-supported model works.KB_EMBEDDINGS_WEIGHT: the cosine fraction of the blended score, in[0, 1](default0.35).0.0is pure lexical (BM25) ordering,1.0is pure semantic. A malformed or out-of-range value fails startup loudly.
Install the extra to make the model runner available:
uv sync --extra embeddings # or: pip install 'data-olympus[embeddings]'The extra pulls in fastembed, which bundles onnxruntime (there is no torch
dependency). The default model ships as a ~33 MB quantised ONNX file that is
fetched once when the feature is first enabled and cached locally; after that
there is no network access. If KB_EMBEDDINGS_MODE=on but the extra or the model
is unavailable, startup fails loudly with an actionable message rather than
silently reverting to lexical-only ranking.
Cost: build adds one batched local embedding pass over the corpus (seconds for a
small KB); storage adds dim x 4 bytes per document in doc_vectors (~1.5 KB
per doc for the 384-dim default). Query-time cost is one query embedding plus a
cosine over the (bounded) candidate pool.
Corpus co-occurrence query expansion
A build-time pass mines the corpus for term pairs that co-occur far more often than chance (positive pointwise mutual information) and stores the top related terms per term. At query time the expander adds those related terms so a query reaches documents that use a different-but-associated vocabulary, without a hand-curated synonym map. This is ON by default and adds no query-time network call. It auto-disables on a corpus below the doc floor (too little signal), and the O(n^2) pair counting is bounded per document.
Tune it with:
KB_COOCCURRENCE_MODE:offdisables it (both the build-time table and the query-time expansion); any other value (including unset) leaves it on.KB_COOCCURRENCE_K: max related terms kept per term (default5).KB_COOCCURRENCE_MIN_COUNT: minimum raw co-occurrence count for a pair to qualify (default3).KB_COOCCURRENCE_MIN_PMI: minimum PMI for a pair to qualify (default0.1).KB_COOCCURRENCE_MIN_DOCS: corpus-size floor below which co-occurrence is auto-disabled (default50).KB_COOCCURRENCE_MAX_DOC_TOKENS: per-document unique-token cap on the pair counting, bounding the O(n^2) work on large docs (default400).KB_COOCCURRENCE_MAX_PAIRS: how many distinct pairs the counter may hold in memory before spilling to a temporary SQLite file (default500000).0disables the spill and keeps everything in memory.
Build memory
The per-document token cap bounds each document's contribution. It does not bound the union of pairs across documents, and that union is what the counter holds. On the maintainers' corpus of 536 markdown files (3.6 MiB) the union was 6.2 million distinct pairs, and the build peaked at 1445 MiB, which is above a 1 GiB container limit on its own. Since the pair space grows superlinearly with corpus size, this is a wall every deployment eventually reaches; where it sits depends on the corpus.
KB_COOCCURRENCE_MAX_PAIRS bounds it. Once the counter reaches that many
entries it is flushed into a temporary SQLite database and cleared, and the
counts are aggregated back out at the end. The table produced is identical
either way, so no search result moves; only memory and build time change. On
the same corpus, measured on one laptop:
KB_COOCCURRENCE_MAX_PAIRS | peak RSS | build time |
|---|---|---|
| (behaviour before this bound existed) | 1445 MiB | 6.6 s |
0, no spill | 502 MiB | 3.2 s |
500000, the default | 105 MiB | 16.3 s |
The default trades build time for a ceiling that does not grow with the corpus.
Raise it, or set it to 0, on a host with memory to spare and a corpus that
fits. The bound is on pair counting specifically: parsed documents, per-document
token sets and the candidate lists sit outside it, and the counter is checked
between documents, so it can exceed the cap by at most one document's own pairs.
The spill writes to a temporary file, so it can fail on a full or read-only disk. That failure does not fail the index build: the build logs a warning and continues with no related-terms table, so the index still serves and only co-occurrence expansion is lost until the next successful build.
Memory between rebuilds
A build's working set is freed when the build returns, but on Linux freed memory does not necessarily leave the process. Two glibc behaviours keep it resident:
glibc serves threads from separate arenas and keeps free memory in them for reuse. The refresh loop runs rebuilds on an executor thread and request handlers run on a worker pool, so several long-lived arenas each end up holding a build-sized amount of free memory.
Free space at the top of a worker thread's arena is returned only when it exceeds the trim threshold, and glibc raises that threshold on its own after large blocks are freed, up to 64 MiB. After a rebuild each worker arena kept about 10 MiB of free top that nothing returned.
Resident memory therefore climbed to a plateau several times the starting size and did not come back down while the server idled. Nothing is leaked at the Python level.
The server pins the trim threshold at 128 KiB when it starts (mallopt, which
also stops the automatic increase), and asks the allocator to return free
memory after the bootstrap build and at the end of every refresh tick, whether
or not the tick rebuilt (malloc_trim(0), which walks every arena, so memory
freed by request handlers between rebuilds is covered as well). The pin is
process-wide, and it also stops glibc adjusting its mmap threshold, so large
blocks keep being served by mmap and returned on free rather than cached in
an arena. If you set the trim threshold yourself, through a non-empty
MALLOC_TRIM_THRESHOLD_ or a glibc.malloc.trim_threshold=<value> entry in
GLIBC_TUNABLES, the server leaves your value alone. A bare or empty
glibc.malloc.trim_threshold entry is not a setting, because glibc ignores a
tunable without a value, so the server pins the threshold as usual. In a separate probe on a heap with four
worker arenas, one malloc_trim(0) usually took between 0.1 and 2.3 ms, with
a slowest observed call of 12 ms; it runs on the refresh executor thread, once
per tick. This is best-effort: glibc
returns whole free pages, not every freed allocation, and a rebuild still
needs its working set while it runs, so the container needs room for the
baseline plus one build. Where the allocator offers neither call, as on macOS,
both steps do nothing.
Measured on Linux (glibc 2.41, CPython 3.13, 1 GiB container) with a 641-file,
4.4 MiB corpus, replaying a burst of eight writes through kb_propose_edit,
each followed by a rebuild, with five of the documents a single
23,000-character line, three times, while three clients issued searches,
consultations and gate checks every second. Resident anonymous memory:
| at start | after 8 rebuilds | after 24 rebuilds and 3 idle minutes | peak | |
|---|---|---|---|---|
| before this change | 110 MiB | 308 MiB | 321 MiB (after 32) | 359 MiB |
malloc_trim after each tick, no threshold | 82 MiB | 176 MiB | 186 MiB | 303 MiB |
| this change | 81 MiB | 124 MiB | 126 MiB | 208 MiB |
Setting MALLOC_ARENA_MAX=2 in the environment, with no code change, lowered
the plateau of the same burst without that traffic from 324 MiB to 223 MiB.
With this change it made no measurable difference in these runs.
Extending the governed action vocabulary
The enforcement gate classifies an action as governed from three shipped lists
in enforce_policy.py: keywords, path globs, and command fragments. A
deployment can govern an action class this product has never heard of without
embedding the server in its own Python:
| Setting | Adds to |
|---|---|
KB_GOVERNED_EXTRA_KEYWORDS | the free-text keyword signals |
KB_GOVERNED_EXTRA_PATH_GLOBS | the governed path globs |
KB_GOVERNED_EXTRA_COMMAND_PATTERNS | the governed command fragments |
Each is a comma-separated list, and each extends the shipped list. There is deliberately no setting that replaces one, because a configuration error would then silently remove enforcement the product already provided. Unset means add nothing, which is exactly the shipped behaviour.
Entries are trimmed and de-duplicated, and are bounded: at most 500 per list and 200 characters each. The classifier runs on every classified action and compiles a regex per keyword, so an unbounded list is an availability problem rather than untidiness. Anything dropped or truncated is logged with a warning naming the variable, because an enforcement setting that quietly does less than it says is worse than one that refuses.
For example, a deployment that must not run window or input tests against a live
desktop can add KB_GOVERNED_EXTRA_KEYWORDS="window test,input test" and have
the gate require a fresh explicit consultation before such an action, without
waiting for that class to appear in a release.
This changes only WHAT is classified as governed. It does not change the rule
that only a fresh explicit consultation clears the gate. Under the default
KB_GATE_CLEARANCE=intent, an operator addition has no keyword mapping: a
consult covers it by naming the keyword, path or command fragment in its intent.
Trigram fuzzy-match fallback
For typos and partial identifiers, an optional trigram index backfills results when the primary FTS query returns few hits. This is OFF by default so the default deployment pays no trigram build or storage cost; when off, the trigram table is neither created nor populated. When on, a primary query returning at or below the threshold is backfilled from the trigram index, and the backfill only ever appends after the primary hits (never reorders them).
Tune it with:
KB_TRIGRAM_MODE:on(case-insensitive) enables it; any other value (including unset) leaves it off.KB_TRIGRAM_FALLBACK_THRESHOLD: primary-hit count at or below which the fallback fires (default3). A malformed or negative value falls back to the default.
Authentication and network security
The server supports optional bearer-token authentication with a per-principal capability model, enforced on both the REST routes and the MCP write tools:
KB_AUTH_TOKENregisters a single full-capabilityoperatorprincipal.KB_AUTH_PRINCIPALS(JSON) registers per-agent tokens with explicit capabilities; a principal lackingauto_commithas its proposals clamped to pending regardless of the client-asserted confidence. When set, it must be a non-empty JSON list of objects, each with a non-empty stringtoken. Anything else (invalid JSON, a single object, an entry that is not an object, or an entry without a usabletoken, such as a misspelled key) fails startup with an error namingKB_AUTH_PRINCIPALSand, where it applies, the entry number. The error never repeats the value. This holds whether or notKB_AUTH_TOKENis also set.
When auth is configured, write, enforcement, and observability routes require a
capable principal; read routes (search/get/list/outline/health) stay
open. When it is unset (the default) every caller is fully trusted, which is only
safe on a trusted private network.
Authentication does not protect read routes, so for confidentiality operators
MUST still deploy on a trusted private network or behind an authenticating
reverse proxy (terminate TLS there). See SECURITY.md for the full threat model,
the route/capability table, payload limits, the tamper-evident audit log, and the
git sync-failure health fields.
Host allowlist behind a proxy (KB_PUBLIC_HOSTNAMES, v0.4.3+)
fastmcp >= 3.4.3 validates the Host header on streamable HTTP
(DNS-rebinding protection) and returns 421 Misdirected Request for
hostnames outside its allowlist. When data-olympus is served behind a
reverse proxy or ingress, set the public hostname explicitly via the
first-class knob (comma-separated, no JSON):
KB_PUBLIC_HOSTNAMES=kb.example.comThis is mapped onto fastmcp's Host-header allowlist when the HTTP app is
built. The older dependency knob FASTMCP_HTTP_ALLOWED_HOSTS='["kb.example.com"]'
still works and is merged with KB_PUBLIC_HOSTNAMES when both are set.
Direct localhost/pod requests are always allowed, so health probes pass while proxied traffic fails: this is the exact shape of the 2026-07-09 kn-dev outage (readiness green, every proxied request 421). To make that failure loud, the server now emits a startup WARN when Host protection is on, it binds a non-loopback address, and no public hostname is allowed. Check the server log for that WARN or for 421 lines if agents suddenly cannot reach the endpoint after an upgrade. Operational details in docs/operations.md section 3.4. (Closes issue #139 / KNA-70.)
Prometheus metrics (GET /metrics, optional metrics extra)
prometheus-client is an OPTIONAL dependency. Install it to enable the
scrape endpoint:
uv tool install 'data-olympus[metrics]' # or pip install 'data-olympus[metrics]'With the extra installed, GET /metrics serves the standard text exposition
format. Without it, /metrics returns 501 Not Implemented (the route exists,
the capability is simply not built into the image) and every metric update
inside the core background loops is a silent no-op, so a standard deployment is
unaffected. Exposed series (prefix data_olympus_):
pending_queue_depth,push_queue_depth,push_queue_frozen(gauges)push_failures_total(counter)staleness_seconds,live_sessions(gauges)tool_calls_total{tool="..."}(counter, per MCP tool)index_build_duration_seconds(histogram) andindex_last_build_timestamp_seconds(gauge)
The gauges are refreshed from the refresh / push-retry loops each tick and also snapshotted on scrape, so a scrape reflects the live queue depths and freshness. (Closes issue #69 / KNA-71.)
Proxy headers and the rate limiter (KB_TRUSTED_PROXIES)
The rate limiter keys on the client remote address (plus principal). Behind an
ingress or reverse proxy, uvicorn by default sees the proxy's address as the
peer, so every client collapses into one remote_addr and the per-IP cap
(KB_RATE_LIMIT_PER_IP_PER_HOUR) throttles all clients as if they were one.
Set KB_TRUSTED_PROXIES to the proxy address(es) (comma-separated) to fix this:
Unset (default): uvicorn runs with
proxy_headersoff.X-Forwarded-Foris ignored andremote_addris the immediate peer. This is the safe default: a direct client cannot spoof its address to dodge the limiter.Set to the proxy IP(s): uvicorn enables
proxy_headersand restrictsforwarded_allow_ipsto those addresses, so it rewritesremote_addrfromX-Forwarded-Foronly when the immediate peer is a trusted proxy. The limiter then sees the true client IP.*: trustX-Forwarded-Forfrom any peer. Only safe when nothing untrusted can reach the port directly (e.g. the port is bound to the pod network and only the ingress can connect). Otherwise a direct client could forge the header.
The gate-check route (KB_GATE_CHECK_RATE_LIMIT_PER_HOUR)
POST /api/v1/gate/check (and the kb_gate_check MCP tool) is the enforcement
hook's freshness probe, called once per gated tool action by every agent. It
therefore must not share the write/consult quota (KB_RATE_LIMIT_PER_HOUR): with
several agents active, and all clients collapsing to one limiter bucket behind an
ingress, a fixed hourly quota self-throttles the whole fleet with 429s. So
gate-check has its own ceiling:
KB_GATE_CHECK_RATE_LIMIT_PER_HOUR=0(default): gate-check is not rate-limited. It does only cheap classification plus a freshness lookup and no writes, so this matches the read routes, which are also unthrottled.A positive value: an explicit per-(address, principal) backstop just for gate-check, independent of the write/consult limiter. Size it above your fleet's real per-hour tool-call volume, not near it, or you reintroduce the self-DoS.
The consult and onboarding/cleanup-plan routes remain throttled by
KB_RATE_LIMIT_PER_HOUR (they write to the ledger or are CPU-heavy).
Audit-log rotation (KB_AUDIT_MAX_BYTES)
The JSONL audit log (KB_AUDIT_LOG_PATH, default /state/audit/events.log) grows
without bound by default. Set KB_AUDIT_MAX_BYTES to a byte threshold to enable
size-based rotation: when the live file passes the threshold, the next append
first renames it to events-<UTC-timestamp>.log and starts a fresh live file.
Chain continuity. The tamper-evident hash chain carries across the rotation boundary: the first event of the new file links to the last hash of the rotated file, so
GET /api/v1/audit/verify(and thekb audit/verifylogic) validates the whole history, rotated segments included, in chronological order. A break at the boundary is caught like any other.Backward compatible. With
KB_AUDIT_MAX_BYTESunset (0), nothing rotates: the log stays a single file exactly as before, and an existing single-file log still verifies unchanged when opened by a rotation-aware build.Reads.
kb auditreads the live file by default (cheap, most-recent-first). A--sincequery also walks rotated segments so history that has rotated out of the live file is still visible; the reverse scan is bounded so a large archive cannot turn one query into an unbounded read.
Manual mv-based rotation (documented in the operator runbook) no longer breaks
the chain when the process keeps running, because the in-memory last-hash carries
forward; but prefer KB_AUDIT_MAX_BYTES so rotation is automatic and the boundary
link is always written. See docs/operations.md for backup/verify procedures.
Git commit identity
Every write commits to the KB repo, so git needs an author/committer identity.
The shipped Docker image sets a default (data-olympus-mcp <data-olympus-mcp@localhost>)
in deploy/docker/entrypoint.sh so the artifact can commit out of the box; the
server's main() also fills unset GIT_* variables as a belt-and-suspenders for
non-Docker runs. Override the identity with:
KB_GIT_AUTHOR_NAME: commit author/committer name (defaultdata-olympus-mcp).KB_GIT_AUTHOR_EMAIL: commit author/committer email (defaultdata-olympus-mcp@localhost).
A pre-existing GIT_AUTHOR_*/GIT_COMMITTER_* value or a real git config
identity is never overwritten.
Running locally
See docs/quickstart.md for the verified local-run procedure using
scripts/run-local.sh.
Running in Kubernetes
See deploy/k8s/ for the kustomize manifests. Apply them with:
# Apply namespace and non-secret resources
kubectl apply -k deploy/k8s/
# Apply the encrypted secret separately (operators use SOPS)
sops exec-file deploy/k8s/secret.sops.yaml 'kubectl apply -f {}'The deploy/k8s/secret.template.yaml file shows the expected secret shape.
Fill in your git remote URL and deploy key, then encrypt with SOPS before
committing. Never commit the unencrypted secret.
The default kubectl apply -k deploy/k8s/ deliberately does not create the
Ingress: it publishes all routes (including the unauthenticated-by-default write
routes) to the LAN. The Ingress is opt-in and enabled only after you set
KB_AUTH_TOKEN; see deploy/k8s/README.md for the enablement steps.
The pod is fully rootless: both the prepare-git initContainer (which stages the
deploy key and does the first-boot /kb-main clone) and the main container run as
uid 65534 with runAsNonRoot: true, all capabilities dropped, and a read-only
root filesystem, so the manifest passes the restricted Pod Security Standard.
The base image is digest-pinned in deploy/docker/Dockerfile; the git SSH host is
build-arg + runtime-env configurable (KB_SSH_KEYSCAN_HOST) for non-GitHub
remotes. Operational procedures (backup, upgrade, recovery playbooks) live in
docs/operations.md.
Running with Docker Compose
docker compose -f deploy/docker/compose.yaml up --buildSee docs/quickstart.md section 5 for the bind-mount pattern to serve a
real bundle.
512 files · 486 not served here
- .gitattributes (not served here)
- .github/CODEOWNERS (not served here)
- .github/ISSUE_TEMPLATE/bug_report.yml (not served here)
- .github/ISSUE_TEMPLATE/config.yml (not served here)
- .github/ISSUE_TEMPLATE/release-scope.yml (not served here)
- .github/ISSUE_TEMPLATE/spec_proposal.yml (not served here)
- .github/PULL_REQUEST_TEMPLATE.md (not served here)
- .github/copilot-instructions.md
- .github/dependabot.yml (not served here)
- .github/labels.yml (not served here)
- .github/workflows/ci.yaml (not served here)
- .github/workflows/hotfix-cut.yml (not served here)
- .github/workflows/maintenance-inventory.yml (not served here)
- .github/workflows/okf-pin-freshness.yml (not served here)
- .github/workflows/package-dry-run.yml (not served here)
- .github/workflows/pr-title-lint.yml (not served here)
- .github/workflows/promote-release.yml (not served here)
- .github/workflows/publish-python-build.yml (not served here)
- .github/workflows/rc-build.yml (not served here)
- .github/workflows/rc-publish-stage.yml (not served here)
- .github/workflows/recut-release-branch.yml (not served here)
- .gitignore (not served here)
- .prose-lintignore (not served here)
- .rules/changelog-per-release.md (not served here)
- .rules/release-planning.md (not served here)
- .rules/release-rollback.md (not served here)
- .rules/release-routine.md (not served here)
- .rules/versioning.md (not served here)
- AGENTS.md
- ATTRIBUTION.md
- CHANGELOG.md
- CLAUDE.md
- CODE_OF_CONDUCT.md
- CONTRIBUTING.md
- LICENSE
- NOTICE
- README.md
- ROADMAP.md
- SECURITY.md
- SPEC.md
- WHY.md
- benchmarks/README.md (not served here)
- benchmarks/__init__.py (not served here)
- benchmarks/ablate.py (not served here)
- benchmarks/chunking.py (not served here)
- benchmarks/corpus/decisions/BENCH-AUTH-TOKENS-1.md (not served here)
- benchmarks/corpus/decisions/BENCH-AUTH-TOKENS-3.md (not served here)
- benchmarks/corpus/decisions/BENCH-AUTH-TOKENS-5.md (not served here)
- benchmarks/corpus/decisions/BENCH-AUTH-TOKENS-6.md (not served here)
- benchmarks/corpus/decisions/BENCH-AUTH-TOKENS-7.md (not served here)
- benchmarks/corpus/decisions/BENCH-AUTH-TOKENS-8.md (not served here)
- benchmarks/corpus/decisions/BENCH-AUTH-TOKENS.md (not served here)
- benchmarks/corpus/decisions/BENCH-CONFIG-RELOADING-1.md (not served here)
- benchmarks/corpus/decisions/BENCH-CONFIG-RELOADING-2.md (not served here)
- benchmarks/corpus/decisions/BENCH-CONFIG-RELOADING-3.md (not served here)
- benchmarks/corpus/decisions/BENCH-CONFIG-RELOADING-4.md (not served here)
- benchmarks/corpus/decisions/BENCH-CONFIG-RELOADING-5.md (not served here)
- benchmarks/corpus/decisions/BENCH-CONFIG-RELOADING-7.md (not served here)
- benchmarks/corpus/decisions/BENCH-CONFIG-RELOADING.md (not served here)
- benchmarks/corpus/decisions/BENCH-IDEMPOTENCY-1.md (not served here)
- benchmarks/corpus/decisions/BENCH-IDEMPOTENCY-2.md (not served here)
- benchmarks/corpus/decisions/BENCH-IDEMPOTENCY-3.md (not served here)
- benchmarks/corpus/decisions/BENCH-IDEMPOTENCY-4.md (not served here)
- benchmarks/corpus/decisions/BENCH-IDEMPOTENCY-5.md (not served here)
- benchmarks/corpus/decisions/BENCH-IDEMPOTENCY-6.md (not served here)
- benchmarks/corpus/decisions/BENCH-IDEMPOTENCY-7.md (not served here)
- benchmarks/corpus/decisions/BENCH-IDEMPOTENCY-8.md (not served here)
- benchmarks/corpus/decisions/BENCH-IDEMPOTENCY.md (not served here)
- benchmarks/corpus/decisions/BENCH-MESSAGE-ORDERING-1.md (not served here)
- benchmarks/corpus/decisions/BENCH-MESSAGE-ORDERING-2.md (not served here)
- benchmarks/corpus/decisions/BENCH-MESSAGE-ORDERING-3.md (not served here)
- benchmarks/corpus/decisions/BENCH-MESSAGE-ORDERING-4.md (not served here)
- benchmarks/corpus/decisions/BENCH-MESSAGE-ORDERING-5.md (not served here)
- benchmarks/corpus/decisions/BENCH-MESSAGE-ORDERING-6.md (not served here)
- benchmarks/corpus/decisions/BENCH-MESSAGE-ORDERING-7.md (not served here)
- benchmarks/corpus/decisions/BENCH-MESSAGE-ORDERING.md (not served here)
- benchmarks/corpus/decisions/BENCH-NEW-AUTH-TOKENS-2.md (not served here)
- benchmarks/corpus/decisions/BENCH-NEW-AUTH-TOKENS-4.md (not served here)
- benchmarks/corpus/decisions/BENCH-NEW-CONFIG-RELOADING-6.md (not served here)
- benchmarks/corpus/decisions/BENCH-NEW-MESSAGE-ORDERING-8.md (not served here)
- benchmarks/corpus/decisions/BENCH-NEW-SAGA-ORCHESTRATION-1.md (not served here)
- benchmarks/corpus/decisions/BENCH-NEW-SAGA-ORCHESTRATION-4.md (not served here)
- benchmarks/corpus/decisions/BENCH-NEW-SAGA-ORCHESTRATION-6.md (not served here)
- benchmarks/corpus/decisions/BENCH-OLD-AUTH-TOKENS-2.md (not served here)
- benchmarks/corpus/decisions/BENCH-OLD-AUTH-TOKENS-4.md (not served here)
- benchmarks/corpus/decisions/BENCH-OLD-CONFIG-RELOADING-6.md (not served here)
- benchmarks/corpus/decisions/BENCH-OLD-MESSAGE-ORDERING-8.md (not served here)
- benchmarks/corpus/decisions/BENCH-OLD-SAGA-ORCHESTRATION-1.md (not served here)
- benchmarks/corpus/decisions/BENCH-OLD-SAGA-ORCHESTRATION-4.md (not served here)
- benchmarks/corpus/decisions/BENCH-OLD-SAGA-ORCHESTRATION-6.md (not served here)
- benchmarks/corpus/decisions/BENCH-SAGA-ORCHESTRATION-2.md (not served here)
- benchmarks/corpus/decisions/BENCH-SAGA-ORCHESTRATION-3.md (not served here)
- benchmarks/corpus/decisions/BENCH-SAGA-ORCHESTRATION-5.md (not served here)
- benchmarks/corpus/decisions/BENCH-SAGA-ORCHESTRATION-7.md (not served here)
- benchmarks/corpus/decisions/BENCH-SAGA-ORCHESTRATION.md (not served here)
- benchmarks/corpus/projects/example-project/BENCH-BLUE-GREEN-DEPLOYS-1.md (not served here)
- benchmarks/corpus/projects/example-project/BENCH-BLUE-GREEN-DEPLOYS-3.md (not served here)
- benchmarks/corpus/projects/example-project/BENCH-BLUE-GREEN-DEPLOYS-4.md (not served here)
- benchmarks/corpus/projects/example-project/BENCH-BLUE-GREEN-DEPLOYS-5.md (not served here)
- benchmarks/corpus/projects/example-project/BENCH-BLUE-GREEN-DEPLOYS-6.md (not served here)
- benchmarks/corpus/projects/example-project/BENCH-BLUE-GREEN-DEPLOYS-7.md (not served here)
- benchmarks/corpus/projects/example-project/BENCH-BLUE-GREEN-DEPLOYS.md (not served here)
- benchmarks/corpus/projects/example-project/BENCH-FEATURE-FLAGS-1.md (not served here)
- benchmarks/corpus/projects/example-project/BENCH-FEATURE-FLAGS-3.md (not served here)
- benchmarks/corpus/projects/example-project/BENCH-FEATURE-FLAGS-4.md (not served here)
- benchmarks/corpus/projects/example-project/BENCH-FEATURE-FLAGS-5.md (not served here)
- benchmarks/corpus/projects/example-project/BENCH-FEATURE-FLAGS-7.md (not served here)
- benchmarks/corpus/projects/example-project/BENCH-FEATURE-FLAGS-8.md (not served here)
- benchmarks/corpus/projects/example-project/BENCH-FEATURE-FLAGS.md (not served here)
- benchmarks/corpus/projects/example-project/BENCH-GRACEFUL-SHUTDOWN-1.md (not served here)
- benchmarks/corpus/projects/example-project/BENCH-GRACEFUL-SHUTDOWN-2.md (not served here)
- benchmarks/corpus/projects/example-project/BENCH-GRACEFUL-SHUTDOWN-3.md (not served here)
- benchmarks/corpus/projects/example-project/BENCH-GRACEFUL-SHUTDOWN-4.md (not served here)
- benchmarks/corpus/projects/example-project/BENCH-GRACEFUL-SHUTDOWN-6.md (not served here)
- benchmarks/corpus/projects/example-project/BENCH-GRACEFUL-SHUTDOWN-7.md (not served here)
- benchmarks/corpus/projects/example-project/BENCH-GRACEFUL-SHUTDOWN.md (not served here)
- benchmarks/corpus/projects/example-project/BENCH-NEW-BLUE-GREEN-DEPLOYS-2.md (not served here)
- benchmarks/corpus/projects/example-project/BENCH-NEW-FEATURE-FLAGS-2.md (not served here)
- benchmarks/corpus/projects/example-project/BENCH-NEW-FEATURE-FLAGS-6.md (not served here)
- benchmarks/corpus/projects/example-project/BENCH-NEW-GRACEFUL-SHUTDOWN-5.md (not served here)
- benchmarks/corpus/projects/example-project/BENCH-OLD-BLUE-GREEN-DEPLOYS-2.md (not served here)
- benchmarks/corpus/projects/example-project/BENCH-OLD-FEATURE-FLAGS-2.md (not served here)
- benchmarks/corpus/projects/example-project/BENCH-OLD-FEATURE-FLAGS-6.md (not served here)
- benchmarks/corpus/projects/example-project/BENCH-OLD-GRACEFUL-SHUTDOWN-5.md (not served here)
- benchmarks/corpus/projects/example-project/BENCH-PAGINATION-1.md (not served here)
- benchmarks/corpus/projects/example-project/BENCH-PAGINATION-2.md (not served here)
- benchmarks/corpus/projects/example-project/BENCH-PAGINATION-3.md (not served here)
- benchmarks/corpus/projects/example-project/BENCH-PAGINATION-4.md (not served here)
- benchmarks/corpus/projects/example-project/BENCH-PAGINATION-5.md (not served here)
- benchmarks/corpus/projects/example-project/BENCH-PAGINATION-6.md (not served here)
- benchmarks/corpus/projects/example-project/BENCH-PAGINATION-7.md (not served here)
- benchmarks/corpus/projects/example-project/BENCH-PAGINATION-8.md (not served here)
- benchmarks/corpus/projects/example-project/BENCH-PAGINATION.md (not served here)
- benchmarks/corpus/projects/example-project/BENCH-SECRETS-HANDLING-1.md (not served here)
- benchmarks/corpus/projects/example-project/BENCH-SECRETS-HANDLING-2.md (not served here)
- benchmarks/corpus/projects/example-project/BENCH-SECRETS-HANDLING-3.md (not served here)
- benchmarks/corpus/projects/example-project/BENCH-SECRETS-HANDLING-4.md (not served here)
- benchmarks/corpus/projects/example-project/BENCH-SECRETS-HANDLING-5.md (not served here)
- benchmarks/corpus/projects/example-project/BENCH-SECRETS-HANDLING-6.md (not served here)
- benchmarks/corpus/projects/example-project/BENCH-SECRETS-HANDLING-7.md (not served here)
- benchmarks/corpus/projects/example-project/BENCH-SECRETS-HANDLING-8.md (not served here)
- benchmarks/corpus/projects/example-project/BENCH-SECRETS-HANDLING.md (not served here)
- benchmarks/corpus/projects/example-project/components/api/BENCH-CIRCUIT-BREAKERS-1.md (not served here)
- benchmarks/corpus/projects/example-project/components/api/BENCH-CIRCUIT-BREAKERS-2.md (not served here)
- benchmarks/corpus/projects/example-project/components/api/BENCH-CIRCUIT-BREAKERS-3.md (not served here)
- benchmarks/corpus/projects/example-project/components/api/BENCH-CIRCUIT-BREAKERS-4.md (not served here)
- benchmarks/corpus/projects/example-project/components/api/BENCH-CIRCUIT-BREAKERS-5.md (not served here)
- benchmarks/corpus/projects/example-project/components/api/BENCH-CIRCUIT-BREAKERS-7.md (not served here)
- benchmarks/corpus/projects/example-project/components/api/BENCH-CIRCUIT-BREAKERS-8.md (not served here)
- benchmarks/corpus/projects/example-project/components/api/BENCH-CIRCUIT-BREAKERS.md (not served here)
- benchmarks/corpus/projects/example-project/components/api/BENCH-DEAD-LETTER-QUEUES-1.md (not served here)
- benchmarks/corpus/projects/example-project/components/api/BENCH-DEAD-LETTER-QUEUES-2.md (not served here)
- benchmarks/corpus/projects/example-project/components/api/BENCH-DEAD-LETTER-QUEUES-4.md (not served here)
- benchmarks/corpus/projects/example-project/components/api/BENCH-DEAD-LETTER-QUEUES-5.md (not served here)
- benchmarks/corpus/projects/example-project/components/api/BENCH-DEAD-LETTER-QUEUES.md (not served here)
- benchmarks/corpus/projects/example-project/components/api/BENCH-HEALTH-CHECKS-1.md (not served here)
- benchmarks/corpus/projects/example-project/components/api/BENCH-HEALTH-CHECKS-2.md (not served here)
- benchmarks/corpus/projects/example-project/components/api/BENCH-HEALTH-CHECKS-3.md (not served here)
- benchmarks/corpus/projects/example-project/components/api/BENCH-HEALTH-CHECKS-4.md (not served here)
- benchmarks/corpus/projects/example-project/components/api/BENCH-HEALTH-CHECKS-5.md (not served here)
- benchmarks/corpus/projects/example-project/components/api/BENCH-HEALTH-CHECKS-6.md (not served here)
- benchmarks/corpus/projects/example-project/components/api/BENCH-HEALTH-CHECKS.md (not served here)
- benchmarks/corpus/projects/example-project/components/api/BENCH-INPUT-VALIDATION-1.md (not served here)
- benchmarks/corpus/projects/example-project/components/api/BENCH-INPUT-VALIDATION-2.md (not served here)
- benchmarks/corpus/projects/example-project/components/api/BENCH-INPUT-VALIDATION-3.md (not served here)
- benchmarks/corpus/projects/example-project/components/api/BENCH-INPUT-VALIDATION-4.md (not served here)
- benchmarks/corpus/projects/example-project/components/api/BENCH-INPUT-VALIDATION-7.md (not served here)
- benchmarks/corpus/projects/example-project/components/api/BENCH-INPUT-VALIDATION-8.md (not served here)
- benchmarks/corpus/projects/example-project/components/api/BENCH-INPUT-VALIDATION.md (not served here)
- benchmarks/corpus/projects/example-project/components/api/BENCH-NEW-CIRCUIT-BREAKERS-6.md (not served here)
- benchmarks/corpus/projects/example-project/components/api/BENCH-NEW-DEAD-LETTER-QUEUES-3.md (not served here)
- benchmarks/corpus/projects/example-project/components/api/BENCH-NEW-DEAD-LETTER-QUEUES-6.md (not served here)
- benchmarks/corpus/projects/example-project/components/api/BENCH-NEW-DEAD-LETTER-QUEUES-7.md (not served here)
- benchmarks/corpus/projects/example-project/components/api/BENCH-NEW-HEALTH-CHECKS-7.md (not served here)
- benchmarks/corpus/projects/example-project/components/api/BENCH-NEW-INPUT-VALIDATION-5.md (not served here)
- benchmarks/corpus/projects/example-project/components/api/BENCH-NEW-INPUT-VALIDATION-6.md (not served here)
- benchmarks/corpus/projects/example-project/components/api/BENCH-NEW-RATE-LIMITING.md (not served here)
- benchmarks/corpus/projects/example-project/components/api/BENCH-OLD-CIRCUIT-BREAKERS-6.md (not served here)
- benchmarks/corpus/projects/example-project/components/api/BENCH-OLD-DEAD-LETTER-QUEUES-3.md (not served here)
- benchmarks/corpus/projects/example-project/components/api/BENCH-OLD-DEAD-LETTER-QUEUES-6.md (not served here)
- benchmarks/corpus/projects/example-project/components/api/BENCH-OLD-DEAD-LETTER-QUEUES-7.md (not served here)
- benchmarks/corpus/projects/example-project/components/api/BENCH-OLD-HEALTH-CHECKS-7.md (not served here)
- benchmarks/corpus/projects/example-project/components/api/BENCH-OLD-INPUT-VALIDATION-5.md (not served here)
- benchmarks/corpus/projects/example-project/components/api/BENCH-OLD-INPUT-VALIDATION-6.md (not served here)
- benchmarks/corpus/projects/example-project/components/api/BENCH-OLD-RATE-LIMITING.md (not served here)
- benchmarks/corpus/projects/example-project/components/api/BENCH-RATE-LIMITING-1.md (not served here)
- benchmarks/corpus/projects/example-project/components/api/BENCH-RATE-LIMITING-2.md (not served here)
- benchmarks/corpus/projects/example-project/components/api/BENCH-RATE-LIMITING-3.md (not served here)
- benchmarks/corpus/projects/example-project/components/api/BENCH-RATE-LIMITING-4.md (not served here)
- benchmarks/corpus/projects/example-project/components/api/BENCH-RATE-LIMITING-5.md (not served here)
- benchmarks/corpus/projects/example-project/components/api/BENCH-RATE-LIMITING-6.md (not served here)
- benchmarks/corpus/projects/example-project/components/api/BENCH-RATE-LIMITING-7.md (not served here)
- benchmarks/corpus/projects/example-project/components/api/BENCH-RATE-LIMITING-8.md (not served here)
- benchmarks/corpus/tech-stacks/backend-nestjs/BENCH-BACKPRESSURE-1.md (not served here)
- benchmarks/corpus/tech-stacks/backend-nestjs/BENCH-BACKPRESSURE-2.md (not served here)
- benchmarks/corpus/tech-stacks/backend-nestjs/BENCH-BACKPRESSURE-3.md (not served here)
- benchmarks/corpus/tech-stacks/backend-nestjs/BENCH-BACKPRESSURE-4.md (not served here)
- benchmarks/corpus/tech-stacks/backend-nestjs/BENCH-BACKPRESSURE-5.md (not served here)
- benchmarks/corpus/tech-stacks/backend-nestjs/BENCH-BACKPRESSURE-6.md (not served here)
- benchmarks/corpus/tech-stacks/backend-nestjs/BENCH-BACKPRESSURE-7.md (not served here)
- benchmarks/corpus/tech-stacks/backend-nestjs/BENCH-BACKPRESSURE-8.md (not served here)
- benchmarks/corpus/tech-stacks/backend-nestjs/BENCH-BACKPRESSURE.md (not served here)
- benchmarks/corpus/tech-stacks/backend-nestjs/BENCH-CANARY-ROLLOUTS-1.md (not served here)
- benchmarks/corpus/tech-stacks/backend-nestjs/BENCH-CANARY-ROLLOUTS-2.md (not served here)
- benchmarks/corpus/tech-stacks/backend-nestjs/BENCH-CANARY-ROLLOUTS-3.md (not served here)
- benchmarks/corpus/tech-stacks/backend-nestjs/BENCH-CANARY-ROLLOUTS-4.md (not served here)
- benchmarks/corpus/tech-stacks/backend-nestjs/BENCH-CANARY-ROLLOUTS-5.md (not served here)
- benchmarks/corpus/tech-stacks/backend-nestjs/BENCH-CANARY-ROLLOUTS-6.md (not served here)
- benchmarks/corpus/tech-stacks/backend-nestjs/BENCH-CANARY-ROLLOUTS-7.md (not served here)
- benchmarks/corpus/tech-stacks/backend-nestjs/BENCH-CANARY-ROLLOUTS.md (not served here)
- benchmarks/corpus/tech-stacks/backend-nestjs/BENCH-CONNECTION-POOLING-2.md (not served here)
- benchmarks/corpus/tech-stacks/backend-nestjs/BENCH-CONNECTION-POOLING-4.md (not served here)
- benchmarks/corpus/tech-stacks/backend-nestjs/BENCH-CONNECTION-POOLING-5.md (not served here)
- benchmarks/corpus/tech-stacks/backend-nestjs/BENCH-CONNECTION-POOLING-6.md (not served here)
- benchmarks/corpus/tech-stacks/backend-nestjs/BENCH-CONNECTION-POOLING-8.md (not served here)
- benchmarks/corpus/tech-stacks/backend-nestjs/BENCH-CONNECTION-POOLING.md (not served here)
- benchmarks/corpus/tech-stacks/backend-nestjs/BENCH-NEW-CONNECTION-POOLING-1.md (not served here)
- benchmarks/corpus/tech-stacks/backend-nestjs/BENCH-NEW-CONNECTION-POOLING-3.md (not served here)
- benchmarks/corpus/tech-stacks/backend-nestjs/BENCH-NEW-CONNECTION-POOLING-7.md (not served here)
- benchmarks/corpus/tech-stacks/backend-nestjs/BENCH-NEW-RETRIES-7.md (not served here)
- benchmarks/corpus/tech-stacks/backend-nestjs/BENCH-NEW-TRACING-5.md (not served here)
- benchmarks/corpus/tech-stacks/backend-nestjs/BENCH-NEW-TRACING.md (not served here)
- benchmarks/corpus/tech-stacks/backend-nestjs/BENCH-OLD-CONNECTION-POOLING-1.md (not served here)
- benchmarks/corpus/tech-stacks/backend-nestjs/BENCH-OLD-CONNECTION-POOLING-3.md (not served here)
- benchmarks/corpus/tech-stacks/backend-nestjs/BENCH-OLD-CONNECTION-POOLING-7.md (not served here)
- benchmarks/corpus/tech-stacks/backend-nestjs/BENCH-OLD-RETRIES-7.md (not served here)
- benchmarks/corpus/tech-stacks/backend-nestjs/BENCH-OLD-TRACING-5.md (not served here)
- benchmarks/corpus/tech-stacks/backend-nestjs/BENCH-OLD-TRACING.md (not served here)
- benchmarks/corpus/tech-stacks/backend-nestjs/BENCH-RETRIES-1.md (not served here)
- benchmarks/corpus/tech-stacks/backend-nestjs/BENCH-RETRIES-2.md (not served here)
- benchmarks/corpus/tech-stacks/backend-nestjs/BENCH-RETRIES-3.md (not served here)
- benchmarks/corpus/tech-stacks/backend-nestjs/BENCH-RETRIES-4.md (not served here)
- benchmarks/corpus/tech-stacks/backend-nestjs/BENCH-RETRIES-5.md (not served here)
- benchmarks/corpus/tech-stacks/backend-nestjs/BENCH-RETRIES-6.md (not served here)
- benchmarks/corpus/tech-stacks/backend-nestjs/BENCH-RETRIES-8.md (not served here)
- benchmarks/corpus/tech-stacks/backend-nestjs/BENCH-RETRIES.md (not served here)
- benchmarks/corpus/tech-stacks/backend-nestjs/BENCH-TRACING-1.md (not served here)
- benchmarks/corpus/tech-stacks/backend-nestjs/BENCH-TRACING-2.md (not served here)
- benchmarks/corpus/tech-stacks/backend-nestjs/BENCH-TRACING-3.md (not served here)
- benchmarks/corpus/tech-stacks/backend-nestjs/BENCH-TRACING-4.md (not served here)
- benchmarks/corpus/tech-stacks/backend-nestjs/BENCH-TRACING-6.md (not served here)
- benchmarks/corpus/tech-stacks/backend-nestjs/BENCH-TRACING-7.md (not served here)
- benchmarks/corpus/tech-stacks/backend-nestjs/BENCH-TRACING-8.md (not served here)
- benchmarks/corpus/universal/foundation/BENCH-CACHING-1.md (not served here)
- benchmarks/corpus/universal/foundation/BENCH-CACHING-2.md (not served here)
- benchmarks/corpus/universal/foundation/BENCH-CACHING-3.md (not served here)
- benchmarks/corpus/universal/foundation/BENCH-CACHING-4.md (not served here)
- benchmarks/corpus/universal/foundation/BENCH-CACHING-5.md (not served here)
- benchmarks/corpus/universal/foundation/BENCH-CACHING-6.md (not served here)
- benchmarks/corpus/universal/foundation/BENCH-CACHING-7.md (not served here)
- benchmarks/corpus/universal/foundation/BENCH-CACHING-8.md (not served here)
- benchmarks/corpus/universal/foundation/BENCH-CACHING.md (not served here)
- benchmarks/corpus/universal/foundation/BENCH-DATABASE-MIGRATIONS-2.md (not served here)
- benchmarks/corpus/universal/foundation/BENCH-DATABASE-MIGRATIONS-3.md (not served here)
- benchmarks/corpus/universal/foundation/BENCH-DATABASE-MIGRATIONS-4.md (not served here)
- benchmarks/corpus/universal/foundation/BENCH-DATABASE-MIGRATIONS-5.md (not served here)
- benchmarks/corpus/universal/foundation/BENCH-DATABASE-MIGRATIONS-8.md (not served here)
- benchmarks/corpus/universal/foundation/BENCH-DATABASE-MIGRATIONS.md (not served here)
- benchmarks/corpus/universal/foundation/BENCH-ERROR-BUDGETS-1.md (not served here)
- benchmarks/corpus/universal/foundation/BENCH-ERROR-BUDGETS-2.md (not served here)
- benchmarks/corpus/universal/foundation/BENCH-ERROR-BUDGETS-5.md (not served here)
- benchmarks/corpus/universal/foundation/BENCH-ERROR-BUDGETS-6.md (not served here)
- benchmarks/corpus/universal/foundation/BENCH-ERROR-BUDGETS-7.md (not served here)
- benchmarks/corpus/universal/foundation/BENCH-ERROR-BUDGETS.md (not served here)
- benchmarks/corpus/universal/foundation/BENCH-LOGGING-1.md (not served here)
- benchmarks/corpus/universal/foundation/BENCH-LOGGING-2.md (not served here)
- benchmarks/corpus/universal/foundation/BENCH-LOGGING-3.md (not served here)
- benchmarks/corpus/universal/foundation/BENCH-LOGGING-4.md (not served here)
- benchmarks/corpus/universal/foundation/BENCH-LOGGING-6.md (not served here)
- benchmarks/corpus/universal/foundation/BENCH-LOGGING-7.md (not served here)
- benchmarks/corpus/universal/foundation/BENCH-LOGGING-8.md (not served here)
- benchmarks/corpus/universal/foundation/BENCH-LOGGING.md (not served here)
- benchmarks/corpus/universal/foundation/BENCH-NEW-DATABASE-MIGRATIONS-1.md (not served here)
- benchmarks/corpus/universal/foundation/BENCH-NEW-DATABASE-MIGRATIONS-6.md (not served here)
- benchmarks/corpus/universal/foundation/BENCH-NEW-DATABASE-MIGRATIONS-7.md (not served here)
- benchmarks/corpus/universal/foundation/BENCH-NEW-ERROR-BUDGETS-3.md (not served here)
- benchmarks/corpus/universal/foundation/BENCH-NEW-ERROR-BUDGETS-4.md (not served here)
- benchmarks/corpus/universal/foundation/BENCH-NEW-LOGGING-5.md (not served here)
- benchmarks/corpus/universal/foundation/BENCH-NEW-SCHEMA-EVOLUTION-3.md (not served here)
- benchmarks/corpus/universal/foundation/BENCH-NEW-SCHEMA-EVOLUTION-8.md (not served here)
- benchmarks/corpus/universal/foundation/BENCH-OLD-DATABASE-MIGRATIONS-1.md (not served here)
- benchmarks/corpus/universal/foundation/BENCH-OLD-DATABASE-MIGRATIONS-6.md (not served here)
- benchmarks/corpus/universal/foundation/BENCH-OLD-DATABASE-MIGRATIONS-7.md (not served here)
- benchmarks/corpus/universal/foundation/BENCH-OLD-ERROR-BUDGETS-3.md (not served here)
- benchmarks/corpus/universal/foundation/BENCH-OLD-ERROR-BUDGETS-4.md (not served here)
- benchmarks/corpus/universal/foundation/BENCH-OLD-LOGGING-5.md (not served here)
- benchmarks/corpus/universal/foundation/BENCH-OLD-SCHEMA-EVOLUTION-3.md (not served here)
- benchmarks/corpus/universal/foundation/BENCH-OLD-SCHEMA-EVOLUTION-8.md (not served here)
- benchmarks/corpus/universal/foundation/BENCH-SCHEMA-EVOLUTION-1.md (not served here)
- benchmarks/corpus/universal/foundation/BENCH-SCHEMA-EVOLUTION-2.md (not served here)
- benchmarks/corpus/universal/foundation/BENCH-SCHEMA-EVOLUTION-4.md (not served here)
- benchmarks/corpus/universal/foundation/BENCH-SCHEMA-EVOLUTION-5.md (not served here)
- benchmarks/corpus/universal/foundation/BENCH-SCHEMA-EVOLUTION-6.md (not served here)
- benchmarks/corpus/universal/foundation/BENCH-SCHEMA-EVOLUTION-7.md (not served here)
- benchmarks/corpus/universal/foundation/BENCH-SCHEMA-EVOLUTION.md (not served here)
- benchmarks/corpus_gen.py (not served here)
- benchmarks/corpus_model.py (not served here)
- benchmarks/docs_tables.py (not served here)
- benchmarks/generate_artifacts.py (not served here)
- benchmarks/generate_embeddings_ablation.py (not served here)
- benchmarks/generate_governance_artifacts.py (not served here)
- benchmarks/governance/projects/example-project/GOV_BLUE_GREEN_DEPLOYS.md (not served here)
- benchmarks/governance/projects/example-project/GOV_CIRCUIT_BREAKERS.md (not served here)
- benchmarks/governance/projects/example-project/GOV_DEAD_LETTER_QUEUES.md (not served here)
- benchmarks/governance/projects/example-project/GOV_IDEMPOTENCY.md (not served here)
- benchmarks/governance/projects/example-project/GOV_MODULE_STRUCTURE.md (not served here)
- benchmarks/governance/projects/example-project/GOV_NEW_AUTH_TOKENS.md (not served here)
- benchmarks/governance/projects/example-project/GOV_NEW_PAGINATION.md (not served here)
- benchmarks/governance/projects/example-project/GOV_OLD_AUTH_TOKENS.md (not served here)
- benchmarks/governance/projects/example-project/GOV_OLD_PAGINATION.md (not served here)
- benchmarks/governance/projects/example-project/components/api/GOV_CACHING_STRATEGY.md (not served here)
- benchmarks/governance/projects/example-project/components/api/GOV_LOGGING.md (not served here)
- benchmarks/governance/projects/example-project/components/api/GOV_MESSAGE_ORDERING.md (not served here)
- benchmarks/governance/projects/example-project/components/api/GOV_NEW_RATE_LIMITING.md (not served here)
- benchmarks/governance/projects/example-project/components/api/GOV_NEW_SAGA_ORCHESTRATION.md (not served here)
- benchmarks/governance/projects/example-project/components/api/GOV_NEW_SECRETS_HANDLING.md (not served here)
- benchmarks/governance/projects/example-project/components/api/GOV_OLD_RATE_LIMITING.md (not served here)
- benchmarks/governance/projects/example-project/components/api/GOV_OLD_SAGA_ORCHESTRATION.md (not served here)
- benchmarks/governance/projects/example-project/components/api/GOV_OLD_SECRETS_HANDLING.md (not served here)
- benchmarks/governance/projects/example-project/components/api/GOV_TRACING.md (not served here)
- benchmarks/governance/tech-stacks/backend/GOV_AUDIT_LOGGING.md (not served here)
- benchmarks/governance/tech-stacks/backend/GOV_BACKPRESSURE.md (not served here)
- benchmarks/governance/tech-stacks/backend/GOV_FEATURE_FLAGS.md (not served here)
- benchmarks/governance/tech-stacks/backend/GOV_FORCE_PUSH.md (not served here)
- benchmarks/governance/tech-stacks/backend/GOV_HEALTH_CHECKS.md (not served here)
- benchmarks/governance/tech-stacks/backend/GOV_INPUT_VALIDATION.md (not served here)
- benchmarks/governance/tech-stacks/backend/GOV_NEW_CANARY_ROLLOUTS.md (not served here)
- benchmarks/governance/tech-stacks/backend/GOV_NEW_SCHEMA_EVOLUTION.md (not served here)
- benchmarks/governance/tech-stacks/backend/GOV_OLD_CANARY_ROLLOUTS.md (not served here)
- benchmarks/governance/tech-stacks/backend/GOV_OLD_SCHEMA_EVOLUTION.md (not served here)
- benchmarks/governance/universal/foundation/GOV_API_VERSIONING.md (not served here)
- benchmarks/governance/universal/foundation/GOV_CONNECTION_POOLING.md (not served here)
- benchmarks/governance/universal/foundation/GOV_DATABASE_MIGRATIONS.md (not served here)
- benchmarks/governance/universal/foundation/GOV_ERROR_BUDGETS.md (not served here)
- benchmarks/governance/universal/foundation/GOV_NEW_CONFIG_MANAGEMENT.md (not served here)
- benchmarks/governance/universal/foundation/GOV_NEW_EXCEL_EXPORT.md (not served here)
- benchmarks/governance/universal/foundation/GOV_NEW_GRACEFUL_SHUTDOWN.md (not served here)
- benchmarks/governance/universal/foundation/GOV_OLD_CONFIG_MANAGEMENT.md (not served here)
- benchmarks/governance/universal/foundation/GOV_OLD_EXCEL_EXPORT.md (not served here)
- benchmarks/governance/universal/foundation/GOV_OLD_GRACEFUL_SHUTDOWN.md (not served here)
- benchmarks/governance/universal/foundation/GOV_RETRY_POLICY.md (not served here)
- benchmarks/governance_corpus.py (not served here)
- benchmarks/governance_queries.py (not served here)
- benchmarks/governance_queries.yaml (not served here)
- benchmarks/governance_results/ablation.json (not served here)
- benchmarks/governance_results/ablation.md (not served here)
- benchmarks/governance_results/embeddings/ablation.json (not served here)
- benchmarks/governance_results/embeddings/ablation.md (not served here)
- benchmarks/methods/__init__.py (not served here)
- benchmarks/methods/base.py (not served here)
- benchmarks/methods/bm25.py (not served here)
- benchmarks/methods/data_olympus.py (not served here)
- benchmarks/methods/grep_read.py (not served here)
- benchmarks/methods/vector_rag.py (not served here)
- benchmarks/methods/whole_dump.py (not served here)
- benchmarks/metrics.py (not served here)
- benchmarks/queries.yaml (not served here)
- benchmarks/query_gen.py (not served here)
- benchmarks/real_corpus/example_bundle_queries.json (not served here)
- benchmarks/real_corpus/example_bundle_result.json (not served here)
- benchmarks/real_corpus_eval.md (not served here)
- benchmarks/real_corpus_eval.py (not served here)
- benchmarks/receipt.py (not served here)
- benchmarks/results/receipt.json (not served here)
- benchmarks/results/report.md (not served here)
- benchmarks/results/results.json (not served here)
- benchmarks/run.py (not served here)
- benchmarks/token_compact.py (not served here)
- benchmarks/tokenizer.py (not served here)
- bin/_kb_detect_workspace.sh (not served here)
- bin/_kb_enforce.py (not served here)
- bin/_kb_fallback.py (not served here)
- bin/kb (not served here)
- bin/kb-enforce-hook (not served here)
- bin/kb-session-recap-hook (not served here)
- bin/opencode/data-olympus-gate.ts (not served here)
- deploy/docker/Dockerfile (not served here)
- deploy/docker/compose.yaml (not served here)
- deploy/docker/entrypoint.sh (not served here)
- deploy/k8s/README.md (not served here)
- deploy/k8s/configmap.yaml (not served here)
- deploy/k8s/ingress.yaml (not served here)
- deploy/k8s/kustomization.yaml (not served here)
- deploy/k8s/namespace.yaml (not served here)
- deploy/k8s/networkpolicy.yaml (not served here)
- deploy/k8s/read-replica/README.md (not served here)
- deploy/k8s/read-replica/configmap.yaml (not served here)
- deploy/k8s/read-replica/deployment.yaml (not served here)
- deploy/k8s/read-replica/kustomization.yaml (not served here)
- deploy/k8s/read-replica/networkpolicy.yaml (not served here)
- deploy/k8s/read-replica/secret.template.yaml (not served here)
- deploy/k8s/read-replica/service.yaml (not served here)
- deploy/k8s/secret.template.yaml (not served here)
- deploy/k8s/service.yaml (not served here)
- deploy/k8s/statefulset.yaml (not served here)
- docs/adoption.md
- docs/comparison.md
- docs/enforcement.md
- docs/glama.md
- docs/maintenance.md
- docs/mcp-registry.md
- docs/okf-profile.md
- docs/operations.md
- docs/quickstart.md
- docs/releases/README.md (not served here)
- docs/releases/adoption-cut-runbook.md (not served here)
- docs/releases/branch-recut.md (not served here)
- docs/releases/placeholder-version.md (not served here)
- docs/releases/pypi-trusted-publishing.md (not served here)
- docs/releases/rc-build.md (not served here)
- docs/releases/rc-publication-stage.md (not served here)
- docs/releases/release-scope.md (not served here)
- docs/releases/repository-settings.md (not served here)
- docs/releases/reviewed-promotion.md (not served here)
- docs/releases/sbom.md (not served here)
- docs/releases/std-u-821-amendment-1.3.md (not served here)
- docs/releases/std-u-821-amendment-1.5.md (not served here)
- docs/releases/v0.10.0.md (not served here)
- docs/releases/v0.11.0.md (not served here)
- docs/releases/v0.2.0.md (not served here)
- docs/releases/v0.3.0.md (not served here)
- docs/releases/v0.3.1.md (not served here)
- docs/releases/v0.3.2.md (not served here)
- docs/releases/v0.3.3.md (not served here)
- docs/releases/v0.3.4.md (not served here)
- docs/releases/v0.3.5.md (not served here)
- docs/releases/v0.4.0.md (not served here)
- docs/releases/v0.4.1.md (not served here)
- docs/releases/v0.4.2.md (not served here)
- docs/releases/v0.5.0.md (not served here)
- docs/releases/v0.6.0-expanded-release-design.md (not served here)
- docs/releases/v0.6.0.md (not served here)
- docs/releases/v0.7.0.md (not served here)
- docs/releases/v0.7.1.md (not served here)
- docs/releases/v0.7.2.md (not served here)
- docs/releases/v0.7.3.md (not served here)
- docs/releases/v0.8.0.md (not served here)
- docs/releases/v0.8.1.md (not served here)
- docs/releases/v0.8.2.md (not served here)
- docs/releases/v0.9.0.md (not served here)
- docs/releases/vulnerability-gate.md (not served here)
- docs/serving.md
- example-bundle/decisions/ADR-001-use-data-olympus.md (not served here)
- example-bundle/decisions/ADR-002-single-writer-serving.md (not served here)
- example-bundle/decisions/index.md (not served here)
- example-bundle/index.md (not served here)
- example-bundle/log.md (not served here)
- example-bundle/memory/accepted/2026-06-20-nestjs-module-naming-collision.md (not served here)
- example-bundle/memory/accepted/index.md (not served here)
- example-bundle/memory/index.md (not served here)
- example-bundle/projects/acme-app/README.md (not served here)
- example-bundle/projects/acme-app/components/api/README.md (not served here)
- example-bundle/projects/acme-app/components/api/index.md (not served here)
- example-bundle/projects/acme-app/index.md (not served here)
- example-bundle/reference/index.md (not served here)
- example-bundle/reference/rest-api-endpoints.md (not served here)
- example-bundle/tech-stacks/backend-nestjs/STD-BN-001-module-structure.md (not served here)
- example-bundle/tech-stacks/backend-nestjs/index.md (not served here)
- example-bundle/universal/foundation/STD-U-001-writing-style.md (not served here)
- example-bundle/universal/foundation/STD-U-002-code-review.md (not served here)
- example-bundle/universal/foundation/STD-U-003-commit-message-format.md (not served here)
- example-bundle/universal/foundation/STD-U-004-commit-message-format.md (not served here)
- example-bundle/universal/foundation/STD-U-702-telegram-cron-routing.md (not served here)
- example-bundle/universal/foundation/index.md (not served here)
- example-bundle/universal/security/STD-U-601-secrets.md (not served here)
- example-bundle/universal/security/index.md (not served here)
- example-bundle/viz.html (not served here)
- example-bundle/workflows/WF-001-review-flow.md (not served here)
- example-bundle/workflows/index.md (not served here)
- glama.json (not served here)
- pyproject.toml (not served here)
- release/adoption-lines.json (not served here)
- release/exceptions.json (not served here)
- scripts/__init__.py (not served here)
- scripts/adoption_ratification.py (not served here)
- scripts/check_benchmark_docs.py (not served here)
- scripts/check_changelog.py (not served here)
- scripts/check_doc_consistency.py (not served here)
- scripts/check_version_free.py (not served here)
- scripts/ci_status.py (not served here)
- scripts/compute-version.sh (not served here)
- scripts/compute_release.py (not served here)
- scripts/deployed_digest.py (not served here)
- scripts/lint_pr_title.py (not served here)
- scripts/maintenance_inventory.py (not served here)
- scripts/okf_conformance.py (not served here)
- scripts/placeholder_version.py (not served here)
- scripts/prose_lint.py (not served here)
- scripts/rc_decide.py (not served here)
- scripts/rc_verify_and_publish.py (not served here)
- scripts/release_artifacts.py (not served here)
- scripts/release_readiness.py (not served here)
- scripts/release_record.py (not served here)
- scripts/run-local.sh (not served here)
- scripts/sbom_evidence.py (not served here)
- scripts/sdlc_recut.py (not served here)
- scripts/sdlc_version.py (not served here)
- scripts/security_alerts.py (not served here)
- scripts/should_tag.py (not served here)
- scripts/smoke_installed_wheel.py (not served here)
- scripts/version_free.py (not served here)
- scripts/vulnerability_gate.py (not served here)
- server.json
- src/data_olympus/__init__.py (not served here)
- src/data_olympus/_vendor/__init__.py (not served here)
- src/data_olympus/_vendor/prose_lint.py (not served here)
- src/data_olympus/audit_log.py (not served here)
- src/data_olympus/audit_trailers.py (not served here)
- src/data_olympus/auth.py (not served here)
- src/data_olympus/capture.py (not served here)
- src/data_olympus/cli/__init__.py (not served here)
- tests/okf/LICENSE.upstream.md
Advantages
- Local-first: the memory stays in files or a database on your own machine (a rule-derived fact from the brain’s files, evidence: README.md)
- Works with 14 MCP clients (a rule-derived fact from the brain’s files, evidence: README.md)
- Plain markdown memory that you can read and edit yourself (a rule-derived fact from the brain’s files, evidence: README.md)
- No API key is named in its files (a rule-derived fact from the brain’s files, evidence: README.md)
- Open licence (Apache-2.0) (a rule-derived fact from the brain’s files)
Limitations
- No ZIP download: install it from its source (a rule-derived fact from the brain’s files)
Security
Risk level: Low risk
This brain runs
Agent instructions that run or fetch something (5)
AGENTS.md:`uv run pytest` on its own is not equivalent.AGENTS.md:`tests/test_server_cli.py` and `tests/test_smoke.py` spawn a subprocess with `sys.executable` and read installed distribution metadata, so without the editable install they fail with `No module named data_olympus.server` and a stale version assertion.AGENTS.md:Anything that gates on `uv run pytest` without the editable install (a release routine, a scheduled job) will block on a false failure.AGENTS.md:Run the linter before the tests.tests/importer-fixtures/.cursorrules:Run the type checker in CI and treat type errors as build failures rather than warnings.
- Low risk: network-instructions (AGENTS.md:75)
- Low risk: scripts-present (scripts/check_benchmark_docs.py)
- Low risk: scripts-present (scripts/okf_conformance.py)
Attribution and licence
- Created by
- Knaisoma
- Declared authors
- knaisoma (declared in pyproject.toml)
- Publisher
- Knaisoma (Official Knaisoma project)
- Source
- knaisoma/data-olympus · commit 6d46033
- Licence
- Apache License 2.0 (files served here)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
AutoGineer is not affiliated with or endorsed by Knaisoma.
Install from source
486 of its files are not served here, so there is no ZIP. knaisoma/data-olympus at 6d46033