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.
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.
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.