The rule from March, found in November.

Groundnote is a self-hosted MCP server. It keeps the rules from your meetings and mail with quote, source and date, so your assistant can find them again.

View on GitHub Read the documentation

A terminal sends a customer's question about a frame in RAL 9005 to a local Groundnote. The answer is a rule from a March meeting, with the quote, the speaker and the minute in the recording.

A real search against a local instance with invented data, formatted by examples/demo/hit.jq.

Why

March, workshop meeting. Mara says:

“matte black frames need two extra days in the oven.”

November, a customer writes:

“Could you paint my frame in RAL 9005 by Friday?”

Nobody remembers the meeting, let alone that RAL 9005 is the paint the workshop calls matte black.

Groundnote keeps that sentence as a rule, together with the meeting it came from and the minute it was said. A person confirms it once. When the mail comes in, the assistant asks Groundnote and gets the rule back with its quote and date. Once nobody has confirmed or reviewed a rule for 180 days (configurable), the answer asks whether it is still valid.

Groundnote decides nothing and answers nobody on your behalf. It tells you what was said, by whom and when.

How it works

  1. Capture

    A meeting transcript or a mail is posted to POST /v1/sources and stored as it arrived. Feeders that do this on their own are not built yet, so for now your own script calls the API.

  2. Propose

    An assistant reads it and calls propose_rule with a statement and a quote. The quote has to be in the stored source, otherwise the proposal is refused. A manual proposal, typed in without a stored source, has nothing to check against, and the approval page marks it as unchecked.

  3. Approve

    Each scope sets a mode. In C, the default without an approval block, every proposal waits for a person. In A and B deterministic code adopts what it may, never a model. Payment data, contradictions, injection signals and the other mandatory exceptions wait for a person in every mode.

  4. Answer

    search_rules returns the rule with its quote and source, earlier and later versions, contradicting and related rules, and where it sits in your structure.

Rules are never edited in place. A change is a new version, and the old one points to its successor.

Four steps: capture a meeting or mail, an assistant proposes a rule with a quote, code adopts it by the scope's mode or a person approves it on a separate page, search answers with the rule and its source.
a
Runs on your machine and is always on. It handles spelling, word forms, German compound words, typos and your synonym list, which is how “RAL 9005” finds “matte black”.
b
Embeds rules and queries with a local ONNX model, fetched once with groundnote model fetch, and fuses its ranks with stage a. Nothing leaves your machine. Off by default.
c
Sends the best candidates to TypeSafe AI's JEV reranker in the USA, which re-orders them within a monthly cap. Off by default.

Every response lists each stage as ran, off, failed, budget or not-allowed, so you can tell when a stage did not run.

Stage a (lexical, local) and stage b (a local embedding model, off by default) are fused; stage c, a reranker in the USA, is off by default. Every answer says which stages ran.

What the measurements say

Stage a against plain full-text search, on a held-out synthetic set: 300 invented rules and 137 queries that were written after tuning and never used for it. Source: lexical report.

The last two rows got worse, which is the price of the extra variants. The search documentation shows every set and what each step adds.

Stage a on the held-out set of 300 invented rules and 137 queries
Plain full-textStage a, defaults
Relevant rules in the top ten (recall@10)67%75%
Rank of the first relevant hit (MRR, 1.0 = always first)0.660.80
Outdated version ranked above the current rule86%7%
Questions without an answer that still get a confident hit worse17%25%
Search time, 95th percentile worse1.2 ms1.7 ms

Stages b and c on a synthetic set of 300 rules and 180 queries, with bge-m3, through the real search. Source: semantic report.

On this set stage b helps most with paraphrases (recall@5 30% to 54%) and with questions asked in the other language, German or English (48% to 85%). Only stage c filters questions without an answer.

Not everything improves. Each stage adds search time, and stage c pushes some categories down: recall@5 falls from 86% to 80% for queries with codes and from 95% to 91% for direct ones. With the model loaded the measuring process used 6.6 GB of memory. The JEV calls of the whole run cost 0.06 USD.

Search stages on the synthetic set of 300 rules and 180 queries
aa + ba + b + c
Relevant rules in the top five (recall@5)73%84%90%
Rank of the first relevant hit (MRR)0.830.890.98
Questions without an answer that still get a hit16 of 1616 of 165 of 16
Search time, median / 95th percentile5 / 8 ms25 / 114 ms241 / 309 ms

Data model

Each scope (a team, a customer area, a project) is its own SQLite file, with no joins across files. Searching several scopes has to be asked for and is only allowed when the grant covers all of them.

Inside a scope, entities such as customers, projects, topics and people form one tree of any depth, and links tie them to rules and sources. More in the knowledge graph documentation.

A scope is one SQLite file. It holds an entity tree, rules with versions, and sources with the quote each rule came from.

Security and privacy

  • Text from mails and transcripts is treated as untrusted. It can only become a proposal, and quotes and names leave the server inside marked blocks.
  • None of the 15 MCP tools can confirm anything. In modes A and B only code can adopt a proposal, and the mandatory exceptions still wait for a person.
  • Secrets come only from the environment, and the server refuses to start when the data directory is readable by others.

Read the threat model and the data flow to the provider before you switch stage c on. Report vulnerabilities privately as described in SECURITY.md.

Quickstart

There is no published image before the first release, so you build it from the repository. This runs in dev-token mode, which is meant for your own machine only. Never publish the port on a public interface in this mode.

  1. Build the image

    git clone https://github.com/groundnote/groundnote.git && cd groundnote
    docker build -t groundnote .
  2. Start it

    Write a groundnote.json with one scope and start the container in dev-token mode, published on 127.0.0.1 only.

  3. Create the scope and grant yourself access

    Without the grant every MCP call answers unauthorized.

  4. Connect Claude Code

    claude mcp add --transport http groundnote http://127.0.0.1:8794/mcp \
      --header "Authorization: Bearer $GN_MCP_TOKEN"

Steps 2 and 3 take a config file and more than a dozen flags, so they stay in one place: the full quickstart in the README, ready to copy.

Status

Groundnote is early and unreleased, and the API may still change.

Built

  • Search stages a, b (local embeddings) and c (JEV reranker with a monthly cap)
  • Approval page with approval modes A, B and C for rules
  • Knowledge graph with its review page
  • MCP server with 15 tools and 5 prompts
  • HTTP API and Markdown export
  • The groundnote CLI as a thin API client, with doctor
  • Backup runs, restore probes and backup nightly for an OS timer, plus backup verify and restore --to

Not built yet

  • API providers for stage b
  • Adopting structure changes in modes A and B; structure always waits for a person today
  • Feeders for meetings and mail
  • Litestream supervision by the service
  • Redaction and encrypted backups

What comes next is in the architecture document, proposals measured on real data are in the roadmap, and changes in the changelog.