local·first·lab
Open source · MIT · Python stdlib, zero dependencies

A second brain that refuses to guess

EdgeDancer is a durable personal-knowledge brain with a disposable embedding index: your notes stay plain Markdown on your own disk, a vector index makes them searchable, and every answer cites the note it came from. When conditions are wrong, a swapped embedding model, an endpoint that stops behaving like itself, a secret about to become durable, it refuses loudly instead of answering plausibly.

Python 3.11+, stdlib only, zero third-party dependencies. No vector database. No network except the model calls you configure, and out of the box it refuses to contact anything but loopback: adding any other host is a reviewed code change, not a config toggle.

See the code on GitHub → Try the demo ↓

The silent failure

Vectors from two different embedding models are not comparable, and most retrieval tools will happily compare them anyway. The stored index loads, ranks, and answers, silently wrong: same dimension, no crash, a confident score attached to a citation that does not belong. Nothing in the response shape tells you the weights underneath changed.

EdgeDancer closes that gap by construction, not by convention. Every index directory is named after a fingerprint computed from the vectors the embedding endpoint actually produces on a frozen canary battery, re-probed on every query. A model swap is a missing directory, not a plausible number. The query resolves to a path that does not exist, and the tool stops rather than guesses.

~/edgedancer · demo/same-stars-different-sky · synthetic demo data

With JavaScript enabled, this panel plots about 40 synthetic notes as stars, projected under an embedding model labeled model-a. Ask highlights the 3 nearest stars by similarity and lists them with scores. Swap embedding model re-projects the same stars into a different constellation under model-b, while the index badge still shows the space id hashed from model-a's coordinates. Asking again produces what the CLI produces: NoIndexForSpace, exit 3, and a printed rebuild command, rendered at the highest contrast in the panel, because refusal is the feature, not an error state to apologize for.

A second toggle then shows the counterfactual: what a system without space partitioning would compute instead, neighbors from mismatched coordinates, confident scores, and citations that are quietly wrong, labeled silently wrong so the contrast is the whole demo. Rebuild index for this space returns to a working Ask under model-b. This description is server-rendered and complete without JavaScript; the interactive version only replaces it once a script actually runs, and never before.

The refusal ladder

Wrong space, no answer

Every index directory is named after a fingerprint computed from the embedding endpoint's own vectors. Swap the model and a query resolves to a directory that does not exist: NoIndexForSpace, exit 3, and a printed rebuild command, never a comparison across incompatible vectors.

An endpoint that stops acting like itself gets refused

Before every chat answer, a behavioural identity canary (ranked logprobs at temperature 0, never completion text) is checked against a stored fingerprint, backed by a disk-persisted circuit breaker. A mismatch refuses the answer and trips the breaker; a matching canary afterward does not clear a trip on its own.

The leak gate scans before anything becomes durable

Writing a note calls one gated function; a non-empty findings list raises before a byte hits disk. The pre-push hook then scans two surfaces, because neither covers the other: the working tree, and every blob the push would actually transfer. The repo is blunt that git push --no-verify bypasses the hook: the write gate is the control that cannot be bypassed, the hook is a speed bump against accident.

Claims wear only the evidence they can show

Every note carries an author_kind and an epistemic grade. A model-authored note that claims proven, verified, or measured with an empty evidence list is refused at write time, by name. A model claim without evidence renders as UNCORROBORATED in the console, and every model answer is labeled author_kind=model, epistemic=asserted.

Where your words live

If EdgeDancer disappears, the brain does not. Everything durable is plain UTF-8: Markdown notes with a YAML-subset frontmatter block, and an append-only JSONL journal, one JSON object per line; cat, less, and python3 -m json.tool are a complete toolchain for reading it. The vector index is the one artifact whose meaning depends on a model's vectors, and it is disposable by design: delete it, keep the brain, rebuild it from the notes whenever the embedding model changes.

Notes from building it

One lesson from the repo's own README, kept here because it generalizes past this project: when a gate fires on your own correct behaviour, change the behaviour, not the gate. A generation id used to trip the leak gate's entropy detector on a large fraction of realistic values; the fix went into the id format itself, never into widening what the gate is allowed to ignore.

get it · quickstart
git clone https://github.com/MacadamiaButter/edgedancer edgedancer && cd edgedancer

# 1. providers.toml ships live example entries: [active] embed already
#    points at LM Studio's default http://127.0.0.1:1234/v1. Probe your
#    own endpoints before trusting an entry; find needs only embeddings,
#    ask additionally needs a chat endpoint that returns logprobs.

# 2. Initialise a brain: git repo, pre-push hook (proven to fire by a
#    negative control), seed leak baseline, starter sources.toml.
python3 edgedancer.py init --brain ~/edgedancer-brain

# 3. Give it something to find: the shipped starter notes, plus optionally
#    your own directories as read-only roots (edit the brain's sources.toml).
cp brain-template/notes/*.md ~/edgedancer-brain/notes/

# 4. Build the index. The leak gate scans every file BEFORE any embedding
#    call; the space id is derived from your endpoint's actual vectors.
python3 edgedancer.py index build --brain ~/edgedancer-brain

# 5. Search.
python3 edgedancer.py find "what lives in the durable layer" --brain ~/edgedancer-brain

One expectation worth setting: doctor will report the missing brain remote until you point it at a private remote you control; that is the tool telling you your brain has one copy, not a bug.

MIT licensed. Issues and design questions go through CONTRIBUTING.md. EdgeDancer sits in the Local First Lab toolbox next to lfl-terminal and lanewatch: stdlib-only tools that run on your own machine.

localfirstlab.org · edgedancer · theme dark · crt low · 0 trackers · 0 third-party requests