Synapse
Operational memory for agents, with governed changes, preserved history, and explicit integrity checks.
Keep decisions, API knowledge and runbooks useful beyond the conversation that created them.
Get started · Workflow · Evidence · Architecture · Documentation
Source and release scope: the diagrams and capability descriptions below refer to this source tree. Distributed binaries can lag behind it. Check
syn --version, the release notes and the relevant contract before depending on a newer command or integration.
What Synapse is
Synapse is a local operational knowledge layer for developers and AI agents. Store project context as Markdown, retrieve it through CLI or MCP, and review changes through governed proposal paths. Your Git workflow and existing authoring tools remain useful.
The distinction is continuity with evidence: a decision should survive the task, keep its source context and expose what was checked before another agent relies on it. Synapse complements Notion, Obsidian, documentation repositories and retrieval engines such as QMD, Mem0 and Zep. It can run alone or supply knowledge to a host such as Soma.
| Responsibility | What Synapse provides |
|---|---|
| Preserve useful context | Typed Markdown records for projects, APIs, people, decisions, tasks and runbooks |
| Govern changes | Proposals, policy checks, explicit review and supported reversal |
| Inspect evidence | Recorded provenance, retained history and scoped verification reports |
| Retrieve for the next task | Local search, source citations and bounded process/MCP responses |
Why it matters
An agent result becomes useful memory through a reviewable change. The local knowledge process contract always queues its proposals; an operator reviews the exact payload before acceptance. Other write paths follow their configured policy, including documented safe application rules.
Each step has a separate responsibility. Review assesses a proposed change. The transaction preserves its recorded decision. Verification checks named properties over the selected records. None of these alone establishes the truth of every sentence or the absence of every natural-language contradiction.
| Evidence | What to inspect | Boundary |
|---|---|---|
| Recorded changes | syn blame, syn diff, syn audit verify | Attribution text alone is not authenticated team identity |
| Storage projection | syn verify | A skipped check means missing prerequisites, not a pass |
| Governed source suffix | syn state verify and selected assurance profile | Explicit baseline, retention, trust and witness inputs determine coverage |
| Historical state | syn at <ref>, syn audit replay | Replay determinism and complete source reconstruction are distinct checks |
| Structured contradictions | Scoped diagnostics and deep verification | Exact supported fields, intervals and references; general prose remains unknown |
Optional model assessments stay advisory. The current grouped EN/PT experiment did not meet its conflict-recall gate, so no classifier is enabled by default. See the measured results and limitations.
Get started
Install from the public distribution repository. Review release verification for checksums, signatures and the expected publisher before running a downloaded executable.
curl -fsSL https://raw.githubusercontent.com/romulofebasi/synapse-releases/main/install.sh | sh
# Alternatively, with Node 18+:
npx @febasi/synapse init ~/brain
npm install -g @febasi/synapse
Create a workspace, record a project and connect an MCP-compatible assistant:
syn init ~/brain
cd ~/brain
syn project add "Payments Platform"
syn person add "Maria Silva" --email maria@company.com --job-title "Tech Lead"
syn person link maria-silva --project payments-platform --role focal
syn api add "Payments API" --auth oauth2 --owner maria-silva
syn search payments
syn mcp install
syn verify
The first interactive syn init offers an optional semantic-model download, about 2.5 GB.
Keyword and graph retrieval work without it. Models are local by default; network providers
require explicit configuration. See models, installation
and the guided tutorial.
How it fits together
Two interfaces call the same core. Markdown is the current content source; SQLite holds a rebuildable search projection and operational state. Pending review rows have their own retention requirements and cannot be recreated merely by reindexing Markdown. Governed source records, retained images and receipts live in separate evidence metadata.
The current source exposes 42 MCP tools, including the versioned knowledge process contract. A host can select a verified binary, read bounded source-cited passages, propose a result and preserve the review boundary. The contract requires no HTTP daemon or additional memory database.
The architecture guide explains layering, transaction recovery and trust boundaries. The MCP reference documents the actual interface.
Knowledge in your existing workflow
Synapse supplies operational memory. Soma supplies an operator workspace for preparing context, supervising work and reviewing results. Other hosts can use the same bounded process contract without adopting Soma.
Current source adapters read Git/Markdown and optional GitHub with fresh revision and access checks. A detected change produces review candidates. It does not silently replace accepted knowledge or write back upstream. Notion, Jira, Confluence and further authoring-tool adapters remain separate integration work.
Local-owner note acceptance and authenticated shared publication use different authority paths. The local knowledge process contract rejects shared authority. Shared source-derived acceptance remains disabled until its dedicated authenticated host workflow is available. See source adapters, source evolution and the shared source contract.
The proof
Evidence should name the workload and its limits. The stored retrieval fixture asks the same 20 questions while six synthetic professional contexts accumulate from 77 to 463 entities. The chart below plots those recorded observations, with no competitor or hypothetical baseline.
This is an internal engineering measurement on macOS arm64, not a field study or a guarantee for other corpora. The original retrieval run did not record its source commit. At the 463-entity endpoint, 17 of 20 target facts rank first and all 20 appear in the top ten. Raw observations: persona-recall.json.
| Additional evidence | What it establishes |
|---|---|
| Agent-in-the-loop benchmark | Bounded insertion, correction, structured conflict and retrieval scenarios; inspect sample sizes and confidence intervals |
| Verification scope | Named invariants, skipped checks, selected trust and explicit exclusions |
| Durable transactions | Crash recovery from retained prepare data and exact SQLite decision markers |
| Governed source history | Acknowledged limited baseline, retained images and independently selected witness checks |
| Shared integration | Current host/authority protocol and the distinction between runtime evidence and a human adoption pilot |
The benchmark harness is in synapse-bench/ in the source repository. Results describe their
recorded run, not universal semantic consistency or every distributed binary.
Day to day
| Intent | Starting point |
|---|---|
| Capture a project fact | syn capture "Maria now owns the Payments API" |
| Inspect recent changes | syn digest |
| Review queued proposals | syn pending list |
| Reverse a supported local write | syn undo |
| Query supported recorded time | syn search payments --as-of 2026-05-01 |
| Check knowledge changes in CI | Memory CI guide |
Review the command contract and selected policy before accepting proposals in bulk.
Documentation
| Start with a task | Reference |
|---|---|
| First workspace and assistant connection | Onboarding, tutorial, MCP |
| Install or verify an artifact | Installation, signed release verification |
| Integrate a host such as Soma | Knowledge process contract, shared source |
| Monitor external knowledge changes | Source adapters, source evolution |
| Understand assurance and evidence | Provability, provenance, auditability |
| Inspect system design | Architecture, ADRs, business rules |
| Find every command and option | CLI reference, configuration, models |
| Assess measured behavior | Benchmarks, plain-language explanation |
Status
The source tree continues to evolve beyond the last packaged release. Consult CHANGELOG.md for source changes and release notes for the exact published artifacts. Markdown schema, CLI and MCP compatibility follow the documented semantic-versioning policy. Experimental adapters and planned integrations keep their own explicit status.
Synapse is part of Febasi’s work to turn practical engineering into reusable capabilities. The product remains usable as a standalone local tool and as an additive knowledge component.
On the name
A synapse passes a signal from one neuron to the next. Synapse carries project knowledge from one task to the next. Lil Syn, the connected indigo mascot, is the human face of that idea.
License
MIT. See LICENSE.
The fast, local proof: from a blank disk to a queryable brain you drive entirely from the terminal: no AI, no config files. The goal is to see it work and decide whether the typed-entity model fits how you think.
Prefer a guided walkthrough: syn setup (language), the exact output you’ll see, and how to introduce yourself? That’s ONBOARDING.md. Want the full human + AI loop: MCP wiring, the propose/accept workflow, federation? That’s TUTORIAL.md.
If you haven’t installed syn yet, INSTALL.md is one paragraph.
1. Make a workspace
syn init ~/brain
cd ~/brain
You now have:
~/brain/
├── .synapse/ # the SQLite index + config
│ ├── config.toml
│ └── index.db
├── projects/
├── people/
├── entities/
├── apis/
├── tasks/
└── notes/
On a fresh, interactive run you’ll be asked one question: whether to download the semantic-search models (~2.5 GB, one-time):
Download the models now? [Y/n]
Say yes for meaning-search (step 4), or skip it: everything below works either way, and you can turn it on later with syn embed --all.
Everything visible to you is plain Markdown. The .synapse/ directory is disposable: delete it, run syn reindex, and you’re back where you were.
2. Add the things in your head
syn project add "Projeto Alfa" --status active --priority high
syn person add "Maria Silva" --email maria@empresa.com --job-title "Tech Lead"
syn entity add "Acme Corp" --kind vendor --contact contato@acme.com
syn api add "API Pagamentos" --auth oauth2 --base-url https://api.pgto.com --owner maria-silva
Slugs are derived automatically (Maria Silva → maria-silva), and the YAML written to disk is human-readable:
---
type: person
id: maria-silva
title: Maria Silva
email: maria@empresa.com
job_title: Tech Lead
memberships: []
created: 2026-05-24T22:00:00Z
updated: 2026-05-24T22:00:00Z
---
3. Connect them
A person exists once and is referenced by every project they touch.
syn person link maria-silva --project projeto-alfa --role focal
syn task add "Migrar webhook v2" --project projeto-alfa --owner maria-silva --due 2026-06-30
syn note add "Por que não Stripe" --kind decision --project projeto-alfa --tag pagamentos
syn note covers what doesn’t fit the other types: decisions, runbooks, meeting transcripts, snippets, references.
4. Ask the graph
syn project list
syn person show maria-silva
syn search pagamentos
syn search pagamentos --tag decisao
syn graph project projeto-alfa
syn info
syn info --format json | jq .total
syn graph walks the link table built from memberships, owner, api, etc. syn search is SQLite FTS5 (fast, exact-term) out of the box. The default binary also ships the semantic layer (opt-out; see INSTALL.md): once you run syn embed --all once (it downloads ~2.5 GB of models the first time), search becomes hybrid: it also matches on meaning, so syn search "the payments decision" finds the right note even if it never says “payments”. From then on entities are embedded as you add them. The full propose/accept loop and semantic walkthrough live in TUTORIAL.md §3b.
5. Audit it
syn doctor
If you removed a file behind the index’s back, or referenced a person that doesn’t exist, you’ll see categorised warnings. Doctor never mutates: it tells you and offers syn reindex.
6. Hand it to your AI
syn mcp
That’s the MCP server on stdio: the door your AI knocks on. Point Claude Code at ~/brain and it answers from your facts, never a hallucination.
That’s as far as quickstart goes: you’ve proven the local brain works. Wiring Claude Code end to end (the client config, the propose/accept loop where the AI suggests and you sanction every write, federation across workspaces) is the job of TUTORIAL.md §4–§7.
7. Free-form notes
echo "# Quick thought\n\nThe webhook idempotency check needs revisiting after vendor migration." \
> notes/quick-thought.md
syn reindex # index picks it up
syn search idempotency
You can edit Markdown by hand and rerun syn reindex, or run syn watch (debounced, shipped in v0.2) to reindex automatically on every save.
What changed?
After step 6 your workspace looks like:
~/brain
├── projects/projeto-alfa/_meta.md
├── people/maria-silva.md
├── entities/acme-corp.md
├── apis/api-pagamentos.md
├── projects/projeto-alfa/tasks/projeto-alfa-001.md
├── projects/projeto-alfa/notes/por-que-nao-stripe.md
└── .synapse/index.db
Six concepts, one graph, two doors: terminal and AI. Commit the whole thing to git and the history of “who owns what” is plain git log.
Where to next
TUTORIAL.md: zero to the full AI loop: MCP wiring, the propose/accept/audit invariant, federation, semantic search.RECIPES.md: eight common workflows verbatim (CSV import, weekly review, briefings, …).COMMANDS.md: full reference of every subcommand.MCP.md: Claude Code wiring and the 20 tools (read, federation, write proposals, maintenance, audit).CONFIGURATION.md: cascade config, locales (--lang en,SYNAPSE_LANG=pt-BR), freshness knobs.ARCHITECTURE.md: why the layout isedge → core → data.
If something goes wrong, syn doctor is the first stop. Lil Syn looks for a reason: a dangling link, an orphan tag, a file the index doesn’t know about. The tool tells you; you decide.
The binaries live in a public mirror: romulofebasi/synapse-releases. The source still lives in romulofebasi/synapse (private). Anyone can install without a GitHub account.
One-liner (macOS, Linux)
curl -fsSL https://raw.githubusercontent.com/romulofebasi/synapse-releases/main/install.sh | sh
Installed? → ONBOARDING.md walks you from here to your first answer (
syn setup→syn init→ first entities), with the real output you’ll see.
The installer:
- detects
uname -s/uname -m, - resolves the latest release tag (or
SYNAPSE_VERSIONif set), - downloads the matching tarball and attempts the documented best-effort checksum/signature checks,
- extracts and drops
synintoINSTALL_DIR(default/usr/local/bin), - on macOS, clears the Gatekeeper
com.apple.quarantineattribute unless configured otherwise.
The installer does not require every verification prerequisite to be present. Read release verification for its exact skip/warning behavior and the strict manual workflow when both publisher and archive checks are required.
Environment knobs
| Variable | Default | Purpose |
|---|---|---|
SYNAPSE_VERSION | latest published tag | Pin an explicitly reviewed release tag. |
INSTALL_DIR | /usr/local/bin | Where to drop syn. Use ~/.local/bin for no-sudo installs. |
SKIP_QUARANTINE | unset | Set to 1 to skip the macOS quarantine clear. |
Examples:
# pin an example release; choose the reviewed tag required for your installation
curl -fsSL https://raw.githubusercontent.com/romulofebasi/synapse-releases/main/install.sh \
| SYNAPSE_VERSION=v1.4.0 sh
# no sudo, no PATH change
curl -fsSL https://raw.githubusercontent.com/romulofebasi/synapse-releases/main/install.sh \
| INSTALL_DIR="$HOME/.local/bin" sh
Manual download
The archive layout is:
synapse-<version>-<target>.tar.gz
└── synapse-<version>-<target>/
├── syn (or syn.exe on Windows)
├── README.md
├── LICENSE
└── CHANGELOG.md
Choose the reviewed tag and matching archive from the public releases. Source documentation can describe newer capabilities than the selected binary:
| Platform | Target |
|---|---|
| macOS Apple Silicon (M1, M2, M3, …) | aarch64-apple-darwin |
| Linux x86_64 | x86_64-unknown-linux-gnu |
| Linux ARM64 | aarch64-unknown-linux-gnu |
| Windows x86_64 | x86_64-pc-windows-msvc (.zip) |
Intel Macs (
x86_64-apple-darwin) are not supported as of v0.5. The ONNX Runtime behind the semantic layer ships no Intel-macOS build, so there is nosynbinary for that target. Apple Silicon is the only supported macOS. (Tracked for a possible later restore via a different backend: ROADMAP SYN-174.)
Linux / macOS
Before the extraction/install steps below, download the selected tag’s SHA256SUMS and
SHA256SUMS.cosign.bundle, then authenticate the manifest and compare the selected archive
using the manual verification guide. The commands below show the
archive layout and installation; archive download alone does not verify its publisher.
VERSION=v1.4.0 # example tag; select the reviewed release you need
TARGET=aarch64-apple-darwin # change for your platform
curl -fsSLO "https://github.com/romulofebasi/synapse-releases/releases/download/${VERSION}/synapse-${VERSION#v}-${TARGET}.tar.gz"
# Authenticate the manifest and verify this archive before extracting (VERIFYING-RELEASES.md).
tar -xzf "synapse-${VERSION#v}-${TARGET}.tar.gz"
sudo install -m 755 "synapse-${VERSION#v}-${TARGET}/syn" /usr/local/bin/syn
xattr -dr com.apple.quarantine /usr/local/bin/syn 2>/dev/null || true
syn --version
Windows (PowerShell)
$version = "v1.4.0" # example tag; select the reviewed release you need
$target = "x86_64-pc-windows-msvc"
$asset = "synapse-$($version.Substring(1))-$target.zip"
$url = "https://github.com/romulofebasi/synapse-releases/releases/download/$version/$asset"
$tmp = "$env:TEMP\$asset"
Invoke-WebRequest $url -OutFile $tmp
# Authenticate the manifest and verify this archive before extracting (VERIFYING-RELEASES.md).
Expand-Archive $tmp -DestinationPath $env:TEMP -Force
$src = "$env:TEMP\synapse-$($version.Substring(1))-$target\syn.exe"
$dst = "$HOME\bin\syn.exe"
New-Item -ItemType Directory -Force -Path (Split-Path $dst) | Out-Null
Move-Item $src $dst -Force
# Add $HOME\bin to your PATH if you haven't already:
[Environment]::SetEnvironmentVariable("Path", $env:Path + ";$HOME\bin", "User")
syn --version
Windows may display a SmartScreen warning depending on the selected artifact and local policy. Check publisher/integrity evidence before deciding whether to run it.
With npm / npx (Node ecosystem)
npx @febasi/synapse --help # run without installing
npm install -g @febasi/synapse # or install the `syn` command
syn --version
The package is a thin launcher; the real binary is delivered through
per-platform optionalDependencies (the esbuild model), so npm installs only the
one matching your OS/arch, with no runtime download. Supported targets:
darwin-arm64, linux-x64, linux-arm64, win32-x64. Published with npm
provenance from its publishing workflow. Inspect the selected package’s actual attestation;
this is separate from manually verifying an archive manifest and cosign bundle.
The unscoped
synapsename is taken on npm by an unrelated package, so Synapse publishes under the@febasiorganization scope.How it is published (SYN-103): the npm packages are built and published from the public
synapse-releasesmirror (.github/workflows/npm-publish.yml), which runs after a release is mirrored. It uses npm trusted publishing (OIDC) (no long-lived token) and, because the mirror repo is public, npm generates provenance attestations automatically. Owner setup (one time): configure a trusted publisher on each of the five@febasi/synapse*packages pointing at thesynapse-releasesrepo + that workflow. The launcher source lives in the mirror undernpm/synapse/.
From source
If your platform isn’t in the table, or you want to hack on syn:
git clone https://github.com/romulofebasi/synapse.git
cd synapse
./scripts/bootstrap.sh # enable git hooks, check toolchain
cargo build --release # default binary (includes the semantic layer): see "Feature flags" below
./target/release/syn --version
You need:
- Rust 1.89+ (see ADR-009 + its SYN-168 amendment).
- A POSIX shell for the hooks.
- macOS, Linux, or Windows (MSRV-tested on Ubuntu in CI).
Feature flags: the v0.5 semantic layer ships by default
cargo build --release produces the default binary: full CLI, MCP
server, FTS5 search and the v0.5 semantic layer: on-device
embeddings (fastembed / BGE-M3), NER (GLiNER), and hybrid search. The
default feature set is ["mcp", "v05_backends"] (SYN-168); the layer
adds only ~25 MB (ONNX Runtime) to the binary and bundles no models.
| Feature | Adds | Cost |
|---|---|---|
v05_backends | default-on: the local semantic layer: fastembed_backend + gliner_backend together (the sqlite-vec entities_vec table, syn embed, hybrid search). | +~25 MB ONNX Runtime in the binary; no models bundled: they download on consent (~2.5 GB multilingual defaults / ~325 MB English opt-downs). |
vector | the sqlite-vec entities_vec table + syn embed + the hybrid search plumbing, with no embedding backend of its own. Implied by every backend feature; rarely selected alone. | tiny |
fastembed_backend | on-device embeddings only (BGE-M3 multilingual default, BGE-small-EN opt-down). Implies vector. | +~25 MB ONNX Runtime; models download on consent (~2.2 GB BGE-M3 / ~65 MB BGE-small). |
gliner_backend | on-device NER only (GLiNER-multi default, GLiNER-base-en opt-down). Implies vector. | models download on consent (~350 MB multi / ~260 MB base-en). |
network_backends | opt-in: OpenAI / Voyage hosted embeddings (sends content to a third party: see IDENTITY.md). Implies vector. | tiny; no local model. |
test_backend | opt-in: a deterministic stub backend: lets you try the mechanics of embedding + hybrid search offline, with no model download. For CI and tinkering, not real semantic quality. | none. |
The default build already includes the semantic layer (ADR-008 / SYN-168). To opt out and produce a minimal core binary: full CLI + MCP + FTS5 search, no embeddings/NER/hybrid, and ~25 MB smaller: build with no default features:
# Minimal core binary (no semantic layer):
cargo build --release --no-default-features --features mcp
# Try the mechanics offline, zero downloads (deterministic stub):
cargo build --release --no-default-features --features "mcp test_backend"
./target/release/syn embed --check # works without any model
# Add hosted embeddings (OpenAI/Voyage) on top of the default layer:
cargo build --release --features network_backends
The default syn already carries the embed command and the
--semantic / --no-vector / --grade search flags. See
COMMANDS.md and CONFIGURATION.md.
Models download on consent, not at build and not on first write. The binary ships without the ~2.5 GB of local models. They are pulled only when you explicitly ask: answering “yes” to the
syn initonboarding prompt, or runningsyn embed --all(orsyn embed <slug>). Until then, writes save without embedding andsyn searchdegrades to FTS + graph: nothing blocks and nothing downloads silently.syn embed --checkis read-only and reportsmodels_ready falsewithout downloading. Budget ~2.5 GB for the multilingual defaults, ~325 MB for the English opt-downs; the cache (overridable viaSYNAPSE_MODEL_CACHE; see CONFIGURATION.md) is shared across every workspace on the machine.Full detail: exact models, source repos, cache paths, offline / air-gapped use, privacy, licenses, and the Linux glibc ≥2.38 requirement: lives in MODELS.md.
Signature verification and operating-system trust
Release archives using the current signing workflow include SHA256SUMS and
SHA256SUMS.cosign.bundle. Verify the selected release’s actual assets, expected tagged
workflow identity and exact archive digest before extraction. Follow
VERIFYING-RELEASES.md for the manual policy and the convenience
installer’s best-effort behavior.
Sigstore authenticates release evidence; it does not provide Apple notarization or Windows code-signing trust. Gatekeeper/SmartScreen can still prompt. Clearing quarantine or accepting an operating-system prompt is not a substitute for checking the artifact’s publisher and integrity. Check the chosen release notes for platform-specific signing status.
Uninstall
Synapse ships its own uninstaller (SYN-74). The flags compose:
syn uninstall # remove the binary at $(which syn)
syn uninstall --purge # also delete the global config dir
syn uninstall --everything # also delete the workspaces registry
syn uninstall --everything --force # skip the y/N prompt (CI-friendly)
--everything implies --purge. Workspace folders (~/brain or wherever you ran syn init) are never touched; they are plain Markdown owned by you.
If syn is no longer on $PATH for some reason, the manual fallback still works:
sudo rm /usr/local/bin/syn # or wherever you put it
rm -rf ~/.config/synapse # global config (Linux)
rm -rf ~/Library/Application\ Support/synapse # global config (macOS)
The deep dive. QUICKSTART installs syn and proves the local brain works in five minutes; this picks up from there and builds the full Synapse + Claude Code loop (the load-bearing invariant where the AI proposes and you sanction every write) then the parts quickstart only teases: semantic search, federation across workspaces, and freshness.
Prerequisite:
synis already installed; the one-liner is in INSTALL.md. If you ran through QUICKSTART you already have a workspace with a couple of entities; skim §1 and land on §4 (the MCP server), where this goes beyond it.
Every command below was validated against a real workspace on macOS. Linux is identical; Windows uses the PowerShell installer (see INSTALL.md).
1. A workspace to work against
A workspace is just a folder. Pick one:
mkdir -p ~/brain && cd ~/brain
syn init
You should see:
workspace inicializado em /Users/you/brain
manifest inicializado em /Users/you/brain/SYNAPSE.md
registrado no registry de workspaces (id: brain-xxxxxx)
On a fresh, interactive syn init, Synapse also asks once whether to download the semantic-search models (~2.5 GB, one-time):
Download the models now? [Y/n]
Say yes to enable meaning-search right away, or skip and turn it on later with syn embed --all (§3b). Either way the workspace is created and nothing blocks: decline and Synapse runs as a keyword + graph brain until you change your mind.
The SYNAPSE.md is your workspace’s identity card: what it’s about, who it’s for. The AI reads it when routing questions across multiple workspaces (more on that in recipe 7).
2. Capture your first entity
Synapse models the world as six entity types: project, person, entity (org / vendor), api, task, note. Start with a project and a person:
syn project add "Cliente Alfa"
syn person add "Maria Silva"
syn person link maria-silva --project cliente-alfa --role focal
Browse what you just made:
syn project list
syn graph project cliente-alfa
The graph already shows the inbound focal link. No magic: link rewrote both frontmatters and an index row.
3. Search
Everything you write lands in an FTS5 index:
syn search alfa
syn search alfa --type project
syn search PKCE # after recipe step 6 below
Search is what makes Synapse worth the friction of typed entities: it stops being a folder of notes and starts being a queryable second brain.
3b. Semantic search (v0.5)
Semantic search ships in the default binary (it’s opt-out: see INSTALL.md). Once its models are present, search does more than match words: it matches meaning, and it connects the dots through the graph.
The models download on your say-so, not behind your back. The ~2.5 GB of on-device models (one-time, shared across every workspace) are pulled only when you consent: syn init offers the download on a fresh, interactive setup, and syn embed --all pulls them any time. Until then, writes still save (just without embeddings) and syn search falls back to fast keyword + graph: nothing blocks on a download you didn’t ask for. (Where they come from, where they live, offline use, privacy and licenses: MODELS.md.)
Turn it on and backfill in one step:
syn embed --all # downloads the models on first run, then embeds every entity (idempotent)
syn embed --check # entities: N · embedded: N · models_ready: true
From here, every new entity you add is embedded and its mentions linked automatically, inside the same transaction (BR-13).
Now search by meaning, not keywords:
syn search "the payments decision" # hybrid (default): finds the note even if it says "pagamentos", never "payments"
syn search --semantic "auth flow" # vector-only: conceptually nearest entities
syn search --no-vector projeto-alfa # FTS + graph, skips the model (fast, exact)
The default hybrid mode fuses three signals (full-text, vector similarity, and graph-neighbourhood) so a query about “the flag from the meeting with Carina” can surface the flag entity even though that note never names it: the graph leg rides in from the meeting note that does.
NER proposes new entities. When the pipeline spots a person/tool/product mention that isn’t an entity yet, it queues a proposal instead of guessing:
syn pending list # look for kind = create_entity_from_ner
syn pending show 3 # inspect the proposed entity
syn pending accept 3 # materialise it (BR-12.3: never without your sign-off)
Grade results to tune ranking (optional, feeds v0.6):
syn search "checkout flag" --grade # prompts relevant? [y/N/skip] per hit
# judgments append to .synapse/search-labels.ndjson
Want fast writes without the model on a given command? SYNAPSE_ENRICHMENT_MODE=off syn note add ….
4. The MCP server
Now the interesting part. syn mcp exposes the workspace to any MCP client over stdio. For Claude Code:
// ~/.claude.json
{
"mcpServers": {
"synapse": {
"command": "syn",
"args": ["mcp"],
"cwd": "/Users/you/brain"
}
}
}
Restart Claude Code, then ask:
“Who is the focal contact for projeto-cliente-alfa, and which APIs do they own?”
Claude calls project_graph, get_entity, and answers with the slugs you typed. No hallucination, no “I don’t have access to that.”
5. The AI proposes, you sanction
This is the load-bearing invariant (BR-12.3): MCP never writes directly. The AI proposes; you accept.
In Claude Code:
“Capture a decision note: we picked PKCE for OAuth on Cliente Alfa. Tag it
auth.”
Claude calls capture_fact (kind defaults to decision). The response includes a pending_id and the exact commands to copy. In your shell:
syn pending list
# id kind status proposed_at source
# 1 capture_fact pending 2026-05-25T15:30:00Z mcp:capture_fact
syn pending show 1 # see the full payload + Claude's reasoning
syn pending accept 1 # apply it
Now syn search PKCE finds the note. The AI created it; you approved it; the markdown is yours.
Same flow for link_entity, update_status, add_workspace_topic, add_workspace_tag.
Not every proposal is right: reject the ones that aren’t with syn pending reject 2 --prune (drops it, keeps the queue clean). And every write the AI does land is on the record: the markdown carries x-ai-* provenance frontmatter and an append-only line goes to .synapse/audit.ndjson. syn audit list replays who proposed what, when, and what you decided; syn audit verify re-hashes the trail against the payloads. The loop isn’t “the AI writes”; it’s “the AI proposes, you decide, and the decision is auditable.”
6. Workspaces share, but only when you say so
Federation (BR-12.4) is opt-in by default. Workspaces stay isolated until you let them join the pool:
cd ~/work-brain && syn init && syn ws include work-brain
cd ~/brain && syn ws include brain
syn ws list
Now syn search pagamentos --all queries both. So does Claude Code via federated_search. Workspaces you didn’t syn ws include remain invisible; the LLM cannot probe whether they exist (see MCP.md > Privacy gate).
7. Stay fresh
If you git clone a workspace that someone else also writes to, Synapse warns when your local copy drifts from origin:
syn: workspace "team-brain" differs from origin's tracked tip; run `git -C ... pull`.
The advisory is never blocking (ADR-011). Suppress per-invocation with --no-freshness, per-workspace with syn ws freshness off <name>, globally with [freshness] enabled = false.
8. Next steps
Whenever the index and the Markdown drift: you edited a file by hand, deleted one behind Synapse’s back, left a dangling link: syn doctor tells you (categorised, never mutating) and offers syn reindex. Make it your first stop when something looks off.
- RECIPES.md: eight common workflows verbatim
- COMMANDS.md: every subcommand (incl.
syn capture, identity +syn cv export) - MCP.md: all 20 tools and the privacy model
- MODELS.md: the semantic models: footprint, cache, offline, privacy, licenses
- CONFIGURATION.md: config layers and env vars
docs/adr/: the load-bearing decisions
You’re set.
Eight workflows you’ll re-run more than once. Every command works against the workspace from TUTORIAL.md; follow that first if you haven’t.
1. Import people from a CSV
You have contacts.csv:
name,role,project
Maria Silva,focal,cliente-alfa
João Santos,dev,cliente-alfa
Carla Lima,pm,cliente-beta
A two-liner:
tail -n +2 contacts.csv | while IFS=, read -r name role project; do
slug=$(syn person add "$name" --format json | jq -r '.id // empty')
[ -n "$slug" ] && syn person link "$slug" --project "$project" --role "$role"
done
syn person add is idempotent on slug: re-running skips existing people.
2. Weekly review: what changed in the last seven days
syn search "" --format json | jq '[.[] | select(.updated >= (now - 7*86400 | todate))]'
For tasks specifically:
syn task list --status in_progress
syn task list --status blocked
Pair with syn graph to see who owns what:
for slug in $(syn task list --status in_progress --format json | jq -r '.[].id'); do
echo "=== $slug ==="
syn graph task "$slug"
done
3. Find stale tasks
A task is stale when updated is older than a threshold. Use syn export --as json for ad-hoc analysis:
syn export --as json --out /tmp/dump.json
jq '[.entities[] | select(.type == "task" and .status != "concluded" and .updated < (now - 14*86400 | todate))
| {id, title, owner, updated}]' /tmp/dump.json
Tweak the 14 to the staleness window you care about.
4. Per-project briefing for an external party
Export only what they need to see:
PROJECT=cliente-alfa
syn export --as json --out /tmp/full.json
jq --arg p "$PROJECT" '
{
project: (.entities[] | select(.type=="project" and .id==$p)),
people: [.entities[] | select(.type=="person" and .frontmatter.memberships[]?.project == $p)],
apis: [.entities[] | select(.type=="api" and (.frontmatter.projects // []) | index($p))],
decisions: [.entities[] | select(.type=="note" and .frontmatter.kind=="decision")],
}
' /tmp/full.json > /tmp/briefing.json
Or render the whole workspace and hand them a URL:
syn export --as html --out /tmp/brain-site
python3 -m http.server -d /tmp/brain-site 8765
# open http://localhost:8765
5. Let the AI propose, you sign off in batch
Working with Claude Code on a long thread, end the session with:
syn pending list # what is queued
syn pending show 1 # spot-check the largest one
syn pending accept 1
syn pending accept 2
syn pending reject 3 --prune # AI was wrong on this one, prune for audit hygiene
Tip: alias acc='syn pending accept', rej='syn pending reject' for fast triage.
6. Rename and merge tags
Tags drift over time (auth, authentication, Auth). Collapse them:
syn tag list # see counts
syn tag rename Auth auth
syn tag merge authentication auth
Both commands rewrite frontmatter on every affected entity and re-index in the same transaction (BR-6.1).
7. Cross-workspace federation with the LLM
After syn ws include on multiple workspaces (see TUTORIAL §6), Claude Code can route across them:
“Compare how
cliente-alfa(work-brain) handles webhooks vs. the pattern inpersonal-brain.”
Claude calls list_workspaces, picks the relevant ones from each description/topics, then federated_search and get_entity_from. Workspaces you didn’t opt in are invisible: neither Claude nor any other client can probe their existence.
Curate identity fields so routing works well:
cd ~/work-brain
syn ws topic add payments
syn ws topic add webhooks
syn ws tag add internal
(Or let Claude propose them via add_workspace_topic; same pending-queue flow as recipe 5.)
8. Continuous reindex while editing
If you’re polishing a few notes in your editor:
syn watch --debounce-ms 500
Every save triggers a debounced reindex. Cancel with Ctrl-C.
Pair with syn open <slug>:
syn open decisao-pkce # opens in $EDITOR, reindexes on close
9. Turn on semantic search for an existing workspace (v0.5)
The default binary already includes the semantic layer (SYN-168); the real gate is the model download, which happens only on consent. The syn init onboarding prompt offers it for new workspaces; for an existing one, syn embed --all is the consent step. (Footprint, cache location, offline use, privacy and licenses: MODELS.md.)
syn embed --check # "models_ready: false" → models not downloaded yet
syn embed --all # first run downloads ~2.5 GB of models (consent), then embeds everything
syn embed --check # "models_ready: true · embedded: N · model: fastembed:bge-m3@…"
syn embed --check is read-only; it reports models_ready without downloading anything. Until you run syn embed --all, writes save without embedding and syn search degrades to FTS + graph; nothing blocks. (On a minimal core build: --no-default-features --features mcp: the embed command and the vector legs are absent entirely.)
From now on every new write is embedded automatically. If you later switch embedding_model in .synapse/config.toml, re-run syn embed --all: it re-embeds only the rows whose stored model no longer matches. To start clean: syn embed --reset-cache && syn embed --all.
10. Choose the right search mode
syn search "billing webhook retries" # hybrid (default): best recall, blends meaning + words + graph
syn search --semantic "race condition" # vector-only: "find conceptually similar", even with no shared words
syn search --no-vector webhook-v2 # FTS + graph: exact-term + link traversal, no model load (fast)
Rule of thumb: hybrid for “find what I mean”, --semantic for “find things like this”, --no-vector when you know the term and want zero cold-start.
11. Review NER-proposed entities
The insertion-time pipeline (BR-13) links mentions to existing entities and proposes new ones it can’t resolve, rather than creating them silently:
syn note add "Sync with Carina about the Stripe migration" --kind meeting
syn pending list # → a create_entity_from_ner proposal for "Carina" / "Stripe"
syn pending show 1 # inspect the proposed type + title
syn pending accept 1 # materialise it, or `reject 1` to dismiss
This keeps the graph growing from what you actually write, while BR-12.3 guarantees nothing is created without your sign-off.
12. Grade search results to tune ranking (v0.6 prep)
syn search "checkout eligibility flag" --grade
# [1/4] entity remote-config Remote Config
# relevant? [y/N/skip] y
# ...
# recorded 4 judgment(s) → .synapse/search-labels.ndjson
The labels accumulate as NDJSON; a future release reads them to tune the hybrid ranking to your sense of relevance. Grade a few dozen real queries and the data is there when v0.6 needs it.
Found a recipe worth sharing? Open a PR on this file.
All commands accept these global flags:
--format text|json,--verbose,--lang <locale>, and (v0.6) the output/automation set:--plain/--raw(raw, unstyled output),--no-color(drop colour, keep structure),--quiet/-q(suppress non-essential lines),--no-input/--yes(never prompt: for CI/agents). Locale precedence:--lang→SYNAPSE_LANG→ system locale →en. Output is rich by default on a TTY (tables, panels, the unicode graph tree, progress, the mascot) and degrades to plain bytes on a pipe /--plain/NO_COLOR;--format jsonis always machine-clean and unstyled.
Workspace lifecycle
| Command | What it does |
|---|---|
syn setup [--force] | Guided global config + self-identity (v0.6, BR-14): locale, output format, then your profile: name, birthdate, profession, employers: saved to a global profile.toml. Non-TTY falls back to defaults and skips identity (never blocks). |
syn init [PATH] [--with-config] | Initialise a workspace. Refuses to overwrite. Runs the guided setup inline on a fresh interactive machine, then seeds the self: true person from your profile (employers become entity(kind=org) linked employed-by). Offers the one-time ~2.5 GB model download. --with-config seeds .synapse/config.toml. |
syn info | Workspace summary: counts per type, total. Stable JSON shape with --format json. |
syn digest [--days N] | “Your week in the graph” (SYN-222): recent activity (created/updated/AI-applied) + a “needs your attention” list (pending to review, untagged notes, disconnected entities, dangling links). Deterministic: no model. --format json for cron/scripts. |
syn context | Print the workspace’s self-describing vocabulary (SYN-311): entity types + their indexed/identity fields, link relations, reserved x-* keys, write-proposal payload shapes, enrichment sources, search modes. --format json mirrors the MCP synapse://capabilities resource: read it before proposing to avoid an invalid kind/rel/key. |
syn reindex | Rebuild the SQLite index from the Markdown source of truth. Idempotent. |
syn doctor | Read-only consistency audit. Exits non-zero on errors. |
Entities
The six types share add | list | show | rm (except task, which adds done).
syn project
syn project add "Display Name" [--desc TEXT] [--status active|paused|concluded|archived] [--priority low|medium|high]
syn project list [--status STATUS]
syn project show <slug>
syn project rm <slug> [--force]
syn person
syn person add "Full Name" [--email X] [--org SLUG] [--job-title "Tech Lead"]
syn person link <person-slug> --project <project-slug> --role <free-form>
syn person list
syn person show <slug>
syn person rm <slug> [--force]
--job-title (cargo formal) is not the same as --role in the link command (papel funcional naquele projeto). See BR-4.4.
syn entity
syn entity add "Name" [--kind vendor|customer|internal|partner] [--contact STR]
syn entity list
syn entity show <slug>
syn entity rm <slug> [--force]
syn api
syn api add "Name" [--base-url URL] [--auth none|apikey|basic|oauth2|jwt|mtls] [--status production|staging|deprecated] [--owner <person-slug>]
syn api list [--status STATUS]
syn api show <slug>
syn api rm <slug> [--force]
syn task
syn task add "Title" [--project SLUG] [--owner SLUG] [--api SLUG] [--due ISO-DATE] [--status open|in_progress|blocked|concluded] [--tag T]...
syn task done <id>
syn task list [--status STATUS]
syn task show <id>
syn task rm <id> [--force]
IDs are auto-assigned: <project>-NNN when scoped, t-NNN otherwise. done stamps closed_at.
syn note
syn note add "Title" [--kind decision|runbook|meeting|snippet|reference] [--project SLUG] [--tag T]...
syn note list
syn note show <slug>
syn note rm <slug> [--force]
Custom entity types (SYN-234 / ADR-028)
Beyond the six built-ins you can define your own types in
.synapse/types.toml (book, meeting, decision, client, …) without
a code change. A type is a closed core (a small whitelist of indexed,
queryable fields) plus an open frontmatter bag validated on read.
# .synapse/types.toml
schema_version = "1.0"
[types.book]
display = "Book"
store_dir = "books" # folder under the workspace root
indexed = ["author", "year", "isbn"] # ≤ 16; these become bindable query keys
identity = ["isbn"] # SYN-254: a capture matching these on an
# existing book is flagged as a duplicate
# and routed to the gate (⊆ indexed)
[types.book.fields.author]
type = "string"
required = true
[types.book.fields.year]
type = "int" # string | int | float | bool | date | ref | list
# SYN-248: per-kind policy (covers built-ins too, which are reserved above).
[policy.person]
decay = false # profile: current-state, never flagged by `syn stale`
syn types lists every registered type (built-ins + yours). Manage instances
with the generic dispatcher (the built-in commands stay for the six core
types):
syn new --type <kind> "Title" [-f key=value]... [--tag T]... [--id SLUG]
syn ls --type <kind> [--status S] [--as-of DATE]
syn show --type <kind> <id>
syn rm --type <kind> <id> [--force]
syn new --type book "Dune" -f author="Frank Herbert" -f year=1965 --tag scifi
syn ls --type book
syn show --type book dune
Field values are passed as repeatable -f key=value pairs (static clap
cannot synthesise a typed flag per runtime-defined field). Each value is
coerced to its declared type and validated on write; a missing required
field or a type mismatch is rejected. Custom-typed entities are reindexed,
FTS-searchable (syn search, MCP search_everything), countable via the
query tool (incl. filter.field over an indexed field), and can be link
endpoints (MCP link_entity).
Current limitations (honest). Custom types are first-class for create / read / list / remove / search (incl.
--type <custom>, SYN-259) / link /update_status/ supersession. The remaining gap is insertion-time semantic embeddings: custom entities are embedded by an explicitsyn embed --all(FTS-only until then): SYN-251. Declaredpattern(regex) field constraints are parsed but not yet enforced (needs aregexdep + ADR; deferred).
Search and graph
syn search <query> [--type <kind>] [--tag T] [--owner SLUG] [--limit N] [--near <slug>] # --type: built-in|custom (SYN-259); --near: graph-distance rerank (SYN-255)
[--semantic | --no-vector] [--as-of DATE] [--grade] [--all]
syn graph project|person|api <slug> [--rel RELATION]
graph walks the materialised links table. search behaviour depends on whether the semantic layer is present:
- Minimal core build (
--no-default-features --features mcp) or models not downloaded yet: plain FTS5, BM25-ranked. The query takes FTS5 syntax (title:foo,"phrase"). - Default build, models present: the default is hybrid: FTS5 + vector similarity + graph-neighbourhood fused with Reciprocal Rank Fusion (k=60). The query is natural language. Flags:
--semantic: vector similarity only (best for “find conceptually similar”, needs an embedded workspace).--no-vector: FTS + graph only; skips the embedding step and its cold-start.--grade: after searching, prompt per hit (relevant? [y/N/skip]) and append the judgments to.synapse/search-labels.ndjson(for v0.6 ranking tuning). Requires an interactive terminal; conflicts with--all.--as-of DATE: valid-time filter (SYN-223 / ADR-027): keep only facts true in the world atDATE(YYYY-MM-DDor RFC-3339), using the SQL:2011 closed-open[x-valid-from, x-valid-until)interval. Facts with nox-valid-*stamps always pass. Applies in every mode (FTS and hybrid); local-only (conflicts with--all). Composes withsyn at <ref>: see the time-travel section.--all: fan out across every federated workspace (plain FTS, not hybrid).
syn search "the firebase feature flag" # hybrid (default)
syn search --semantic "auth and sessions" # vector-only
syn search --no-vector "checkout-v2" # FTS + graph, no model load
Filters (
--type/--tag/--owner) apply in every mode. When the models haven’t been downloaded yet (or the workspace is un-embedded), hybrid search degrades gracefully to FTS+graph rather than failing, and never triggers a download.
Trust-weighted ranking (SYN-250 / ADR-029). The hybrid ranker multiplies each hit’s relevance by its deterministic trust score (the same curve
syn stale/syn blameuse), bounded to[0.5, 1.0]: a decayed or low-confidence AI fact sinks beneath an equally-relevant fresher one, but trust never fully suppresses a strong textual match. Human-authored facts carry no penalty (full weight).
Embeddings (syn embed)
The semantic layer ships in the default binary (opt-out, SYN-168): to drop it, build a minimal core binary with --no-default-features --features mcp. The ~2.5 GB of local models are not bundled; syn embed --all (or syn embed <slug>) is the explicit consent step that downloads them on first use (the other consent path is answering “yes” to the syn init onboarding prompt).
syn embed --all # download (on first use) + embed every entity; skips rows whose content + model already match
syn embed <slug> # (re-)embed one entity
syn embed --check # show cache state (entities, embedded, current vs stale model, models_ready): read-only, never downloads
syn embed --reset-cache # drop every vector + meta row; next --all re-embeds from scratch
syn embed --force # with --all/<slug>: re-embed even when the content hash matches
Writes already embed automatically at insertion time once the models are present (see Insertion-time enrichment below), so syn embed --all is mainly for backfilling an existing workspace the first time you populate the cache, or after switching embedding_model. --check is the quick way to see whether the index is populated and on which model; its output (text and JSON) carries a models_ready field: false means the models haven’t been downloaded yet, and --check reports it without triggering a download.
Insertion-time enrichment (BR-13)
On the default (semantic) binary, once the models are present, every write (project add, note add, an accepted MCP proposal, …) runs a synchronous pipeline inside the transaction: it embeds the entity, runs NER, and links mentions. You don’t call anything: it just happens.
If the models haven’t been downloaded yet, the write still succeeds: enrichment is skipped gracefully with a one-time note (“semantic models not downloaded yet: write saved without embedding. Run syn embed --all…”), and the write never triggers a download. Models are pulled only on explicit consent: answering “yes” to the syn init onboarding prompt (a TTY-only prompt; non-interactive syn init just prints a one-line breadcrumb and never blocks), or running syn embed --all. Once present, the pipeline:
- writes the vector to
entities_vec(so the entity is immediately searchable semantically); - creates typed
linkrows for mentions that resolve to existing entities (surfaced bysyn graph); - queues a
create_entity_from_nerproposal in the pending queue for a mention that does not resolve yet: accepting it (syn pending accept <id>) materialises the new entity (BR-12.3: nothing is created without your sign-off).
Control it with [enrichment] mode in config (off / heuristic / heuristic+ner) or the SYNAPSE_ENRICHMENT_MODE env var per invocation (e.g. SYNAPSE_ENRICHMENT_MODE=off syn project add … for a fast write that skips the model). See CONFIGURATION.md.
Tags
syn tag list # tags with counts (desc)
syn tag rename <old> <new> # rewrites every entity's frontmatter and the index
syn tag merge <source> <target>
Workspaces and federation
syn ws list # every workspace ever seen
syn ws show <id|name> # full row + SYNAPSE.md
syn ws include <id|name> # opt this workspace into cross-workspace queries (BR-12.4)
syn ws exclude <id|name> # remove from the federated pool
syn ws forget <id|name> # remove from the registry (folder is left intact)
syn ws gc # sweep registry rows whose root has been deleted
syn ws topic add|rm <value> # curate SYNAPSE.md topics
syn ws tag add|rm <value> # curate SYNAPSE.md tags
syn ws edit [name] # open SYNAPSE.md in $EDITOR
syn ws refresh [name] # re-read SYNAPSE.md from disk and refresh the registry cache
syn ws freshness on|off <name> # toggle the per-workspace freshness advisory (SYN-93)
Federation is opt-in (default off). See ADR-010 and the Privacy gate section in MCP.md.
Workspace maintenance
syn reindex # rebuild the SQLite index from the source-of-truth Markdown
syn watch [--debounce-ms 250] # auto-reindex on filesystem change (SYN-65)
syn doctor # audit consistency
syn open <slug> [--no-reindex] # open an entity in $EDITOR, reindex after exit (SYN-70)
syn export --as json [--out FILE] # dump every entity + link as JSON (SYN-67)
syn export --as html --out DIR/ # render a static site (SYN-66)
syn config get|set|unset <key> [val] # workspace-scoped TOML knobs (SYN-69)
--as (not --format) avoids colliding with the global --format text|json flag.
syn import (SYN-228)
The honest inverse of syn export --as obsidian: import a Markdown vault as
governed proposals (BR-12.3: nothing touches the workspace until you accept):
syn import --from <PATH> # a .md file, or a directory walked recursively
Each .md becomes a queued capture_fact proposal. A note with YAML
frontmatter keeps its type (when it names a registered kind, else note),
title, tags, and scalar fields; a frontmatter-less note (plain Obsidian
markdown) becomes a note whose title is its first # heading (or the
filename) and whose body is the whole file. Review with syn pending list,
then syn pending accept --all. The contract is deliberately Markdown + YAML
frontmatter (the format Synapse itself emits) not a reverse-engineered
external schema, so it round-trips your own export.
Uninstall
syn uninstall # remove the binary (asks in tty; --force to skip)
syn uninstall --purge # also delete the global config
syn uninstall --everything # also delete the workspaces registry
Workspace folders are never touched: your Markdown belongs to you.
Destructive prompts
Every <type> rm asks for confirmation in a TTY. Non-TTY runs require --force; running in a script without it now fails loudly rather than deleting silently (SYN-71).
Pending proposals (SYN-60..62, SYN-92)
The MCP server enqueues every write proposal here instead of touching the workspace. Review and apply with:
syn pending list # currently-pending proposals
syn pending list --all # include accepted + rejected
syn pending show <id> # full payload + reason + source
syn pending accept <id> # apply (via Workspace::tx) and mark accepted
syn pending accept 1 3 5 # accept several at once (each applies atomically)
syn pending accept --all # batch-approve every pending proposal (SYN-185)
syn pending reject <id> # mark rejected; row kept for audit
syn pending reject <id> --prune # delete the row instead of keeping it
syn pending reject --all # batch-reject everything (add --prune to delete)
See BR-12.3: MCP must never write without explicit human confirmation. The pending queue is per-workspace, lives in .synapse/index.db, and the row id is monotonic per workspace.
Undo an accepted decision (syn undo, SYN-287 / ADR-039)
syn undo <id> # reverse a previously accepted decision (same id as `pending list --all`)
It reverses a capture_fact create (removes the entity), a link_entity
(deletes the edge), and an update_status (restores the prior status from a
pre-image captured at accept time, SYN-292): all on a just-accepted,
uncommitted write, no git commit required. It refuses (with guidance to
revert via git) for supersessions, custom-typed creates, and batches, and
refuses if the fact was changed after the decision (the conflict guard never
clobbers later work). Every undo is atomic and is
itself recorded on the audit trail (the original accepted entry is never
deleted). undo is a human corrective action and is CLI-only by design, it is deliberately not an MCP tool, so the agent can’t erase its own accepted
history (the agent proposes, the human disposes: ADR-022/039).
Ambient capture (syn capture)
syn capture "Why we picked PKCE for auth" # the one-liner: drop a thought in seconds as a raw note (SYN-221)
echo "Why we picked PKCE" | syn capture # …or pipe stdin; a bare TTY opens $EDITOR
syn capture --from-git # seed people (commit authors) + a project (the repo) from git, as proposals (SYN-220)
syn capture --from-git ~/code/myproj # …from a specific repo; --max N caps the authors proposed
syn capture "<text>" is the promoted capture path (SYN-221): zero schema at the moment of capture; it queues a raw note, and the AI can later propose the typed/linked version (deferred structuring). Everything still goes through the pending queue (BR-12.3).
Governance, audit & forensics
This is the differentiated surface: prove what the AI changed, when, and on
whose behalf, and undo it. All of it reads the source-of-truth Markdown, the
x-ai-* provenance frontmatter, and git.
syn blame <slug> # AI provenance lineage: who/what touched the entity, when, on whose behalf (SYN-240)
syn diff <slug> [--since <ref>] # per-field before/after of an entity since a git ref (SYN-240)
syn changes --since <rfc3339> [--limit N] # what changed since an instant: audit decisions + per-field deltas (SYN-256)
syn timeline <slug> # walk the git history of one entity (SYN-121)
syn connections <slug> [--hops N] [--max-nodes N] [--semantic] # ego graph as JGF (SYN-244); --semantic adds explainable mutual-kNN edges (SYN-245)
syn stale [--limit N] # AI facts whose trust score has decayed: advisory (SYN-224)
syn types # list registered entity types (built-ins + types.toml; SYN-234)
Time travel (SYN-122). Query the workspace as of any git ref: what you knew at commit R:
syn at <ref> -- search <query> # search the workspace as it was at <ref>
syn at <ref> -- show <type> <id> # show an entity as it was at <ref>
Bitemporal (SYN-223 / ADR-027). Two orthogonal time axes, each a separate filter, composable:
- Transaction-time: what we knew at commit R:
syn at <ref>(above). - Valid-time: what was true in the world at date D:
--as-of D.
syn search <query> --as-of 2025-01-01 # what was true on that date (current history)
syn at <ref> -- search <query> --as-of 2025-01-01 # "using only what we knew at <ref>, what was true on D"
Valid-time comes from the x-valid-from / x-valid-until frontmatter keys
(closed-open [from, until)); facts without them always pass.
Supersession (ADR-027 / ADR-031). A captured fact carrying
x-supersedes: <id> closes the superseded fact’s interval as an append, never a delete: the older fact gets an x-valid-until (when it stopped being
true) plus an x-invalidated-at belief stamp (when we learned it). Three
deterministic rules: new-after-old closes the predecessor, older-than-known
is born already-expired, disjoint keeps both. The captured fact also carries
x-recorded-at (belief-time it entered the store). Built-in F1 only for now;
custom-type close + temporal replay are v0.10. --as-of filters the current
history; at <ref> reconstructs the tree at a commit.
Field corrections are bitemporal too (SYN-315 / ADR-046). An update_status
is an in-place, single-field edit, not a whole-fact supersession. It records
valid-time at the field level: the prior value is appended to the entity’s
x-field-history frontmatter, so syn ls <type> --as-of <before> reconstructs
the pre-correction status while the entity itself stays visible on both sides of
the correction. The history lives in the Markdown, so syn reindex rebuilds it
(disposable, verify-honest).
Audit trail (syn audit). The append-only .synapse/audit.ndjson:
syn audit list [--all] # recent entries (newest first); --all includes archived months
syn audit show <id> # one entry by its pending id
syn audit verify # re-hash each row's payload vs the recorded payload_sha256 (SYN-135)
# : exits 65 on any mismatch; the tamper-evidence check
syn audit replay <id> [--execute] # replay an accepted proposal: visual diff by default
syn audit prune <YYYY-MM> # delete archive months older than DATE (live log untouched)
syn audit stale # cross-workspace ranking of stalled open commitments (SYN-123)
Portable receipts (syn provenance, SYN-247). A signed, self-verifying
proof of an entity’s provenance that anyone can check without Synapse:
syn provenance export <slug> [--out FILE] # signed receipt (ed25519)
syn provenance verify <FILE> # verify a receipt's signature; needs no workspace access
See PROVENANCE.md, AUDITABILITY.md, and IDENTITY.md for the data model behind these.
Provable non-corruption (syn verify, SYN-263 / ADR-035).
Prove the AI memory was not silently corrupted: reproducible, signed,
offline. Runs the runtime invariants and emits a verdict; --format json
emits the full signed synapse.verify-report/1 (an in-toto Statement signed
with your ed25519 identity):
syn verify # text report: per-invariant pass/fail + trust-root caveat
syn verify --format json # the signed synapse.verify-report/1 report
syn verify --deep # also INV-4 (replay the governed decision journal, SYN-266) + INV-13 (logical contradictions, SYN-316)
syn verify --rebuild # attestation: on INV-1 mismatch, list EVERY diverging fact (SYN-269)
syn verify --since <file> # also INV-12: prove history is an append-only extension of a witness (SYN-270)
Offline consistency proof (syn audit witness + verify --since, SYN-269 /
ADR-037). syn audit witness
emits a tiny ed25519-signed checkpoint of the audit history (git HEAD +
per-author segment prefixes + count); publish it. Later, syn verify --since <witness> proves the current history only ever extended it (HEAD descends
from the witnessed commit and every witnessed segment prefix is byte-stable) so a force-push or mid-log rewrite is caught offline, without a remote. A
published witness lets a third party hold you to your history.
syn audit witness [--out FILE] # signed witness checkpoint (default .synapse/audit/witness.json)
Rebuild attestation (--rebuild, SYN-269). Makes the claim your memory is
a reproducible build of your files literally checkable: a from-scratch rebuild
of the index is compared to the live index, and on any mismatch the signed
report names the specific diverging facts (diffoscope-style ± <type> <id>),
not just the first, so you see exactly where the index stopped being a faithful
build. A clean run is the signed attestation itself (binds git_ref +
inputs_hash). A hosted store structurally cannot offer this.
The invariants: INV-1 reindex fixpoint (the index is a reproducible build
of the Markdown: a from-scratch in-memory rebuild yields the same canonical
dump; the keystone), INV-2 audit payload integrity, INV-3 history
integrity (hash-chain + signed checkpoint), INV-5 no silent overwrite
(supersession closes its predecessor). With --deep, INV-4 also replays
the governed decision journal in a scratch workspace to check it applies
cleanly and deterministically (SYN-266 / ADR-036):
a journal-integrity check, not store reconstruction (Synapse is
state-based; reconstruction is INV-1 + git syn at), and INV-13 runs a
deterministic logical-contradiction sweep (SYN-316): no overlapping
valid-time values for one field (x-field-history), no supersession cycle, no
inverted interval. It guards logical coherence, catching contradictions
introduced by a direct Markdown edit that bypasses the gate: the one class
INV-1 (storage coherence) and the propose-time gate (SYN-242) cannot see.
Deterministic and model-free (no NLI/judgement). INV-8 (stored-receipt round-trip) is
checks native stored receipt signatures in governed workspaces. Missing or unsigned receipts report skipped; invalid evidence fails. Legacy export-on-demand receipts do not create coverage. Independent signer trust/identity require opt-in v2 inputs. Exit codes: 0 no checked invariant failed (skips may remain), 1 an invariant was violated. The report always prints the trust-root caveat: tamper-evident,
not tamper-proof: a lone local clone cannot detect its own owner’s rewrite
without a witness. MCP counterpart: the verify tool.
--from-git (ADR-025) inverts the capture value-timing: a fresh workspace seeds itself from the repo’s history (authors → people, repo → project, with contributor memberships), as queued proposals (BR-12.3), bot-filtered and idempotent. Review and apply the lot with syn pending accept --all.
Freshness advisory
The workspace freshness signal (SYN-93 / SYN-93b, ADR-011) is advisory, never blocking. Phase 2 adds a bounded (3s) network git ls-refs probe of origin to catch commits you haven’t fetched yet; any network/auth/timeout failure is silent (status unreachable, previous state retained). Three escape hatches:
SYNAPSE_NO_FRESHNESS=1 syn info # silence for this invocation
syn info --no-freshness # same effect
syn ws freshness off <name> # per-workspace, persisted
Globally disable in config:
[freshness]
enabled = false
MCP
syn mcp # starts the stdio MCP server (feature-gated, default-on)
24 tools across local read / federation / maintenance / audit / integrity / write
proposals. Reads include search_knowledge (hybrid, optional type filter),
search_everything (SYN-226: hybrid RRF over all kinds incl. custom,
no type filter), query (structured aggregation incl. filter.field over a
custom indexed field), get_entity, get_entities, list_by_type,
project_graph, workspace_overview, recent_changes. All of these now
reach custom types (SYN-234). Writes go through the pending queue
(BR-12.3): capture_fact (creates a built-in or custom-typed entity),
link_entity (any registered kind as an endpoint), update_status,
propose_batch, plus the workspace-manifest tools. See MCP.md
for client wiring.
Agent assets (v0.6, SYN-175 / SYN-184)
The binary carries the agent-facing assets and emits them on demand, so they never drift from the release that ships them.
syn skill install [--dir PATH] [--force] # write the Claude Agent Skill (SKILL.md) to ~/.claude/skills/synapse-second-brain/
syn agents init [--dir PATH] [--force] # write an llms.txt (cwd by default) orienting any AI agent
syn plugin install [--dir PATH] [--force] # scaffold a Claude Code plugin: plugin.json + skill + .mcp.json + a SessionStart hook
All three refuse to overwrite without --force and honour --format json.
Environment variables
| Variable | Effect |
|---|---|
SYNAPSE_LANG | Locale override (en, pt-BR). Same as --lang. |
SYNAPSE_PLAIN | Any non-empty value forces raw, unstyled output (same as --plain). |
SYNAPSE_NO_COLOR / NO_COLOR | Drop ANSI colour, keep structure. TERM=dumb does the same. |
SYNAPSE_AGENT | Mark the caller as an AI agent: implies --plain and --no-input (no styling, no prompts). |
SYNAPSE_PROFILE | Path to the global self-identity profile.toml (overrides the default location; used by tests + advanced setups). |
SYNAPSE_NO_FRESHNESS | Any non-empty value silences the freshness advisory for the invocation. |
SYNAPSE_ENRICHMENT_MODE | Override [enrichment] mode per invocation (off / heuristic / heuristic+ner). Use off for a fast write that skips the model. Honoured on the default (semantic) build; ignored on a minimal core build. |
SYNAPSE_MODEL_CACHE | Override where embedding/NER models are cached. Default is the platform cache dir + /synapse/models. Honoured on the default (semantic) build; ignored on a minimal core build. |
Output and exit codes
| Code | Meaning |
|---|---|
0 | success |
1 | user error (validation, not found) |
2 | internal bug (panic caught) |
64 | usage error (bad clap args) |
65 | data format error (frontmatter, YAML) |
74 | I/O error (disk, database) |
--format json returns canonical, byte-stable JSON on every read command: list, show, tag list, pending list|show, ws list|show, at … show, plus search/info/doctor/graph/audit/cv, and machine-readable errors carry {error, kind, exit_code} on stderr (SYN-188/189/204). Text mode is rich by default on a TTY (colour, ✓/▲ glyphs, tables, entity panels, the unicode graph tree, progress spinners/bars, the graph-constellation mascot); it degrades to plain bytes on a pipe and is fully suppressed by --plain/--raw/SYNAPSE_PLAIN, while --no-color/NO_COLOR/TERM=dumb drop only colour and --quiet/-q drops non-essential lines (ADR-019/021).
Conventions worth knowing
- Slugs are kebab-case ASCII, derived automatically. Renaming is a separate command (
<type> rename old new), not an edit. - Frontmatter round-trips preserve field order; safe to edit by hand without producing noisy diffs.
- Writes are all-or-nothing: filesystem + index commit together, rollback together (BR-6.1).
- A workspace is any folder whose ancestors contain
.synapse/;synfinds it walking up fromcwd.
syn commit-verify --roster <JSON>
Verifies raw Git HEAD SSHSIG bytes in the git namespace against explicitly
pinned local signing keys. Emits synapse.commit-verification/1 JSON and exits
nonzero for unsigned, unsupported, invalid, unknown or ineligible signatures.
The same pure core serves MCP commit_verify. A compatible roster array keeps
v1 behavior. A synapse.commit-roster/1 object additionally requires an explicit
--roster-sha256 (roster_sha256 in MCP) selected outside the mutable workspace.
It emits v2 with revision/pin/freshness and rejects missing/wrong pins, stale
snapshots, future signing claims and unsigned/unknown signatures.
--require-current additionally requires current key eligibility; historical
administrative revocation can preserve a prior valid signature. Reported
compromised_at invalidates signatures at or after its onset. Neither profile
establishes team authority or an independently trusted historical timestamp.
Roster entries have issuer, subject, OpenSSH public_key, Unix-second
not_before/expires_at and optional revoked_at/compromised_at. Signing-time validity and
current_key_valid are separate. Author names/emails do not establish identity;
commit time is signer-controlled, and current team membership needs independent
revalidation. OpenPGP and alternate SHA256 commit headers are explicitly unsupported.
A roster supplied by a caller is a local trust decision, not an authority-issued
binding. The snapshot includes schema, bounded revision, observed_at/expires_at
Unix seconds and unique signers; its normative SHA-256 prefixes fixed-order JSON
with synapse.commit-roster/1 and NUL. Unobserved offline revocations remain
unknown. See ADR-055/064 and the CI test generating actual Git/OpenSSH commits.
The official Git configuration documents
gpg.format=ssh; verification uses the normative git SSHSIG namespace.
Governed source suffix: syn state
This is separate from native v1 syn verify: current Markdown remains the
content source, while the optional governed suffix preserves complete accepted
before/after manifests and retrievable raw images. Previous history is unknown.
syn state baseline --reason 'adopt reviewed current knowledge' \
--acknowledge-unknown-history --acknowledge-plaintext-retention
syn state verify
syn state witness --key-dir /private/existing-identity > /independent/state-witness.json
syn state verify --since /independent/state-witness.json --trusted-key '<pinned public key>'
syn state reconcile --expected-parent '<reviewed head SHA256>' --reason 'reviewed external API update'
syn state receipt '<accepted event ID>'
These commands return native JSON objects in both output modes. Verify exits
nonzero if uninitialized, damaged, missing retained images or source is
Unattested. Reindex cannot turn external edits into accepted history. A
reconciliation records the previous accepted state and actual observed state
separately; a stale expected parent denies it. Mutation IDs are stable during
recovery, and Workspace::tx_with_event_id supports caller request IDs without
repeating an accepted ID’s effects; retrieve original evidence with receipt.
Baseline/reconcile accept optional --context <JSON> containing actor_claim
(issuer/subject/device_key), proposal/rule/context SHA-256 values and a reason.
These are explicit claims until authority/receipts verify them. No password,
token, private key or raw prompt belongs in context/reason. Witness export needs
an existing signing key, and verification needs a separately trusted pin; its
embedded key alone does not establish trust. Preserve the witness independently.
Scope covers regular Markdown recursively outside .git/.synapse components;
symlinks/unportable paths and exceeded budgets deny instead of silently skipping.
Retained deleted images are plaintext private history with no automatic GC; use
filesystem encryption/access controls. Purging an image loses reconstruction
coverage. The framework does not publish raw images or commitments externally.
See ADR-057 for normative bytes, budgets and
adversary/platform boundaries.
MCP counterparts: state_baseline, state_verify, state_reconcile,
state_witness, state_receipt. Each calls the same core as the CLI. State verify
reports tool error when uninitialized or Unattested; field attribution remains
explicitly unverified without authority-issued receipts. These tools do not
claim general natural-language consistency, trusted timestamps or team authority.
Native receipts and explicit assurance profiles (SYN-325)
syn state receipt-policy --input <policy.json> [--expected-policy <active-digest>]
syn state signed-receipt <event_id>
syn state witness --key-dir <existing-key-directory> --include-receipts > <independently-retained-witness.json>
syn state verify --profile strict-local --trust <reviewed-trust.json> --trust-sha256 <independent-digest> --since <retained-witness.json> --trusted-key <independent-key-pin>
syn state verify --profile basic
syn state verify --profile strict-team
Without --profile, the existing synapse.governed-state/1 source report stays
available. Explicit profiles use synapse.state-assurance/2; passed is the
selected policy verdict. Basic can succeed with absent/unsigned receipts and
explicit gaps (strict_requirements_met:false). Strict-local requires matching
source/images, all receipts from the limited baseline, independently pinned key
validity/freshness and a v2 witness covering the exact selected tip/receipt set.
A prefix-only witness or old v1 source-only witness is partial, not sufficient.
Strict-team additionally requires independently selected current issuer roots,
exact resource scope and fresh complete actor/device observations supplied with
--team <reviewed-team-inputs.json>. Every retained native receipt must match
its original issuer/source seal and event-specific admission, with an independent
v3 witness covering both receipt and original seal sets. Missing, stale, copied
or untrusted inputs fail; a local key alone does not establish team authority.
A successful historical verdict grants no current publication permission.
Configure signing before baseline; no back-signing of unsigned history. Policy
JSON contains schema synapse.receipt-write-policy/1, profile strict_local, an
absolute existing key_dir, a reviewed public trust snapshot and its normative
trust_sha256. Snapshot schema is synapse.local-receipt-trust/1: revision,
observed_at/expires_at and unique keys with public_key, not_before/not_after,
revoked_at and compromised_at. Times are integer Unix seconds, end exclusive;
nullable timing fields remain explicit null. Digest is SHA-256 of its schema
plus NUL and fixed-order compact JSON. No auto-generated/trusted signer is used.
An explicit expected public policy digest permits key rotation/trust refresh; prior receipts and public trust revisions stay retained. A wrong prior digest, missing key, permissive Unix private-key mode or invalid/stale signing trust blocks before source installation. Losing the active policy pointer cannot silently downgrade retained strict configuration. Private key paths are local configuration only, never exported in receipts; bearer/private bytes never enter source, evidence or logs.
Native receipts bind full transition/context/source digests and public signing policy; independently retained v2 witnesses additionally bind the ordered exact receipt set. Verification-at-claimed-time is distinct from current key approval: a later administrative revocation can preserve earlier evidence; compromise at or before that claim invalidates it. The clock remains signer-controlled and no offline verifier can know an unobserved revocation. Receipt validity is not semantic truth, organizational authority or a trusted historical timestamp.
MCP counterparts state_receipt_policy and state_signed_receipt share the CLI
core. state_verify accepts profile/trust/trust_sha256; state_witness accepts
include_receipts and optional include_shared_seals (v3 requires receipt coverage).
Team verification accepts the typed team input shared with CLI. Failed selected profiles return tool error plus inspectable
JSON; the native signed-receipt result is an object with nullable receipt.
See ADR-058.
Scoped offline authority evidence (SYN-326)
syn state binding-verify --bundle <signed-bundle.json> --trust <reviewed-issuer-roots.json> --trust-sha256 <independent-digest> --expect <selected-identity-and-scope.json> [--claimed-at <epoch-microseconds>]
The bundle contains signed binding and snapshot only. Root review/digest and
expected authentication issuer, tenant/subject UUIDs, device/fingerprint and exact
organization/workspace/project/knowledge GUID are selected separately. The current
governed workspace must match that GUID; otherwise the report fails. No access,
refresh, M2M, password or private key belongs in these files. Account API identity
is not represented as OIDC without such an adapter.
state_binding_verify is the MCP counterpart with bundle/trust/trust_sha256/expect
and optional claimed_at. It reports an inspectable object and tool error for failed
signature, root pin, identity/scope, revoked/compromised/expired or partial selected-key
evidence. Claimed/observed/root times here are Unix microseconds, distinct from native
source receipt seconds. Signatures and exact canonical schemas live in the shared
pure synapse-evidence Rust package; ADR-059
defines the contract and independent fixtures.
A passing synapse.binding-verification/1 proves this scoped identity observation;
source_integrity_checked:false requires separate state verification and
shared_acceptance:unknown_without_online_event_bound_admission requires fresh
SYN-231 admission. It neither changes source/pending data nor makes strict-team
verification pass. Snapshot writer/key activity is an observed point, not evidence
of unobserved later revocations or new action authority.
Historical shared admission (SYN-231 component)
syn state admission-verify --bundle <grant-binding-snapshot.json> --trust <reviewed-roots.json> --trust-sha256 <independent-digest> --expect <selected-reviewer-change-policy.json>
MCP state_admission_verify takes bundle/trust/trust_sha256/expect and uses the
same core. The bundle contains admission, reviewer binding and fresh selected-key
snapshot. Independently selected expect contains reviewer, exact change and policy.
The actual governed GUID must match. Missing/stale/wrong-scope evidence denies.
The verdict authenticates a historical authority decision;
source_installation_verified:false and
new_operation_authority:requires_current_online_decision state its limits.
This does not enable strict-team writes or replace the prepare/sign/commit host
protocol, source receipts, search/citation ACL or independent witness. See ADR-060.
Scoped contradiction diagnostics (SYN-319)
syn contradictions [--fact note/api-contract] [--fact api/catalog] [--limit 50] [--proposal operation.json] [--assessment advisory.json]
MCP contradictions {request:{facts:[],limit:50,proposal:null,assessment:null}}
uses the same core. Each fact is {type,id}. Report schema is
synapse.contradiction-diagnostics/1; typed exact findings include stable rule
IDs, finding digests, fact/field/interval references, indexed paths and exact
checked scope. Omitted diagnostics/counts and unavailable checks are explicit.
Cycles resolve the full indexed reference graph before scope filtering. Bare
supersession targets must resolve to one globally unique fact ID; ambiguous
targets now deny acceptance instead of invalidating a row chosen by query order.
Unambiguous cross-type targets continue working. No source is changed by this verb.
An empty result is clear only within the checked structured classes. General prose/unindexed semantics stay unknown. Structured native proposal flips are a separate comparison, never an automatic review. Optional external assessment JSON names model_revision, rubric_revision, evaluation_revision, classification (contradiction/entailment/unknown), confidence and abstained. It is explicitly caller-asserted; this binary does not claim to run that model. Abstention requires unknown class and null confidence. Confidence cannot approve a write, change a rule or create an exact invariant result. See ADR-062 and SYN-323 model evaluation.
Versioned knowledge process API
knowledge describes actual build/protocol capabilities or takes bounded versioned
request JSON through --request <file|->. Query/propose/review share the MCP knowledge
tool. Acceptance and exact-review rejection are explicit operator CLI actions; the
MCP agent door refuses both. Legacy rejection refuses terminal rows without adding
a contradictory audit entry.
See request shapes, scopes, budgets, citations and proof limits.
syn --format json knowledge
syn --no-input --no-freshness --format json knowledge --request request.json
syn shared-source --input <JSON> [--operator-bridge]
Request schema 1 shares typed metadata/operator dispatch with MCP shared_source.
The MCP agent can describe public configuration; operator actions deny there.
Native bootstrap/exact pending acceptance requires a separate current signing
host through the bounded two-frame stdin/stdout bridge. No token/private key is
accepted. Shared content requires a fresh scope read proof; old local/automatic
write doors cannot publish without the exact host seal. This protocol foundation
is documented in SHARED_SOURCE; real Soma issuer/Keychain and
full strict-team composition remain separate acceptance work.
Team inputs use schema synapse.team-assurance-inputs/1, public trust and its
independent trust_sha256, selected scope and unique actors containing
external expected binding expectations and current signed snapshot objects.
Authority timestamps use microseconds; existing local source observation and
local trust timestamps remain seconds. Historical device eligibility is assessed
at original issuer permit issuance, not the source preparation clock. Later
administrative revocation preserves preceding decisions while
currently_approved:false prevents confusing history with current permission.
Use syn state witness --include-receipts --include-shared-seals --key-dir <independent-key-directory> and independently retain the resulting v3 witness
and its separately selected public key. No retroactive receipts or team history
are fabricated for a local/unsigned baseline.
Standard evidence exchange: syn state attest
state attest is a separate opt-in in-toto Statement v1 export; native report
formats and defaults remain available. The subject hashes exact exported native
evidence artifact bytes. Basic/strict assurance, originals, selected public
trust/witness inputs, skipped checks and excluded surfaces are preserved.
syn state attest --profile basic
syn state attest --profile strict-local --trust <reviewed-trust.json> \
--trust-sha256 <independent-digest> --since <witness.json> \
--trusted-key <independent-witness-key> --dsse-key-dir <private-export-key-dir> \
--artifact-output <new-file-outside-source.json>
syn state envelope-verify --input <dsse.json> --trusted-key <independent-export-key> \
--artifact <exact-optional-subject-artifact.json>
The optional DSSE export key signs only the outer statement. Independent envelope
verification reports signature and subject integrity; native source verification
is not rerun and current publication authority is not granted. A failed native
policy still exports its honest statement and exits non-zero. Artifact output
cannot overwrite an existing file or modify the source. MCP state_attest and
state_envelope_verify use the same cores; no MCP private-key path exists.
See the versioned predicate.
Scoped advisory decisions (SYN-323)
syn decisions --fact note/a --fact note/b [--provider rules|HOST_ALIAS] reads an exact bounded source subset and emits synapse.source-decision-diagnostics/1. MCP decisions accepts {request:{facts:[{type:"note",id:"a"},{type:"note",id:"b"}],provider:"rules"}} through the same core. There are 42 MCP tools.
Source read authorization, accepted revision integrity, deterministic contradictions, topic, API version, environment and validity precede inference. Pairs are evaluated in both directions because entailment is directional. Unknown/truncated context or exhausted budgets produces abstention. The default rules baseline recognizes identical text and abstains from arbitrary prose. Models never approve or mutate memory.
See provider configuration and reproducible evaluation. Local NLI and Jev are opt-in experiments; the initial EN/PT benchmark failed its recall gate. No model download, Python runtime or hosted credentials are required by default.
Revisioned source acquisition (SYN-335)
syn sources --request '{"connector":"docs","document_id":"catalog","cursor":null,"notification_revision":null,"cancelled":false}' polls one exact host-configured document. --request - reads bounded JSON from stdin; MCP sources takes the same object in request and calls the same core. It returns current revision/digest/access provenance or an explicit no-content unavailable/denied result. It never creates accepted notes or publishes upstream patches.
See configuration, access mapping and adapter coverage. Git/Markdown and opt-in GitHub are implemented; other tools remain separately tracked adapters. There are 42 MCP tools.
Scoped source impact (SYN-327)
The same sources request accepts an optional operation (absent means {"op":"read"}). observe reconciles current source revision and returns only authorized matching dependencies; review pins a proposal ID; suggest_patch requires that reviewed ID and expected_revision, and returns an unpublished suggestion. queue creates an exact source-linked native note proposal; accept requires explicit CLI operator action, pending ID, exact review digest and a fresh matching upstream read. Generic pending accept and MCP cannot bypass that source check. Current adapters always refuse publish, including requests with reviewer consent.
See evolution states, dependency mapping and acceptance limits. Accepted historical local annotations do not establish current upstream truth; current upstream passages are served through fresh source acquisition.
Versioned knowledge process contract
knowledge is an additive CLI/MCP contract for local process clients such as Soma.
It wraps existing core/index/source transactions; it does not create another memory
database, an HTTP daemon, Rust FFI or a replacement for operational run history.
syn --no-input --no-freshness --format json knowledge
syn --no-input --no-freshness --format json knowledge --request request.json
syn --no-input --no-freshness --format json knowledge --request - < request.json
The default operation describes actual protocol/build/OS/architecture/workspace and supported capabilities. A release/version string does not authenticate the publisher. Clients independently select signed artifact metadata/publisher roots or an explicitly reviewed user-installed digest, verify architecture, and pin the actual executable. This repository remains private; no artifact publication or credential workaround is performed by adding the contract.
Request and response
A request has exactly schema, scope, budget, operation. Describe may omit
scope/budget; workspace operations require both. Payloads are bounded to 256 KiB.
Valid requests produce synapse.knowledge-response/1 with ok, result, error.
Fixed error codes do not echo raw request/source text. Invalid transport/file/JSON
inputs can fail before core dispatch; hosts must handle typed malformed/process errors.
{
"schema": "synapse.knowledge-request/1",
"scope": {
"root": "/absolute/selected/workspace",
"knowledge_workspace": "<actual acknowledged GUID or null for legacy reads>",
"authority": "local-owner",
"facts": []
},
"budget": {
"max_output_bytes": 262144,
"max_latency_ms": 5000,
"max_tokens": null,
"tokenizer": null
},
"operation": {"op": "query", "query": "database", "type": "note", "limit": 16}
}
Root/GUID must match the selected workspace. Exact facts filters contain typed
{"type":"note","id":"decision"} identities and enter SQL before candidate
retrieval; they are local selection, not remote ACL grants. Only local-owner is
supported. A shared request fails with unsupported_shared_authority; it never falls
back to local privilege or treats a cached binding as a current team grant.
CLI knowledge suppresses background freshness/model work. Query uses scoped lexical FTS directly, including builds with vector support; it does not embed or call a model. Current native source acceptance still respects separately configured native policy and any consented enrichment behavior. Unrelated workflows can ignore an unavailable optional knowledge dependency.
Citations, omissions and verification
Query returns actual raw Markdown prefixes, typed fact identity, exact raw whole-file SHA-256, path, provided byte/line range, accepted head, current source-manifest digest, effective native policy digest and explicit omissions/additional-candidate coverage. It validates raw source identity and consistency before/after retrieval under the writer lease. Governed external drift fails; uninitialized legacy reads remain partial. The returned prefix may not contain every lexical match; retrieval relevance is not semantic truth or a calibrated confidence score.
The profile is basic: an acknowledged source suffix does not attest pre-baseline
history, authorship, independent witnessing, every index projection, general language
consistency or agent attention. Those facets stay explicit. Retrieved documents are
task evidence, never permission or executable instructions.
max_output_bytes bounds encoded response; max_latency_ms bounds elapsed core read
work. The host must additionally bound stdout and enforce hard process deadline and
cancellation. Filesystem work cannot be preempted by an elapsed-time check alone.
Committed writes are not relabeled failed solely because a core latency target elapsed;
after a host timeout inspect/retry the exact request/queue/receipt state.
Optional max_tokens requires the explicit offline tokenizer
cl100k_base:tiktoken-rs-0.12.1. Actual BPE counts apply to supplied passage text only;
metadata tokens are not counted. It is not an estimate of an arbitrary agent/model’s
hidden prompt or tokenization. Unsupported tokenizer/budget combinations fail rather
than guessing from characters. The tokenizer assets are included by the pinned
upstream implementation, with no model fetch.
Proposal, review and operator decisions
propose takes a bounded note capture and exact context:
{
"op": "propose",
"capture": {"title": "Reviewed result", "body": "Chosen knowledge", "kind": "decision", "tags": []},
"reason": "Explicit local review",
"context": {
"schema": "synapse.knowledge-proposal-context/1",
"knowledge_workspace": "<actual GUID>",
"idempotency_key": "<client UUID>",
"run_id": "<actual operational run UUID>",
"context_sha256": "<actual immutable Soma context digest>",
"expected_head": "<exact query head digest>",
"source_manifest_sha256": "<exact query manifest digest>",
"policy_sha256": "<exact query native policy digest>",
"sources": [{"path": "notes/source.md", "sha256": "<original raw source digest>"}]
}
}
Run/context links are caller evidence references, not authenticated identity or proof that an agent used the sources. An initialized acknowledged baseline is required. Propose always queues; it does not auto-apply even when an old safe-auto-apply policy would allow another write tool. Identical retries return the same pending row; a changed request with the same key conflicts. Idempotency lasts while the operational index and pending rows are retained. Explicit prune/index destruction is not claimed to retain a durable request ledger.
review takes pending_id and returns exact retained payload/context plus
review_sha256. The operator CLI accept takes both and rechecks source head,
manifest, original source revisions, native policy and reviewed bytes inside the
existing native source transaction. Old pending accept doors invoke the same opted-in
source/policy guard; switching clients cannot make a stale proposal current.
The MCP knowledge tool uses the same core with agent authority: describe/query/
propose/review are available, but accept returns local_operator_acceptance_required.
Reject returns local_operator_rejection_required there.
This preserves the existing agent/human approval split. Local CLI actions are explicit
operator/process intent, not authenticated shared reviewer identity or a sandbox against
an already privileged local process. Current team/source admission requires the separate
shared bridge; its absence is never an authorization fallback.
The negotiated reject operation takes pending_id and review_sha256 in the
same scope/budget envelope. Its success result is
{"pending_id":42,"status":"rejected","review_sha256":"<review digest>","retried":false}.
The writer lease and SQLite transaction protect exact retained-row comparison,
pending-state transition and the live audit decision. An unchanged rejected review
can be retried with retried:true, without another audit append. Changed bytes fail
with review_changed; invalid IDs/digests fail with invalid_pending_id or
invalid_review_digest; accepted/undone states refuse with invalid_input.
Rejection preserves source notes, accepted seals, retained payload/context and prior
audit history. Because it publishes nothing, a stale proposal can be discarded after
source/head/proposal-policy drift. Acceptance keeps all existing freshness checks.
Legacy pending reject shares the writer/state guard, but its ID-only command does
not attest a displayed review and refuses repeated terminal-state decisions.
Clients must check that descriptor operations contains reject; older binaries
remain supported by retaining the pending intention and showing an unsupported
rejection result, without falling back to the legacy command.
The audit append precedes SQLite commit. A crash or commit failure between them can leave a pending row with an already appended rejection entry. Exact retry is idempotent for committed retained rows, not a claim of crash-safe exactly-once audit delivery. See ADR-071.
Verification
Production core fixtures cover scoped raw citations, exact named tokenization, output
omissions, wrong root/GUID/shared requests, drift, exact retries, changed requests/review,
stale source/policy through old accept doors, queue-only policy and second-run reuse.
tests/knowledge_mcp_smoke.py runs actual native CLI/stdio parity and the operator-only
accept/reject split. tests/test_guarded_pending_rejection.py provides real-binary
unittest acceptance evidence for exact rejection, terminal-state audit protection,
retention, stale-source discard, scope and writer contention. All old command formats/tools remain; the additive MCP surface is 42 tools.
Soma’s actual pinned-binary integration verifies the full read/run/review/propose/accept/
reuse path, while preserving its full private execution records.
Native shared source bridge, protocol foundation
This is the staged Synapse side of SYN-231/325/326. It introduces current scoped permits, protected reads and a recoverable host signing port. It does not yet prove the complete Soma issuer/Keychain runtime or strict-team dashboard profile; those issues remain open until the paired real-account integration is verified.
An existing local/unsigned ledger cannot be silently relabeled as a shared origin. Use an explicitly selected fresh workspace/policy and acknowledge unknown preceding history and plaintext retention before creating its limited baseline. Shared configuration pins a scope and reviewed authority-root digest independently of returned device/issuer artifacts. Ordinary local workspaces remain usable.
shared-source takes request schema 1 from a bounded --input file or one stdin
line. describe returns public configuration metadata. configure requires the
explicit operator door and an independent policy hash/CAS. An MCP agent can
inspect metadata but cannot configure/bootstrap/accept; a self-declared role or
model confidence cannot manufacture approval or an issuer signature.
For commit_candidate (or the retained limited bootstrap spelling), the explicit --operator-bridge holds
the native writer lease while staging source/index state. It emits one bounded
synapse.source-seal-required/1 frame with the exact prospective record,
unsigned native receipt and its normative signing bytes in base64. The parent
must enforce a finite child deadline/cancellation, validate exact scope/actor/key,
and return a synapse.shared-source-seal/1 public envelope on stdin. No daemon,
key file, private key or bearer/M2M credential is part of this bridge.
The native verifier requires genuine detached device receipt integrity, independently selected issuer roots, complete device bindings/key observations, current writer/key eligibility, exact historical review where needed and a fresh operation-specific issuer permit. That permit binds actual scope/policy/context, source event/sequence/head/record and complete signed receipt digest. Read, limited bootstrap and publication purposes are disjoint. Issuance before the native transaction start, wrong purpose/receipt/policy/actor/scope, missing trust or expired authority denies before installation. The lifetime is explicitly at most fifteen seconds; it is not an instant-revocation or trusted-client-clock promise. Publication also checks expiry/policy immediately before SQLite COMMIT.
Prepare-v4 retains the original public policy/actor/receipt/permit/binding/review bytes with the same exact-digest native source/index decision. An uncommitted crash restores preimages; a committed crash replays original public evidence before source record/head success without a host, private key or new signature. Older prepares 1/2/3 remain recoverable. Unsupported older binaries reject v4. A retained historical admission never authorizes another native transaction.
Shared content/read/receipt/resource/prompt and federation API doors require a
fresh read-purpose capability in an explicit SYNAPSE_SHARED_READ_PROOF file
supplied by the authenticated host. Keep that public capability owner-private
outside Git, renew it per SDK-authenticated request/reconnect, and retain its
finite expiry. It permits only the configured registered scope, not another
project or publication. There is no silent local fallback. API controls do not
sandbox a privileged host’s direct disk access.
Current proof: genuine Ed25519 fixture-root/device signatures through an actual native two-frame process; exact CLI/MCP metadata parity, 42-tool surface and operator denial; current scope/read expiry and old write-door denial; and eight actual process-death prepare/commit/evidence/head recovery boundaries, reopened twice without a signing host. The fixture-only test is separate from the real Soma SDK/Keychain proof below and from a consenting human adoption pilot.
See ADR-065 and SOMA-45. Independent witness coverage must include original shared-seal metadata as well as original receipts before strict-team assurance can report complete required coverage.
Exact staged handoff
Use the operator shared-source stage_baseline or stage_pending operation before requesting an issuer decision. Stage results contain candidate_sha256, original source event/sequence/record and, for queued facts, the exact public shared change. Staging retains original source images and releases the writer lease without installing source or accepting the queue. ReviewPending requires current scoped read authority and exposes the actual queued bytes with their review commitment.
CommitCandidate resumes only the explicitly selected candidate digest. The native record, original preparation observation and Markdown bytes are reused; the live index projects those same bytes. Current source/head/context/review policy and a fresh source permit are still required. A death after issuer acknowledgment before native WAL preparation leaves a durable candidate for explicit retry. Missing or changed source refuses installation instead of overwriting external edits.
Built-in capture/status/link mutations and supported batches are covered. Custom and NER mutations remain pending with an explicit unsupported result. The original operational queue row must remain available for the queued review guard; candidate retention does not promise automatic recovery of a separately destroyed index. Frontmatter/preparation timestamps remain untrusted claims, distinct from original issuer observation and current authorization time. See ADR-067.
The local-owner knowledge process contract explicitly refuses configured shared sources. Generic current-scoped reads and the shared operator bridge remain usable; no shared source is relabeled as a local owner grant. Portable original receipt and seal-set codecs support an independent checkpoint issuer without source body or private signing material crossing the process boundary.
Offline provisional notes
queue_note accepts only bounded caller-supplied note fields and a reason. It
needs no network or read capability and enqueues a provisional local claim; it
returns only the new pending ID and explicit unverified/pending facets. No source
content is read, installed or attested. Queue inspection, staging and publication
still require current scoped read/review/operation authority. An authenticated
actor cannot be self-declared in this offline request, and no path, external target,
private-key or authorization slot exists in its capture shape. Both CLI and MCP use
the same core behavior. Generic shared-source reads retain their capability checks.
The legacy accept spelling fails with an explicit requirement to use
stage_pending and then the independently selected commit_candidate. A retry
cannot silently choose a new random candidate after an issuer acknowledged an
older event. Ordinary account-free local-owner acceptance remains available.
Real Soma integration now includes two local Febasi SDK accounts and actual Keychain device keys, distinct review of immutable conflicting notes, native source publication and a backend-issued v3 checkpoint verified by the native strict-team verifier against an independently selected issuer key. Later device revocation preserves historical evidence and denies current reconnect/publication. This proof is separate from a consenting human workflow pilot, which remains outstanding.
Explicit source revision acquisition
Synapse monitors selected upstream documentation through optional outer adapters. It preserves canonical source revision/digest/citation/access provenance and proposes knowledge separately. It does not become the authoring application or import an entire enterprise automatically.
Host configuration and access
Set SYNAPSE_SOURCES_CONFIG to an owner-private regular JSON file, at most 64 KiB, outside the knowledge workspace. Logical aliases and stable document IDs are the only selection accepted from CLI/MCP. Review these mappings as host capabilities; neither Markdown nor agents choose a token, identity, executable or arbitrary URL.
{
"schema": "synapse.source-adapters/1",
"connectors": {
"docs": {
"kind": "git_markdown",
"repository": "/absolute/selected/git/repository",
"reference": "main",
"documents": {
"catalog": {"path":"docs/api.md","readers":["local-owner"],"api_version":"v2","environment":"prod"}
}
},
"github-docs": {
"kind": "github",
"owner": "selected-owner",
"repository": "selected-repository",
"reference": "main",
"key_env": "SYNAPSE_GITHUB_SOURCE_TOKEN",
"documents": {
"catalog": {"path":"docs/api.md","readers":["EXACT_ROOT_SUBJECT_UUID"],"api_version":"v2","environment":"prod"}
}
}
}
}
Local-owner mode uses local-owner by default. A separately trusted local host may set SYNAPSE_SOURCE_ACTOR; this is an explicit host mapping, not account authentication. Shared workspaces ignore that override and derive the subject from the current verified Root read capability. Source document readers must include that exact subject. The Root policy is checked before adapter selection/transport and again before returning content. Host policy bytes are hashed as acl_reference and checked again after acquisition; changes during execution deny output.
Git reads only a pinned commit object through fixed git commands, with user/global/system Git settings disabled, no hooks and a 10-second overall process deadline. Uncommitted edits are ignored; symlink/submodule objects and path traversal are rejected. A stable document ID can be explicitly remapped to a renamed Markdown path; its URI/revision changes visibly.
GitHub requires network_backends; key_env:null permits public reads without credentials. Credentials remain only in the environment. Use narrowly scoped GitHub App/user tokens with repository Contents read access. Current GitHub API access intersects the host document ACL; success is not a write grant. Resolve the branch/ref to an exact commit, read its Contents bytes, and recheck the ref with the same credential before output. Redirects/download URLs are never followed. Deleted/restricted/expired/rate-limited/failed sources expose no stale body. A fresh next poll is the bounded retry; no unbounded automatic retry loop runs.
Bounds, cursors and write policy
One request selects one exact document. Content is bounded to 256 KiB and the GitHub JSON response to 512 KiB, with a 10-second total upstream deadline. There is no background scheduler or secret-bearing webhook server. cancelled:true aborts before transport; in-progress work is bounded by its deadline. Webhooks are untrusted hints. Cursors/hints that disagree with the fresh upstream revision trigger a bounded latest-state reconciliation; this does not claim complete intermediate revision/event history. Replayed hints cannot replace current bytes with their claimed version. Source evolution owns durable dedup and impact candidates (SYN-327).
Both adapters advertise write_back:false. The request schema has no publication operation or reviewer-consent field that can unlock it. Suggested patches require a future explicit write capability plus exact upstream revision and reviewer consent. Possessing a token with broader GitHub permissions does not enable writes. Default acquisition remains read-only and makes no local source changes.
Adapter coverage
| Adapter | Current delivery | Separate integration obligations |
|---|---|---|
| Git/Markdown | pinned local commit reads/poll/current-state reconciliation | reviewed upstream write-back remains disabled |
| GitHub Markdown | optional official Contents/commit API reads | App installation/token lifecycle remains host-owned |
| Obsidian | Markdown in a selected Git repository works through Git adapter | unversioned vault monitoring is not implemented |
| Notion | tracked under SYN-335 extension scope | page/block revision and per-user ACL mapping; not implemented |
| Jira | tracked under SYN-335 extension scope | issue/version/event visibility and ACL mapping; not implemented |
| Confluence | tracked under SYN-335 extension scope | page versions/restrictions and cursor reconciliation; not implemented |
tests/sources_smoke.py exercises actual Git update/deletion, repeated notifications, policy revocation, cancellation, uncommitted edits, traversal denial and CLI/MCP parity. Optional SYNAPSE_TEST_LIVE_GITHUB=1 uses the authenticated local gh credential only in process memory for a read-only private repository probe; CI never requires it.
Primary transport contract: GitHub Contents API, commit API. ADR-049 and ADR-069 govern the separation of acquisition, source authority and acceptance.
Source evolution and scoped impact
A detected source revision creates review candidates. It never silently replaces an accepted note or treats an unavailable copy as current truth. The source API always acquires current upstream revision/access before serving passages or projecting impact details.
Dependencies and access
The private connector configuration accepts a top-level consumers array. It maps explicit consumes / depends_on dependencies from a connector/document to squad/project decisions and owned documentation. This is reviewed host configuration, not an inferred permission grant.
{
"id":"squad-a-integration",
"owner":"EXACT_AUTHENTICATED_SUBJECT",
"relation":"consumes",
"connector_id":"docs",
"document_id":"catalog",
"api_version":"v2",
"environment":"prod",
"readers":["EXACT_CURRENT_OPERATOR_SUBJECT"],
"fact":{"type":"project","id":"squad-a"},
"documentation_uri":"https://selected.example/squad-a/integration"
}
The current operator must be allowed by the source document and consumer ACL; the candidate owner must also be allowed by the source document. Exact version/environment filters apply before retrieving the optional native consumer identity. Missing native facts receive no candidate. The host must keep these explicit source/consumer grants current; they do not infer account membership from names. Shared operator identity comes from the verified Root read capability; an environment override cannot impersonate a shared subject.
For the API v2 authentication example, two matching authorized squads get source-linked review candidates. A restricted owner, v1 consumer and test-environment consumer get no recipients/details. Native consumer_fact, relation, owned documentation reference and full source citation accompany the candidate. No notification is sent to people automatically; the caller receives authorized candidates for a dashboard/workflow.
Observation and lifecycle
Metadata records exact connector/document/revision/digest/observation time/access hash/URI/version/environment. Repeated deliveries of the same revision/access/content deduplicate candidates. A reused immutable revision with changed bytes fails as divergence. A cursor/hint gap reconciles the latest upstream state with bounded acquisition; unobserved intermediate revisions are not invented. A stable document ID can be explicitly remapped after a rename.
States distinguish observed revisions, analyzed dependencies, proposed candidates, explicit review, native accepted annotation, superseded candidates and unavailable sources. A new revision supersedes previous candidates. Deletion, revocation or acquisition failure removes current availability and returns no old passage or old impact detail. Restored access creates a candidate that needs fresh review; prior approval is not reused silently.
The operational lineage is a bounded, owner-private metadata ledger per verified host actor, under .synapse/source-monitor, with durable atomic replacement while holding the native writer lease. It retains no upstream body or credential. History/candidate count is bounded to 512 and serialized state to 4 MiB; exhaustion fails rather than silently dropping history. This ledger is not a signed accepted-knowledge proof. Accepted source history remains the native governed transaction evidence. Existing Git/index/receipt distinctions continue to apply.
Review, patch and native acceptance
Use the same syn sources --request / MCP sources contract with an additive operation:
{"connector":"docs","document_id":"catalog","cursor":null,"notification_revision":null,"cancelled":false,"operation":{"op":"observe"}}
review takes proposal_id. suggest_patch takes the reviewed ID, current expected_revision and bounded replacement; it produces a source-linked suggestion with a replacement digest, not a published edit. Revision, access policy or document mapping changes invalidate it. Both current adapters have write_back:false; publish fails even with explicit consent or a broadly privileged token. A future write adapter needs explicit capability, reviewer consent and an upstream concurrency precondition.
queue takes a reviewed proposal and explicitly authored annotation body. The exact note payload retains x-upstream-source, x-source-impact, x-source-consumer and x-source-dependency. Repeated identical queue requests return the original row. The queue remains pending until an operator uses accept with the exact returned pending_id and review_sha256. Fresh upstream access/revision/digest/policy must still match; exact pending bytes are checked again inside the native transaction. Generic native acceptance refuses source-derived queue rows without fresh adapter evidence, and MCP cannot invoke operator acceptance. Native source/history/index/status commit together through the existing transaction primitive.
This first acceptance slice is local-owner only. Shared observation/impact uses authenticated Root reads, but shared source-derived acceptance requires a dedicated Root host workflow and remains disabled. It cannot be bypassed through generic shared candidate sealing. The native accepted note is a historical, locally authored annotation, not a replicated live upstream page. Generic note retrieval preserves that historical annotation; it does not assert fresh remote authorization or currentness. Current external passages and impact views must use the source API. Privileged raw filesystem access remains outside the API’s access-control contract.
Upstream reads and local commits have optimistic preconditions, not an atomic transaction across GitHub and the local filesystem. No distributed write guarantee, complete enterprise ACL synchronization, background worker or human pilot ROI is claimed. Queue/index recovery has the same limits as the existing native operational queue; rebuilding an index does not invent missing review rows.
Verification
tests/source_impact_smoke.py uses actual Git commits and the native CLI/MCP on owned synthetic fixtures. It proves two-squad fan-out with version/environment/restricted-owner exclusions, idempotent notifications/queueing, review, explicit native acceptance and its preserved source citation, generic/MCP approval denial, changed authentication source, rename, stale patch/queued acceptance, revocation/deletion and no upstream mutation. Native state verify validates accepted local history before/after source changes. Pure tests cover immutable revision divergence, access-precondition changes, restored-access review and supersession. These are engineering conformance tests, not a consenting human field pilot.
See ADR-070, SYN-327 (#312), SYN-335 (#96) and the optional decision experiment.
This guide describes the current source tree. A published executable can implement an older contract; inspect its version and release notes before integrating a newer surface. The ADR index records the decisions and the limits of each implementation slice.
Runtime topology
| Layer | Responsibility | Dependency rule |
|---|---|---|
| Edges | CLI parsing, MCP transport, host/source/model adapters | Call typed core use cases; keep credentials and transport concerns outside core |
| Core | Retrieval, proposals, review, source decisions and verification | Share behavior across CLI and MCP; no clap, rmcp or direct transport dependencies |
| Data | Markdown store, SQLite index, configuration and evidence metadata | Provide persistence primitives used by the core |
Every read or write supported by an edge must bind to the corresponding shared core
operation. An interface-specific approval rule is a drift defect. Synchronous SQLite work
runs through spawn_blocking at the MCP boundary; other CLI commands do not need a
long-running server.
Knowledge lifecycle
The knowledge process contract is a bounded CLI/MCP interface for hosts such as Soma. It returns source-cited passages with raw whole-file digests, accepted head, source manifest and explicit omissions. Hosts pin the actual executable and enforce stdout limits, cancellation and hard deadlines independently of the core budget.
Knowledge proposals always queue. An operator reviews the exact payload, then acceptance rechecks source head, manifest, original source revisions, policy and review digest. An MCP caller may describe, query, propose and review; it cannot invoke local operator acceptance. Other write tools retain their own documented proposal and safe-application policy rules. Read content and model confidence never grant permission.
Content, projection and operational state
| State | Role | Preservation requirement |
|---|---|---|
| User Markdown | Current knowledge content and typed frontmatter | Keep in the user’s selected Git/backup workflow |
| SQLite search projection | FTS, links, tags and optional vectors | Rebuild derived retrieval data with syn reindex |
| Operational queue | Pending payloads, context and review guards | Retain separately; Markdown cannot reconstruct lost pending rows |
| Governed source metadata | Accepted transitions, retained images, original receipts and seals | Preserve with the governed workspace; missing evidence reduces or fails coverage |
| Independently selected trust and witnesses | Verifier roots, pins and observed checkpoints | Keep independently of the artifacts they are meant to constrain |
yaml_serde handles frontmatter with ordered mappings
(ADR-054). Serialization preserves the
supported field semantics and order; it does not preserve every original YAML comment or
quoting choice. Entity identity is (type, id), so project/alfa and api/alfa may coexist.
Slugs follow the documented ASCII kebab-case rules. Explicit rename operations update
supported references through one workspace transaction.
The index materializes relationships such as project membership, API ownership and task scope. Every SQLite connection enables foreign keys. Reindexing repairs the derived projection; it cannot manufacture lost review context or erase a governed source mismatch.
Durable source transactions
The transaction protocol is documented in ADR-056, extended by ADR-057, ADR-058 and ADR-065.
| Stage | Durable decision and recovery behavior |
|---|---|
| Acquire | Take the cooperative writer lease and recover any preceding intent |
| Prepare | Stage writes/deletes, retain preimages and fsync a versioned prepare document |
| Install | Install the prepared source while retaining recovery material |
| Commit | Commit index changes and an exact prepare-digest marker in the same SQLite transaction |
| Publish evidence | Publish the retained original receipt, source record, shared seal where required, and accepted head |
| Recover | A matching committed marker rolls forward; an absent marker restores preimages; unexpected content fails closed |
Repeated recovery reuses the original committed bytes. It does not request a new signature or reinterpret historical approval as current permission. Changed database incarnation, path escapes and unexpected external edits preserve evidence and refuse unsafe repair.
The writer lease is advisory. External editors and raw filesystem readers can observe a short installation window. Unix directory fsync provides the documented power-loss ordering; other platforms have a narrower durability claim. A privileged disk owner, complete disk loss and dishonest hardware require independently retained evidence and backups.
Source history and verification
An explicitly acknowledged baseline starts the governed Markdown suffix. Unknown preceding history remains unknown. In an opted-in workspace, mutations require current content to match the accepted manifest; external changes need explicit reconciliation.
Source images are retained as private plaintext metadata. Manual retention, filesystem permissions and encryption policy matter: hashes provide no confidentiality. Attachments, keys, arbitrary non-Markdown files and excluded directories are outside the documented Markdown evidence scope.
| Verification facet | What it can establish | What it cannot establish alone |
|---|---|---|
syn verify | Named projection, history and supported structured invariants | Universal truth, complete receipt coverage or authenticated team authority |
| Source manifests and transitions | Selected raw bytes and causal ordering from the acknowledged baseline | Unknown pre-baseline history or independent non-rewrite evidence |
| Stored receipt signatures | Integrity of available original receipt bytes | A trusted employee/device identity from an embedded key alone |
| Strict-local assurance | Required selected source, receipt, trust and witness inputs | Current organizational permission |
| Strict-team assurance | Required original shared evidence with independently selected roots, lifecycle observations and witness coverage | Permission for a new publication or every natural-language claim |
A successful historical check has a different purpose from a current operation permit. Missing, unsigned, stale or skipped inputs remain visible. Complete strict profiles deny required gaps instead of silently downgrading them. See provability, ADR-066 and ADR-067.
Retrieval and optional models
Hybrid retrieval combines FTS5/BM25, optional vector search and graph-neighborhood signals
through reciprocal rank fusion. A model-ready local binary can use the configured semantic
backends; without consented model assets, retrieval degrades to supported lexical/graph
paths without downloading a model implicitly. --semantic and --no-vector select the
documented search modes.
The default semantic stack uses local embeddings and entity recognition. Optional network providers require explicit configuration. Insertion-time enrichment can add supported vectors and link proposals, subject to the native transaction and proposal rules. The model guide explains downloads, cache and provider choices.
The bounded host knowledge query intentionally uses scoped lexical retrieval and does
not start model inference. General contradiction assessment is a separate experimental
advisory port. Exact diagnostics check selected fields, intervals and reference classes;
probabilistic output cannot change those results, source or authority. The current grouped
experiment failed its recall gate, so it establishes no default semantic-consistency model.
See ADR-062,
ADR-068 and the
measured experiment.
Hosts, source adapters and shared authority
A local-owner workflow remains account-free. A host such as Soma can add account, workspace, project and device authority without sending private signing keys or bearer credentials to Synapse. The native shared bridge requires fresh scoped public host evidence; the local knowledge contract rejects shared authority rather than falling back to local privilege.
Shared candidates freeze exact source bytes before independent review. Publication resumes an explicitly selected candidate and requires fresh current admission. Historical seals remain historical evidence after their operation lease expires. Supported shared built-in mutations, offline provisional notes and the remaining adoption-pilot boundary are described in SHARED_SOURCE.md.
Current external source adapters read Git/Markdown and optional GitHub. Every passage or impact projection rechecks upstream revision and access; there is no stale remote-body cache fallback. A change produces bounded review candidates and metadata lineage. Native local annotations preserve exact upstream citations, while shared source-derived acceptance and all upstream write-back remain disabled in this slice. Notion, Jira and Confluence adapters are planned integrations. See source adapters and source evolution.
Code map and maintenance
| Concern | Entry point |
|---|---|
| CLI and MCP | src/cli.rs, src/cli/, src/mcp/ |
| Bounded host knowledge contract | src/core/knowledge/ |
| Governed source and shared bridge | src/core/governed_state.rs, src/core/shared_source.rs, src/core/source_candidates.rs |
| Durable persistence | src/store/transaction.rs, src/store/transaction/prepare.rs, src/store/governed_state.rs |
| Exact diagnostics and verification | src/core/exact_contradictions.rs, src/core/verify/ |
| Retrieval and models | src/core/search/, src/core/embeddings/ |
| Portable public authority contracts | crates/synapse-evidence/ |
The current source exposes 42 MCP tools; consult MCP.md for the actual surface. Do not infer a packaged release’s tool count from the source-tree documentation. Module size thresholds and review rules remain in ADR-015. Update diagrams when a responsibility, arrow or trust boundary changes, using the visual asset notes.
Changelog
All notable changes to synapse are documented in this file.
Format inspired by Keep a Changelog; versioning follows Semantic Versioning. Generated automatically from Conventional Commits by git-cliff.
[1.5.1]: 2026-10-18
This patch repairs release documentation packaging after the Windows v1.5.0 archive used checkout-converted line endings. Documents now use exact committed Git bytes, and all four archive changelogs must match the source receipt before signing or publication. The strict npm consumer is unchanged. Documentation-site diagrams also use pinned local renderer assets. This adds no CLI or MCP feature.
✅ Testing
-
SYN-343: Record independent release producer controls (bebda1f)
-
SYN-343: Expose checkout-dependent release documentation (316fe40)
-
SYN-342: Challenge chapter and CLI diagram coverage (fc14858)
-
SYN-342: Expose raw diagrams in the generated docsite (467f0eb)
🐛 Bug Fixes
-
SYN-343: Bind packaged documents to committed source bytes (c902f44)
-
SYN-342: Bundle pinned Mermaid assets in the docsite (140b2b5)
📝 Documentation
-
SYN-343: Align contributor release instructions (17d596f)
-
SYN-343: Record frozen patch candidate verification (66f44b0)
-
SYN-342: Preserve readable diagram layouts (dd10f9b)
-
SYN-342: Record independent publication gate challenges (52e9fd9)
-
SYN-342: Record bounded docsite diagram evidence (2bfad74)
🤖 CI
- SYN-342: Require independent publication coverage tests (cedfcce)
🧹 Chores
- SYN-343: Prepare patch version and reviewed release notes (e644232)
[1.5.0]: 2026-10-18
♻️ Refactoring
- SYN-231: Isolate preparation and document projection (5740e20)
✅ Testing
-
SYN-340: Require metadata-free release packaging (2355ea9)
-
SYN-340: Challenge originating release workflow boundaries (d9ef69f)
-
SYN-338: Challenge native rejection boundaries (271bb19)
-
SYN-338: Preserve seals and retry timestamps (7c9801e)
-
SYN-325: Require explicit missing team identity failure (ac6548d)
-
SYN-326: Verify issuer rotation and historical trust (b6777b1)
-
SYN-329: Keep audit fixtures inside retention window (c87dcde)
-
SYN-328: Prove pre-push child repository isolation (3714fa1)
✨ Features
-
SYN-327: Govern scoped source impact proposals (6b5aa85)
-
SYN-335: Add explicit revision source adapters (3f5cf03)
-
SYN-323: Add scoped advisory decision providers (7b2eee1)
-
SYN-320: Export standard native state attestations (efae9ed)
-
SYN-231: Queue provisional offline shared notes (5fedf25)
-
SYN-231: Retain exact source candidates before review (df488f0)
-
SYN-325: Compose historical team source assurance (6bc3b38)
-
SYN-231: Seal native shared source operations through a host (05b98c5)
-
SYN-261: Pin commit roster revisions and validity (2ffa90b)
-
SYN-321: Add bounded versioned knowledge process contract (b56163c)
-
SYN-319: Report scoped exact contradictions without prose claims (e97ea67)
-
SYN-318: Gate memory ci with pinned assurance policy (af42a51)
-
SYN-231: Verify exact historical shared admissions (63af722)
-
SYN-326: Verify portable scoped authority evidence (5db71d9)
-
SYN-325: Persist receipts and require local assurance (5beacf3)
-
SYN-324: Preserve governed source state and witnesses (c810f32)
-
SYN-261: Verify commit SSHSIG against pinned trust (7d1010d)
-
SYN-316: Add INV-13 logical-contradiction invariant to verify (701e065)
🐛 Bug Fixes
-
SYN-340: Omit host metadata from release archives (76ea7e0)
-
SYN-340: Bind mirror receipt to originating event head (e2960ea)
-
SYN-340: Reject ambiguous release artifact staging (8b8e727)
-
SYN-338: Guard exact pending rejection decisions (d272447)
-
deps: Update rustls for current security advisories (ece3c34)
-
SYN-318: Verify stripped native ci artifacts within budget (6512983)
-
SYN-324: Preserve workspace writes across commit failures (fd58423)
-
SYN-330: Merge security and knowledge framework contracts (4f627bb)
-
SYN-330: Replace unsupported YAML and patch advisories (5923c07)
-
SYN-328: Isolate git environment before pre-push checks (911739b)
💄 Style
- SYN-316: Inline let-and-return in inv13 test helper (8d82f51)
📝 Documentation
-
SYN-340: Retain verified producer compatibility evidence (0557133)
-
SYN-340: Preserve independent release boundary evidence (679fafc)
-
SYN-340: Regenerate integrated 1.5.0 release notes (a728745)
-
SYN-339: Record independent dependency review (bb1edeb)
-
SYN-339: Freeze stable validation evidence (4bde86d)
-
test: Document required native contract gates (6646799)
-
diagrams: Merge semantic and evidence corrections (0722253)
-
diagrams: Clarify topology and restore measured chart data (c419a80)
-
product: Merge the visual memory architecture (b0293df)
-
guides: Align evidence and distribution boundaries (696562f)
-
product: Visualize governed memory and integration boundaries (9b9d732)
-
standards: Merge readable copy cleanup (07c6e4c)
-
standards: Rewrite readable copy without em dashes (bd57e60)
-
standards: Merge English positioning and public copy (b3bc498)
-
standards: Align English positioning and public copy (56b22dd)
-
SYN-109: Publish searchable canonical documentation (6072d46)
-
SYN-325: Align evidence copy with Febasi conventions (f712bea)
-
SYN-236: Reconcile backlog IDs and execution priority (195f1aa)
-
SYN-322: Define knowledge framework and trust contracts (444dd93)
-
research: 2026 market positioning and Febasi substrate brief (3ceddd0)
-
readme: Correct npm install wording (no runtime download) (15911b3)
-
readme: Restructure the proof, scorecard hero and 2-up grid (1c16a3f)
-
readme: Surface verify invariants, mcp tools, npm, day-to-day (56c4139)
-
changelog: Release v1.4.0 (f4608d8)
🤖 CI
- SYN-338: Unify required native contract validation (c32d85c)
🧹 Chores
-
SYN-340: Integrate verified dependency pruning (14673ae)
-
SYN-339: Prune unused dependency declarations (4f51d6a)
-
SYN-340: Prepare reviewed 1.5.0 release inputs (98cf748)
[1.4.0]: 2026-07-13
♻️ Refactoring
- SYN-103: Move npm publishing to the public mirror (option B) (b255007)
✨ Features
-
SYN-103: Npm/npx wrapper via per-platform packages (#83) (d793acd)
-
SYN-102: Homebrew tap formula + release automation (#82) (bf5e9c3)
-
SYN-108: Sign release artifacts with keyless cosign (#88) (a88c949)
📝 Documentation
-
SYN-103: Point npm package at the public mirror + humanise copy (720bfa8)
-
changelog: Release v1.3.0 (6b88949)
🧹 Chores
-
release: V1.4.0 (0b1c5dd)
-
SYN-102: Drop the Homebrew distribution path (9b56772)
-
SYN-103: Publish the npm wrapper under the @febasi org scope (90309fb)
[1.3.0]: 2026-07-07
✨ Features
-
bench: Phase-B VACUUM + bitemporal grader assertion (#281,#286) (a65caf0)
-
SYN-300: Thread –workspace into syn mcp install (#287) (15d3e7f)
🐛 Bug Fixes
- SYN-315: Make update_status corrections bitemporal (#289) (c88ec28)
📝 Documentation
🧹 Chores
- release: V1.3.0 (f4a187b)
[1.2.0]: 2026-07-06
♻️ Refactoring
-
SYN-304: Keep search and mcp modules under ADR-015 budget (e221264)
-
SYN-301: Split embed/hybrid under ADR-015 (0 red) (6208ed2)
✅ Testing
-
SYN-310: Declare new MCP surface in the parity manifest (f25b023)
-
SYN-298: OutputSchema strict-client conformance + bench README (8e95525)
✨ Features
-
SYN-306: Composite parallel search + compatibility score (fe1457c)
-
SYN-309: Actionable MCP errors + read-time _meta hints (ca02543)
-
SYN-312: Five governed-workflow MCP prompts (e7d9154)
-
SYN-311: Self-describing vocabulary via syn context + resource (ba8ce57)
-
SYN-310: Workspace_health MCP tool (agent-facing digest) (c1e6665)
-
SYN-304: Fix agent-asset drift + no-drift test; surface verify (9122132)
-
SYN-304: Per-hit relevance signal on hybrid search (fa3a128)
-
SYN-304: Add MCP tool annotations and fix doc currency (838b48b)
-
SYN-303: Governed capture from agent-supplied context (MVP) (8b01b91)
-
SYN-254: Normalise identity match (case/accent/whitespace) (e4597bb)
-
SYN-301: Passage-level chunked embeddings + opt-in reranker (d802cb1)
-
SYN-301: Deterministic structure-aware markdown chunker (452064f)
-
brand: Visual system + 6 benchmark/brand cards for the READMEs (7e902b7)
-
SYN-298: Qa –distractors: the noise-scale axis for scenario D (2ae60dc)
-
SYN-300: Syn mcp –workspace: serve a brain from any directory (31d234b)
-
SYN-298: Bitemporal-probe: SYN-223 –as-of correctness, isolated (ed3f133)
-
SYN-298: Scenario D: Q&A three-leg attribution + LLM judge (6bf349e)
-
SYN-298: Correction + abstention scenarios; capture agent answer (59fc894)
-
SYN-298: Gate-probe: SYN-242 contradiction gate in isolation (55bd6d5)
-
SYN-298: Isolate agent to MCP, retry zero-tool turns, Wilson CI (68833e6)
-
SYN-298: Headless Claude Code driver: pass@1 + pass^k + cost (8204468)
-
SYN-298: Synapse-bench loop: deterministic state grader (fbeaaa4)
-
SYN-296: Persona-bench: recall-at-scale on persona corpora (055e4f4)
-
SYN-296: Add 6 persona fixtures for realistic bench scenarios (a7e63e1)
-
SYN-292: Syn undo Phase 2: reverse link + update_status (5e2a0c4)
-
SYN-293: Real-backend RAM/quality charts + clean-models command (d05b4d5)
-
SYN-293: Add breaking-point bands to the latency scaling chart (034eb81)
-
SYN-293: Benchmark v2 phase A: latency & index-size curves vs N (2a114f6)
-
SYN-106: Freeze the v1.0 MCP tool surface (+ BR-104/105 policy) (918482c)
-
SYN-290: Reproducible benchmark stamps + deterministic SVG charts (bbb1c54)
-
SYN-289: Synapse-check: GitHub Action running syn verify in CI (469577e)
-
SYN-288: Syn mcp install: zero-friction MCP registration (b0fe276)
-
SYN-287: Syn undo: first-class reversal of an accepted create (9b9437b)
🐛 Bug Fixes
-
SYN-271: Reindex –relink + exclude all enrichment edges from dump (97269d1)
-
SYN-301: Reranker scores the snippet, not the full body (7a97698)
-
SYN-298: Retry transient claude -p crashes too, not only zero-tool (aed8fd1)
-
SYN-297: Emit MCP outputSchema strict clients accept (5c686e0)
-
SYN-293: Index hard negatives (kind: reference, not note) (d99c735)
💄 Style
- synapse-bench: Cargo fmt corpus.rs (a07e352)
📝 Documentation
-
adr: Accept ADR-044 with the persona-bench k-sweep result (82afdfc)
-
adr: Add ADR-044 (composite search) and ADR-045 (model policy) (6521080)
-
SYN-303: Add ADR-043 for governed agent-supplied capture (85f62f9)
-
brand: Add retrieval-quality chart + LTR research note (8f2d021)
-
SYN-301: Accept ADR-042, record measured recall@1 0.75 to 0.85 (3f56f97)
-
SYN-301: Mark plan 023 in progress (chunker landed) (8ffcbeb)
-
SYN-301: Add ADR-042 and plan 023 for retrieval quality (a2ffaee)
-
brand: New wordmark subtitle governed memory for AI agents (70698d6)
-
readme: Logo hero, tables, new write-flow banner, benchmark guide (71c468e)
-
readme: Professional rewrite with brand visuals (e2d1d09)
-
SYN-298: Record noise-scale sweep + bitemporal-probe results (d881819)
-
SYN-298: Scenario-B finding: update_status is not bitemporal (361b1f9)
-
readme: Add two brand-styled mermaid diagrams (0d089cf)
-
SYN-298: Scenario D result: e2e 100% beats recall@10 80% (b0b39a6)
-
readme: Slim to brand + key benchmarks; add BENCHMARKS.md (3c67cb9)
-
SYN-298: Consolidate round-2 results (B, D-abstention, gate-probe) (1350dd6)
-
SYN-298: First agent-in-the-loop findings + scenario fixtures (61a48a2)
-
ADR-041: Agent-in-the-loop benchmark driver decision (07f3382)
-
SYN-298: Agent-in-the-loop benchmark research + methodology (f761c79)
-
SYN-296: Persona recall curve + finding (no degradation at scale) (0adddb3)
-
SYN-293: Refresh real charts with adversarial hard negatives (fe306ad)
-
SYN-294: Correct the FTS5 external-content claim (it’s standard) (66f3e70)
-
research: Benchmark redesign: scaling, resources, correctness (87c78be)
-
SYN-215: Set Lil Syn as primary mascot, constellation secondary (c5a9699)
-
SYN-291: Currency pass to v1.0/v1.1 + benchmark embed + MIGRATING (5d9ae36)
-
repo: Refresh CLAUDE.md current-state to v1.0.0 + v1.1.0 plan (7c15b6f)
-
changelog: Release v1.0.0 (e016b5f)
🧹 Chores
-
release: V1.2.0 (eac07dc)
-
SYN-102: Add Homebrew formula (pending tap repo publication) (9c506f8)
-
repo: De-reference removed spike from .gitignore comments (785a951)
-
repo: Remove dead SYN-89 embedding-backend spike (0b4ba23)
[1.0.0]: 2026-06-21
♻️ Refactoring
-
core: Split pending/verify test modules (ADR-015) (e33fb4e)
-
SYN-281: Group verify flags + one built-in link extractor (d89c407)
⚡ Performance
-
SYN-277: Score syn stale from the indexed frontmatter (82ab35e)
-
SYN-275: Bulk-load body/tags to drop the embed_all n+1 (d33b4ce)
✅ Testing
-
SYN-280: Harden migration paths + document expand-only policy (4cda405)
-
SYN-279: Harden apply (disjoint supersede, link FK, multi-EAV) (e2ff923)
-
SYN-278: Lock custom ref-link reindex round-trip + fix comment (4048abb)
-
SYN-268: Fuzz the untrusted input boundary (proptest-first) (e1c5c8f)
-
SYN-267: Stateful supersession + temporal proptest (9bea0ae)
-
SYN-265: Invariant proptest suite for index coherence (0862cf1)
-
SYN-264: Differential CLI-MCP port-parity suite (ab3ef77)
✨ Features
-
SYN-270: Offline witness checkpoint + verify –since consistency (e4c1eab)
-
SYN-269: Syn verify –rebuild attestation with localized diff (3838a09)
-
SYN-266: Deterministic replay engine + clock seam + verify –deep (a533587)
-
SYN-263: Syn verify + signed report on both doors (58e7bbe)
-
SYN-262: Canonical logical index dump + verify report schema (3856ccd)
🐛 Bug Fixes
-
SYN-274: Reject path-traversal in types.toml store_dir (17bd617)
-
bench: Repair API drift so synapse-bench compiles and runs (3b9f517)
📝 Documentation
-
SYN-282: Add provable-non-corruption positioning whitepaper (67ed91b)
-
SYN-276: Record the ort rc posture for v1.0 (ADR-038) (aeb155b)
-
SYN-273: Fix stale tool count (24) and adr count (37) (1ed4218)
-
SYN-272: Lead the README with syn verify and the four pillars (cc9995d)
-
handoff: V1.0 Hardening self-contained next-session prompt (909a64b)
-
roadmap: Add v1.0 Hardening phase (syn verify, SYN-262..270) (6bbf486)
-
research: Provable non-corruption study for syn verify (v1.0) (5a3b2af)
-
changelog: Release v0.11.0 (c543962)
🧹 Chores
- release: V1.0.0 (26d21d0)
[0.11.0]: 2026-06-17
⚡ Performance
- SYN-251: Skip re-embed+NER when embeddable content is unchanged (cbb7dd3)
✅ Testing
- SYN-258: Temporal-correctness invariant for at + as-of (d0179d6)
✨ Features
-
SYN-249C: Doctor reports HEAD commit signature posture (ADR-034) (cf5d0d0)
-
SYN-248: Per-type profile-vs-log decay policy (ADR-033) (ebb2997)
-
SYN-255: Opt-in –near anchor-distance reranker (e1e8bcd)
-
SYN-245: Explainable mutual-kNN semantic edges in connections (aecf733)
-
SYN-254: Identity-field duplicate detection routed to the gate (7de6daa)
-
SYN-259: Valid-time pushdown to FTS candidates + search –type (7b3d684)
🐛 Bug Fixes
-
SYN-248: Use is_none_or for the decay-policy lookup (clippy) (55c2409)
-
SYN-260: Enforce no-unwrap lint + repair inert pre-commit fmt gate (271d9d8)
📝 Documentation
-
roadmap: Mark v0.11 active issues complete in project state (ffdab59)
-
roadmap: Reshape v0.11 issues per 4-subagent direction study (bd96c8e)
-
changelog: Release v0.10.0 (e1aaf5c)
🧹 Chores
- release: V0.11.0 (df7a8a8)
[0.10.0]: 2026-06-15
✨ Features
-
SYN-242: Propose-time contradiction detection forces the gate (4b6fbc1)
-
SYN-252: Proposal-quality contract prompt + docs (0198e81)
-
SYN-250: Trust-weighted hybrid ranking, shared decay curve (48008ed)
-
SYN-257: Kind-aware set_field: custom update_status + F1 close (a173ec2)
-
SYN-256: Syn changes CLI + blame/diff MCP tools (both doors) (3cfa28d)
📝 Documentation
-
mcp: Fix local-read tool count comment (9 -> 11) (90b829d)
-
roadmap: Mark v0.10 complete in project state (4ba1992)
-
roadmap: Refocus v0.10 on memory-quality, defer rest to v0.11 (1bc1ef6)
-
mcp: Correct tool count to 21 (search_everything + query) (89606d6)
-
changelog: Release v0.9.0 (f601a7d)
🧹 Chores
- release: V0.10.0 (c4b85d5)
[0.9.0]: 2026-06-14
♻️ Refactoring
-
SYN-227: Split reindex tests into reindex/progress_tests.rs (3bf3848)
-
SYN-234: Split custom-type reindex into reindex/custom.rs (d3244f5)
-
audit: Split chain + checkpoint into submodule (ADR-015) (aa57f48)
⚡ Performance
- SYN-227: Fix super-linear reindex (8s -> 0.4s at 10k) (1a1d1df)
✅ Testing
- SYN-227: Synthetic-corpus perf budget for reindex + search (ee998e2)
✨ Features
-
SYN-223: Supersession write rules + belief-time axis (ADR-031) (446fe18)
-
SYN-223: Add as-of valid-time filter across search, list, and at (b8a603d)
-
SYN-223: Valid-time schema + reindex population (ADR-027) (0152d39)
-
SYN-240: Mcp recent_changes per-field deltas (99f9168)
-
SYN-228: Syn import: Markdown vault to governed proposals (82745d8)
-
SYN-234: Materialize graph edges from custom ref fields (5ca373c)
-
SYN-226: Search_everything MCP tool (RRF over all kinds) (9173a5f)
-
SYN-234: Governed MCP create of custom-typed entities (fcb6986)
-
SYN-234: Query filter by custom indexed field (EAV payoff) (2e1d926)
-
SYN-234: Syn ls/show/rm –type <kind> for custom entities (5bbcbcd)
-
SYN-234: Syn new –type <kind> creates custom-typed entities (40ccb54)
-
SYN-234: Reindex registered custom types into entities + EAV (3cea002)
-
SYN-234: Add registry resolve gate and validate_doc to core::types (8ffa099)
-
SYN-234: Add entity_fields EAV table for open-type indexed fields (e27f05e)
-
SYN-228: Syn export –as obsidian (markdown vault + wikilinks) (2cdaf3c)
-
SYN-234: Types.toml validation registry + syn types (4612bce)
-
SYN-247: Syn provenance: portable signed provenance receipts (6ac53ab)
-
SYN-244: Syn connections: ego graph as JGF (SYN-243 contract) (995315a)
-
SYN-224: Syn stale: advisory list of decayed AI facts (c6ab60d)
-
SYN-241: Deterministic per-fact trust score (ADR-029) (35302ab)
-
SYN-240: Syn diff: per-field before/after over git (bf8d637)
-
SYN-240: Syn blame: AI provenance lineage of an entity (6980735)
-
SYN-249: Per-author audit segments (merge-safe writes) (b79ae36)
-
SYN-249: Attribute audit entries to the deciding identity (ebe95ff)
-
SYN-239: Wire chain + checkpoint into syn audit verify (279c6af)
-
SYN-239: Ed25519-signed audit checkpoint (truncation defense) (fbbb824)
-
SYN-239: Hash-chain the audit trail + chain verification (820bf1f)
🐛 Bug Fixes
-
docs: Close intra-doc link and html tags for cargo doc (c1929b2)
-
SYN-234: Custom types can anchor project_graph (9f56631)
-
SYN-234: Cover custom types in stale, doctor and HTML export (a19d4d5)
-
SYN-234: Kill the EntityType::Note fallback in row_to_entity (395a442)
-
SYN-234: Allow custom-typed entities as link endpoints (3abf8d3)
-
SYN-234: Reach custom types from MCP reads (contract phase) (bd35deb)
-
store: Make entity removal atomic with the index DELETE (a462df1)
-
hooks: Word-boundary pt matching to avoid English collisions (e665f60)
📝 Documentation
-
SYN-223: Mark v0.9 complete in project state (eb424cf)
-
handoff: Self-contained next-session prompt + v0.9.0/v0.10 state (2e282e6)
-
roadmap: V0.9.0 release cut + v0.10 phase + research positioning (7bd066e)
-
research: Comparative agent-memory field study + roadmap input (f123bd5)
-
handoff: Record (1)(2)(3) completion + SYN-227 reindex finding (89b0c85)
-
handoff: Record consolidation pass + remaining contract gaps (c5d6824)
-
roadmap: Apply the v0.9 cut list and define minimum v1.0 (c9792c2)
-
brand: Reposition the headline from the gate to the proof (e57398c)
-
commands: Document the v0.9 surface and add doc-gates (73ee1fa)
-
handoff: Record SYN-234 (full) + SYN-226; map remaining blockers (f1e3f19)
-
SYN-234: Resolve ADR-028 EAV-vs-generated-columns spike (e79d01b)
-
handoff: V0.9 session handoff: state + remaining order (7bffaa7)
-
claude: Record v0.9 progress (10 work-items shipped) (b63484b)
-
claude: Record v0.9 progress (8 shipped; schema chain deferred) (05e2577)
-
claude: Record v0.9 progress (SYN-239 done, SYN-249 wip) (f649e67)
-
adr: Accept ADR-026..030 (v0.9 governance foundation) (10f4d00)
-
adr: Revise ADR-026 two-tier + add ADR-030 git-native signed audit (0c719ef)
-
adr: Add ADR-026..029 for v0.9 governance foundation (53c45f1)
-
roadmap: Re-anchor v0.9 to temporal governance & adoption (35ebf00)
-
changelog: Release v0.8.0 (9590bb5)
🧹 Chores
- release: V0.9.0 (b83f611)
[0.8.0]: 2026-06-11
♻️ Refactoring
- core: Split pending tests into submodule (ADR-015) (c1e9633)
✨ Features
-
SYN-222: Syn digest: weekly activity + attention prompt (9c56ff9)
-
SYN-221: Frictionless syn capture one-liner (inline text) (f081456)
-
SYN-220: Ambient git capture via syn capture –from-git (ADR-025) (0dcb04c)
-
SYN-185: Batch approval over syn pending (ADR-024) (7ec12fc)
📝 Documentation
-
release: Align README and CLAUDE for v0.8.0 (6196d76)
-
SYN-229: Reposition brand around governed memory (9bd4ffd)
-
release: Align README/MCP/SKILL/CLAUDE with v0.7.0 (a02c5e2)
-
changelog: Release v0.7.0 (98c576c)
🧹 Chores
[0.7.0]: 2026-06-08
♻️ Refactoring
-
mcp: Replay_proposal client-driven, no sampling (SYN-219) (bc9eea3)
-
core: Split pending apply paths into apply submodule (ADR-015) (fd8c8af)
✨ Features
-
SYN-183: Read-only query tool, structured aggregation (ADR-023) (51161d3)
-
SYN-218: Confidence-tiered write policy (ADR-022) (7310ac2)
-
SYN-217: Incremental reindex via dirty-tracking ledger (facab8a)
-
SYN-237: Onboarding fp32/int8 embedding choice + dense int8 BGE-M3 (4bc9496)
-
core: Atomic propose_batch + crash-safe accept (SYN-202, SYN-216) (5ac4e57)
🐛 Bug Fixes
- models: Reconcile model-size figures to measured truth (SYN-214) (6bed78c)
📝 Documentation
-
roadmap: Restructure v0.7-v0.9 + commercial from strategic audit (d2da947)
-
adr: Accept ADR-022: confidence-tiered write policy (BR-12.3) (e02c2f8)
-
roadmap: Reprioritise v0.7; defer async + Intel macOS to v2.0 (f901d63)
-
changelog: Release v0.6.0 (9786f2c)
🤖 CI
- workflows: Disable push-on-main trigger (PR + manual only) (84f506a)
🧹 Chores
[0.6.0]: 2026-06-07
✨ Features
-
SYN-200: Branded model-download experience (7f622df)
-
SYN-197: Animated mascot reveal on onboarding (ac906c3)
-
SYN-199: Charming empty + healthy states (157f0b1)
-
SYN-198: Brand banner on bare syn and –version (370824f)
-
SYN-196: Brand module: graph-constellation mascot + wordmark (d5e9d19)
-
SYN-201: OutputSchema on MCP read tools (5e4275e)
-
SYN-184: Syn plugin install: scaffold the Claude Code plugin (e0b350b)
-
SYN-175: Syn skill install / syn agents init: emit agent assets (2f2065f)
-
SYN-182: Propose_batch: one accept for many proposals (a9bf156)
-
SYN-181: Get_entities batch read with field projection (5b7cc2c)
-
SYN-180: Recent_changes delta mcp tool over the audit trail (75b765c)
-
SYN-179: Workspace_overview digest mcp tool (0a8ba20)
-
SYN-178: Resource_link on search hits and get_entity (b574b3b)
-
SYN-177: StructuredContent + compact json on all mcp reads (5ebd869)
-
SYN-192: Branded progress animation + federation spinner (e10e731)
-
SYN-191: Tty progress for embed –all, reindex and watch (edad154)
-
SYN-193: Unicode graph tree, show panels, clean search snippets (52d7515)
-
SYN-190: Unicode tables for list/info/doctor on a rich TTY (a775fa0)
-
SYN-171: Capture self-identity globally and seed self person (7b3ea91)
-
SYN-188: Writes emit structured JSON under –format json (292c4c9)
-
SYN-188: Machine-readable JSON errors with branchable kind (8658dea)
-
SYN-176: Bound list_by_type MCP read (limit + cursor) (7f47f66)
-
SYN-186: Styled output + –plain/–no-input/–quiet (ADR-019) (cde313f)
🐛 Bug Fixes
-
mcp: Refresh tool descriptions + build router once (eea23bd)
-
SYN-213: Rich rendering for federated_search hits (f44fe7e)
-
SYN-211: Don’t clobber a same-slug non-self person on seed (f4cb7fe)
-
SYN-210: Make the “no self person” error actionable (2872522)
-
SYN-209: Validate a real calendar birthdate (95b8497)
-
SYN-207: Validate per-leaf payload in propose_batch (d4dd7fd)
-
SYN-205: Consume birthdate in cv; correct the schema doc comment (8b02e7c)
-
SYN-206: Materialise self->org graph edge from affiliations (8d7e920)
-
SYN-204: Honour –format json on list/show/tag/pending/ws/at-show (2dd0fb1)
-
SYN-203: Uniform {result, freshness?} envelope on every read (0139f59)
📝 Documentation
-
release: Bring readme + docs up to date for v0.6 (b18f914)
-
SYN-202: Register atomic propose_batch follow-up in v0.7 roadmap (29b1004)
-
SYN-196: Register brand & delight cluster in roadmap (e377bde)
-
adr: Adopt anstream/anstyle terminal styling (ADR-019); v0.7 split (141efef)
-
roadmap: Rendering opt-out contract (SYN-195) (4619cb1)
-
roadmap: Plan premium cli output (SYN-192..193) (d474b09)
-
roadmap: Plan cli ux & agent-friendliness (SYN-186..191) (5c26d48)
-
roadmap: Number syn agent as SYN-185 (carry-over) (0010cd7)
-
roadmap: Plan mcp agent-ergonomics tickets (SYN-176..184) (ea6df57)
-
mcp: Correct tool count + federated/get_entity shapes vs code (69e1152)
-
roadmap: Plan syn skill install for v0.6 (SYN-175) (56788a2)
-
readme: V0.5.0 released; Next is v0.6 (f162e87)
-
changelog: Release v0.5.0 (b2ba0ad)
🧹 Chores
- release: V0.6.0 (de901fc)
[0.5.0]: 2026-06-05
♻️ Refactoring
-
SYN-153: Split cli/audit.rs (611 lines) into cli/audit/* (e4ba676)
-
SYN-152: Split cli/at.rs (620 lines) into cli/at/* (3f087e7)
-
SYN-151: Mcp/server.rs to dispatcher pattern (cf5d1b1)
✨ Features
-
SYN-168: Opt-out semantic layer + consent-gated model download (8435ca0)
-
SYN-93b: Workspace freshness Phase 2: network ls-refs via gix (3c932e7)
-
SYN-165: Opt-in OpenAI + Voyage network embedding backends (ce188ad)
-
SYN-166: Syn search –grade captures relevance labels (a9ee65c)
-
SYN-88: Mirror search modes in the MCP search_knowledge tool (0cf3254)
-
SYN-162: Synapse-bench harness + relink_all + ship-gate report (61426bc)
-
SYN-86: Hybrid RRF search + –semantic/–no-vector modes (SYN-87) (95e0eb2)
-
SYN-161: Heuristic linker + ner-span resolver (1bcf29b)
-
SYN-160: Insertion-time enrichment pipeline run_on_write (BR-13) (9321e40)
-
SYN-85: Syn embed –all|<slug>|–check|–reset-cache (bedeac6)
-
SYN-164: Gliner ner backend via kreuzberg-gliner-rs (rc.12 fork) (e369e68)
-
SYN-83: Fastembed-rs production embedding backend (BGE-M3 default) (8d5ade5)
-
config: Enrichment block + model cache dir resolver (0412cca)
-
SYN-84: Deterministic embedding + ner stubs (test_backend feature) (8494dfe)
-
SYN-82: Embedding + ner backend traits (semantic contracts) (596db14)
-
SYN-81: Wire sqlite-vec via auto_extension; entities_vec table (7fbd3b5)
-
SYN-80: Vector cargo feature + migrations_vec/ scaffolding (557b92b)
🐛 Bug Fixes
-
SYN-168: Align clippy.toml msrv with Cargo.toml (1.89) (97b7552)
-
SYN-168: De-risk v0.5 release: native linux builds + MSRV 1.89 (f7af50e)
-
SYN-86: Sanitise hybrid FTS query to OR-of-quoted-terms (05ddb08)
-
core: Unbroken rustdoc links in embeddings::factory (ffd26be)
💄 Style
-
SYN-168: Rustfmt readiness.rs (one-line is_some_and) (7c23a1c)
-
index: Cargo fmt after SYN-80 split (36bf8d9)
-
mcp: Cargo fmt fixups after SYN-151 (ff127b9)
📝 Documentation
-
install: Mark Intel macOS unsupported + track SYN-174 (f37fd48)
-
changelog: Release v0.5.0 (151aa15)
-
onboarding: Add ONBOARDING.md as the linear first-run guide (bed5445)
-
roadmap: Plan v0.6 mandatory setup + identity onboarding (0989df5)
-
changelog: Release v0.5.0 (8a500ee)
-
SYN-168: Onboarding-first first-run + tutorial deep-dive (f1632e9)
-
changelog: Release v0.5.0 (2b5a60d)
-
SYN-168: Add MODELS.md: the semantic models reference (953aa58)
-
SYN-168: Fix stale MCP tool count + round out the TUTORIAL (9ee6c82)
-
readme: Restructure §Status as a table + per-version bullets (dd20ef1)
-
semantic: Document the v0.5 semantic layer across user-facing docs (7c27552)
-
README: Mark v0.5 feature-complete (f2c9489)
-
README: V0.5 core complete: bench harness landed (4ff3731)
-
SYN-167: Editor-integration section promoting syn lsp (717dfcc)
-
README: Syn-164 landed via kreuzberg-gliner-rs fork (6fdd900)
-
README: Refresh §Status for v0.5 semantic layer in progress (46537ab)
-
SYN-152: Drop intra-doc links into private submodules (03887dd)
-
SYN-151: Drop intra-doc links into private items (0f23350)
-
adr: Stress-test ADR-008 against lancedb / surrealdb / vectorlite (05cc0dd)
-
SYN-89: Adr-008: embedding backend, vector storage, ner backend (640116e)
-
SYN-160: Close v0.5 brief; BR-13 codified; ROADMAP refreshed (c72700d)
-
researchs: Lock v0.5 brief decisions: bge-m3 + gliner core (c030706)
-
researchs: V0.4 closure read + v0.5 semantic layer brief (2ca6a37)
-
readme: Refresh status with v0.3 + v0.4 + lsp visibility (b37a418)
-
changelog: Release v0.4.0 (1777684)
🤖 CI
-
release: Drop x86_64-apple-darwin (no ort Intel-macOS prebuilt) (3449db5)
-
release: X86_64-apple-darwin on macos-15-intel (macos-13 retired) (f32d2c2)
-
SYN-168: Build macOS x86_64 natively on macos-13 (92ad0f1)
-
SYN-154: File-size lint enforcing ADR-015 red/yellow tiers (9f37a95)
🧹 Chores
- release: V0.5.0 (a39b4be)
[0.4.0]: 2026-05-27
✨ Features
-
SYN-142: Full as-of fts index reconstruction via in-memory sandbox (d6613a3)
-
SYN-140: Tag-level federation redaction via Redactable trait (8c5d0d1)
-
SYN-139: Mcp resources/list cursor pagination (c06ea5f)
-
SYN-143: Syn ws clone <git-url> (915e778)
-
SYN-146: Syn doctor –lint-provenance (4334abe)
-
SYN-137: X-ai-history chain on entity re-mutations (a59d834)
-
SYN-130: Syn lsp companion via tower-lsp-server (ADR-018) (0db9e16)
-
audit: Replay –execute + replay_proposal MCP tool (SYN-136) (ad12dee)
-
at: List, info, graph subcommands as of ref (SYN-141) (d176071)
-
mcp: Real Tasks progress + cancellation (SYN-138) (b59a318)
-
audit: Syn audit verify: re-hash payload vs sha256 (SYN-135) (6a4ae48)
-
cv: Syn cv export: deterministic Markdown CV (SYN-133) (909c4aa)
-
identity: Syn experience + skills auto-derive (SYN-132) (e13c2b3)
-
identity: Ed25519 signing, keygen, revocation (SYN-131) (ba3b103)
-
mcp: Split server.rs into focused submodules (SYN-150) (ef6a125)
💄 Style
- at: Trim trailing blank line (cargo fmt) (d8adf2c)
📝 Documentation
-
roadmap: Close v0.4 hygiene gaps before release (1110fdd)
-
SYN-140: Fix rustdoc intra-doc links (81dcb6d)
-
SYN-147: Adr-016, adr-017, identity user-facing doc (dce5bef)
-
researchs: V0.4 brief: refinement & extensibility (7a5346b)
-
changelog: Release v0.3.0 (76e3464)
🧹 Chores
- release: V0.4.0 (ff4c2eb)
[0.3.0]: 2026-05-25
✨ Features
-
identity: Me.synapse portable bundle prototype (SYN-125) (8da8165)
-
audit: Syn audit replay <id> (SYN-113, v0.3 scope) (2d6d3e4)
-
cli: Syn capture: zero-friction stdin → pending (SYN-124) (bd9ddda)
-
audit: Syn audit stale –over WINDOW cross-workspace (SYN-123) (f796a96)
-
cli: Syn at <ref> – search|show subset (SYN-122) (17129d9)
-
cli: Syn timeline <slug> walks entity history (SYN-121) (3a3acc4)
-
mcp: Mcp_tasks feature placeholder (SYN-118) (0656170)
-
mcp: Three named prompts (SYN-116) (8289f37)
-
mcp: Resources surface (SYN-115) (88f47d7)
-
cli: Syn audit show|list|prune (SYN-112) (3f136ec)
-
provenance: X-ai-* frontmatter stamps on accept (SYN-111) (ec4de9c)
-
federation: Capability disclosure server-card (SYN-117) (9d847f8)
-
pending: Context_snapshot column for replay (SYN-114) (6bebd42)
-
audit: Append-only audit trail (SYN-110) (8789e1e)
🐛 Bug Fixes
-
ci: Apply cargo fmt + enforce in pre-push hook (38810e4)
-
ci: Mirror trigger uses workflow_run.head_branch, not the api (3bac81d)
📝 Documentation
-
adr: Adr-015 file-size threshold + modular split policy (69d52bb)
-
adr: Adr-012/013/014 + audit and provenance user docs (244cc2f)
-
roadmap: V0.3 milestone opened, 18 issues filed (not 17) (3b01c74)
-
researchs: V0.3 brief locked + roadmap fase v0.3/v0.4 (f55a63c)
-
researchs: New folder + v0.3 multi-context brief (5ec2433)
-
changelog: Release v0.2.0 (f7090f9)
🧹 Chores
- release: V0.3.0 (138e4d0)
[0.2.0]: 2026-05-25
✨ Features
-
index: Last_modified_commit per entity (SYN-68) (66ec591)
-
cli: Completions + man pages (SYN-63 + SYN-64) (f466534)
-
cli: Watch + export + open + config + uninstall + rm guard (fcf0590)
-
mcp: Pending queue and 5 write proposal tools (SYN-60..62, SYN-92) (f58650c)
-
freshness: Workspace drift advisory (SYN-93 Phase 1) (8dab4e4)
-
mcp: Federation read tools (SYN-78 + SYN-92 read slice) (cf3fcfa)
-
core: Federation helper and –all reads (SYN-77 + SYN-79) (c1015d5)
-
cli: Syn ws registry and manifest curation (4f556d7)
-
registry: Workspace registry + SYNAPSE.md manifest (f2a35a8)
📝 Documentation
-
audit: Align every doc with the v0.2 surface that shipped (3fca048)
-
tutorial: Walk-through + recipes (SYN-72 + SYN-73) (92d764a)
-
adr: Adr-011 workspace freshness + br-12.5 (edcc39c)
-
adr: Extend adr-010 with workspace identity card (7abf0cb)
-
adr: Adr-010 workspace federation and v0.2 issues (6cdbc59)
-
brand: Horizontal wordmark hero + source-code disclaimer (7a76170)
-
brand: Full mascot gallery and per-doc placement (3b5e723)
-
brand: Mascot, slimmer README, docs/ split (b303990)
-
changelog: Release v0.1.0 (4c1c54f)
🤖 CI
- mirror: Publish private releases to synapse-releases (public) (0d9c047)
🧹 Chores
[0.1.0]: 2026-05-24
♻️ Refactoring
- core: Fluent args, BSD exit codes and output helpers (ef36fca)
✅ Testing
-
workspace: Sqlite-failure rollback restores filesystem (SYN-53) (0e7892d)
-
slug: Five proptest invariants (SYN-52) (68f9ee6)
-
cli: End-to-end fixtures and insta snapshots (SYN-50/51) (7e00fd3)
✨ Features
-
info: Stable JSON output and totals (SYN-29) (ff6bc68)
-
i18n: Fluent loader plus pt-BR and en coverage (c25182c)
-
cli: Tasks with sequential IDs and done marker (SYN-21) (3cd7f75)
-
mcp: Stdio server with four read tools (SYN-39/40/41/42/43/44) (394a8f3)
-
config: Cascade config, setup and init polish (SYN-30/31/32) (cf33c3d)
-
cli: Tag ops and graph queries (SYN-23/26/27) (a15a7c2)
-
cli: Note type, FTS5 search and workspace doctor (SYN-22/24/25/28) (44d61dc)
-
cli: Wire AppDeps, dispatch trait and CRUD (SYN-1/4/11/15..20) (d87da7c)
-
store: Atomic tx and index DDL (SYN-3/6/7/8/9/10) (422a7f4)
-
SYN-5: Slug primitives, EntityType enum and typed Frontmatter (bb603ec)
🐛 Bug Fixes
-
release: Commit_preprocessors must be a sequence, not a map (7e50089)
-
deny: Patch RUSTSEC-2026-0009 (time) and ignore 0140 (build-only) (3f1eab1)
-
docs: Use absolute intra-doc links for Commands and find (62eca10)
📝 Documentation
-
api: Re-enable warn(missing_docs) on the crate (SYN-49) (9bf0f37)
-
readme: Tighten identity, add SAYS backronym as in-joke (f4067cc)
-
repo: Move design notes to docs/design and drop issues.py (32a712a)
-
business: Add normative BUSINESS_RULES.md (BR-1..BR-12) (b7998a1)
-
roadmap: Document ID gap policy and add issues generator (19aad29)
-
roadmap: Add detailed ROADMAP covering v0.1 to v1.0 (4960a17)
-
adr: Record MSRV policy and release pipeline (006, 007) (3522c9d)
📦 Build
- release: Wire git-cliff, release workflow and seed CHANGELOG (3986ce8)
🤖 CI
- workflow: Paths filter, cancel-in-progress, slimmer matrix (2f6daf3)
🧹 Chores
-
cli: Remove leftover .tmp files from doc-script (599b265)
-
deps: Bump MSRV to 1.88 (ADR-009 supersedes ADR-006) (d5e224e)
-
SYN-0: Bootstrap synapse repository scaffold (2cc63a7)
Source of truth for the Synapse identity. If you are editing the README, docs, or any external surface, pull from here.
Mascot: Lil Syn
A round indigo character with cream eyes, designed by Claude Design. Lil Syn is the personality of the brand; the wordmark is the identity. They show up together in headers, never overlap.
Mascot resolution (SYN-215)
The brand has two marks; this is the settled relationship between them (decision 2026-06-21):
- Lil Syn is the primary, warm mascot: the character that fronts the README, docs headers, install/celebration moments, and anything human-facing. It carries the personality.
- The graph constellation is the secondary, technical mark: used where an abstract, system-level glyph fits better than a character: the favicon, social/OG card, CI badges, and diagram motifs. It is not a rival mascot; it is the technical face of the same identity.
They never compete in the same slot: human-facing → Lil Syn; technical/glyph slot → constellation. When in doubt, use Lil Syn.
The full cast
| Variant | Vibe | Use it when… |
|---|---|---|
| classic | dancing, arms up | Generic header, default avatar, neutral context |
| stack | three Lil Syns connected by amber sparks | The hero. Whenever you want to show the metaphor: README top, ARCHITECTURE, “two doors, one core” |
| firing | arms high, gold particles bursting | Celebration. Install completed, release notes, milestone shipped |
| thinking | one arm raised toward a tiny sun | Reasoning, AI work, MCP docs |
| hearts | heart-shaped eyes, floating pink hearts | Affection. Footers, “built with care”, thank-you notes |
| sleepy | eyes closed, soft ZzZ | Idle states. Empty workspaces, (no entities), watcher resting |
| confused | arms wide, orange ? floating | Errors, troubleshooting, syn doctor warnings |
Files
assets/branding/
├── BRAND.md
├── lil-syn-classic.svg / .png (@2x)
├── lil-syn-stack.svg / .png (@2x)
├── lil-syn-firing.svg / .png (@2x)
├── lil-syn-thinking.svg / .png (@2x)
├── lil-syn-hearts.svg / .png (@2x)
├── lil-syn-sleepy.svg / .png (@2x)
└── lil-syn-confused.svg / .png (@2x)
All SVGs are flat (no filters), single-fill-per-shape, optimised to roughly 4–5 KB. PNGs are 2048×2048 for clients that don’t render SVG inline (GitHub markdown does: prefer SVG).
Colour palette
The primary three carry the whole brand. The extended set appears only in the variants that need it: never invented elsewhere.
Primary
| Token | Hex | Where |
|---|---|---|
| Indigo | #5B3EE0 | Brand colour. Lil Syn body. Headings, links, badges. |
| Cream / Paper | #F6F4EF | Light backgrounds. Lil Syn eyes. |
| Mustard | #FFB454 | Accent. Lil Syn’s question mark. Warnings, callouts. |
Extended (variant-specific)
| Token | Hex | Where |
|---|---|---|
| Gold | #FFD23F | Particles in the firing variant. Celebration accent. |
| Pink | #FF6B9D | Hearts in the hearts variant. Reserved for affection. |
Greys derive from cream: #1A1A1A near-black for body text, #E7E2D6
for hairlines, #888880 for muted captions.
Typography
- Display / wordmark: Sora: geometric sans, for
Synapse, h1/h2 in marketing surfaces. - Body: system stack (
-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif) for docs/READMEs that render on GitHub. - Code / terminals: JetBrains Mono for CLI screenshots and marketing inline code.
Voice
The product is the connection between your projects. The mascot is the smaller, curious side: it cheers when things work, looks puzzled when something is missing, dozes when nothing’s happening.
Voice should match the variant on screen:
- Indigo, factual, unfancy as the default.
- A flash of cheer next to firing for a shipped milestone. Name a version or count only when verified for that release.
- A hint of warmth next to hearts (“built with care”).
- Direct apology next to confused (“error: entity ‘foo’ not
found: try
syn project list”). - Never corporate. Never marketing-speak.
- Lead with governed operational memory in the README hero, installation and MCP onboarding. Explain proposals, review, supported reversal and explicit verification scope. The neuron metaphor explains continuity; concrete evidence explains value. Write policies can permit safe application, so do not claim that an agent never writes or that every change always receives human review (ADR-022).
Canonical product statement
Operational memory for agents, with governed changes, preserved history, and explicit integrity checks.
Use this statement in README, public docs and integration material. Short captions can name a specific task, such as “Carry reviewed knowledge into the next task”, but must not enlarge the assurance claim. Keep supporting copy in English and do not use em dashes.
Claims and product status
- Lead with continuity of work, reviewable changes and the evidence available to inspect.
- Name the selected verification scope. A pass over stored bytes does not prove that all claims are true or that all natural-language contradictions have been detected.
- Keep source-tree capabilities distinct from the last packaged release. A current source MCP count or new process contract is not automatically available in an older binary.
- Identify planned integrations near their diagram. A host boundary does not make every client, shared workflow or authoring-tool connector available.
- A signed receipt is evidence over exact bytes. Identity, current organizational permission and independently retained witnessing require their own selected inputs.
- Do not use absolute slogans such as “the memory your agents cannot silently corrupt” or an unqualified promise that every fact is provable. Keep historical wording in its original dated evidence when needed, rather than promoting it as current positioning.
Visual documentation
Use the product’s indigo, paper and amber palette with a restrained grid, generous spacing and readable labels. Lil Syn remains the human-facing identity; diagrams explain concrete responsibilities, data movement and trust boundaries rather than adding decorative networks.
The overview kit holds editable static SVGs for the governed lifecycle, core topology, tool integrations and recorded retrieval evidence. Pair every figure with searchable English text and meaningful alt text. Preserve raw benchmark data and its limits. Do not invent comparison baselines or imply a model assessment grants authority.
Where each variant currently lives
| Surface | Variant |
|---|---|
README.md: hero | stack |
README.md: footer | hearts |
docs/INSTALL.md | firing (you’re about to ship) |
docs/QUICKSTART.md | classic |
docs/MCP.md | thinking |
docs/ARCHITECTURE.md | stack (same metaphor, second appearance) |
docs/COMMANDS.md | classic |
docs/CONFIGURATION.md | classic |
docs/CONTRIBUTING.md | hearts (welcome the contributor) |
Inside syn doctor output (future) | confused for ERRORs, classic for clean |
Inside syn search empty result (future) | sleepy |
synapse-releases README | classic |
When in doubt, classic.
For compliance reviewers: Synapse never writes from MCP without a human-accepted proposal (BR-12.3). Every accept, reject and prune appends one JSON line to
.synapse/audit.ndjson(ADR-013) before the workspace mutation. Accepted entities also carry per-file provenance frontmatter (x-ai-*, ADR-012). The trail and the stamps are independent and you can read either one without running Synapse.
1. The two layers
Synapse’s audit story has two layers, each answering a different question:
| Layer | File | Question it answers |
|---|---|---|
| Audit trail | .synapse/audit.ndjson (+ audit-archive/<yyyy-mm>.ndjson after rotation) | Did this decision happen, and when? |
| Provenance frontmatter | x-ai-* fields on the entity’s markdown (PROVENANCE.md) | Is this file AI-authored, and from which decision? |
The trail is the regulator’s receipt. The frontmatter is the file’s self-description.
See also
PROVABILITY.md: the audit trail this document describes is one of the integrity classessyn verifyturns into a signed, reproducible proof. Where this page answers “can I read what happened?”, that one answers “can I prove the memory wasn’t silently corrupted?”: including the honest tamper-evident-not-tamper-proof ceiling.
2. The audit trail
Every decision the pending queue makes: accept, reject, prune, appends one JSON line:
{
"id": 7,
"kind": "capture_fact",
"decision": "accepted",
"proposed_at": "2026-05-25T15:00:00Z",
"decided_at": "2026-05-25T15:30:00Z",
"source": "mcp:capture_fact",
"reason": "user-supplied justification",
"payload_sha256": "sha256:f8e2c1…"
}
idmatchessyn pending showand survives the row’s deletion.decisionisaccepted | rejected | pruned.payload_sha256ties the trail entry to the original proposal payload. The payload itself lives on the queue row until the user prunes it: the hash forever links the two.
Read the trail
syn audit list # live tail (last 100, newest first)
syn audit list --all # include archive months
syn audit list --decision accepted # filter by decision
syn audit list --source mcp: # filter by source prefix
syn audit show 7 # one entry
syn audit list --format json | jq # the canonical interchange
For a human-friendly replay of one decision against the current workspace state:
syn audit replay 7 # surface payload + context snapshot
# + current entity state diff
In v0.3 syn audit replay does not re-run the original prompt
against a model: it surfaces enough to spot drift visually. The
v0.4 follow-up will optionally route through the configured MCP
client to re-run the prompt and diff the output.
Retention and pruning
Default retention is 90 days. After that, on the next append,
rotation moves stale entries into
.synapse/audit-archive/<yyyy-mm>.ndjson and rewrites the live tail.
The archive is never auto-deleted.
syn audit prune --before 2025-01 # remove archive months only
# live tail untouched
Tune retention in ~/.config/synapse/config.toml (or the workspace
config):
[audit]
retention_days = 90 # default; set to a smaller number to roll faster
What the trail never does
- It does not auto-delete anything.
syn audit pruneis the only path to physical removal, and it only touches the archive. - It does not drop unparseable lines on rotation, a corrupted line stays as a forensic artifact, not as a silent deletion.
- It does not lie. If the audit append fails after a successful
workspace mutation, the queue row stays
pendingso the reviewer can see “this needs to retry”; the trail is never written past what actually happened. See ADR-013 for the ordering rationale.
3. The propose / sanction loop
MCP write tool pending queue workspace
───────────────── ─────────────── ─────────
capture_fact ─► enqueue (id=N)
link_entity ◄── syn pending list
update_status ◄── syn pending show N
add_workspace_topic ◄── syn pending accept N
add_workspace_tag apply ────────────► Workspace::tx
audit-append (accepted, id=N)
mark accepted
This is BR-12.3 in one paragraph: MCP never writes directly.
Every write tool returns {pending_id, accept_command, reject_command, kind, summary} and the resulting entity lands on disk only when the
human runs syn pending accept.
4. EU AI Act Article 50 mapping
Article 50 enforcement begins 2026-08-02. Mapping the obligations to Synapse:
| Obligation | Synapse mechanism |
|---|---|
| Machine-readable provenance on AI-authored content | x-ai-* frontmatter (PROVENANCE.md, ADR-012) |
| Decision trace (what informed the action) | pending.context_snapshot JSON + payload_sha256 audit entry |
| Pre-action approval (calibrated autonomy) | The pending queue (BR-12.3) |
| Reproducibility | syn audit replay <id> against context_snapshot |
| Tamper-evident record | .synapse/audit.ndjson append-only + rotation |
The inline = false config flag is the escape hatch for users who
need clean entity files but still want the regulatory receipt, the
audit trail keeps the same metadata regardless.
5. References
- BUSINESS_RULES.md §12: BR-12.3 through BR-12.5, the normative rules this doc operationalises.
- ADR-012: frontmatter stamps.
- ADR-013: audit.ndjson format and rotation.
- ADR-014: portable identity bundle (separate but related: provenance + identity are the two human-owned signatures Synapse ships).
- MCP.md: the MCP write tools and the response shape every accept-command piggybacks on.
- PROVENANCE.md: companion document focused on the per-file frontmatter stamps.
Benchmarks
Synapse is benchmarked along two axes that matter to a real user: does it scale to my project and machine? and does the governed-memory story actually hold when a real AI agent drives it? The first is deterministic and offline; the second drives a live agent (Claude Code) over MCP and grades the resulting state, not the transcript.
- Methodology contract (scaling): ADR-040
- Agent-in-the-loop design + driver decision: ADR-041, plan 022, and the findings notes (methodology · first results).
- All numbers are owner-run, not CI (agent runs cost tokens + are
non-deterministic; ADR-041). The harness lives in
synapse-bench/.
1. Scaling: latency & index size vs corpus size
Will it handle your project, and where does it break? The honest answer is a curve over corpus size, not a single number.
The shape is the point: FTS-only (BM25) stays nearly flat: it tracks the result set, not the corpus: while vector-flat climbs with N (a brute-force cosine scan over every embedding, O(N·d); the line that eventually breaks), and hybrid (RRF) tracks both. The index grows linearly on disk:
(These two are the stub backend at large N, the latency/size mechanics are
real; the vectors aren’t semantic, so these charts are about scaling shape, not quality.)
2. Resources: will it fit on my machine?
With the real backend (BGE-M3 on-device), the answer is floor + slope: the
model is the ~1.8 GB floor, your data adds to it gently.
The binding constraint is the model, not your notes, a 16 GB machine fits far more
than a single user will ever write. Embedding runs at ~13 docs/s on this CPU (a one-time
ingest cost); reindex of the index structure stays in the tens of ms.
3. Retrieval correctness at scale
“Fast” is worthless if the right fact stops ranking. The honest correctness signal comes
from realistic data: 6 fictional professionals’ full quarters
(persona fixtures, ~460 entities, 320
cross-referenced notes) + a 120-question needle bank (persona-bench, SYN-296). We
fix one professional’s 20 facts and watch recall as the other five careers pile into the
same brain:
The raw observations contain six measured points. At 77 entities, 19 of 20 target facts appear in the first ten results; at the five larger measured sizes, all 20 do. First-result recall ranges from 75 to 90 percent and ends at 85 percent for 463 entities. This internal synthetic fixture does not establish quality, capacity or robustness for other workloads. The original run’s source commit is unknown.
The historical illustration in brand/images/retrieval-quality.png now derives from this
same canonical chart. Its previous hand-positioned curve did not match the recorded values.
No new benchmark or matched competitor comparison is claimed here.
recall@1 improved from 0.75 to 0.85 (SYN-301 / ADR-042). The measured weakness at the
top of the list was that an entity was embedded as a single whole-entity vector, so a
fact buried in a long note was averaged away. Embedding at passage granularity (a
deterministic Markdown chunker, one vector per chunk pooled back to its parent by best
distance) lifted full-haystack recall@1 from 0.75 to 0.85 with no recall@10 regression
(recall@10 is 0.95 at the first measured point and 1.0 at the other five). An opt-in cross-encoder
reranker ([enrichment] rerank, ADR-042 Track B) is also available; on this corpus
chunking already captured the recall@1 gain and the reranker was neutral at scale, so it
ships off by default: useful to try on longer-document workloads, not a headline win
here. Raw data: persona-recall ·
stub · real.
Reproduce (the model is cached & reused; clean-models reclaims the GBs):
cd synapse-bench
cargo run --release -- sweep --tiers 500,2000,8000 --reps 5 --out ../docs/assets/bench
cargo run --release --no-default-features --features real -- sweep --tiers 200,800,2000 --out /tmp/r
cargo run --release --no-default-features --features real -- persona-bench --out /tmp/p
cargo run --release -- clean-models
4. Agent-in-the-loop: does governed memory hold under a real agent?
The distinctive benchmark: drive a real agent (Claude Code, headless) through the full
governed-memory loop over MCP (insert / correct / contradict / retrieve) and grade
the resulting memory state via syn verify’s canonical dump (the τ-bench
get_data_hash() analog), not the transcript. The agent is isolated to the MCP tools
(a system prompt + --disallowedTools block file/shell/web access) so the benchmark
measures MCP-driven memory, not the agent reading project files. Because the CLI gives no
seed control, every task runs N times and reports pass@1 (capability) with a 95%
Wilson CI, pass^k (reliability = every run passes its assertions), and cost.
Run it (owner-run; needs claude + credentials; see the harness README):
cd synapse-bench
cargo run --release -- drive --task fixtures/tasks/insert-01.json --runs 15 --syn-bin "$(command -v syn)" --out /tmp/insert.json
cargo run --release -- gate-probe --seed seeds/aurora --payload fixtures/gate-probes/aurora-archived.json --out /tmp/gate.json
cargo run --release -- qa --needles fixtures/needles/ana-yamamoto.json --persona fixtures/personas/ana-yamamoto --limit 10 --judge-model claude-sonnet-4-6 --syn-bin "$(command -v syn)" --out /tmp/qa.json
Results (Claude Code 2.1.197; judge = Sonnet over an Opus agent)
| Scenario | measures | N | result |
|---|---|---|---|
A: insertion (insert-01) | extract the fact, resist chit-chat, land it | 15 | pass@1 80% (CI 55–93%); structural extraction 87% |
B: correction (correct-01) | update the existing entity (no duplicate), status corrected | 8 | pass@1 88%: agent chose update_status on the existing entity every time |
C: contradiction, agent (contradict-01) | non-corruption when a conflicting claim arrives | 10 | 100% non-corruption: the agent surfaces the conflict itself and asks; the existing fact stays intact |
C: contradiction, gate (gate-probe) | SYN-242 detects + gates a conflicting write, in isolation | det. | detect + gate 100%; hard-negative control (same value → no flag) |
D: retrieval Q&A under noise (qa) | three-leg attribution + LLM judge | 10 | recall@10 80%, e2e 100%, oracle 100% |
Two findings that shape the positioning:
-
Contradiction defence is two independent layers. A capable agent surfaces a contradiction in its own reasoning and asks the human before ever filing the write, so the agent-in-the-loop path shows non-corruption, and the gate (SYN-242) is the non-bypassable floor for when the agent doesn’t notice (a weaker/autonomous agent, a subtle conflict, too much data).
gate-probemeasures that floor directly: it detects and gates a conflictingupdate_status, 100%, with a hard-negative control. -
Scenario D separates retrieval from generation, and the agent beats single-shot retrieval. Each needle is answered three ways over a persona haystack: retrieval (gold in
syn searchtop-k), oracle (gold injected, the generation ceiling), and end-to-end (agent via MCP), with a reference-guided judge on a different model. On a 10-needle run: recall@10 = 80% but e2e = 100% (oracle ceiling 100%). The gap is the finding: the two needles the top-10 retrieval missed were still answered correctly end-to-end, because the agent issues multiple MCP searches and reasons across related notes. So the MCP-driven loop recovers retrieval gaps a single ranked query would drop; the oracle−e2e gap (here zero) isolates genuine read failures when they occur.
Under noise, retrieval holds. Piling synthetic distractors into the haystack
(qa --distractors N; the agent’s token cost is ~flat in N because it searches via MCP,
so only the one-time embed scales) leaves the answer intact:
| distractors | recall@10 | e2e | oracle |
|---|---|---|---|
| 0 (persona) | 80% | 100% | 100% |
| +2 000 | 100% | 80%¹ | 100% |
| +8 000 | 100% | 100% | 100% |
¹ the lone e2e dip is a 5-needle-sample claude -p blip, not degradation. Recall@10 and the
oracle ceiling stay at 100% through 8 000 distractors, the retrieval cliff the flat-vector
scaling curve predicts lives at far larger N (tens of thousands+), not the low thousands a
real second brain holds. The 100 000 tier is a ~2 h one-time embed (documented, not run).
Reliability, not just capability, is the headline for a “can’t silently corrupt” tool: the
recurring ~1/N miss in these runs is a transient claude -p crash (now retried), not
Synapse: the governance behaviours (update-on-existing, abstention, non-corruption,
gate detect-and-gate) are effectively clean.
Honest limits. Judge and agent are both Claude (a weaker preference-leakage guard than
a cross-family judge): audit judge–human agreement before trusting the e2e number; the raw
verdicts are kept for that. The distractor-scale sweep (1k/10k/100k) and the bitemporal
--as-of correction assertion are the next slices (plan 022).
The Synapse benchmark, explained for non-experts
This is the plain-language companion to BENCHMARKS.md. It explains what the metrics mean, how the recorded tests ran, which results they support and where their evidence is limited. The observations are owner-run engineering experiments, not a human field study or a promise about every workload, agent or released executable.
The question it tries to answer
Imagine an assistant maintaining a team’s notebook: API owners, decisions, constraints and open tasks. A useful benchmark needs to check whether the assistant preserves the expected recorded state, whether supported conflicting changes receive the intended review and whether later questions retrieve the right evidence.
The question is: under the recorded scenarios, what changed, what remained intact and what did the checks actually cover? Storage integrity, supported structured conflicts and factual truth are different questions. A result about one does not establish the others.
There are two test families:
- Scaling, with deterministic offline measurements of latency, size and resources.
- Agent in the loop, where a real agent uses MCP and the harness grades the resulting state or answer. These runs require the selected provider and are not offline tests.
The current source overview adds a plot of the stored persona retrieval observations. Its 20-question fixture is separate from the 10-question agent Q&A scenario below. Do not merge their sample sizes or present one as a rerun of the other.
Glossary, in human terms
Distractors. Irrelevant records deliberately added to make retrieval harder. Finding the same target facts among 2,000 or 8,000 additional records tests those particular noise fixtures. It does not reproduce every kind of accumulated organizational knowledge.
Recall@10. The fraction of target facts found within the first ten ranked results.
recall@1 asks whether the fact ranks first. A hit measures retrieval against the recorded
reference; it does not certify that the reference or generated answer is true.
Noise chart. A plot of retrieval results as distractors increase. In the recorded agent Q&A noise probes, top-ten recall was 80% without distractors and 100% at the two measured noise levels, +2,000 and +8,000. That is a small set of observations, not a flat curve proven at every corpus size. The separate persona fixture starts at 95% top-ten recall and reaches 100% at its larger measured corpus sizes.
Saturation. A metric reaches 100% on its tested sample. This can indicate a successful scenario or a test too small/easy to discriminate. “10 of 10 runs” makes the sample visible; it is not a general guarantee.
Gate. The deterministic proposal guard for supported structured changes, such as certain status or relationship conflicts and identity collisions. It does not require a model to notice those supported cases. General disagreements in prose are outside that detector’s scope, and unsupported proposal classes must not be counted as semantically consistent. See ADR-062.
e2e (end-to-end). The agent receives a question, uses only the allowed MCP tools and produces an answer. This checks the complete tested agent/tool loop rather than one search. It remains an engineered scenario with a selected model, fixture and grading method.
The three attribution legs. Each question is examined in three ways:
- Retrieval: is the reference fact present in the raw top-k results?
- Oracle: can the model answer when given the reference evidence directly?
- e2e: can the agent find and use evidence through MCP without that direct injection?
Differences help investigate failures. A high oracle score with a lower e2e score suggests problems in the retrieval/reasoning/tool loop. It does not identify one cause conclusively without inspecting the run.
pass@1 vs pass^k. The harness reports single-attempt success frequency as pass@1.
Its pass^k field records whether every graded run passed its assertions. In this
harness it is an all-runs result, not a separately estimated population success probability.
Neither a lucky success nor a small all-pass sample proves future reliability.
95% Wilson confidence interval (CI). With 12 successes in 15 insertion attempts, the observed rate is 80%, with a Wilson interval of about 55% to 93%. The width reflects sampling uncertainty under the experiment’s assumptions. It is not a forecast that production performance will fall within that range on a different workload.
How the tests were actually run
The agent-in-the-loop harness limits Claude Code to Synapse MCP tools. A
--disallowedTools block removes file, shell and web tools from the allowed interface. This
keeps the scenario focused on MCP-driven memory instead of direct repository reads. It is
a harness configuration, not a sandbox against a separately privileged host process.
After each task, the harness grades the canonical recorded state and explicit assertions.
A verification report checks its named invariants; signing depends on the available key and
selected policy. A canonical dump or signature does not establish the truth of all prose.
This state-oriented grading is analogous to the τ-bench get_data_hash() approach, rather
than treating a convincing transcript as successful storage.
The agent is nondeterministic, so the harness repeats each task. A different-model judge (Sonnet judging an Opus agent) scores Q&A against references. Both models are from the Claude family, so that is not independent human adjudication or a cross-family assessment.
The recorded scenarios are insertion (A), correction (B), a supported conflicting change
(C, with agent behavior and the isolated gate measured separately), and retrieval Q&A (D).
The harness in synapse-bench/ in the source repository and methodology retain reproduction details.
What the recorded runs show
| Scenario | What it measures | Sample | Recorded result |
|---|---|---|---|
| A: Insertion | Extract the target fact, resist chit-chat and save it | 15 runs | 80% pass@1 (12/15; 95% CI about 55% to 93%); structural extraction 87% |
| B: Correction | Update the existing entity without a duplicate | 8 runs | 88% pass@1 (7/8); the agent selected update_status on the intended entity in every run |
| C: Conflict, agent | Preserve the existing fact when the fixture supplies a conflicting claim | 10 runs | 10/10 preserved the existing fact; the agent raised the conflict and requested clarification |
| C: Conflict, gate | Detect and hold the supported conflicting update_status in isolation | Deterministic fixture | All tested conflicting writes were held; the same-value negative control did not flag |
| D: Retrieval Q&A | Raw retrieval, reference-injected oracle and MCP agent answer | 10 questions | 80% recall@10, 10/10 e2e answers correct, 10/10 oracle answers correct under the recorded judge |
The agent Q&A noise probes are distinct recorded samples:
| Added distractors | Recall@10 | e2e answer score | Oracle answer score |
|---|---|---|---|
| 0 | 80% | 100% | 100% |
| 2,000 | 100% | 80% | 100% |
| 8,000 | 100% | 100% | 100% |
The methodology attributes the +2,000 e2e dip to a transient claude -p failure on a
five-question sample and records a retry. The observed dip remains part of the evidence;
a suspected infrastructure cause does not prove the absence of retrieval or answer-quality
problems in other runs.
In the baseline Q&A sample, two reference facts were missing from the first raw top-ten search, yet the agent produced correct answers after additional searches. This shows recovery of those two gaps in that run. It does not imply that agents recover every retrieval miss.
The conflict scenario separates two mechanisms. The agent voluntarily notices a conflict; Synapse separately holds a supported structured conflict when the isolated guard is tested. The first depends on agent behavior. The second checks its documented rule classes and configured write path. Neither result establishes arbitrary natural-language contradiction detection, universal non-corruption or a privileged-process security boundary.
Where the benchmark is weak (honest self-critique)
- Small samples. The agent scenarios use 8 to 15 runs. The intervals are wide, and repeatedly running a similar fixture does not create broad field coverage.
- Related judge and agent. Sonnet and Opus share a model family. Style preferences or shared blind spots can affect grading. Cross-family and human review would add evidence; neither is implied by the current scores.
- Saturated metrics. Several samples reach 100%. More diverse cases, harder negatives and repeated seeds are needed before drawing broader conclusions.
- Execution failures affect results. The five-question noise probe includes an infrastructure-associated answer-score dip. Report retries and failures instead of presenting only the successful rerun.
- The 100,000-distractor tier was not run. Its approximately two-hour embedding cost is an estimate in the methodology. It supplies no measured quality result at that size.
- Stub scaling is mechanical evidence. The large scaling curves use nonsemantic vectors. Their latency/size measurements do not establish semantic retrieval quality.
- The persona fixture has incomplete provenance. Its stored JSON records 20 questions,
macOS arm64 and six corpus sizes, but the original run’s source commit is
unknown. It cannot establish performance of every later development commit or packaged release.
Weaknesses of Synapse itself that the benchmark exposed
- Insertion needs better reliability. Three of fifteen attempts failed their complete insertion assertions. Saving the wrong or incomplete context is a practical weak point, even if a later storage-integrity check passes.
- First-result retrieval still misses targets. Early persona notes reported around 0.78 recall@1; the dedicated chunking comparison reported a full-haystack improvement from 0.75 to 0.85 (SYN-301, ADR-042). In the stored 463-entity endpoint, 17 of 20 targets rank first and all 20 rank in the top ten. Those are separate recorded comparisons, not a new measurement of every current release. An opt-in reranker did not improve that corpus’s endpoint further.
- Agent caution is not a Synapse guarantee. The 10/10 preserved-fact sample depends on the agent asking first. The isolated structured gate has its own evidence and scope; it cannot replace reasoning about every possible conflicting sentence.
- Agent-driven temporal correctness remains a separate coverage question. The original
agent benchmark did not assert complete
--as-ofcorrectness. Thebitemporal-probechecks the supported write path; later native tests and current source contracts must be inspected independently. Do not infer a completed agent-loop temporal proof. - Semantic models have an ingestion and memory cost. The documented multilingual default download is about 2.5 GB. The measured embedding throughput, around 13 documents per second on its recorded CPU, makes large initial imports expensive. That is not a universal throughput estimate or a guarantee that later queries are unaffected by resource contention.
Use the raw persona observations, complete benchmark methodology and verification scope together when assessing these results.
Run
syn verifyon every pull request so a team’s AI-written knowledge fails the build if it was silently corrupted. This turns the v1.0 headline (syn verify) from a local command into a shared gate.
Quick start
Add a workflow that runs the action against your Synapse workspace:
# .github/workflows/synapse.yml
name: synapse
on: [pull_request]
jobs:
verify:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: romulofebasi/synapse@v1
with:
version: '1.0.0' # pin it: never float a gate to latest
working-directory: 'brain' # your Synapse workspace
args: '--rebuild' # diffoscope-style: names every diverging fact
On every PR the job installs the pinned syn, runs syn verify --rebuild in
your workspace, writes the report to the job summary, and fails closed: a
non-zero exit (a diverging fact, a broken hash-chain, a tampered index) breaks
the build. A clean workspace passes.
Inputs
| Input | Default | What it does |
|---|---|---|
version | 1.0.0 | Synapse release to install (downloaded from synapse-releases). Pin it: a CI gate must be reproducible. |
working-directory | . | Path to the Synapse workspace to verify. |
args | --rebuild | Flags for syn verify. Use --rebuild to name every diverging fact, add --deep for replay-determinism, or --since <witness> for the offline append-only proof. |
What it proves
syn verify --rebuild checks the index is a bit-for-bit rebuild of your
Markdown, the audit hash-chain + signed checkpoint are intact, and no fact was
silently overwritten, and on a mismatch it names the exact diverging fact. See
docs/PROVABILITY.md for the full guarantee and its honest
ceiling, and docs/COMMANDS.md for the syn verify surface.
Notes
- The action installs a prebuilt
synfor the runner’s OS/arch from the publicsynapse-releasesrepo; supported runners: Linux (x64/arm64), macOS (arm64), Windows (x64). - Pin
@v1(or a tag) on the action and pinversion:on the input: both, so the gate can’t drift under you. - This composes with the multi-channel install work (Homebrew / cargo-binstall); when a stable channel lands, the install step simplifies.
Synapse reads configuration in cascading layers. Later layers override earlier ones:
- Compiled-in defaults (zero config).
- System config:
/etc/synapse/config.toml(POSIX only; skipped on Windows). - Global user config: see Paths below for the OS-specific location.
- Workspace config:
<workspace>/.synapse/config.toml. - Runtime overrides: CLI flags and environment variables.
Missing files at any layer are not errors: they just contribute nothing.
Config schema
# ~/.config/synapse/config.toml (or wherever your OS puts it)
# IETF language tag. Omit to follow the system locale.
lang = "pt-BR"
# Default --format when not overridden. text | json
format = "text"
# Workspace freshness advisory (SYN-93, ADR-011).
[freshness]
enabled = true # global kill-switch
interval_minutes = 30 # minimum gap between two checks on the same workspace
silent_cli = false # suppress the stderr line; MCP enrichment is independent
# Write-proposal auto-apply policy (SYN-218, ADR-022). Evolves BR-12.3
# from an absolute gate into a confidence tier.
[proposals]
# "manual" (default): every MCP proposal queues for `syn pending accept`
# (the pure propose/accept invariant).
# "safe" : auto-apply only the low-risk, reversible tier
# (today: link_entity between two entities that already
# exist and are not the `self` person); everything else
# still queues. Auto-applied writes are stamped and
# recorded on the audit trail as `auto-accepted`.
auto_apply = "manual"
# Semantic layer (v0.5 / BR-13): embedding + NER pipeline that runs
# inside Workspace::tx on every write. Active in the default build
# (the semantic layer ships by default, SYN-168); on a minimal core
# build (`--no-default-features --features mcp`) the block is still
# parsed but ignored, so the same config travels across builds.
[enrichment]
mode = "heuristic+ner" # "heuristic" (no NER) | "heuristic+ner" | "off"
embedding_model = "bge-m3" # fp32 ~2.2 GB (default); "bge-m3-int8" (~560 MB, same 1024-dim space, small recall cost); "bge-small-en-v1.5-q" (~65 MB, English only)
ner_model = "gliner-multi" # ~350 MB cached, multilingual (default); or "gliner-base-en" (~260 MB, English only)
ner_threshold = 0.85 # spans below this confidence are dropped
# model_cache_dir = "/custom/path" # override the platform default (see below)
# Opt-in network embedding provider (SYN-165): default is local
# fastembed (content never leaves the laptop). "openai"/"voyage" send
# each entity's text to a third party per write; warned at `syn init`.
# Requires a build with `--features network_backends`. See IDENTITY.md.
# provider = "openai" # or "voyage"; default is local fastembed
# [enrichment.openai]
# model = "text-embedding-3-small" # key via OPENAI_API_KEY env var (preferred) or api_key here
# base_url = "https://api.openai.com/v1" # override for a proxy / Azure gateway
# [enrichment.voyage]
# model = "voyage-3" # key via VOYAGE_API_KEY env var or api_key here
The whole [enrichment] block takes effect on any build that includes the semantic layer, which is the default build (cargo build --release, SYN-168). A minimal core build (--no-default-features --features mcp) parses and ignores the block, so one config file travels across builds. The local models are not bundled and download only on consent (the syn init onboarding prompt or syn embed --all): see INSTALL.md → Feature flags for the footprint (~2.5 GB for the multilingual defaults, ~325 MB for the English opt-downs).
Only fields actually consumed by code live in the schema. Extra fields are preserved on read but ignored: handy for staging future settings.
Paths
| Layer | macOS | Linux | Windows |
|---|---|---|---|
| Global config | ~/Library/Application Support/synapse/config.toml | ${XDG_CONFIG_HOME:-~/.config}/synapse/config.toml | %APPDATA%\synapse\config.toml |
| System config | /etc/synapse/config.toml | /etc/synapse/config.toml | n/a |
| Workspace config | <workspace>/.synapse/config.toml | same | same |
| Model cache (v0.5) | ~/Library/Caches/synapse/models/ | ${XDG_CACHE_HOME:-~/.cache}/synapse/models/ | %LOCALAPPDATA%\synapse\models\ |
Paths are resolved by the etcetera crate (ADR-001 + SYN-30).
The model cache directory may be overridden, in precedence order, by [enrichment] model_cache_dir = "…" in any config layer or by the SYNAPSE_MODEL_CACHE environment variable. First-run model downloads (~2.2 GB BGE-M3 + ~350 MB GLiNER-multi v2.1 quantised by default: about 2.5 GB total) land here and are reused across workspaces; sharing the cache between repos is intentional. The English opt-down models are far smaller (~65 MB BGE-small + ~260 MB GLiNER-base ≈ 325 MB).
For the full story on the models: source repos, when/how they download, offline & air-gapped provisioning, what touches the network, privacy posture, licenses, and platform compatibility (incl. the Linux glibc ≥2.38 requirement): see MODELS.md.
Environment overrides
| Variable | Effect |
|---|---|
SYNAPSE_LANG | Locale override (en, pt-BR). Same as --lang. |
SYNAPSE_NO_FRESHNESS | Any non-empty value suppresses the freshness advisory for this invocation. |
SYNAPSE_MODEL_CACHE | Override the model cache directory (see Paths). |
SYNAPSE_ENRICHMENT_MODE | Override [enrichment] mode for a single invocation (off / heuristic / heuristic+ner). Handy for a one-off fast write that skips the BR-13 insertion-time pipeline without editing the workspace config, e.g. SYNAPSE_ENRICHMENT_MODE=off syn project add …. |
Bootstrapping the global config + identity (v0.6)
syn setup # guided in a TTY: locale, format, then your self-profile
syn setup --force # overwrite existing config + re-capture identity
As of v0.6 syn setup is guided and compulsory (BR-14.1): on a fresh
interactive machine syn init runs it inline before proceeding, and it
captures a global self-profile: name, birthdate, profession, employers:
into a profile.toml kept separate from config.toml (tool behaviour
cascades; identity does not). That profile seeds the self: true person into
every workspace on syn init (ADR-020). Override the profile path with
SYNAPSE_PROFILE.
Non-TTY (CI, piped install, --no-input, SYNAPSE_AGENT) falls back to
compiled defaults silently and skips identity capture: syn setup/syn init
never block a build.
Locale precedence (BR-10.1)
--lang <CODE> > SYNAPSE_LANG=<CODE> > sys-locale > en (fallback)
Examples:
syn --lang en project add "Projeto Alfa" # one-off override
SYNAPSE_LANG=pt-BR syn info # shell-level override
unset SYNAPSE_LANG && syn info # fall back to system
Locale strings follow IETF BCP-47 (en, pt-BR, es-MX, …). Unknown locales silently fall back to en (every key is guaranteed in en per BR-10.2).
Environment variables
| Variable | Purpose |
|---|---|
SYNAPSE_LANG | Locale override (see precedence above). |
SYNAPSE_PLAIN | Force raw, unstyled output (same as --plain/--raw). |
SYNAPSE_NO_COLOR / NO_COLOR | Drop ANSI colour, keep structure (TERM=dumb too). |
SYNAPSE_AGENT | Mark the caller as an AI agent: implies --plain and --no-input. |
SYNAPSE_PROFILE | Path to the global self-identity profile.toml (overrides the default location). |
SYNAPSE_MODEL_CACHE | Override the model cache directory (see Paths). |
SYNAPSE_ENRICHMENT_MODE | Override [enrichment] mode for one invocation (off / heuristic / heuristic+ner). |
SYNAPSE_NO_FRESHNESS | When set (any value), suppresses the freshness advisory for this invocation (same as --no-freshness). CI-friendly. |
RUST_LOG | Standard tracing-compatible log filter (e.g. RUST_LOG=synapse=debug). |
INSTALL_DIR | Used by the install.sh installer only; ignored by the binary. |
Output & styling (v0.6, ADR-019/021)
Output is rich by default on a TTY: colour, ✓/▲ glyphs, unicode tables,
entity panels, the graph tree, progress spinners/bars, and the
graph-constellation mascot. It degrades to plain bytes on a pipe and is
controlled by: --plain/--raw (or SYNAPSE_PLAIN) for fully raw output;
--no-color/NO_COLOR/TERM=dumb to drop colour but keep structure;
--quiet/-q to suppress non-essential lines; --no-input/--yes to never
prompt. --format json is always machine-clean and unstyled.
Workspace freshness (SYN-93)
The [freshness] block governs the advisory described in detail in ADR-011. Three knobs and a per-workspace override:
| Knob | Where | Default | Effect |
|---|---|---|---|
freshness.enabled | global / system / workspace config | true | Master kill-switch. false means no check ever fires and the advisory is silent. |
freshness.interval_minutes | same | 30 | Minimum minutes between two checks on the same workspace. Two parallel commands seeing a stale stamp may both fire; the race is benign. |
freshness.silent_cli | same | false | When true, the CLI stderr line is suppressed even when a workspace is behind/diverged. The MCP enrichment is independent. |
freshness_disabled | per-workspace, in the registry | 0 | Toggle with syn ws freshness on|off <name>. Set to opt this workspace out without disabling the feature globally. |
SYNAPSE_NO_FRESHNESS | env var | unset | Per-invocation kill (cascades over every config). |
--no-freshness | CLI flag (global) | absent | Same effect as the env var, but only on the current command. |
Future settings (not in v0.1)
The cascade is intentionally minimal today. Coming up:
| Setting | Purpose | Ticket |
|---|---|---|
editor | $EDITOR override for syn open | SYN-70 (v0.2) |
mcp.transport | Pick stdio / HTTP for the MCP server | SYN-207 (backlog) |
watcher.debounce_ms | syn watch debounce | SYN-65 (v0.2) |
Embedding/NER backend selection shipped in v0.5 as the
[enrichment]block above, not a future setting.
Add them via a versioned migration (the same pattern the index uses); never by silent format change.
Synapse is currently invite-only on the source repo. If you have access, this is the operational guide. If you don’t and would like to, open a thread.
One-time setup
git clone https://github.com/romulofebasi/synapse.git
cd synapse
./scripts/bootstrap.sh # activates .githooks/ and checks toolchain
Required:
- Rust 1.89+ (matches
Cargo.tomland native file locking) - POSIX shell for the git hooks
Recommended (the bootstrap script lists what’s missing):
cargo-nextest: faster test runnercargo-deny: license/advisory/ban auditcargo-shear: orphan dep detectortypos: pre-commit typo catchergit-cliff: local changelog preview
Commit messages (mandatory)
Conventional Commits, with scope:
type(SCOPE): subject
type ∈ {feat, fix, docs, refactor, perf, test, chore, build, ci, revert, style}SCOPEis the SYN-N ticket (e.g.SYN-60) or an area (cli,mcp,index,store,core,model,i18n,config,deps,ci,hooks)subjectis imperative, lowercase first letter, ≤72 chars total, no trailing period!before:marks a breaking change:feat(SYN-60)!: rename roles to memberships
Enforced by .githooks/commit-msg and re-enforced by .github/workflows/commit-lint.yml on every PR.
Examples that pass:
feat(SYN-22): introduce note entity type
fix(index): respect PRAGMA foreign_keys on reopened connections
docs(adr): record adoption of rmcp 1.7
chore(deps)!: bump rusqlite 0.38 to 0.39
Before pushing
The pre-push hook requires Cargo and rejects failed validation:
cargo fmt --all -- --check
cargo clippy --all-targets --all-features -- -D warnings
cargo nextest run --all-features # falls back to cargo test
python3 scripts/check-native-contracts.py
cargo doc --no-deps --all-features # if src/ changed
You can run these any time:
cargo fmt --check
cargo clippy --all-targets --all-features -- -D warnings
cargo nextest run --all-features
cargo deny check
Adding a feature
- Ticket exists? If yes, the SYN-N is your scope. If no, open one tied to the right phase milestone (
v0.1/v0.2/v0.3/v0.4/v0.5/v1.0). - Business rule exists? Check
docs/business/BUSINESS_RULES.md. If your change contradicts a rule, the rule changes first (PR + ADR). - Where does code go? Edge (CLI or MCP) calls the same function in
core/. A feature that lives only in one edge is a bug. - File-size sanity (ADR-015). Before adding 100+ lines to an existing file, check
wc -l. Yellow tier (500-800 lines): the reviewer must call it out. Red tier (800+): a refactor issue must exist and be linked, and the new code must either be part of the refactor or already remove lines. - Tests. Unit tests next to the code; integration tests in
tests/cli.rs; snapshot tests withinsta. Property tests on parsing/encoding code. - Smoke test the release binary. After clippy + unit tests pass, build
--releaseand exercise the feature end-to-end against a temp workspace.cargo testcovers logic; smoke covers integration (filesystem, CLI parsing, MCP stdio). Don’t mark the ticket done without it. - Translate. Every user-visible message gets a key in
i18n/en/synapse.ftlandi18n/pt-BR/synapse.ftl, in the same commit as the feature.
File-size policy (ADR-015)
Code-review caps to keep the project legible:
| Tier | Lines | What it means |
|---|---|---|
| Green | < 500 | Healthy. No action. |
| Yellow | 500-800 | The reviewer of any PR touching the file flags it. A refactor proposal is welcome, not forced. |
| Red | 800+ | A refactor issue must exist and be linked from every PR adding code. New code blocks merge unless it (a) net removes lines, or (b) is part of the refactor. |
Line count is a heuristic, not dogma: what matters is cohesion. A 600-line file of schemars-derived argument structs is fine; a 600-line file of braided business logic is not. ADR-015 has the full playbook and the principles (single responsibility per module, dependency inversion at boundaries, use-case isolation, edge/core/data layering preserved).
Current candidates (2026-05-25): none in red tier. src/mcp/server.rs was the example that drove ADR-015 and landed at ~640 lines (yellow tier) after the SYN-150 split: every other module in src/mcp/ stays green.
Adding an ADR
A non-obvious decision needs an Architecture Decision Record. Copy docs/adr/template.md, name it NNN-kebab-title.md, link from docs/adr/README.md. One decision per file.
Releasing
Maintainers prepare releases through ordinary reviewed source changes:
# Generate a candidate section with the reviewed git-cliff version and cliff.toml.
git cliff --unreleased --tag vX.Y.Z --strip all --offline --no-exec
# Validate committed manifest/lock versions, notes and publication controls.
python3 scripts/check-release-tests.py -v
python3 scripts/check-release-independent-tests.py -v
python3 scripts/check-release-document-tests.py -v
Replace the candidate section rather than prepending it twice. Preserve older
sections exactly. Commit matching root manifest and lockfile versions with the
reviewed notes, then require exact-source CI, MSRV and independent review. Only
after those gates pass, create the authorized annotated version tag on that
reviewed source. Use Rômulo Febasi <romulo@febasi.com.br> for both commit and tag
identity. Changelog generation alone does not create a Git tag.
The tagged workflow builds four native targets: Linux x86_64 and ARM64, Apple Silicon macOS, and Windows x86_64. It packages document bytes from committed Git blobs, checks every archive changelog against the source receipt, and signs the checksum manifest before publication. It does not write back to main. The public mirror preserves the original assets and signature. npm independently verifies the exact source receipt, signature and archive layout before publishing five platform/launcher packages with OIDC.
Existing release tags and assets are never replaced by the workflow. Fix a published packaging defect with a new reviewed patch version. An explicit retry of an unpublished release must select the existing tag as the workflow ref and input; a branch dispatch does not retain the required signing identity.
See ADR-007 and release verification for the complete contract and platform limits. Checksums and signatures authenticate selected bytes; they do not prove independent build reproducibility.
House rules
- Don’t add a dependency without an ADR.
- Don’t reach for
unsafe:#![forbid(unsafe_code)]is set at the crate root. - Don’t introduce
serde_yamlorserde_yml: both are banned indeny.toml(ADR-004). - Don’t use
unwrap()in library code; doctests use?with# Ok::<_, Error>(()). - Don’t shell out to
gitfrom production code: usegixif needed. - Don’t add a CLI message without a Fluent key.
If something here is wrong, edit it (this file is the source of truth for contributors).
Ship it.
Documentation publication
The documentation site is generated with pinned mdBook 0.5.4 from canonical tracked product Markdown. It includes README, quickstart, installation, tutorial, recipes, command/contract references, architecture, business/design/research docs, the complete canonical ADR index and ADR pages, and changelog. Browser-side full-text search covers the generated chapters. No manual duplicate ADR index is maintained.
Internal session handoffs and authoring templates are excluded from public publication. Only tracked regular product files, branding assets and explicitly sanitized decision evaluation artifacts enter the book; untracked notes, environment files, private keys, database files and model weights are never copied. Publication records the exact source commit and source file hashes in publication.json.
Editorial standard
Follow the Febasi writing rule. Maintain English copy in README, product statements, ADRs, design pages and the site title. Do not use em dashes in authored prose. The canonical positioning is:
Operational memory for agents, with governed changes, preserved history, and explicit integrity checks.
Review the rendered homepage and affected chapters as well as source Markdown. The publisher preserves tracked source text; it does not translate or normalize legacy chapters automatically. Editorial changes must preserve historical decision status, code examples, identifiers and link targets.
Mermaid diagrams
The generated book uses official mdbook-mermaid 0.17.1, compatible with
mdBook 0.5. The Pages workflow verifies both official release archive SHA-256
values before unpacking either executable. The standard mdbook-mermaid install
command copies its bundled Mermaid runtime and initializer into the generated
book. mdBook publishes both as local scripts, including on nested ADR pages.
There is no runtime CDN dependency and no change to canonical Markdown fences.
Upstream license banners remain in the bundled assets.
The publication verifier checks each chapter’s fenced diagram count against its HTML containers, rejects raw Mermaid code blocks, and requires the pinned local runtime and initializer in that order. Missing, changed, remote or escaping script references fail publication. These checks prove generated structure and asset identity. They do not execute JavaScript or prove SVG rendering, typography or layout. Browser acceptance must separately inspect diagrams, console errors, light/dark themes and nested-page navigation before closing SYN-342 (#347). See ADR-072.
Local validation
Install the same pinned official tools used by .github/workflows/pages.yml
without compiling them, then run the full documentation publication suite:
python3 scripts/check-docsite.py -v
python3 scripts/check-docsite-independent-tests.py -v
For a persistent preview:
python3 scripts/prepare-docsite.py --output /tmp/synapse-docsite
mdbook-mermaid install /tmp/synapse-docsite
mdbook build /tmp/synapse-docsite
python3 scripts/verify-docsite.py /tmp/synapse-docsite
python3 -m http.server 8080 --bind 127.0.0.1 --directory /tmp/synapse-docsite/html
Use an absent dedicated output directory; regeneration never deletes an arbitrary folder. The verifier checks all generated chapter paths, tracked asset coverage, search contents, canonical ADR coverage and absence of private/runtime artifacts. It rejects em dashes in rendered chapter/navigation text, accessible labels and maintained SVG text. Tracked documentation SVGs are published with the book. Browser verification additionally checks actual search results and navigation. GitHub pull requests build and validate an artifact; merged main deploys through GitHub Pages with separate minimal deployment permissions. Both official tool archives are pinned by version and SHA-256 in the workflow.
The independent publication review preserves the original eight-mutant campaign and its three survivors. The added tests independently derive complete chapter coverage from tracked canonical sources and invoke the actual verifier CLI on broken non-release diagram pages. Pages requires both suites, 14 original methods plus three independent methods. Changes to either suite also select the documentation workflow. Final workflow wiring and browser acceptance remain distinct from the original frozen review.
Domain rollout
The initial Pages URL is https://romulofebasi.github.io/synapse/. The requested canonical URL is https://synapse.febasi.com.br/. The repository uses Pages workflow deployment; its Git repository remains private.
The authoritative DNS provider is Cloudflare. To finish the custom-domain acceptance of SYN-109, an authorized operator must provide access to the zone or create this new record:
| Field | Value |
|---|---|
| Type | CNAME |
| Name | synapse |
| Target | romulofebasi.github.io |
| Proxy | DNS only during GitHub Pages domain/certificate validation |
Verify the exact domain through GitHub’s ownership/domain validation, configure the Pages custom domain after DNS resolves, wait for its certificate and enable HTTPS. The book’s site-url must be / for the custom domain (initial project-site prefix is /synapse/). Validate the final URL, HTTPS certificate, redirects, chapter links and browser search before marking #89 complete. Do not point a DNS record at a disabled/unowned Pages site.
The source publication and default Pages URL can be delivered while zone access is unavailable; that does not satisfy the requested custom-domain acceptance. No credentials belong in this document or generated artifact.
Primary references: mdBook installation, GitHub Pages custom domains.
Mermaid references: official mdbook-mermaid v0.17.1, mdBook local JavaScript configuration.
TL;DR: Synapse ships an LSP companion (
syn lsp) so any editor that speaks LSP gets entity-aware completion, hover, go-to- definition and dangling-reference diagnostics on every.mdfile in a workspace. Build with thelspfeature, point your editor at thesyn lspbinary, done.
1. Build with the LSP feature
cargo install synapse --features "mcp lsp"
# or, from this repo:
cargo build --release --features "mcp lsp"
# binary lands at target/release/syn
Confirm the subcommand:
syn lsp --help
# Run the LSP companion on stdio (slug/type/tag completions, hover,
# go-to-definition, dangling-ref diagnostics): SYN-130.
The server reads .synapse/index.db from the workspace root; runs
syn reindex once if completions look stale.
2. What you get
- Completion in
.mdfiles:- Inside
[[<cursor>: every workspace entity slug. - After
type: <cursor>: the six entity types (project, person, entity, api, task, note). - Inside
tags: [<cursor>: every distinct tag in the workspace.
- Inside
- Hover over
[[slug]]: entity title + first paragraph of the body. - Go-to-definition on
[[slug]]: jumps to the entity’s markdown file. - Diagnostics:
[[slug]]with no matching entity is flagged as a Warning,source: "synapse".
3. Helix
~/.config/helix/languages.toml:
[language-server.syn-lsp]
command = "syn"
args = ["lsp"]
[[language]]
name = "markdown"
language-servers = ["syn-lsp"]
roots = [".synapse"]
Helix auto-discovers the workspace root by walking up to .synapse/.
4. Neovim (nvim-lspconfig)
init.lua:
local lspconfig = require("lspconfig")
local configs = require("lspconfig.configs")
if not configs.syn_lsp then
configs.syn_lsp = {
default_config = {
cmd = { "syn", "lsp" },
filetypes = { "markdown" },
root_dir = lspconfig.util.root_pattern(".synapse"),
settings = {},
},
}
end
lspconfig.syn_lsp.setup({})
5. VS Code (and Cursor)
VS Code does not auto-discover non-marketplace LSPs, so it needs the
thinnest possible extension. Drop this into a folder and code --install-extension .:
// package.json
{
"name": "synapse",
"version": "0.0.1",
"engines": { "vscode": "^1.94.0" },
"activationEvents": ["onLanguage:markdown"],
"main": "./extension.js",
"contributes": {
"languages": [{ "id": "markdown", "extensions": [".md"] }]
}
}
// extension.js
const { LanguageClient, TransportKind } = require("vscode-languageclient/node");
let client;
function activate() {
client = new LanguageClient(
"synLsp",
"Synapse LSP",
{
command: "syn",
args: ["lsp"],
transport: TransportKind.stdio,
},
{
documentSelector: [{ scheme: "file", language: "markdown" }],
workspaceFolder: undefined,
},
);
client.start();
}
function deactivate() { return client && client.stop(); }
module.exports = { activate, deactivate };
Cursor inherits the same extension API; the config above works identically.
6. Zed
~/.config/zed/settings.json:
{
"lsp": {
"syn-lsp": {
"binary": { "path": "syn", "arguments": ["lsp"] }
}
},
"languages": {
"Markdown": { "language_servers": ["syn-lsp"] }
}
}
7. Troubleshooting
No completions. Run syn reindex in the workspace. The LSP
server reads from .synapse/index.db; if it’s missing or empty
nothing comes back. Also verify your editor is actually launching
the server: most editors expose an “LSP log” command that shows
the stdio handshake.
[[slug]] warning is wrong. The diagnostic source is synapse
and it fires when no entity with that slug exists in the index. The
fix is either (a) create the entity (syn project add foo, etc.) or
(b) update the link.
Hover shows nothing. Hover is only implemented for [[slug]]
references. Hovering on free text returns nothing by design.
Go-to-definition opens the wrong file. Each slug must be unique across the workspace: the index returns the first match. If you have two entities with the same slug in different types, rename one.
8. References
- ADR-018: why we picked
tower-lsp-serverover the inactivetower-lsp. - SYN-130, acceptance criteria for the v0.4 LSP companion.
- LSP 3.18 spec: https://microsoft.github.io/language-server-protocol/.
For compliance reviewers: Synapse’s identity bundle is a signed, human-owned artefact. The human runs
syn identity keygen, signs the bundle on export, and the importer verifies against a workspace-localtrusted_keysallow-list. Revocation is in-band: a bundle’srevokedarray propagates to every consumer who re-imports. The curriculum surface (syn experience,syn skills,syn cv export) is reproducible without an LLM: same inputs always produce identical bytes. ADR-014, ADR-016, ADR-017 cover the design rationale.
1. What a bundle is
me.synapse.json is a single JSON file the human carries across
employers. Shape:
{
"synapse_protocol": "synapse/v1",
"kind": "identity-bundle",
"exported_at": "2026-05-25T18:54:15Z",
"person": {
"id": "romulo",
"title": "Rômulo",
"frontmatter": { /* full YAML mirror as JSON */ },
"body": "/* markdown body */"
},
"attestations": [
{ "type": "project", "id": "cliente-alfa", "role": "tech-lead",
"since": "2024-01-01", "title": "Tech lead Cliente Alfa" }
],
"skills": [
{ "name": "rust", "count": 17 },
{ "name": "mcp", "count": 9 }
],
"revoked": [],
"signature": "base64-64-bytes-ed25519",
"public_key": "base64-32-bytes-ed25519"
}
The bundle is tiny (a low double-digit KB even with dozens of attestations) and human-readable. Any text editor can open it. That is the point: a regulator or recruiter who does not have Synapse installed can still read the artefact.
2. Signing & verification
# Once per human, kept on the laptop.
syn identity keygen
# wrote private key to ~/.config/synapse/identity/private.key: public: <base64>
# Export: signs with the configured key.
syn identity export --out me.synapse.json --key ~/.config/synapse/identity/private.key
# Import: verifies against [identity] trusted_keys in config.toml.
syn identity import me.synapse.json
# pessoa "romulo" importada de me.synapse.json
The signed bytes are canonical JSON: keys recursively sorted, the
signature field omitted, public_key included. Two implementations
must agree on the canonicalisation byte-for-byte; that is why
Synapse owns both the producer and the verifier today and the format
is byte-stable.
Trust model:
- If
trusted_keysis empty, any embeddedpublic_keyis accepted. This is the single-user mode: you sign your own bundle, you import it on another machine, it Just Works. - If
trusted_keysis non-empty, the bundle’spublic_keymust appear in the list. This is the cross-org mode: an employer or recruiter pins the keys they trust. - An unsigned bundle (v0.3 format) errors on import unless you pass
--allow-unverified. The imported person stampsverified: falseso downstream consumers can see the trust level at a glance.
ADR-016 covers the full rationale. The crypto layer is ed25519 via
ed25519-dalek 2.1; no PKI, no Sigstore: local-first by design.
3. Revocation
A bundle author who realises an attestation is wrong (employer dispute, role title was inflated, security incident) does not need to chase down every consumer. Revocation is in-band:
syn identity revoke me.synapse.json --target project/cliente-alfa --key ~/.config/synapse/identity/private.key
# "project/cliente-alfa" revogado: bundle agora lista 1 revogação(ões)
The bundle’s revoked array now contains project/cliente-alfa. The
next consumer to syn identity import me.synapse.json filters
attestations whose {type}/{id} (preferred) or bare id appears in
revoked before writing to attested_involvement. Re-signing happens
automatically when --key is provided; without --key, the old
signature is dropped so the bundle does not lie about being verified.
Revocation is additive. The author cannot un-revoke; that is a deliberate property: once you have told a consumer “this attestation is wrong”, it stays wrong.
4. Capturing experience
The bundle’s attestations array comes from the self: true person
entity’s attested_involvement frontmatter. The CLI to manage it:
# Add an entry.
syn experience add "Tech lead at Cliente Alfa" \
--target project/cliente-alfa \
--role tech-lead --since 2024-01-01
# Review.
syn experience list
# Remove every entry matching project/cliente-alfa.
syn experience rm project/cliente-alfa
Each add rewrites the self person’s markdown atomically. The
person entity must exist with self: true in its frontmatter: Synapse
refuses to guess which person is the human behind the workspace.
As of v0.6 you normally don’t create it by hand. syn setup captures
a global self-profile (name, birthdate, profession, employers) into
profile.toml, and syn init seeds the canonical self: true person
into every workspace from it: “capture once, seed everywhere” (BR-14,
ADR-020). Employers become entity(kind=org) nodes, linked from the self
person by an employed-by graph edge (recorded under affiliations and
materialised by the seed / syn reindex). The seeded frontmatter looks
like:
---
type: person
id: ada-lovelace
title: Ada Lovelace
self: true
birthdate: 1815-12-10 # optional; rendered by the detailed cv export
headline: Mathematician # → cv headline
affiliations:
- org: analytical-engine-co
memberships: []
---
If there’s no profile yet (a non-interactive install), set self: true
by hand on the entity that represents you, or run syn setup and re-init:
cv/identity export point you at syn setup when no self person
exists. Override the profile path with SYNAPSE_PROFILE.
syn experience is not capture_fact (BR-12.3). It runs
synchronously and writes directly. The pending queue is for
LLM-proposed writes; experience entries are claims the human is
making about their own past, so they bypass the queue.
5. Skills derivation
syn skills ranks the workspace’s tag frequency:
syn skills --limit 10
# 17 rust
# 9 mcp
# 7 cli
# 5 observability
# ...
A tag that recurs across many entities is a topic the human spends
real time on. The v0.4 algorithm counts every occurrence of a tag
across every entity in the workspace; the top N (default 20, capped
200) ships in the bundle’s skills array and renders in the CV.
This is the simplest defensible signal. ADR-017 lists the richer signals reserved for v0.5+ (project depth, decision count, owner-resolved attribution). If the ranking surprises you, the fix is usually to clean up tag noise or rename a tag, not to call ranking broken.
6. CV export
syn cv export --out cv.md
# CV gravado em cv.md
syn cv export --style minimal # flat skill list, no decisions section
syn --format json cv export # structured intermediate for custom renderers
The Markdown sections are fixed and ordered:
# {name}+ optional_headline_Born: {birthdate}: detailed style only, when the self person has abirthdate(v0.6, SYN-205)- Contacts line (
email · github · linkedin · website) ## Summary: the person body## Skills: top N fromsyn skills(counts indetailedonly)## Experience:attested_involvement, newest first## Recent decisions(detailed only): last 5 notes withkind: decision, capped with an explicit comment marker
Two runs against the same workspace produce byte-identical output. That is the contract: a regulator or recruiter who reproduces the file gets the same bytes you handed them. An LLM is welcome to polish the result afterwards (copy the Markdown into your model of choice and ask for a rewrite) but the canonical CV is the deterministic one.
PDF export is deferred to v0.5+. Today the workflow is syn cv export | pandoc -o cv.pdf if you need PDF.
7. Worked example: full round-trip
# 1. Generate a key (once).
syn identity keygen
# 2. Mark yourself.
$EDITOR people/romulo.md
# … set `self: true`, fill in email/github/headline, save.
# 3. Record involvement.
syn experience add "Tech lead Cliente Alfa" \
--target project/cliente-alfa --role tech-lead --since 2024-01-01
syn experience add "Dev Cliente Beta" \
--target project/cliente-beta --role dev \
--since 2022-06-01 --until 2023-12-01
# 4. Export the bundle, signed.
syn identity export --out me.synapse.json \
--key ~/.config/synapse/identity/private.key
# 5. Hand the bundle to someone.
# They configure their workspace's config.toml:
# [identity]
# trusted_keys = ["<your base64 public key>"]
# Then:
syn identity import me.synapse.json
# → pessoa "romulo" importada; verified=true.
# 6. Render the CV (on any machine that has the workspace + bundle).
syn cv export --out cv.md
8. Troubleshooting
“importando bundle sem assinatura” / --allow-unverified.
You exported without --key (or the key file was unreadable). The
bundle is still importable but the consumer needs to opt in with
--allow-unverified. The imported person carries verified: false.
Fix it by re-exporting with a real key.
“assinatura do bundle inválida ou não-confiável”.
Either the bundle was tampered with after signing (canonical JSON no
longer matches the signature), or the public_key is not in the
consumer’s trusted_keys allow-list. Compare the bundle’s
public_key against your config; add it explicitly via
syn identity import --trusted KEY for one-shot acceptance.
Lost private key.
You can no longer sign new exports or new revocations of bundles you
already produced. Bundles already signed with the lost key remain
verifiable forever. The recovery path is: syn identity keygen to
make a new key, re-export your bundle with it, and ask consumers to
add the new key to their trusted_keys. There is no way to
“transfer” trust from the old key to the new one, that would
defeat the point of asymmetric crypto.
No self: true person.
syn experience and syn identity export both refuse without
exactly one person flagged self: true. Add the flag to the
frontmatter of the person entity that represents you. If you have
multiple, pick one: federation (BR-12.4) does not span identities,
each workspace has exactly one self.
8b. Network embedding providers: the privacy trade-off (SYN-165)
By default the v0.5 semantic layer embeds on device (fastembed +
BGE-M3): your notes never leave the laptop, which is the whole point of
a local-first second brain (BR-12.1).
Two opt-in network providers exist for users who want a hosted model instead:
[enrichment]
provider = "openai" # or "voyage"; default is local fastembed
[enrichment.openai]
# Prefer the env var OPENAI_API_KEY over a key in this file: a key on
# disk is a secret that may get committed (BR-12.2).
model = "text-embedding-3-small"
Equivalent block for Voyage under [enrichment.voyage]
(VOYAGE_API_KEY, default model voyage-3). Both are requested at
1024 dimensions so the vector fits the existing table.
What you are trading. With a network provider, the full text of every entity is sent to that third party on every write: every note, meeting, decision, and person record. The provider’s data-handling policy, not Synapse, then governs that content. This is the explicit exception to BR-12.1, and Synapse makes it loud:
syn initprints a warning when a network provider is inherited from a config layer.- Each time a network backend is instantiated (CLI write or MCP server
start), it prints a one-time
content is sent to <provider>warning to stderr. - Synapse stores no API key itself: it reads the env var (or your config) at call time and keeps nothing.
The feature is compiled in only with --features network_backends; a
default build cannot send anything anywhere.
9. References
- ADR-014: original bundle format (v0.3 prototype).
- ADR-016: mandatory ed25519 + revocation (v0.4).
- ADR-017: experience array, skill derivation, CV format.
- AUDITABILITY.md: the broader audit story; the identity layer sits next to it.
- PROVENANCE.md:
x-ai-*stamps on AI-authored entities.
syn mcp starts a Model Context Protocol server over stdio. Any MCP client can plug in. We document Claude Code below; the wiring is the same for any client that supports stdio MCP servers.
What gets exposed
Twenty-five tools. Twelve read-only on the current workspace (incl. query, the read-only aggregation tool, SYN-183, and workspace_health, SYN-310); four read-only across opt-in workspaces (SYN-78, SYN-92 read slice); one maintenance tool (reindex, SYN-138); one audit tool (replay_proposal, client-driven: it returns replay material the agent re-runs; no server-side sampling, SYN-219); one integrity tool (verify, SYN-263: proves the memory was not silently corrupted); six write proposals routed through a human confirmation queue. Write tools never touch the workspace directly: the AI proposes; you sanction (or, under the safe auto-apply policy, low-risk reversible writes apply and are stamped auto-accepted; SYN-218).
The read contract (SYN-177/178/201/203). Every read tool returns the
uniform { "result": <payload>, "freshness"? } envelope as MCP
structuredContent (read that field directly: no prose parsing),
plus a compact-JSON text mirror for clients without structured
support. Each read tool advertises an outputSchema on tools/list,
and search_knowledge hits + get_entity attach a resource_link to
synapse://entity/{type}/{id} so a result is openable with resources/ read: no second get_entity round-trip.
Read-time hints (SYN-308). The high-value reads (search_knowledge, search_everything, workspace_overview, list_by_type, recent_changes, blame) attach a deterministic _meta sidecar, a sibling of structuredContent, so it never touches the outputSchema contract. It carries workspace (which brain answered), a precision breakdown of the SYN-304 relevance bands ({strong, good, partial}) and result_count for searches, and a next_step, the cheapest sensible follow-up (triage-then-get_entity, paginate with cursor, call workspace_health, propose an update on a low-trust blame). The agent reads _meta to judge whether results are on-topic before spending tokens, and errors carry a matching data.remediation (SYN-309, below).
Workspace-local
| Tool | Purpose | Inputs |
|---|---|---|
search_knowledge | Search the whole workspace. Hybrid (FTS + vector + graph, RRF) by default; degrades to plain FTS5 when the semantic models aren’t downloaded yet (or on a minimal core build). | query, optional type/tag/owner/limit/mode/as_of |
get_entity | Returns the frontmatter + body of one entity as {"markdown": "<full text>"}. | type, id |
get_entities | Batch read (SYN-181): many entities in one call, each optionally projected to chosen fields (fields: ["title","status","body"]) instead of full markdown. A missing/mistyped item yields a per-item error. Capped at 100. | items: [{type, id, fields?}] |
list_by_type | Lists entities of one type with optional status filter. Bounded + paginated (SYN-176): a full page means more remain. | type, optional status, limit (default 200), cursor (offset), as_of (valid-time date, SYN-223) |
project_graph | Inbound + outbound link neighbours of an entity. | type, id, optional rel |
workspace_overview | One-call digest (SYN-179): per-type counts, total, and the most-recently-updated entities: orient without one list_by_type per type. | optional recent (default 10, max 50) |
workspace_health | Agent-facing health (SYN-310): recent activity + hygiene (pending reviews, orphans, dangling, untagged) + a deterministic next_steps list, each with the exact syn command to hand the human. MCP counterpart of syn digest. | optional days (default 7) |
recent_changes | “What changed since I last looked” since an RFC-3339 instant. Returns decisions (audit log at/after since, newest first: SYN-180) and deltas (per touched entity, the field-level before/after with AI authorship: {slug, type, author, accepted_at, changes:[{field, kind, before, after}]}: SYN-240, git-derived against the commit that predates since). CLI counterpart: syn changes (SYN-256). | since, optional limit (caps decisions; default 100, max 500) |
blame | AI provenance lineage of an entity, who/what touched it, when, on whose behalf, with the deterministic trust score (SYN-240/256). MCP counterpart of syn blame. | slug |
diff | Per-field before/after of an entity over git since a ref (default HEAD); provenance keys excluded (SYN-240/256). MCP counterpart of syn diff. null when not a git repo. | slug, optional since |
query | Read-only aggregation (SYN-183, ADR-023): count entities by a whitelisted dimension, optionally filtered: compute totals out of context without paging entities. Takes no SQL; reads only entities/tags. Returns {group_by, buckets:[{value,count}], total}. | group_by (type|status|owner|priority|due|tag), optional filter (type/status/owner/priority/tag), limit (1–1000) |
verify | Prove the AI memory was not silently corrupted (SYN-263 / ADR-035): runs the integrity invariants (reindex fixpoint, audit payloads, history chain + checkpoint, supersession close) and returns {ok, report}, a signed synapse.verify-report/1. ok=false ⇒ an invariant was violated. deep adds INV-4 (governed-journal replay integrity, SYN-266 / ADR-036) + INV-13 (deterministic logical-contradiction sweep: overlap/cycle/inverted interval, guards the un-gated direct-edit path, SYN-316); rebuild lists every diverging fact on an INV-1 mismatch (SYN-269). Offline; tamper-evident, not tamper-proof. MCP counterpart of syn verify. | optional deep, rebuild (bool) |
search_knowledge mode (v0.5, SYN-88). Mirrors the CLI search
modes: "hybrid" (default, FTS + vector + graph fused with RRF),
"semantic" (vector similarity only), or "no-vector" (FTS + graph,
skips the embedding step). The semantic layer ships in the default
server (SYN-168); the vector legs go quiet: every search falls back to
plain FTS5: when the models haven’t been downloaded yet or on a minimal
core build (--no-default-features --features mcp). The hits keep the
{type, id, title, snippet} shape (plus matched_by/relevance/compatibility
when fused: SYN-304/306), wrapped in the {result, freshness?}
envelope described above; a client that does not set mode keeps the
default hybrid ranking. The independent legs (FTS + vector) run in parallel on
separate connections (SYN-306 / ADR-044); the fused order is a pure function of
the per-leg orders, so it stays deterministic. There is no separate semantic_search tool, the mode parameter delivers it without duplicating the search path.
Enrichment on accept (v0.5, BR-13). With the semantic models present,
accepting a write proposal (or any other workspace write) runs the
insertion-time enrichment pipeline inside the transaction: the new entity
is embedded into entities_vec and its mentions are linked, so it is
immediately findable by the next search_knowledge call. NER mentions
that don’t resolve queue their own create_entity_from_ner proposals for
review. Before the models are downloaded, the write still succeeds, enrichment is skipped gracefully and never triggers a download (run syn embed --all to backfill once the models are in place).
Federation (v0.2, SYN-78 + SYN-92 read slice)
| Tool | Purpose | Inputs |
|---|---|---|
list_workspaces | Every workspace the user opted in for cross-workspace queries. Identity fields (description, topics, tags, language) come from each workspace’s SYNAPSE.md. | none |
federated_search | Keyword search (FTS + graph, no per-workspace vector leg) fanned out across opt-in workspaces. Each workspace’s hits carry the same search_knowledge hit shape. Returns [{workspace, id, hits}]. | query, optional type/tag/owner/limit/workspaces |
get_entity_from | Read one entity from a specific opt-in workspace. | workspace, type, id |
get_workspace_manifest | Read the SYNAPSE.md identity card of an opt-in workspace. | workspace |
Write proposals (v0.2, SYN-60/61/62/92)
Every write tool below enqueues a proposal; it does not touch the workspace. The response carries the pending_id plus accept_command / reject_command strings the LLM can surface verbatim. The user reviews with syn pending show <id> and applies with syn pending accept <id>. See BR-12.3: MCP não escreve sem confirmação humana.
| Tool | Purpose | Inputs |
|---|---|---|
capture_fact | Propose creating a new entity (note, decision, person, project, …). | type, title, optional body/tags/fields/reason |
link_entity | Propose linking two entities with a labelled relation. | from_type, from_id, to_type, to_id, rel, optional reason |
update_status | Propose updating an entity’s status field (task done, project paused). | type, id, status, optional reason |
add_workspace_topic | Propose appending a topic to an opt-in workspace’s SYNAPSE.md. | workspace, topic, optional reason |
add_workspace_tag | Propose appending a tag to an opt-in workspace’s SYNAPSE.md. | workspace, tag, optional reason |
propose_batch | Bundle several proposals into ONE pending row the human accepts with a single syn pending accept (SYN-182). Each item is {kind, payload} where kind ∈ capture_fact/link_entity/update_status/topic_add/tag_add (note: topic_add/tag_add, the leaf-kind names) and payload is that tool’s usual fields, validated up front. Applied in order on accept (best-effort sequential, not one atomic transaction: atomicity tracked as SYN-202). | proposals: [{kind, payload}], optional reason/context |
Every write tool also accepts an optional context JSON snapshot (SYN-114) recorded with the proposal for syn audit replay.
Response shape for every write tool:
{
"pending_id": 7,
"accept_command": "syn pending accept 7",
"reject_command": "syn pending reject 7",
"kind": "capture_fact",
"summary": "note \"PKCE OAuth decision\""
}
Write tools return this object as structuredContent plus a compact-JSON text mirror (the read envelope is for read tools only). The same use cases back the CLI; there is no “second truth”.
Freshness advisory (SYN-93, ADR-011)
Every tool response that touched a workspace can carry a freshness block when the workspace’s tracked origin branch differs from the local one (or the check has timed out: Phase 2). The shape is:
{
"result": <original tool payload>,
"freshness": {
"workspace": "client-alfa",
"status": "diverged",
"behind_count": -1,
"last_check": "2026-05-25T14:00:00Z",
"advice": "git -C /Users/.../client-alfa pull"
}
}
Rules:
resultis always present (the uniform envelope, SYN-203); only thefreshnessblock is conditional; it is attached when there is something to say, and omitted forup_to_date/unknownstatuses (silence is the polite default).behind_count = -1in Phase 1 means “histories differ; the precise count lands with SYN-93b”.adviceis always a concretegit -C <root> pullcommand the LLM can surface verbatim.- The check itself is filesystem-only and rate-limited (default 30 min); the AI never pays a network round-trip for it.
Disable per-workspace with syn ws freshness off <name>. Disable globally with [freshness] enabled = false in any config layer. The MCP enrichment ignores silent_cli; that knob only governs the CLI stderr line.
Privacy gate (BR-12.4, SYN-79)
Federation defaults OFF. A workspace only appears in list_workspaces, contributes hits to federated_search, or is readable via get_entity_from / get_workspace_manifest after the user runs syn ws include <name> on it.
This is a structural invariant, not a check: every fan-out path in the codebase goes through crate::core::federation::fan_out{,_filtered}, which hard-codes Registry::list_federated() (the SQL filter is WHERE federated = 1). Non-opt-in ids passed to federated_search.workspaces are silently dropped: distinguishing “exists but not federated” from “does not exist” would leak the bit BR-12.4 is meant to keep private. The same applies to get_entity_from and get_workspace_manifest: both raise an indistinguishable entity '<name>' not found for “no such workspace” and “exists but private”.
Claude Code
The zero-friction path (SYN-288): let syn write the config for you:
syn mcp install # Claude Desktop (claude_desktop_config.json)
syn mcp install --client claude-code # project-local .mcp.json in the current dir
syn mcp install --workspace my-brain # pin a brain by name/path (SYN-300 / #287)
syn mcp install --print # print the JSON snippet, write nothing
syn mcp uninstall # remove just the synapse entry
--workspace <path|name> (SYN-300 / #287). By default the installed config
runs syn mcp with no cwd, so it only finds a brain when the client launches
inside the workspace. Pass --workspace and install bakes --workspace <ref>
into the config args, so the server serves that brain from any directory, the cross-project “query my brain from another repo” flow, with no manual editing.
install merges a synapse entry into the existing config: other MCP
servers and unrelated keys are left untouched, the prior file is backed up to
*.json.bak, and re-running updates the entry in place (idempotent). It points
command at the absolute path of the running syn binary.
Prefer to edit by hand? Add this to ~/.claude.json (or run
claude mcp add synapse …):
{
"mcpServers": {
"synapse": {
"command": "syn",
"args": ["mcp"],
"cwd": "/Users/you/brain"
}
}
}
commandis whateversynyou installed (which synto confirm).cwdis the workspace you want the server to read from.- No environment variables needed for v0.1.
Serve a brain from another project (cross-directory)
syn mcp reads the workspace from the current directory by default, so a client
launched in a different project (not itself a Synapse workspace) has nothing to
serve. To point the server at your brain wherever you are, pass --workspace
(SYN-300 P1): an absolute path, or a registered name/id from the global
catalog (syn ws list; every syn init registers there):
# by path
claude mcp add synapse -- syn mcp --workspace /Users/you/brain
# or by registered name/id: works from any project directory
claude mcp add synapse -- syn mcp --workspace my-brain
Now Claude Code in any repo answers from that brain: you don’t have to cd
into it. (This is cleaner than the cwd field above, which depends on the client
honoring a working directory; --workspace resolves the brain itself.)
Restart Claude Code and ask, in any project:
“Who is the focal contact for projeto-alfa, and which APIs do they own?”
Claude calls project_graph(type=project, id=projeto-alfa) to find Maria, then get_entity(type=person, id=maria-silva) to read her file, then surfaces the answer with the right slugs.
Other MCP clients
If your client speaks MCP over stdio, the launch command is the same:
syn mcp
Synapse also advertises resources on resources/list: the workspace manifest, the server card, synapse://capabilities (SYN-311, the self-describing vocabulary: entity types + their fields, link relations, reserved x-* keys, write-proposal payload shapes, enrichment sources; read it before proposing to avoid an invalid kind/rel/key, the same dump the CLI syn context prints), every entity, and the recent audit trail.
Synapse advertises tool schemas (input + output JSON Schema) on tools/list, each with MCP tool annotations (readOnlyHint on every read, no destructiveHint on the additive write proposals, idempotentHint on reindex) so a client can auto-allow reads and gate writes without guessing. The 25 names are search_knowledge, search_everything (SYN-226, RRF over every kind), query (SYN-183, read-only aggregation), get_entity, get_entities, list_by_type, project_graph, workspace_overview, workspace_health (SYN-310), recent_changes, blame (SYN-256), diff (SYN-256), verify (SYN-263, proves non-corruption), list_workspaces, federated_search, get_entity_from, get_workspace_manifest, reindex (SYN-138, emits Tasks progress under --features mcp_tasks), replay_proposal (SYN-136/SYN-219, client-driven: it returns replay material the agent re-runs: no server-initiated sampling, which the MCP spec has since deprecated in SEP-2577), capture_fact, link_entity, update_status, add_workspace_topic, add_workspace_tag, propose_batch.
Actionable errors (SYN-309)
A recoverable condition (unknown id/type, a missing/invalid proposal field, no workspace, a model still downloading) comes back as a JSON-RPC invalid_params error whose data carries a stable code (the i18n key), a category (user/transient), recoverable/retryable flags, and a remediation string naming the exact next step (e.g. “unknown type: read the synapse://capabilities resource for the registered types”, or “no such entity: discover ids with search_knowledge”). The remediation is also appended to the human-readable message. Genuine faults (a corrupted audit trail, a migration mismatch) stay internal_error but are still annotated, so a client never has to parse prose to know whether the agent can retry. Internal paths and SQL are never leaked (ADR-023 posture).
Locking down what the AI sees
The server’s cwd workspace is always fully readable through the local read tools (search_knowledge, search_everything, get_entity, get_entities, list_by_type, project_graph, workspace_overview, workspace_health, recent_changes, plus the read-only query aggregation). Other workspaces are only reachable when the user has opted them in:
cd ~/work-brain && syn ws include work-brain # join the federated pool
cd ~/client-acme-brain && syn ws include client-acme-brain
syn ws exclude work-brain # leave anytime
The federation tools (list_workspaces, federated_search, get_entity_from, get_workspace_manifest) only ever see workspaces marked federated = 1. Workspaces left at the default (private) never leak through the server, even if the LLM literally asks for them by name. See the Privacy gate section above.
The simplest segmentation is still one workspace per audience:
~/brain/ ← personal: notes, decisions, drafts
~/work-brain/ ← work: APIs, stakeholders, tasks
~/client-acme-brain/ ← per-client
Run one syn mcp per cwd (Claude Code spawns one process per mcpServers entry); opt only the workspaces you trust into the federated pool.
Inspect the server by hand
Useful when something is off. The protocol is JSON-RPC over stdio:
cd ~/brain
{
printf '%s\n' \
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"manual","version":"0"}}}' \
'{"jsonrpc":"2.0","method":"notifications/initialized"}' \
'{"jsonrpc":"2.0","id":2,"method":"tools/list"}' \
'{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"search_knowledge","arguments":{"query":"pagamentos"}}}'
sleep 1
} | syn mcp | jq .
You’ll see serverInfo + the full tools/list schema set (25 tools, each read carrying an outputSchema) + a search_knowledge result whose structuredContent is the {result, freshness?} envelope, with resource links on the hits. With the semantic models present the result is hybrid-ranked; add "mode":"semantic" to the arguments for vector-only.
Shipped in v0.5
| Capability | Ticket |
|---|---|
Hybrid ranking in search_knowledge + the mode parameter (FTS + vector + graph, RRF) | SYN-86 / SYN-88 |
| Insertion-time enrichment on accepted proposals (embed + NER + link) | SYN-160 / SYN-161 |
Network ls-refs freshness (precise behind_count, unreachable fallback) | SYN-93b |
Prompts
Nine named prompts (the synapse: namespace), each a deterministic function of (name, args, workspace): no LLM round-trip to assemble:
synapse:capture-decision: guidedcapture_factflow for a decision note.synapse:daily-review: yesterday’s accepted proposals + open tasks.synapse:weekly-brief: audience-tailored weekly briefing.synapse:proposal-contract(SYN-252): the proposal-quality contract an agent should read before a capture session.synapse:onboard-workspace(SYN-312): first-run orientation: read the capabilities + overview, confirm the self-identity, learn the propose→accept gate. Start a fresh session here.synapse:orient(SYN-312): the cheapest-read decision table (which read tool for which question) as a fetchable prompt.synapse:catch-me-up(SYN-312): resume with a delta viarecent_changessince a given instant, not a full re-read.synapse:resolve-contradiction(SYN-312): when acapture_factreturns acontradictssignal, walk the supersede-don’t-duplicate path (x-supersedes).synapse:capture-and-verify(SYN-312): the full governed-write loop: propose → hand over the accept command → offersyn verify.
Contradiction detection (SYN-242)
At propose-time Synapse checks, deterministically and on structured fields, whether a write contradicts an accepted fact: same subject + predicate, different object: an update_status that flips a field to a different value, or a link_entity that re-points the same edge to a different relationship. A contradiction is never “low-risk reversible”, so it is forced to the human gate even under auto_apply = safe, and the write tool’s response carries a contradicts: {subject, predicate, existing, proposed} signal. Resolve by superseding the existing fact (x-supersedes, SYN-223): invalidation, not duplication, not by adding a near-duplicate. Free-text (NLI) contradiction and same-subject detection across separate captures (identity resolution) are deferred to v0.11 (SYN-245/254).
Proposal-quality contract (SYN-252)
The differentiator of governed memory is that a human accepts each fact, so the scarce resource is the reviewer’s attention. A good proposal respects:
- Atomicity: one subject–predicate–object per proposal; never bundle unrelated claims.
- Observation date:
x-valid-fromis when the fact became true in the world, not when you learned it (transaction-time comes from git; don’t duplicate it). - No echo: search first; propose deltas, not duplicates of what’s already there.
- Supersede, don’t duplicate: a changed fact carries
x-supersedes: <old-id>(the old interval is closed on accept: invalidation, not deletion, SYN-223). - No meta-extraction: facts about the domain, not about the conversation.
- Attribution: cite the source; provenance (
x-ai-*) records who/when automatically. - Typed over prose: prefer a typed entity + indexed fields when the domain has structure (
syn types).
Agents fetch the canonical copy via prompts/get synapse:proposal-contract; this section is the human-readable mirror.
What doesn’t exist yet (and when)
| Capability | Ticket | Phase |
|---|---|---|
| HTTP/SSE transport (today: stdio only) | - | backlog |
When syn mcp hands back a (no hits) response, that’s Lil Syn dozing on an empty index. The fix is usually one of: a missing tag, a typo, or just not enough indexed yet. Run syn search <term> from your shell to see if you find it before blaming the AI.
TL;DR: 0.x → 1.0 is a no-op for your data. The Markdown is the source of truth and the index is disposable, so there is nothing to migrate by hand: upgrade the binary and run
syn reindexif anything looks off. v1.0 freezes the public surface under semver; this page says exactly what that covers.
What “1.0 stable” means
The 1.0 stability promise covers the Synapse public surface under semantic versioning (BR-11.2):
- the Markdown + frontmatter schema (the on-disk format your notes live in),
- the CLI (
synsubcommands and flags), - the MCP tool surface (the 24 tools an agent sees).
A breaking change to any of these requires a major bump (and, for commits, a
! per the conventional-commit policy). Additive changes (new optional
frontmatter keys, new commands, new MCP tools) are minor.
What the promise does not cover (documented, not a broken promise):
- The transitive ML runtime (
ort/ONNX Runtime). v1.0 ships on anortrelease candidate, transitively forced by the embedding/NER stack, with a tracked exit to the final release: see ADR-038. The semantic models download on consent and the index is disposable, so any runtime change is re-derivable, never silent data loss. - The SQLite index schema (
.synapse/index.db). It is disposable: see below, so it is deliberately not part of the semver surface.
The index is disposable (so migrations are low-stakes)
The SQLite index is a pure function of your Markdown (BR-1.4 / BR-7.2.1). If an upgrade changes the index format, you do not migrate data: you rebuild it:
rm -rf .synapse/index.db && syn reindex # or: syn reindex --force-rebuild
No user data lives only in the index, so the worst case of any index-schema
change is a rebuild. Index migrations are expand-only (new columns are
nullable and populated by reindex, never required at migration time), and
applied migrations are checksum-locked: see
BR-7.2.1. You can prove the rebuild is faithful
with syn verify (syn verify --rebuild names any
divergence).
Upgrading from 0.x
- Update the binary (see
docs/INSTALL.md; thesynapse-checkAction pins a version for CI). - Open a workspace. If
syn doctororsyn verifyflags anything, runsyn reindex. That is the entire migration. - (Optional) MCP clients:
syn mcp installnow wires Synapse into your client config for you (seedocs/MCP.md).
There is no frontmatter rewrite, no manual schema step, and no data export/import needed to move from 0.x to 1.0: the format was stable in practice across 0.x and is now stable by promise.
Reverse migrations
Forward migrations are the norm; reversal is not generally needed because the
index is disposable (drop it and reindex on the older binary). A basic
schema-reverse path for the rare in-place-downgrade case is tracked as a v1.1
item (SYN-105). Until then: to downgrade, check out the older syn, delete
.synapse/index.db, and reindex.
In one line: the local embedding and NER models download only with your consent. Once the required files are cached and local backends are selected, those inference paths work offline. Explicit hosted providers and other external integrations have separate network behavior.
The default binary ships the semantic layer compiled in (opt-out, ADR-008 §SYN-168), but the model weights are not bundled. This page is the single source of truth for what gets downloaded, from where, to where, how big, and what it means for privacy, licensing, and compatibility.
Which models
Two models, two jobs. Both run locally on CPU via a statically-linked ONNX Runtime: no GPU, no Python, no separate runtime install.
| Job | Default model | Dim / output | Cached size | Languages | Source repo (Hugging Face) | License¹ |
|---|---|---|---|---|---|---|
| Embeddings (vector search) | bge-m3 | 1024-dim | ~2.2 GB | multilingual (en, pt, es, fr, de, it, ja, ko, zh) | BGE-M3 (BAAI), fetched by fastembed from Hugging Face | MIT |
| NER (entity discovery) | gliner-multi | spans | ~350 MB | multilingual | onnx-community/gliner_multi-v2.1 (onnx/model_quantized.onnx) | Apache-2.0 |
Total default footprint: ~2.5 GB (one-time download, shared across every workspace on the machine). The binary itself only grows ~25 MB (the ONNX Runtime).
Quality vs speed: the embedding profile (SYN-237)
The default bge-m3 is fp32: maximum recall, ~2.2 GB. The same model quantised to int8 is a lighter, faster, lower-memory alternative at a small recall cost. The guided syn init asks which you want (Quality is the recommended default); you can also set it directly:
| Profile | embedding_model | Dim | Cached size | Notes |
|---|---|---|---|---|
| Quality (default) | bge-m3 | 1024 | ~2.2 GB | fp32, maximum recall |
| Speed | bge-m3-int8 | 1024 | ~560 MB | int8 (gpahal/bge-m3-onnx-int8), lighter & faster, small recall cost |
Both are multilingual and 1024-dim (the same vector space) so switching is just a re-embed (syn embed --all), never a schema change. With bge-m3-int8 + gliner-multi, the multilingual footprint drops to ~900 MB.
Lighter, English-only opt-downs
Set these in .synapse/config.toml ([enrichment]) if multilingual quality or disk is not worth ~2.5 GB to you:
| Job | Opt-down model | Dim | Cached size | Notes |
|---|---|---|---|---|
| Embeddings | bge-small-en-v1.5-q | 384 | ~65 MB | English-only, quantised, ~5× faster |
| NER | gliner-base-en | spans | ~260 MB | English-only, quantised (onnx-community/gliner_base) |
English opt-down total: ~325 MB. See CONFIGURATION.md for the exact [enrichment] keys.
¹ Licenses are the upstream model-card licenses at time of writing: verify them on the linked model card for your use case; you (not Synapse) are responsible for complying with each model’s terms. ONNX Runtime is MIT-licensed.
Where the models come from
Both models are fetched over HTTPS from the Hugging Face Hub (huggingface.co / its CDN):
- Embeddings: the
fastembedcrate resolvesBGE-M3to its Hugging Face repo and downloads the ONNX file + tokenizer. - NER: Synapse calls the Hugging Face hub client (
hf-hub) directly foronnx/model_quantized.onnx+tokenizer.jsonfrom the repo above.
The fetch is anonymous: no Hugging Face account or token is needed for these public repos. As with any HTTPS request, Hugging Face’s servers see your IP address and a user-agent for the duration of the download; no information about your workspace, notes, or queries is ever sent: Synapse only issues GETs for model files.
Both transports use rustls (not system OpenSSL), so musl/Alpine and minimal containers work without extra system libraries.
Where they’re cached
Downloads land in a per-OS cache directory and are reused across every workspace on the machine (sharing is intentional: you download BGE-M3 once, not once per brain):
| OS | Default model cache |
|---|---|
| macOS | ~/Library/Caches/synapse/models/ |
| Linux | ${XDG_CACHE_HOME:-~/.cache}/synapse/models/ |
| Windows | %LOCALAPPDATA%\synapse\models\ |
Inside it: …/models/fastembed/… (embeddings) and …/models/gliner/<repo>/… (NER). A small .synapse-ready-<model> sentinel marks a model as fully downloaded; that file is how a write or search knows the model is present without trying to fetch it (SYN-168).
Override the location (precedence: config → env):
# .synapse/config.toml (or any config layer)
[enrichment]
model_cache_dir = "/data/synapse-models"
SYNAPSE_MODEL_CACHE=/data/synapse-models syn embed --all
Point both at the same shared path (e.g. an NFS mount or a team image) to provision a fleet from one download.
When they download (consent, never ambush)
The models are pulled only on an explicit action: never silently on your first syn project add:
syn initonboarding: on an interactive terminal, a freshsyn initasks “Download the models now? [Y/n]”. Decline and it explains exactly what you keep and lose; you can change your mind later.syn embed --all: the explicit “turn it on now” command; downloads on first run, then embeds the whole workspace.
Until a model is present, Synapse degrades gracefully: writes still save (without embeddings), and syn search falls back to fast keyword (FTS5) + graph. Check the state any time; this command is read-only and never triggers a download:
syn embed --check # … models_ready: false → not downloaded yet
Offline & air-gapped use
With the required model files cached and a local backend selected, embedding and NER inference run on device without sending note/query text to a model provider. Changing models or explicitly refreshing an incomplete cache can require another consented download.
This scope covers the local model path. Explicit hosted providers, remote source adapters and an external MCP client or host have separate network behavior. An air-gapped deployment must select local providers and configure those other paths accordingly.
For machines that must never reach huggingface.co:
- Pre-seed the cache. Download the models on a connected machine, then copy the cache directory (see table above) to the target, or point
SYNAPSE_MODEL_CACHEat a pre-populated shared path. The.synapse-ready-*sentinels travel with it. - Run without models. Set
[enrichment] mode = "off"(orSYNAPSE_ENRICHMENT_MODE=offper command) to skip the pipeline entirely, and usesyn search --no-vectorfor keyword + graph search with zero model load.
Enrichment is one-way: reindex drops inferred links (SYN-271)
Enrichment edges (the inferred mentions / references graph links and the vector embeddings) are produced at write time, not by reindex. reindex rebuilds the deterministic projection of your Markdown (entities, tags, structural links from frontmatter); it does not re-run the linker or the models. Two consequences:
- A
reindex --full(a from-scratch rebuild) drops the inferredmentions/referencesedges until they are re-derived. - Entities created before you downloaded the models have fewer inferred links than those created after.
This is a disposability property, not an integrity problem: syn verify deliberately excludes enrichment edges from its canonical dump (they are model/heuristic-derived, not a pure function of reindex(markdown)), so a rebuilt index still verifies clean.
To restore the heuristic inferred links (slug / @mention / URL: deterministic, model-free, offline) after a --full rebuild:
syn reindex --relink # re-derive mentions / references edges; never re-runs NER, never creates entities
--relink is idempotent and queues no proposals: it only re-creates links to entities that already exist. The NER-derived mentions:<kind> edges come back on the next write of each entity (or a re-embed), since they need the model.
What touches the network (and what never does)
| Path | Local backend (default) | Hosted backend (opt-in) |
|---|---|---|
| Model download | Explicit HTTPS GET from Hugging Face when required assets are absent or change | No local embedding model required |
| Embedding a write or query | Cached local-model inference does not send entity/query text to a model provider | Sends the supplied entity/query text to the selected provider |
Local inference is the default (BR-13). Under that model path, Synapse does not send your note, entity or query text to an embedding provider. This is a boundary of local model execution, not a blanket promise about hosted backends, configured remote source adapters or the application consuming MCP results. Synapse has no built-in telemetry.
The hosted backends (network_backends feature: OpenAI / Voyage) are opt-in and require
an explicit [enrichment] provider. Enabling one sends text to that provider when the
configured embedding operation runs, including supported writes and semantic queries.
syn init warns when a network provider is active. See IDENTITY.md and
CONFIGURATION.md for configuration and egress details.
Compatibility
- CPU-only. The models run on the CPU via ONNX Runtime; no GPU or CUDA is required or used.
- Platforms. macOS (Apple Silicon only: the ONNX Runtime has no Intel-macOS build, so
x86_64-apple-darwinis unsupported; ROADMAP SYN-174), Linux (x86_64 + aarch64), Windows (x86_64). - Linux glibc ≥ 2.38. The prebuilt ONNX Runtime that ships with the semantic layer is linked against glibc ≥ 2.38 and GCC 13+ libstdc++. Practically: Ubuntu 24.04+, Debian 13 (Trixie)+, Fedora 39+ and equivalents. Older distros (Ubuntu 20.04/22.04, Debian 12) lack the symbols (
__isoc23_strtol,__cxa_call_terminate) and the binary will not run there: build a core binary (--no-default-features --features mcp) for those, or upgrade the base. (This is also why the release pipeline builds the Linux artifacts onubuntu-24.04runners: SYN-168.) - Disk. Budget ~2.5 GB for the multilingual defaults (~325 MB for the English opt-downs) in the cache directory, plus ~25 MB in the binary.
- First use and latency. Download time depends on the selected models, cache and connection. Inference time depends on hardware, text length and batch size. Measure the chosen configuration before setting a latency target.
Managing the cache
syn embed --check # cache state (read-only; models_ready flag)
syn embed --all # download (if needed) + embed everything; idempotent
syn embed --reset-cache # drop every vector + meta row (forces a clean re-embed)
Switching models. Change embedding_model / ner_model in .synapse/config.toml, then syn embed --all: it re-embeds only the rows whose stored model id no longer matches. To reclaim disk from a model you no longer use, delete its sub-directory under the model cache (see table) by hand; Synapse never deletes downloaded weights on its own.
See also
- INSTALL.md: feature flags, opting out, the footprint table.
- CONFIGURATION.md: the
[enrichment]block, cache-path override, env vars. - ADR-008: why BGE-M3 + GLiNER + sqlite-vec, and the opt-out amendment.
- IDENTITY.md: the opt-in hosted providers and their privacy trade-off.
The linear path: what you run, in order, and exactly what you’ll see, from a freshly-installed syn to a workspace your AI can reason about. In a hurry? QUICKSTART is the same journey as a six-command cheat-sheet. Going deep on the AI loop? That’s TUTORIAL.
Prerequisite:
synis on yourPATH. If not, INSTALL.md is one paragraph (and notes the Linux glibc ≥ 2.38 requirement for semantic search).
Step 1: Guided setup: language, defaults & who you are (syn setup)
syn setup writes your global config once per machine. As of v0.6 it is
guided: after locale + output format it captures a short self-profile (name, birthdate (optional), profession, employers) saved to a global
profile.toml (separate from config.toml). Press Enter to accept a default
or skip an optional field:
$ syn setup
◉───◉
│ ╲ │
◉───◉───◉
│ ╱
◉
locale [en-US]: ← your language: en, pt-BR, es-MX, …
output format [text|json] [text]:
Full name: Ada Lovelace
Birthdate (YYYY-MM-DD) [skip]: 1815-12-10
Profession / headline [skip]: Mathematician
Employers (comma-separated) [skip]: Analytical Engine Co.
✓ identity saved for Ada Lovelace → ~/.../synapse/profile.toml
Language precedence is --lang > SYNAPSE_LANG > system locale > en. The
profile is captured once and seeded into every workspace as your self
person (Step 5): “capture once, seed everywhere” (BR-14, ADR-020).
Compulsory on a fresh interactive run, never blocking otherwise (BR-14.1).
syn initruns this guided setup inline the first time on a TTY. In a non-interactive context (CI, pipe,--no-input,SYNAPSE_AGENT) it falls back to silent defaults and skips identity: automation never waits on a prompt. Override the profile path withSYNAPSE_PROFILE.
Step 2: Create your first workspace (syn init)
A workspace keeps current knowledge in Markdown with operational metadata beside it. Create one:
$ syn init ~/brain
workspace initialised at /Users/you/brain
seeded manifest /Users/you/brain/SYNAPSE.md
registered in workspace registry (id: brain-a1b2c3)
What just happened:
- the canonical folders (
projects/ people/ entities/ apis/ tasks/ notes/) and the.synapse/metadata directory were created. The search projection is rebuildable; the whole directory is not a disposable cache; SYNAPSE.md: the workspace’s identity card: was seeded. Its name defaults to the folder name (brain); open it and replace the “Describe what this workspace is about…” brief with one concrete paragraph. That text is what the AI reads to route questions across workspaces;- the workspace was registered so federation and
syn wscan find it. It starts private and non-shareable: nothing is shared until you opt in.
Keep .synapse/ with the selected workspace backup. It can hold configuration, pending
proposals/review guards, durable transaction recovery data, retained source images, receipts
and signing/trust policy. Markdown can rebuild derived retrieval data, but cannot recreate
lost pending rows or missing historical evidence. Signing keys may use separately configured
paths and need their own preservation policy. See architecture before
removing an index or metadata directory.
(Messages appear in your configured language: the example above is en; on pt-BR you’d see “workspace inicializado em …”.)
Step 3: The semantic models (one-time, with your consent)
Right after syn init, on a fresh interactive setup, Synapse asks once whether to download the on-device search models:
Semantic search is built in: Synapse can find notes by meaning (not just
keywords) and grow the graph from people/tools it spots in your text. It
needs a one-time ~2.5 GB model download (cached, shared across every
workspace on this machine).
Download the models now? [Y/n]
- Yes → downloads ~2.5 GB once (shared across all your workspaces), then meaning-search is live.
- No / skip → Synapse runs as a keyword + graph brain. You keep
syn search,syn graph, links, MCP: everything except meaning-search and automatic entity discovery. Turn it on any time withsyn embed --all.
Either way nothing blocks, and nothing downloads silently. Check the state: read-only, never triggers a download:
$ syn embed --check
entities 0
embedded_total 0
models_ready false ← not downloaded yet
Footprint, cache location, offline/air-gapped use, privacy and licenses: MODELS.md.
Step 4: Capture what’s in your head
syn project add "Client Alpha" --status active
syn person add "Maria Silva" --email maria@company.com --job-title "Tech Lead"
syn person link maria-silva --project client-alpha --role focal
syn note add "Why we picked PKCE" --kind decision --project client-alpha --tag auth
Each prints what it created (project "Client Alpha" created (id: client-alpha)). Slugs derive automatically; the YAML on disk is plain and hand-editable. In an acknowledged
governed workspace, external edits require explicit reconciliation; reindexing does not
approve them. A person exists once and is referenced by every project they touch.
Step 5: Your identity & CV
Synapse can carry a portable, signed record of your work across employers.
The anchor is one person entity that represents you, flagged
self: true. As of v0.6 you don’t create it by hand: if you completed the
guided setup (Step 1), syn init already seeded this self person into
the workspace from your global profile (name, birthdate, headline, and your
employers as linked entity(kind=org) nodes). Confirm with:
syn person show <your-slug> # self: true, birthdate, headline, affiliations
No profile yet (e.g. a non-interactive install)? Run
syn setupto capture it: the nextsyn initseeds theselfperson. Or setself: trueby hand on the entity that represents you. (cv/identity exportwill tell you to do exactly this if noselfperson exists.)
With your self person in place:
syn identity keygen # ed25519 keypair for signing the bundle
syn experience add "Tech Lead, Payments" --target project/client-alpha --role tech-lead
syn cv export # deterministic Markdown CV from the graph
“Who you are” is captured once by the guided
syn setup(name, birthdate, profession, employers → globalprofile.toml) and seeded as exactly oneselfperson per workspace onsyn init(BR-14 / SYN-171/172).cv/identity exportrequire that singleselfperson and point you atsyn setupif it’s missing. See IDENTITY.md.
Step 6: Search it, and hand it to your AI
syn search alpha # keyword (FTS5): fast, exact-term
syn search "the payments decision" # hybrid (if models are on): matches meaning
syn graph project client-alpha # walk the links
syn mcp # expose the workspace to Claude Code over stdio
Point Claude Code at the workspace so it can retrieve your recorded facts and their context. Check its citations and conclusions; access to stored knowledge does not guarantee a correct answer. The full human + AI loop (propose/accept/audit, federation and freshness) is TUTORIAL.md.
You’re set
| Want to… | Go to |
|---|---|
| The fast command cheat-sheet | QUICKSTART.md |
| The full AI workflow (MCP, propose/accept, federation) | TUTORIAL.md |
| Common workflows (CSV import, weekly review, briefings) | RECIPES.md |
| Everything about the semantic models | MODELS.md |
| Config, locales, env vars | CONFIGURATION.md |
If something looks off, syn doctor is always your first stop.
For evaluators and adopters: this is the long-form argument behind one line in the README: that Synapse can prove specific non-corruption properties of an AI’s memory, offline, from your own git, rather than estimate them with a benchmark. It cites the command (
syn verify) and the decisions (ADR-035/036/037) that make the claim true, and it states: plainly, > where the proof stops. The honesty is the point: a tool that says “tamper-evident, not tamper-proof” is worth more than one that hand-waves “cryptographically secure.”
Current implementation boundary (2026-10-12)
At 3ceddd0, verification covers the documented deterministic projection, not universal
knowledge correctness. INV-5 validates declared supersession closure; it does not detect every
unrecorded historical edit. INV-13 checks overlapping intervals, cycles and inverted intervals,
not general prose contradiction. INV-8 is Skip because receipts are export-on-demand; a basic
report permits skipped checks and can be unsigned if no key is available. ADR-034 reports commit
signature presence; cryptographic commit/allowlist verification remains SYN-261.
The trust ladder below describes architectural assurance goals; L4 is not fully enforced in the current build. ADR-051, ADR-052 and ADR-053 record the extension path, with implementation issues. These designs do not add runtime guarantees by themselves.
1. The problem: AI memory drifts silently
An assistant with long-term memory accumulates facts. Over a long enough horizon, some of those facts get silently overwritten, contradicted, or mangled, and the failure is invisible, because the memory keeps answering confidently. The field’s published answer to this is better auto-correction plus judge-scored benchmarks: run a suite, have an LLM grade whether the memory stayed coherent, report a percentage. (HaluMem, for instance, measured auto-update correctness below 26%.)
A judge score is an estimate. It tells you, probabilistically, that memory tended to stay correct on a sample. It cannot hand you a specific, checkable guarantee about your workspace right now, it depends on a model’s judgement, and it cannot be reproduced bit-for-bit by a skeptical third party.
2. The thesis: prove three integrity classes, offline, per-fact
Every integrity system in the literature proves one of three things:
- (A) Content integrity: this artifact is the one that was attested (a hash or signature over the bytes).
- (B) History integrity: the append-only log was not reordered, truncated, or rewritten (a consistency proof).
- (C) Derived-state integrity: the output was reproducibly derived from the input (a deterministic rebuild).
Synapse is structurally positioned to prove all three at once, offline, per fact: because the truth is your Markdown in your git, and the SQLite index is a pure function of it. That last property is the whole game: the index is a reproducible build of your files. Delete it, rebuild it, and you must get the same logical state back; if you don’t, something is wrong and the tool can say exactly which fact diverged.
This user-held source makes independent rebuild checks practical. A hosted or self-hosted system can adopt equivalent evidence mechanisms using exported sources, snapshots, authenticated logs and deterministic projections; Markdown is a useful implementation choice, not an exclusive prerequisite.
3. The mechanism: syn verify
syn verify assembles primitives Synapse already ships (audit verify +
reindex + at/--as-of) and reframes them as proofs, each contributing
one line to a signed, reproducible report. The same core::verify::run backs
both doors: the CLI command and the MCP verify tool (docs/COMMANDS.md,
docs/MCP.md).
The canonical dump (ADR-035). A raw .db is not byte-deterministic: rowids,
FTS internals, and float vectors vary run to run. So verify never hashes the
database file. It computes a canonical logical manifest: ordered rows, with
the non-reproducible surface (FTS, pending queue, reindex ledger, vectors,
mentions:* enrichment) excluded or pinned to model@precision. That manifest
is byte-stable across calls and across a reindex --full: that stability is
the foundation everything else stands on.
The invariants checked on a default run:
| Invariant | Class | What it proves |
|---|---|---|
| INV-1 reindex fixpoint | (C) | digest(reindex(markdown)) == digest(live index) over the canonical dump, the index is a reproducible build of your files. The keystone. |
| INV-2 content integrity | (A) | every fact’s recorded payload_sha256 matches its Markdown; no orphan facts, no orphan audit rows. |
| INV-3 history integrity | (B) | the audit hash-chain + signed checkpoint are intact (Tier-2), or the git DAG + per-author monotonic segments are (Tier-1). |
| INV-5 supersession closure | (A/B) | declared successors have closed predecessors; changes without declared supersession are outside this check. |
| INV-8 receipts | (A) | currently skipped: there is no stored-receipt set to verify. |
Opt-in deepenings:
--deepadds INV-4 (replay determinism): replaying the accepted-decision journal in a scratch index is deterministic. (See §5 on why this is framed as determinism, not store reconstruction: that distinction is deliberate and honest.) It also adds INV-13 (logical coherence, SYN-316): a deterministic, model-free sweep for contradictory facts (overlapping valid-time values for one field, supersession cycles, inverted intervals). This is the one check that catches a corruption introduced by a direct Markdown edit that bypasses the gate: INV-1 proves the index is a faithful build of the Markdown, but if the Markdown itself was hand-edited into a contradiction, only INV-13 sees it.--rebuildmakes INV-1 diffoscope-style: on a mismatch it doesn’t just say “diverged,” it names every diverging fact. “Your memory is a reproducible build of your files, and if it isn’t, here is exactly which fact broke.”--since <witness>adds INV-12 (offline consistency): it proves your audit history is an append-only extension of a previously published witness, using git ancestry + segment-prefix stability, with no remote and no network (ADR-037).
The proof artifact. A signed synapse.verify-report/1, shaped like an in-toto
Statement: {subject:{workspace, git_ref, inputs_hash}, predicate:{tool_version, invariants:[{id,status,evidence}], checkpoint, witness_status, ts}, sig, pubkey}.
When an identity key is available, it is signed with the existing ed25519 identity
(reusing the SYN-247 receipt path); otherwise signature/public_key remain absent. It is reproducible: anyone with your repo at git_ref
re-runs syn verify and gets a bit-identical inputs_hash and the same verdicts.
Two further integrity properties (port parity (CLI ≡ MCP for every read) and temporal correctness (
at <ref> -- search --as-ofreconstructs the state that held)) are proven by the differential and property-based test suites (SYN-264/265), not as lines in the per-workspace report. They are invariants of the build, checked in CI, rather than of your workspace at verify time.
4. The ladder: L1 → L4
A workspace earns a higher rung as its trust root strengthens. The ladder is framing for “how strong is this proof,” borrowed in spirit from SLSA; it is not a network service:
- L1: Audited. Append-only audit trail exists; decisions are recorded.
- L2: Hash-chained. The trail is self-chained and an ed25519 checkpoint is signed; local tampering is evident.
- L3: Git-witnessed. The workspace is versioned; integrity rides git’s
Merkle DAG and a published witness can catch a history rewrite (
--since). - L4: Signed-identity (target). Genuine signature validation against known, non-revoked identities would attest authorship (ADR-030). ADR-034 only ships posture; SYN-261 and ADR-053 track enforcement.
5. The honest ceiling
A proof you can trust is a proof that states its own limits. Synapse’s are written into the ADRs, not buried:
-
Tamper-evident, not tamper-proof (ADR-037). A lone local clone cannot detect its own owner’s rewrite: there is no witness to contradict a history that was rewritten and re-signed end to end.
syn verifytherefore prints its trust root: local-only, checkpoint-witnessed, or remote/published-witness. A tracked remote, or a publishedsyn audit witness, is what lets a third party hold the owner to their history. This is exactly where competitors say “cryptographically secure” and stop; Synapse says where the evidence ends. -
Replay is determinism, not store reconstruction (ADR-036). It is tempting to claim “replay the audit log and you get the store back.” For a state-based, hand-editable Markdown store with a hash-only log and a wall-clock, that reconstruction is unsound, and ADR-036 says so explicitly. So INV-4 proves the weaker, true thing: replay is deterministic (pinned via a clock seam), and reconstruction of state is the job of INV-1/INV-7 (rebuild from the files), not of replay.
verify --deepis a bounded journal-integrity check, not a store-from-log rebuild. Stating this is what keeps the keystone claim (INV-1) credible. -
Canonicalisation is the one real prerequisite. Because the database isn’t byte-deterministic, the entire argument rests on the canonical logical manifest (ADR-035) being stable and complete. That is a property to keep tested, not assume.
-
Current local baseline. No mandatory Rekor/Fulcio, OIDC or Trillian witness-net, no runtime model-checker, no RDF/OWL, no IPFS/CID. Synapse adopts the proofs from that literature (Merkle DAG, consistency proofs, reproducible-build comparison, in-toto attestation shape) and runs them over git and ed25519 it already has: offline, dependency-light, deterministic over ML.
6. Interoperability and evidence
User-held content, explicit acceptance history and rebuild checks are useful in both standalone and embedded use. Other systems may expose snapshots or authenticated event logs through a provider adapter. Describe the evidence each provider supplies instead of assuming that a hosted system cannot produce it. Synapse complements existing knowledge/retrieval tools.
The assurance boundary is what the report actually checks, from which trusted baseline, with which signer/authority/witness evidence. Proposed transition journals, stored receipts and strict profiles improve that boundary; probabilistic prose diagnostics remain separate.
See also
docs/COMMANDS.md: thesyn verifysurface (--deep,--rebuild,--since) andsyn audit witness.docs/AUDITABILITY.md: the audit trailsyn verifychecks, and the EU AI Act Article 50 mapping.- ADR-035: canonical logical dump + signed report schema.
- ADR-036: replay determinism vs. store reconstruction (the honest reframe).
- ADR-037: offline witness + the tamper-evident ceiling.
docs/researchs/2026-06-16-provable-non-corruption-syn-verify.md: the research brief this whitepaper consolidates.
For compliance reviewers: every entity created or mutated by an accepted MCP proposal carries six
x-ai-*frontmatter fields naming the source, the decision trace, the model, the prompt hash, the accept time, and the accepter. The metadata is C2PA-aligned, lives on the markdown itself, and isgit diff-readable. The escape hatch is[provenance] inline = false; the audit trail in.synapse/audit.ndjsonkeeps the same metadata regardless.
1. The six fields
After syn pending accept <id> of a capture_fact or
update_status proposal, the resulting entity carries:
x-ai-source: mcp:capture_fact
x-ai-decision-trace-id: pending:7
x-ai-model: anthropic/claude-opus-4-7
x-ai-prompt-hash: sha256:f8e2c1aa…
x-ai-accepted-at: 2026-05-25T15:30:00Z
x-ai-accepted-by: romulo@febasi.com.br
| Field | Meaning |
|---|---|
x-ai-source | How the proposal entered the pending queue. Conventional prefixes: mcp:, cli:. Examples: mcp:capture_fact, cli:capture (SYN-124). |
x-ai-decision-trace-id | Stable trace identifier. v0.3 uses pending:<row-id> because the row is monotonic per workspace and matches what syn pending show shows. |
x-ai-model | Best-effort model id. Set when the MCP client carries x-ai-model or model in the optional context snapshot field of the write tool. Omitted when unknown. |
x-ai-prompt-hash | sha256:<hex> of the canonical proposal payload at enqueue time. Identical to the payload_sha256 field of the matching audit.ndjson row: they link the file to the trail. |
x-ai-accepted-at | RFC-3339 timestamp of the acceptance. |
x-ai-accepted-by | Identifier of the accepter: [provenance] accepted_by config wins; falls back to $USER; finally to "unknown". |
The fields live under the x-ai- namespace so existing YAML readers,
the doctor lint (BR-9.1), and any third-party tool treat them as
forward-compatible extensions. BR-3.4 preserves insertion order, so
the stamps land after the caller-supplied per-type fields and read
as a trailing banner.
2. What does NOT carry stamps
Only mutators that produce or modify a markdown entity stamp it:
capture_fact→ stamps the new entity.update_status→ stamps the existing entity in place.
Mutators that touch auxiliary data do not stamp anything:
link_entitymutates thelinkstable.add_workspace_topic/add_workspace_tagmutateSYNAPSE.md, the manifest is not an entity in the BR-1.4 sense and the attribution lives in the audit trail instead.
The audit trail records every decision regardless of whether the markdown got stamped.
3. The escape hatch: inline = false
Workspaces in regulated industries that need clean entity files for publication / export can disable inline stamping while keeping the audit trail intact:
[provenance]
inline = false
accepted_by = "compliance-team@org" # explicit override of $USER
When inline = false:
- The five (or six)
x-ai-*fields do not land on the entity. .synapse/audit.ndjsonstill records the same metadata.syn audit show <id>is the single source of truth for the decision.
4. Worked example
Starting workspace contains nothing. The user via Claude Code captures a decision:
You: "Capture a decision: we picked PKCE for OAuth on Cliente Alfa. Tag it `auth`."
Claude calls capture_fact with type=note, kind=decision,
title=PKCE for Cliente Alfa OAuth. The proposal lands at pending:1.
The user reviews:
$ syn pending show 1
id 1
kind capture_fact
status pending
proposed_at 2026-05-25T15:00:00Z
source mcp:capture_fact
reason conversation 2026-05-25, decision-making call
payload:
{
"type": "note",
"title": "PKCE for Cliente Alfa OAuth",
"body": "We picked PKCE for OAuth on Cliente Alfa…",
"tags": ["auth"],
"fields": {"kind": "decision"}
}
syn pending accept 1 writes:
---
type: note
id: pkce-for-cliente-alfa-oauth
title: PKCE for Cliente Alfa OAuth
kind: decision
x-ai-source: mcp:capture_fact
x-ai-decision-trace-id: pending:1
x-ai-prompt-hash: sha256:f8e2c1aa…
x-ai-accepted-at: 2026-05-25T15:30:00Z
x-ai-accepted-by: romulo@febasi.com.br
tags:
- auth
created: 2026-05-25T15:30:00Z
updated: 2026-05-25T15:30:00Z
---
We picked PKCE for OAuth on Cliente Alfa…
And .synapse/audit.ndjson gets a single new line:
{"id":1,"kind":"capture_fact","decision":"accepted","proposed_at":"2026-05-25T15:00:00Z","decided_at":"2026-05-25T15:30:00Z","source":"mcp:capture_fact","reason":"conversation 2026-05-25, decision-making call","payload_sha256":"sha256:f8e2c1aa…"}
The entity and the audit row are now linked by payload_sha256 and
x-ai-prompt-hash (same SHA).
5. Reading the stamps from outside
Any tool that can read YAML frontmatter can read the stamps. Common patterns:
# How many entities in this workspace are AI-authored?
grep -rl '^x-ai-source:' projects/ people/ entities/ apis/ tasks/ notes/
# Which decisions came from cli:capture vs mcp:?
ag '^x-ai-source: ' --no-numbers | sort -u
# Strip stamps before exporting (e.g. for public publication)
syn export --as html --out /tmp/site # already strips by design
# the static site exposes body, not frontmatter
The stamps are part of the markdown; git diff shows them naturally
and they survive in any clone of the workspace.
6. What does NOT make the cut
- No PGP / per-entity signature. Disproportionate to the threat model; ed25519 is reserved for the portable identity bundle (ADR-014) where the threat model warrants it.
- No chained hash between stamps and audit log. Slot reserved for a future ADR if a downstream tool asks for it.
- No
x-ai-historyof previous stamps. When an entity is mutated twice via the pending queue the second accept overwrites the stamps; full history lives in the audit trail. Slot reserved for v0.4 if demand justifies.
7. References
- ADR-012: the load-bearing decision behind every field above.
- AUDITABILITY.md: companion document focused on the workspace-wide audit trail.
- BUSINESS_RULES.md §12: normative rules around AI safety, federation, freshness.
- MCP.md: the MCP write tools that originate every stampable proposal.
Verifying a Synapse release (SYN-108)
Release signatures and checksums authenticate the bytes you download under a selected publisher identity. They complement workspace verification, which checks different properties. Neither mechanism proves general factual truth, complete supply-chain safety or freedom from defects.
The release workflow uses keyless Sigstore / cosign
with short-lived signing credentials, rather than a project-maintained long-lived release key. The signing identity is the GitHub
Actions release workflow (.github/workflows/release.yml on the pushed tag), and
its signature evidence includes the public Rekor
transparency log entry. For a release using this workflow, inspect these assets:
- the platform archives (
synapse-<version>-<target>.tar.gz/.zip), SHA256SUMS: a checksum manifest over every archive,SHA256SUMS.cosign.bundle: the cosign signature + certificate + Rekor entry forSHA256SUMS.
The SYN-340 release workflow additionally includes release-metadata.json in
the signed manifest. It records the selected source commit, package/changelog
input digests and exact release-notes digest. Validate its manifest entry before
using it to compare source and notes. This is a declaration authenticated under
the selected workflow identity, not independent build provenance or proof of
reproducible compilation. Earlier releases can lack this receipt.
Verify (one time: install cosign)
Install cosign
(brew install cosign, or the release binary from sigstore/cosign).
Verify a download
Choose a release tag and platform explicitly, then retain its SHA256SUMS,
SHA256SUMS.cosign.bundle and selected archive together. Source-tree features can be newer
than the binary on that tag; consult its release notes rather than inferring capabilities
from this documentation. Before extraction, authenticate the checksum manifest:
# Example tag, not a request for the newest release. Select the reviewed tag you need.
SYNAPSE_RELEASE_TAG=v1.4.0
# 1. Authenticate the checksum manifest under the exact selected workflow/tag identity.
cosign verify-blob \
--bundle SHA256SUMS.cosign.bundle \
--certificate-identity "https://github.com/romulofebasi/synapse/.github/workflows/release.yml@refs/tags/${SYNAPSE_RELEASE_TAG}" \
--certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \
SHA256SUMS
# → "Verified OK"
# 2. Compare downloaded archives against the authenticated manifest (GNU sha256sum).
sha256sum --check --ignore-missing SHA256SUMS
# → synapse-<version>-<target>.tar.gz: OK
Both checks must pass for the specific archive you intend to extract. Confirm that it
is present, has a matching manifest entry and reports OK; a successful check of another
file is insufficient. On macOS, shasum -a 256 <archive> prints the digest for exact
comparison with its authenticated entry. Replace the placeholder with the actual filename.
An absent bundle, unexpected identity, unsupported verification format or failed check stops
this manual workflow. A valid signature remains valid when identical assets are mirrored;
it is an unexpected signing identity or changed bytes that the checks reject.
What this proves (and does not)
- Establishes: the manifest verifies under the selected tagged workflow identity and issuer, and the selected archive matches a digest in that authenticated manifest.
- Does not establish: bug-free source, operating-system signing/notarization, exhaustive build provenance, workspace integrity or the truth of stored claims. Retained bundle verification depends on the cosign version, trusted roots and verification policy.
syn verify separately reports named properties of a selected workspace, with prerequisites,
exclusions and skipped checks. A successful report is not a universal proof that no silent
alteration ever occurred. See the verification scope.
Convenience installer behavior
The public installer is best-effort. It compares the archive checksum when the manifest, matching entry and local hash utility are available, and a detected mismatch stops installation. A missing manifest warns and continues; a missing entry or hash utility can skip comparison.
When cosign and the bundle are available, it attempts signature verification. A signature
failure warns and continues on the checksum path; unavailable cosign or bundle can skip the
signature check. Pinning SYNAPSE_VERSION chooses the requested tag but does not make these
checks mandatory. Use the explicit manual workflow above when both checks are required.
This guide does not change the installer or imply that Gatekeeper/SmartScreen trust is supplied
by Sigstore signing.
Release preparation and mirror limits
The prepared SYN-340 pipeline consumes a committed version and generated changelog from an exact existing tag. It pins every platform build to that commit, uses the locked graph and refuses an incomplete four-platform inventory. It creates no source commits and writes no source branch. The mirror compares the original tag/source receipt, notes, asset hashes and tagged workflow signature, then copies the same bytes and bundle. The mirror tag is a distribution reference; it does not establish that the public mirror contains the private source commit.
Both publication paths refuse an existing release, including an identical rerun, and stop when API errors prevent verifying absence. They contain no automatic delete/replace recovery. This protects the normal workflow from accidental replacement; administrators can still alter GitHub objects. A partial failed publication needs owner inspection. See ADR-007 and the 1.5.0 preparation record. Preparation alone creates no tag, archive, verified signature or released capability.
Canonical documentation in forward releases
Starting with the SYN-343 producer correction, source publication checks the full changelog inside every platform archive against the exact committed-source receipt before creating the checksum manifest or invoking Sigstore. Packaging reads README.md, LICENSE and CHANGELOG.md from Git blobs, including on Windows. This correction shipped in v1.5.1. The actual publication verification checks the signed source and mirror artifacts, raw documents in all four archives, five npm packages and the host launcher. Previously published v1.5.0 artifacts are outside this forward correction. Downstream verification remains strict. See the producer evidence and remaining limits.
The separate retained-evidence review recomputes inventory, document, SRI and binary agreement and records reviewer roles.
Synapse visual overview kit
These figures explain the current source tree with the official Synapse palette.
They are editable SVG source, with no remote fonts, scripts or embedded runtime.
Desktop and vertical mobile variants share the same responsibilities and claims.
README and architecture figures select the vertical version below 650px with an HTML
picture element.
Keep the files readable in both GitHub and the generated documentation site.
| File | Responsibility and evidence |
|---|---|
memory-foundation.svg | Stable content model: Markdown/Git, derived local search and CLI/MCP access. Suitable for the distribution mirror without newer integration claims. |
governed-memory-loop.svg | Local proposal, exact review, accepted note and separately scoped evidence. The knowledge process contract always queues; other paths follow their configured policy. |
one-core-topology.svg | Current shared core and its interfaces. Retained evidence and operational queue state are distinct from rebuildable search data. |
knowledge-in-context.svg | Synapse as an additive component for Soma and other hosts. Current source adapters are read-only; future authoring-tool adapters are labeled planned. |
retrieval-evidence.svg and mobile variant | Recorded observations from docs/assets/bench/persona-recall.json: 20 fixed questions, six synthetic contexts, 77 to 463 entities, internal macOS arm64 run. The original run’s source commit is unknown. |
Editing and review
Use Indigo #5B3EE0, Paper #F6F4EF and Amber #FFB454 from
the brand guide. Muted colors must retain contrast. Keep labels short,
add a viewBox, descriptive title/description and meaningful alt text at every use.
Pair figures with searchable text so small screens do not depend on image labels alone.
Readers can open an SVG at full size when inspecting dense technical detail.
Change an arrow only when its data flow or responsibility is supported by current source and the linked contract. Keep historical evidence, current capability and planned work separate. Review the source/release boundary before copying a figure into the public artifact repository. Do not imply every record is true, signatures prove current permission, or an absent verification input passed.
The retrieval chart is rendered with Matplotlib from the unchanged JSON observations.
plot-retrieval.py is the reproducible figure generator; it does not run a benchmark.
Run it with an existing Python environment containing Matplotlib:
python assets/branding/overview/plot-retrieval.py
The generator rewrites only retrieval-evidence.svg and its mobile variant in the asset directory. It has no runtime
role, imports no product credentials and downloads no models. Inspect the resulting image
at typical README width before committing it. Do not invent a baseline, smooth the measured
points or omit corpus/sample limitations. To change the data, run and document a new
benchmark separately.
Connection semantics
Declare the view before drawing edges. one-core-topology.svg is a dependency view:
operators and Soma invoke CLI; MCP clients invoke MCP; both interfaces call the same core.
Solid arrows show calls or dependencies, with responses omitted. The narrow layout groups
storage responsibilities without introducing storage-to-storage calls.
governed-memory-loop.svg is a transition view. The review question has a conditional
acceptance edge; other outcomes stay pending. The later-task connector bypasses the evidence
summary because that summary is not a mandatory verification-success stage.
Keep arrowheads attached to actual node ports. Do not merge independent clients into an unlabeled bus. Give a shared junction a visible dot, and preserve the same relationships when reflowing a figure for mobile. Color supports the product identity; it does not grant permission or establish knowledge truth.
The historical PNG in brand/images/retrieval-quality.png is now a raster export of the
canonical retrieval SVG. Its HTML preview embeds the same SVG so it cannot silently drift
through separately positioned chart coordinates. See the source repository’s brand/README.md
for the export command.
knowledge-in-context.svg labels host calls, owned-content operations and incoming adapter
evidence separately. Read-only adapters do not receive a write-back edge. The foundation
illustration is a conceptual content-availability flow, not a dependency or deployment
diagram. Its left-to-right order does not mean SQLite calls a CLI process.
Synapse 1.5.0 release preparation
Tracking: SYN-340 (#343). Status at the original preparation freeze: source preparation only. The sections below preserve that historical evidence and its then-pending gates. The later publication outcome follows here.
Actual publication outcome
After reviewed source and CI gates, annotated v1.5.0 selected merged commit
b86833767bcc3fa726dd60cfb19fe5d9d8036a07. Source workflow
39095566496
built all four platforms and published seven signed assets. Mirror workflow
39096975503
preserved them in the public release.
The independent npm consumer then rejected the Windows archive’s changelog: checkout conversion produced CRLF bytes that differed from the exact committed source digest. Publication stopped before publishing any npm package. This does not invalidate the manifest signature or authorize normalizing consumer inputs. SYN-343 (#348) tracks canonical document packaging and the forward 1.5.1 correction. The existing 1.5.0 tag and assets are retained unchanged. These actual outcomes do not retroactively change the scope of the historical preparation evidence.
Candidate scope
v1.4.0 is 73 commits behind source baseline
21242ae0c2a2d592dedad439631ecc8e0a97df1e. The generated candidate adds the
bounded versioned knowledge process contract and guarded review decisions,
Memory CI, scoped diagnostics, explicit revision adapters and source-impact
proposals, portable authority evidence, historical shared admissions and native
state attestations. These are additive capabilities; guarded writes retain their
review and authorization prerequisites. Nothing here claims that all stored
knowledge is true or that advisory output grants write authority.
Cached git-cliff 2.12.0 independently recommends v1.5.0. The range contains
no ! or BREAKING CHANGE marker. That inspection supports the minor candidate
but does not replace API compatibility review. Cargo.toml and the root
Cargo.lock package are prepared as 1.5.0. The generated changelog retains the
historical 1.4.0 and earlier sections. Its date is the UTC preparation date, not
a claim that publication occurred.
Final integration and validation
- SYN-339 PR #344 merged as
a86212b518387138e1e3634ac7518c4667e38c40. Its required source checks, including Rust 1.89 and native contracts, passed; optional Pages deployment was skipped. Those checks apply to SYN-339, not a compiled 1.5.0 candidate. - This preparation integrated that main commit through
14673ae. The manifest and 590-package lockfile equal merged main except for the root 1.5.0 version. The candidate section was regenerated once from the resulting 82-commit range after v1.4.0, including SYN-339 and SYN-340. Historical 1.4.0 and earlier sections retain their exact bytes. Final source review remains required. - Run the actual release-validator controls, formatting, full Rust tests, production feature compilation, native contracts, dependency policy, docs and MSRV on that final source. Do not reuse an older binary as release proof.
- Require independent review of the workflow permissions, exact tag/source selection, source/notes receipt and retained platform/feature restrictions.
- The user’s existing authorization covers keeping releases updated after the actual review and validation gates pass. Create the final version tag on that reviewed source commit. The release workflow validates the real tag and committed notes before the four native platform builds. A manual retry must select that existing tag; it cannot publish a branch.
After the required gates and tag creation, an explicit retry uses the tagged workflow:
gh workflow run release.yml --ref v1.5.0 -f tag=v1.5.0
Selecting main as the dispatch ref is rejected even when its tag input is valid: checkout does not change the OIDC workflow identity, which must remain the exact tagged release workflow used by signature verification. This example is an operator procedure, not a command executed during preparation.
The preparation command used the installed tool, without installation or a new Rust target directory:
git cliff --bumped-version --offline --no-exec
git cliff --unreleased --tag v1.5.0 --prepend CHANGELOG.md --offline --no-exec
python3 scripts/check-release-tests.py -v
python3 scripts/check-release-independent-tests.py -v
git diff --check
--tag here supplies a virtual changelog context and creates no Git tag. Do not
prepend the same version twice when regenerating: replace its generated section
while retaining the historical sections. The original 20 omitted messages,
and 21 after integration, are merge
prose outside Conventional Commit syntax; feature/fix commits remain listed.
Actual limits
Preparation ran locked offline Cargo metadata, without compiling or creating a target directory, and no Xcode or native platform build. It proves no published signature. The validator’s private Git tags and synthetic archive bytes are test fixtures, not product releases. Actual cosign verification and all four native matrix builds remain release gates. Linux archives require modern glibc and libstdc++; Intel macOS, OS signing/notarization and generic musl compatibility are not supplied. The mirror preserves the original signing identity and refuses existing releases; owner-level GitHub changes remain possible.
Local verification receipt
The actual validator CLI passed 13 unittest methods using private committed Git
fixtures, private test tags and synthetic asset bytes. Controls reject wrong tag
or commit, dirty inputs, inconsistent manifest/lock versions, duplicate or wrong
changelog sections, changed source receipts/notes, branch-based signing identity,
missing/extra/symlink archives, duplicate originating archive basenames, changed
archive hashes and attempted overwrite.
The originating-run receipt selects its tag even when branch environment values
say main. A synthetic breaking Conventional Commit rendered the BREAKING
marker through the installed generator without changing Git history.
Read-only live GitHub controls establish the CLI’s actual API behavior: an absent
synthetic release tag returned validation exit 0; existing v1.4.0 returned 1.
No release object was created, edited or deleted. PyYAML and actionlint are not
installed; Ruby Psych parsed all three changed workflow files, and bash -n
parsed 13 workflow shell blocks after expression substitution. These are syntax
checks, not GitHub Actions execution or schema acceptance.
The allowlisted receipt
records exits and source/log hashes. Local raw controls remain in
/tmp/syn340-release-preparation-20261017. Source CI, MSRV, four native builds,
actual signature production/verification and publication remain pending.
Legacy workflow runs and old tags retain their old workflow definitions; do not
rerun the old publication pipeline as a substitute for this reviewed candidate.
The added staging control initially had a misplaced fixture block and failed
with FileExistsError, before its intended checks completed. That setup error
is retained as staging-fixture-setup-error.log, separately from the restored
13-method pass; it is not a release-validator regression or assertion RED.
Independent originating HEAD correction
The independently frozen workflow tests from cc02d5f reproduced a real
assertion failure: a receipt naming valid source commit A was selected even
though the authenticated originating workflow_run.head_sha named B. Artifact
download by run ID alone did not establish that commit relationship. This was
a trigger/source binding defect, not a signature forgery; original cosign
verification still protected authenticated artifact bytes.
The exact failing test was adopted unchanged and its RED retained under
/tmp/syn340-origin-head-binding-20261017. Automatic selection now passes the
authenticated event HEAD to select --expected-commit, and compares it with the
receipt before writing any tag output. Manual tag selection remains separate.
The restored gate passes the original 13 validator methods and all nine actual
workflow-shell methods, using recording gh/cosign/cargo executables. No real
Cargo build, signature operation or publication is supplied by those fixtures.
CI now invokes both scripts. The original independent mutation campaign and
earlier invalid fixture evidence remain separate from this corrected run.
Existing authorization covers the release update after actual source review, CI/MSRV and platform/signature gates; these records do not add another generic permission step or claim that publication has occurred.
Producer and downstream compatibility follow-up
The original unmodified macOS tar command produced ten members in an owned local
fixture because of AppleDouble host metadata. Its initial five-member fixture
precondition failed, so that attempt is invalid compatibility evidence, not a
product assertion RED. The unchanged actual packaging-shell regression separately
failed its observed COPYFILE_DISABLE assertion. Setting that variable to 1
produces the five declared members without weakening downstream selection.
The complete validator and workflow-shell suites now pass 13 and 10 methods.
SYN-341 hardens the public npm publication boundary and has independent archive identity and CI-admission corrections. Its reviewed public-repository merge and checks are required before creating the next product tag. The original source receipts remain historical; this follow-up requires exact new-head CI.
Independent SYN-338 rejection verification
Source reviewed: 7c9801e7166afd743c91fc6ecd31f7eb0167b9e9. Production behavior comes from
d2724478e5a250a9702abce441da78a5da3b50d2; the RED baseline is
0722253e00e6dea4c1914de092009879b2844d2c. See ADR-071.
The reviewer used an isolated worktree and an APFS clone of the completed build cache.
All production mutants were compiled with --no-default-features --features mcp,
debug information disabled, incremental compilation disabled and two build jobs.
Each binary fixture uses private HOME/XDG directories, an absent profile and disabled enrichment.
The author worktree and the immutable Soma integration binary were left untouched.
Executed challenge
Each round freezes its test files. Mutations are real production changes, not mocks. The original round runs all six existing acceptance cases. The strengthened round runs all 15 rejection acceptance cases, including a compiled direct-core probe. Typed unittest results are captured with the test-first skill adapter.
| Production mutation | Original six cases | Strengthened 15 cases |
|---|---|---|
| core-lease | survivor-acceptance-suite | assertion-killed |
| terminal-state | assertion-killed | assertion-killed |
| pending-review-digest | invalid-runtime | assertion-killed |
| payload-digest | invalid-runtime | assertion-killed |
| retry-audit | assertion-killed | assertion-killed |
| retry-timestamp | assertion-killed | assertion-killed |
| first-audit-duplicate | survivor-acceptance-suite | assertion-killed |
| audit-failure-ignored | survivor-acceptance-suite | assertion-killed |
| root-scope | survivor-acceptance-suite | assertion-killed |
| guid-scope | survivor-acceptance-suite | assertion-killed |
| read-fact-scope | survivor-acceptance-suite | assertion-killed |
| shared-source-denial | survivor-acceptance-suite | assertion-killed |
| mcp-agent-authority | survivor-acceptance-suite | assertion-killed |
Original outcomes: {'survivor-acceptance-suite': 8, 'assertion-killed': 3, 'invalid-runtime': 2}.
Strengthened outcomes: {'assertion-killed': 13}.
The two original digest attempts raised AttributeError when an unexpected success
returned error: null. They are invalid runtime attempts, not assertion kills.
The existing negative assertions now tolerate a null error object while retaining
the same required error codes. The strengthened digest challenges reach typed assertions.
The six-case survivors are scoped to that acceptance suite. Existing Rust tests already cover the direct core lease and terminal states, and the existing MCP smoke covers the agent decision boundary. No repository-wide survivor claim is made.
Independent assertions
- Call actual
pending::rejectwhile a nativeWriterLeaseremains held, after workspace discovery. - Deny legacy decisions on accepted, auto-accepted, undone and rejected rows; retain exact rejected retries.
- Preserve an old decision timestamp, every retained row field and audit bytes on exact retry.
- Deny query/review/rejection with another root or workspace GUID.
- Filter actual lexical reads to the selected fact identities.
- Deny local reads and rejection when a valid shared-source policy requires current authority.
- Deny actual stdio MCP rejection with
local_operator_rejection_requiredand no state/audit change. - Record exactly one matching audit entry for the first live rejection.
- Roll back the SQLite transition when audit append fails.
No defect was reproduced on the intended production implementation during this review. The added tests close acceptance evidence gaps and make wrong behavior fail by assertions.
Reproduction and evidence
CARGO_TARGET_DIR=/tmp/synapse-rejection-adversary-target-20261017 \
CARGO_PROFILE_DEV_DEBUG=0 CARGO_PROFILE_TEST_DEBUG=0 \
CARGO_INCREMENTAL=0 CARGO_BUILD_JOBS=2 \
cargo build --no-default-features --features mcp --bin syn --example guarded_pending_review_probe
SYN_REJECTION_BINARY=/tmp/synapse-rejection-adversary-target-20261017/debug/syn \
SYN_REJECTION_CORE_PROBE=/tmp/synapse-rejection-adversary-target-20261017/debug/examples/guarded_pending_review_probe \
python3 -m unittest discover -s tests -p "test_guarded_pending*.py"
Local review artifacts are retained under /tmp/synapse-rejection-adversary-evidence-20261017:
original-results.json, strengthened-results.json, per-mutation production.patch,
build.log, typed reports and gate/evidence.json. The manifests record binary/source
SHA-256, source revision, build exit and before/after test hashes.
Final frozen SHA-256 values:
tests/test_guarded_pending_rejection.py:ab2c8805c96cb8c4c678a1532ec019456ae8f8d255fecbc585675679a595dac5.tests/test_guarded_pending_independent.py:a63038fdb734f36acd4e9b4562f6539ccbaf16a389fa17d8493eb4115296c507.examples/guarded_pending_review_probe.rs:6bf588fd8cb4f35b70338f43020d664a250dd34ae1dd04a10cfabd031f02d53f.
Intended implementation and review checks
After every mutant was restored, the intended source was rebuilt and the complete
15-case rejection acceptance gate passed: 15 passed, zero failures, errors or skips.
The typed capture is final-restored-green/evidence.json under the evidence directory.
Formatting, Python syntax and diff whitespace checks passed. The native core probe
also passed a focused Clippy check with warnings denied; its log is retained as
final-clippy-probe.log. The parent owns the broader repository quality gate.
Toolchain: Rust 1.95.0, Cargo 1.95.0 and Python 3.14.6. Both mutation replay scripts and exact toolchain outputs are retained in the same local evidence directory.
Limits
The Python gate proves the complete reviewed rejection acceptance suite, not the entire Rust repository. The parent collects the native full-suite and quality gates separately. Rust panic output has no supported typed test-first adapter here. Existing ignored Rust worker/performance cases must remain visible in the parent report.
Audit and SQLite remain separate durability domains. Live writer exclusion, committed exact retry and audit-failure rollback do not prove crash-safe exactly-once audit delivery. A crash after append and before SQLite commit may retain an audit with a pending row.
SYN-339 dependency inventory verification
Tracking: SYN-339 (#341).
Source baseline: 21242ae0c2a2d592dedad439631ecc8e0a97df1e.
Status: stable-toolchain gates passed against source commit
4f51d6a4e37356cf9761fcc79b9530132a72de58. Independent source review found no actionable removal issue. The Rust 1.89 CI
build remains a publication prerequisite. No diagnostic tool installation, release
or tag was performed.
The independently modified primary checkout and developer benchmark are outside
this worktree and remain untouched.
Inventory
| Declaration | Source evidence | Candidate change |
|---|---|---|
anstream | No direct imports; clap still owns its transitive stream adapter | Remove direct runtime declaration |
anyhow | The actual binary reports typed synapse::Error; build script returns a boxed standard error | Remove direct runtime declaration |
fluent | The loader is imported through i18n_embed::fluent | Remove direct runtime declaration |
i18n-embed-fl | No fl! or crate imports; fluent_language_loader! expands through i18n_embed | Remove unused macro declaration |
gray_matter | Frontmatter code uses the maintained yaml_serde boundary directly | Remove direct runtime declaration |
tracing-subscriber | No direct subscriber initialization or imports | Remove direct runtime declaration |
rstest | No imports or attributes across production, tests, examples or build script | Remove unused dev declaration and old roadmap reservation |
build clap, clap_complete, clap_mangen | build.rs uses only vergen_gix; actual generation imports are in src/cli/generate.rs | Remove build declarations; preserve all three runtime declarations |
| cargo-shear ignore table | Four entries already have real source use; rstest is an obsolete reservation | Remove the complete obsolete ignore table |
All Rust sources, feature declarations, examples and the actual loader macro implementation were inspected. Static search cannot replace compilation of macro expansion and every supported feature. No runtime behavior or compiled-size claim is inferred from this inventory.
cargo update --offline --workspace reconciled the lockfile without compiling.
It added or upgraded zero package versions and removed 21 unreachable packages.
The package count changed from 611 to 590. The exact removed declarations,
removed package versions and candidate hashes are recorded in the adjacent
inventory receipt.
The product remains Rust 1.89. anstream, anyhow and fluent remain transitive
where the retained dependencies need them. Direct-declaration removal is not a
claim that those packages disappeared from the binary or graph.
The removed i18n-embed-fl path was the only locked parent of
proc-macro-error2. Its now-unused advisory exception is removed. The maintained
YAML decision and all other bans/advisory policy remain in force. No new advisory
exception is introduced and dependency pruning is not a runtime security proof.
Diagnostic semantics
The installed package manifest identifies cargo-shear 1.8.0, while its version
command prints dev. The observed executable SHA-256 is
c22d74ed51f276869f7134d51861e599ac4b0dee2752c0ccd58616eace2ea585.
Its --format=json is unsupported. A fresh direct command, with the subprocess
status captured before any later command, reports six unused errors and exits 1:
cargo shear --locked --offline --color never
The earlier historical report of exit 0 is not reproduced. It is not evidence of a proven tool defect. The pinned 1.8.0 implementation returns 1 for analysis errors and 2 for processing/rendering errors. Warnings alone may return zero. Text diagnostics and a compiler error are not an application assertion RED or a supported structured test adapter.
The selected diagnostic package is pinned to 1.8.0. An equivalent installation
can request cargo install cargo-shear --version '=1.8.0' --locked; this task used
the existing verified installation. Its observed host binary hash identifies
this run, not every installation of that package version.
The actual control used an isolated valid Cargo fixture containing an unused
serde = "=1.0.228" declaration. Locked offline Cargo metadata succeeded. The
direct diagnostic returned 1 and named serde; after removing the declaration
and reconciling the fixture lockfile, it returned 0. The candidate repository
also returned 0 with no issues. No compiler was invoked for this control. Exact
commands and statuses are retained in diagnostic-control.json in the raw
evidence directory. A missing package, compiler failure, processing error, empty
result or unrelated warning does not satisfy the control. A later CI gate should
pin the verified tool version, invoke it directly and preserve its subprocess
status. Do not raise the product MSRV to install a diagnostic utility.
Completed stable gates and remaining limits
The new source-only checkout is approximately 14 MiB. Its local target directory
was not created. Baseline, reconciliation and validation logs are retained in
/tmp/syn339-adversary-preparation-20261017.
The inactive Synapse adversarial Cargo target was identified for a coordinated handoff. The owner reclaimed 874 MiB of root-package outputs while retaining its dependency artifacts. All compilation used that warm custom target with two jobs, debug information disabled and incremental output disabled. An initial Clippy attempt used the wrong debug profile and was deliberately interrupted with exit 255. Its log is retained separately and is not an acceptance result. The smaller Soma55 Synapse target belongs to another native journey; it is not assumed available. Pinned guarded executables and all raw evidence remain outside cleanup.
| Gate | Actual result |
|---|---|
| Formatting, file-size and diff checks | Passed |
| Dependency inventory and valid unused-declaration control | Candidate 0, unused declaration 1, restored fixture 0 |
| Clippy, all targets and all features, warnings denied | Passed in 2m 38s on Rust 1.95.0 |
| Full all-feature Rust tests, including doctests | 555 passed, zero failed, nine existing ignored |
| Native guarded rejection and direct core contracts | 15 passed, zero skipped |
| Native Memory CI contracts | Seven passed, zero skipped |
| API docs, all features, warnings denied | Passed |
| Cargo deny advisories, bans, licenses and sources | Passed |
| Pre-push Git environment and required-gate regression | Passed |
| Rust 1.89.0 offline metadata | Passed; no higher declared dependency MSRV |
| Rust 1.89 actual compilation | Deferred to exact-source PR CI under resource coordination |
The nine ignored Rust tests comprise four model/network checks, four subprocess
workers launched by their owning crash tests and one release performance budget.
No model download, live API or release performance result is claimed. The native
runner built the actual candidate binary and direct core probe in
/tmp/synapse-rejection-adversary-target-20261017; it did not use the preserved
guarded binary. That external pinned binary retained its original SHA-256.
The actual MSRV build remains an acceptance prerequisite. Offline metadata checks declared compatibility and cannot prove compilation. No separate cold MSRV cache was started while higher-priority native and review gates held the disk budget. No Xcode or native application UI work was performed by this task.
The Cargo update reference describes the conservative workspace update used here. Stable compiler and native results prove the unchanged functional gates on this host. Independent review is complete; exact-source Rust 1.89 CI is still required before publication acceptance.
Independent source review
A separate reviewer pinned the implementation at 4f51d6a and inspected all 304
tracked Rust files, conditional/test/build/example paths and relevant procedural
macro expansions. The reviewer independently reconciled all 21 removed package
versions, retained versions, lockfile edges and receipt hashes. Runtime CLI
generators remain declared; embedded localization still uses the retained loader
and its required features. The removed advisory exception has no remaining
package in the lockfile. No actionable finding was identified.
This is source review with explicit bounds. The reviewer did not rerun compiler gates or claim that this host establishes every release target. The existing Linux MSRV and CI gates remain required.
SYN-340 independent release preparation review
Reviewed source preparation at 8b8e72720bea1eaef37490bb671ef168f7e816fe, followed
by the author’s exact originating-HEAD correction
e2960ea4c447e879d1415d4c00e0fbb855589d99. The reviewer changed no production
implementation. The review worktree is independent of the author’s checkout.
Result: PASS for the reviewed source preparation and controlled boundaries. This does not approve a tag or release and does not replace the pending external gates. No remote references, public releases or user identity were changed.
Observed defect and correction
The original mirror selected an artifact’s valid tag and commit without comparing
that commit to the authenticated originating workflow_run.head_sha. A controlled
artifact from source A could therefore authorize selection for unrelated run B.
The pristine focused test ran and failed one typed assertion: selection returned 0
instead of refusing to emit outputs. The other eight independent workflow checks
passed after fixture setup was corrected. This was a P2 run-correlation defect,
not proof of signature forgery or unauthenticated asset publication.
The unchanged test was committed in
cc02d5fa7dad3dc93a84f32fb5dcc49cfcb988c2. The author adopted it unchanged, passed
GitHub’s originating HEAD through the workflow into select --expected-commit,
and compares the validated receipt before opening the output file. Both control
scripts now run in CI. Independent replay passed all 22 methods. Removing either
the comparison or its workflow argument produced the original relevant assertion
failure against the unchanged nine-method workflow suite.
Review and adversarial scope
- The tagged version, lockfile root package and first unique reviewed changelog section agree. The stated range independently contains 73 commits after 1.4.0 and zero breaking commit markers. The additive 1.5.0 candidate remains subject to final integration and public-contract review.
- Tag input validation and quoted environment transport constrain dynamic shell values. Actual validator controls reject mismatched or uncommitted inputs.
- Manual source release dispatch must use the selected tag as the workflow ref. Checking out a tag does not rewrite the running workflow’s identity; GitHub exposes the workflow ref and workflow SHA separately from source selection. GitHub OIDC reference.
- The privileged mirror reads the selected run’s artifact, validates its source
binding and later checks source notes, exact asset inventory, hashes and
original tagged workflow signature arguments.
workflow_runcan grant a later workflow secrets/write authority, so artifact data needs validation. GitHub workflow event documentation. - The originating artifact staging gate rejects unexpected directories before copying, including an extra artifact containing a duplicate archive basename.
- Both publication bodies use create after an explicit absent-release control.
Existing releases and API failures deny publication. There is no delete,
force-tag, asset-clobber or source-main write in these paths. The source release
also requests an existing tag through
--verify-tag; the mirror intentionally uses a distribution reference rather than claiming private source history. GitHub CLI release-create reference.
Executed challenges
The original frozen 13-method suite was run against 17 independent production mutants: 11 assertion kills and 6 survivors. The survivor set exposed coverage gaps for a valid-but-uncommitted input and workflow-only boundaries. Those results are retained unchanged. Six identical patches were then replayed against the added focused controls; all six were killed by the relevant assertion. The separate corrective campaign removed the new binding comparison and its composition argument one at a time; both were assertion-killed with the tests frozen.
The report preserves exact patches, test hashes before/after, private log hashes, commands and verdicts. An early standalone publication fixture omitted the copied validator in the runner directory; that missing-file setup attempt is INVALID, not product RED. Six early strengthened patch applications used the wrong path strip and ran zero tests; they are separately INVALID. The same patches were then applied correctly. Neither invalid setup is counted as a mutation kill.
Final gates and limits
The final restored source passed 13 validator methods in 5.760 seconds and 9 actual
workflow shell methods in 5.799 seconds, with zero failures, errors or skips.
gh, cosign and cargo are controlled executables for these tests. The actual
Python validator, Git fixtures and extracted shell bodies execute. No real Rust
build, cryptographic signature verification, GitHub Actions run, remote tag or
publication is claimed. Synthetic archive fixtures do not prove archive contents
or platform behavior.
SYN-339 exact-source CI including MSRV, final integrated notes regeneration,
actual four-platform production builds, authentic signing/verification and owner
publication approval remain required. Historical public mirror releases remain
untouched. Raw logs and xcresult are not published; this review creates no native
artifacts. The adjacent evidence.json is an allowlisted review receipt.
SYN-342 documentation diagram evidence
Issue #347 fixes raw Mermaid
code fences in published mdBook chapters. The source and tests freeze is
140b2b52640d29bbc774709adeba728cab5864e2, based on b868337.
This report adds no product, version, tag, release or npm changes.
The actual plain mdBook build ran one targeted method and failed by a typed
AssertionError: the release-pipeline diagram remained code.language-mermaid.
The RED commit is 467f0eb. An initial post-fix test expected unhashed script
names and failed. That fixture expectation was corrected to mdBook’s actual
content-hashed filenames. Its failed report is retained, with no second product
defect claim. The initial raw-fence rejection remains in the final test.
The complete relevant suite runs python3 scripts/check-docsite.py -v using
verified official mdBook 0.5.4 and mdbook-mermaid 0.17.1 binaries. All 14 methods pass,
with zero errors, skips or expected failures. The reviewed typed collector also
records 14 passes. The CI command rejects empty, filtered, skipped and expected
failure results. A separate generated build at the exact source freeze passes
all 138 chapter, 56 tracked asset, search, ADR, privacy and editorial checks.
It contains four diagram containers across three chapters, with the pinned local
runtime before the pinned initializer. No runtime CDN is configured.
The suite exercises actual publication, source-byte preservation, per-chapter coverage, missing/changed assets, reversed load order, remote and escaping script references, raw fences, missing containers and nested fence examples. It also checks pinned archive verification precedes unpacking in the Pages workflow. These are builder-authored negative controls; independent mutation review is pending. No separate renderer or source Markdown copy is maintained.
The official plugin warns that it was built against mdBook 0.5.0 when invoked by 0.5.4. The generated build succeeds. Both use the 0.5 preprocessor API.
Browser acceptance remains pending. Structural HTML and pinned bytes do not prove that JavaScript renders SVG, that the console is clean, or that diagrams are legible in both themes and on nested pages. The earlier deployed HTML is a raw-source observation, not a browser-rendering assertion. No browser, Cargo, GUI, Swift, Xcode or publication action ran in this task.
evidence.json records exact identities, commands, counts and SHA-256 digests. Raw logs, downloaded tools and generated HTML remain private outside Git. The later browser check and independent adversarial report must be recorded separately before closing the issue.
Independent follow-up
The independent review retains eight original variants: five assertion kills, three survivors and no invalid attempts. Three additional coverage controls kill the original survivor patches unchanged. The restored combined suite passes 17 actual methods. This follow-up does not rewrite the original 14-method source freeze, chapter counts or then-pending gates above. Pages now invokes both suites; the final integration validation is recorded separately. Browser acceptance remains pending.
Actual browser follow-up
The browser receipt records actual SVG execution and visual review for all four diagrams. Local rendering exposed an unreadable horizontal knowledge flow. Vertical arrangement and wrapped labels preserve the same relations and improve the reviewed text scale; sequence labels and the publication topology were adjusted for the same book column. Earlier structural tests and mutation receipts retain their original scope. Hosted deployment and mobile layout are not established by this local review.
Actual hosted acceptance
Pages deployment 39101189584
succeeded at source aea21468b93e25e42551d97560085d4a4a2284cc. The public
publication receipt identifies that same commit. Actual browser inspection found
four rendered SVGs across the release, knowledge-framework and publication ADR
pages, with zero raw Mermaid fences and zero renderer-error elements. Their node
and edge counts, view boxes and effective label sizes match the reviewed local
layout. Directed flow arrows and the dashed committed-transition return were
visually checked at the desktop viewport.
The previous public release page had zero Mermaid SVGs and one raw fence. The
allowlisted hosted.json receipt preserves both observations and private
screenshot digests. This is desktop browser evidence, without a mobile, alternate
theme or universal accessibility claim.
SYN-342: Independent documentation publication review
The renderer preparation and verification source was frozen at
140b2b52640d29bbc774709adeba728cab5864e2. The actual 14-method suite passed using
the pinned official mdBook and mdbook-mermaid binaries. Their executed bytes
matched the verified Darwin archive contents. No browser or JavaScript execution
is claimed.
Original mutation campaign
Eight plausible production variants ran against the unchanged original tests. Five failed by relevant assertions; three survived. No attempt had runner errors, skips, missing results or other invalid infrastructure outcomes.
| Production variant | Original 14 methods |
|---|---|
| Omit the newly added diagram ADR from the generated chapter set | Survived |
| Omit canonical Quickstart from the generated chapter set | Survived |
| Run CLI diagram verification only for the release-pipeline chapter | Survived |
| Accept a missing diagram container | Assertion kill |
| Accept raw Mermaid code fences | Assertion kill |
| Accept initializer references before the runtime | Assertion kill |
| Identify the Mermaid runtime by filename instead of pinned bytes | Assertion kill |
| Normalize canonical source bytes during preparation | Assertion kill |
The chapter omissions left the producer’s own manifest self-consistent. Existing tests derived coverage from that manifest, so they did not establish the complete canonical source inventory. Direct calls to the diagram helper checked individual pages but did not prove that the production CLI invoked it for non-release pages. These are demonstrated gaps in the original tests, not additional defects in the unchanged pristine renderer source.
Independent strengthening
Three additional tests in scripts/check-docsite-independent-tests.py
compare the manifest with the complete Git-tracked product chapter policy, check
each independently expected generated page, and invoke the actual publication
CLI after removing a diagram or initializer from a non-release chapter. A generic
runner error cannot satisfy those CLI controls: the tests also require the
intended verifier assertion reason.
The pristine combined suite passed all 17 methods. The three original survivor patches then ran byte for byte against the frozen strengthened tests. All three were killed by assertions, with no survivors or invalid attempts. The release-only CLI variant failed both non-release controls. Restored production passed all 17 methods in 4.444 seconds, with no failures, errors, skips or expected failures. The original eight-variant verdicts remain unchanged and separate.
The allowlisted receipt records exact production/test identities, patch and private log digests, original verdicts, strengthened replays and restored results. Raw logs, downloaded tools and generated books remain private. No production fix was needed for this test-coverage correction.
Required invocation and scope
Run both actual suites with the reviewed official tools on PATH:
python3 scripts/check-docsite.py -v
python3 scripts/check-docsite-independent-tests.py -v
Both invocations must be required by Pages validation. The original workflow only invoked the original 14 methods. Wiring the independent command into CI is a subsequent implementation change and is not included in the original frozen mutation review. Validate the final integrated source separately.
These tests exercise real mdBook preparation, the actual Mermaid preprocessor, generated HTML and the production publication CLI. They establish chapter inventory, structural diagram coverage and local script-byte identity. They do not establish SVG rendering, JavaScript execution order in a browser, console behavior, themes, layout, search interactions or the deployed Pages artifact. Ubuntu tool execution, final hosted CI, browser acceptance and publication remain separate gates. No Cargo, native GUI, Swift, Xcode, push, tag or release action was performed in this review.
SYN-343: Canonical release documentation
The published v1.5.0 Windows archive contains a full changelog with 1,317 CRLF sequences. Normalizing those sequences to LF reproduces the tagged source digest exactly. The strict npm consumer correctly refused the changed bytes before publication. This repair prepares a new patch release and preserves all existing v1.5.0 tags, assets, notes and manifests.
The producer now packages README.md, LICENSE and CHANGELOG.md directly from exact HEAD Git blobs. Source metadata retains its schema version 1 field set and hashes committed inputs after checking for dirty manifest, lockfile and changelog edits. The actual source validation stage checks each staged archive full changelog against the verified receipt before generating a signable manifest.
Executed evidence
At RED commit 6422c9f9efd1e8c54f92ff3782414cc0ac55ae7d, three new methods
produce eight typed AssertionError failures, zero errors and zero skips. An
actual Git autocrlf checkout changes the three document files. The actual Bash
packaging stage and local tar then expose the changed archive bytes. A separate
receipt assertion observes a worktree-dependent digest. The actual pre-sign
source validation stage wrongly admits changed full changelogs in each of four
platform archives. A canonical control succeeds before the platform corruption checks.
After the correction, the complete release control scope passes 26 methods: 13 existing validator methods, 10 existing workflow methods and three new canonical-document methods. There are zero failures, errors or skips. The existing actual-tar fixture now commits README and LICENSE before packaging. Its original assertions are unchanged, verified by comparing assertion ASTs. The npm consumer is unchanged.
python3 scripts/check-release-tests.py -v
python3 scripts/check-release-independent-tests.py -v
python3 scripts/check-release-document-tests.py -v
The new test module is invoked by actual source CI, whose scripts/** path
selection already includes it. The allowlisted receipt
records source, evidence and observed published-archive digests. Raw logs remain
private. Three earlier harness attempts are excluded: incorrect shell comment
extraction, subtest cleanup contamination and zero unittest discovery. They remain
preserved as invalid attempts rather than assertion evidence.
Limits and next gates
Local execution uses real Git, Bash and tar with representative ZIP archives. It does not establish Windows 7z execution, real platform binary builds, real Sigstore verification, registry publication or the new hosted release. Existing recording gh, cosign and cargo fixture controls remain recording fixtures. No Rust compilation was performed for this packaging change. Independent mutation review is recorded below; exact final-head required CI remains pending before publication.
The bounded reader checks the declared full-changelog member without extracting archive files. It complements the strict downstream consumer and does not claim independent build reproducibility or general archive authenticity.
Frozen patch candidate repeat
After the separately reviewed 1.5.1 version and notes preparation, frozen commit
30ce83f93cc36e1c98cfa6dc1535d775f263e59f passed the same complete 26-method
release control scope in 31.246 seconds with zero failures, errors or skips.
The before and after source/test hashes match. This is a local fixture gate,
not execution of the new four-platform release matrix.
The patch input receipt records the installed git-cliff version, generated commit range, root-package-only manifest/lock change and exact preservation of the v1.5.0 and older changelog sections. Required source CI and actual release publication remain pending.
Additional independent producer review
A different reviewer executed eight plausible wrong-production variants against the frozen assertions. All eight failed relevant assertions, with 22 explicit AssertionError failures, no unrelated errors, no skips and no survivors. The complete 26-method scope passed before the campaign and again after exact source and test restoration.
The controls challenge working-copy packaging and receipt hashes, dirty-input admission, omitted Windows or other platform checks, newline normalization, manifest generation before validation, and ignored validation failure. The order control fails because a signable manifest exists even though document rejection still returns a failure. An additional actual Git autocrlf probe confirms schema version 1, its six top-level fields and all three committed input hashes.
The independent receipt and reviewed patches retain frozen hashes, per-control assertion witnesses and exact limits. This review does not establish Windows 7z execution, real platform builds, actual Sigstore signing or the forward release’s hosted acceptance.
Synapse 1.5.1 publication verification
The actual v1.5.1 release from reviewed commit 23ba013d9b57588a58faf6c7ba4b16a9ecd3085f passed source, public mirror, npm byte comparison and host launcher verification. This is a separate read-only verification of published bytes, performed by the producer-repair author. It does not replace the separate independent source and mutation review or convert local fixture controls into publication proof. The machine receipt records exact hashes, workflow identities, limits and retained nonpassing attempts.
| Boundary | Actual result |
|---|---|
| Source release | Run 39103431019 passed tagged inputs, four platform builds and publication. |
| Public mirror | Run 39104711966 passed automatically. All seven names, sizes, API SHA256 digests and notes match the source release. |
| Asset admission | Seven complete nonzero assets and the exact five-entry manifest were checked before any archive parsing. |
| Signature | Real cosign verified SHA256SUMS against the exact source workflow identity for refs/tags/v1.5.1 and issuer https://token.actions.githubusercontent.com. |
| Canonical documents | README, LICENSE and the full CHANGELOG in all four actual archives equal immutable Git blobs. This includes the actual hosted Windows ZIP. No line ending normalization was applied. |
| npm publication | Run 39104728647 passed automatically. A fresh complete acquisition checked five exact package names and versions, SHA512 SRI and advertised provenance. |
| Binary comparison | All four npm platform binaries equal their authenticated source archive binaries in size and SHA256. |
| Host launcher | A private HOME, cache and npm configuration install with --ignore-scripts passed. The installed launcher and host binary matched checked npm bytes before node syn.js --version exited zero with syn 1.5.1. |
The macOS arm64 binary SHA256 is 6d9bfddf188475767b94712d8971cff9fa66f9aa088a4b3805f9ac59b531f401, size 37,689,216 bytes. It was exported from the authenticated public archive for a separate Soma compatibility check. This report does not incorporate that separate product test.
Two initial cosign wrapper invocations had incompatible relative asset paths and exited nonzero. Their stderr was not retained by the frozen wrapper. An actual absolute-path diagnostic and the unchanged wrapper with absolute paths then verified the same sealed bytes. Those setup failures remain recorded and are not signature failures or passing evidence.
The first npm acquisition stopped at a Windows E404 after downloading three Unix packages. A fresh exact Windows query later succeeded, and a separate complete acquisition passed. The original incomplete fetch and 404 observations remain retained. One registry diagnostic initially mishandled a JSON string error response and was corrected before the typed response was recorded. No package was republished, and no cause for the temporary registry availability gap is asserted.
Only macOS arm64 executed locally, through the launcher version command. Other platform results establish byte agreement plus their hosted build outcomes. Source signature verification was actually performed; npm provenance signature verification, independent reproducible builds, safe-code, live inference, notarization and other platform execution are outside this proof. Raw logs, caches, bundles and downloads remain private. This audit did not republish v1.5.0 or overwrite existing protected binaries. Its historical evidence remains separate.
Independent publication evidence review
PASS within the bounded retained-evidence review after one wording correction. Both source and public publication copies have identical reviewed bytes. The original candidate remains preserved.
The reviewer independently recomputed the sealed seven-asset inventory and exact five-entry checksum manifest, compared all four archives with immutable source commit 23ba013d9b57588a58faf6c7ba4b16a9ecd3085f, and checked full README, LICENSE and CHANGELOG bytes, including the Windows ZIP. All four npm platform binaries match their source archive binaries. All five package tarball SHA512 SRI values, launcher version, four exact optional dependency versions, retained host bytes and proof digests agree.
The original final paragraph and receipt limit implied broader historical preservation than the measured evidence supported. The two revised strings now describe this audit’s actions and explicitly avoid an exhaustive older-state claim. No other README or receipt bytes changed. Final SHA256:
- README.md:
1ee849bdf0b5969c2f5161b957232199959a5d7afbcb06cf63f251e9f4c86035 - receipt.json:
c522056423f4fa465ee7a983c10c2b1859e60685455de3f9db3110666c1d19e9
The retained real cosign command uses the exact source tagged workflow identity and GitHub Actions issuer with the sealed checksum subject and pinned tool digest. This review inspected that proof but did not rerun cosign. npm provenance metadata presence is distinct from independent provenance signature verification.
Actual postpublication execution was performed by the producer-repair author. This reviewer previously prepared the reusable private verification helpers, and now independently recomputed the retained-byte comparisons directly with stdlib rather than importing those helpers. Neither role is presented as a fresh independent attack on the publication pipeline. The separate original independent producer 8/8 campaign remains distinct and was not replayed.
No fresh network acquisition, installation, binary execution, publication, build or GUI action was performed. Original host execution covers macOS arm64; other platforms have archive, SRI and byte-identity proof plus their retained hosted build outcomes. Setup-invalid and partial acquisitions remain separately recorded. No exhaustive baseline of older publication state is claimed. Public candidate and report scans found no private absolute paths, raw logs or credential material. Detailed checks and limits are in independent.json.
Scoped decision providers and evaluation
SYN-323 adds a read-only port, not a replacement for INV-13, source authority or governed review. Source evidence is untrusted input. Typed classifications and signatures on their provenance cannot prove a classification correct.
Optional host configuration
Set SYNAPSE_DECISION_CONFIG to a bounded, owner-private JSON file outside the knowledge workspace. CLI/MCP requests select only an alias. No executable, endpoint, secret or configuration path is accepted from Markdown or tool arguments.
{
"schema": "synapse.decision-providers/1",
"providers": {
"experimental-local": {
"kind": "local_nli",
"python": "/absolute/reviewed/venv/bin/python",
"python_sha256": "SHA256_OF_INTERPRETER_BYTES",
"model_dir": "/absolute/reviewed/model",
"manifest_sha256": "SHA256_OF_MANIFEST_BYTES",
"threshold": 0.9,
"timeout_ms": 15000,
"max_calls": 8
},
"experimental-jev": {
"kind": "jev",
"model": "jev-1.13.0",
"key_env": "SYNAPSE_JEV_KEY",
"egress_scopes": ["EXACT_GOVERNED_WORKSPACE_GUID"],
"acknowledge_source_egress": true,
"timeout_ms": 5000,
"max_calls": 2
}
}
}
The Python adapter runs the repository’s fixed worker with isolated interpreter mode, local-only safetensors, trust_remote_code=false, artifact hashes and no inherited key environment. This is a trusted host runtime, not an OS sandbox: review the interpreter, installed packages and manifests. Optional setup uses a separate virtual environment; pinned experiment dependencies are torch 2.9.1, transformers 4.57.3, onnxruntime 1.23.2 and sentencepiece 0.2.1. manifest.json has schema synapse.local-decision-model/1, repository, exact revision, license and files mapping relative names to {sha256,bytes}. Model artifacts remain outside this repository.
Each process cold-loads its model; benchmark warm pair latency is not end-to-end CLI latency. Input is bounded to 8 KiB per source and 384 tokenizer tokens per pair; overflow abstains rather than hiding truncation. Up to eight calls share a 25-second operation budget, with a 15-second maximum per call. Errors and timeouts abstain. Hash mismatch or a different observed model/rubric rejects the response. Interpreter and model configuration are reviewed host capabilities, not remote permissions.
Hosted Jev requires the network_backends feature, a pinned model version, explicit consent for both source scopes and a separately configured environment key. It uses the official Choice HTTP contract, disables redirects and limits request/response bytes and calls. No credentials are persisted in source. Provider confidence is explicitly provider_claimed; local softmax exports confidence:null. Jev was not run in this experiment because credentials were unavailable; API contract tests are not measured quality, latency or calibration.
Reproduction
The corpus is original sanitized synthetic evidence, reviewed by the coding agent for the narrow policy contract. It is not human field labeling. Its immutable hash and whole-family split were fixed before threshold selection. EN/PT and adversarial variants from a family never cross train/heldout. No external model or prompt instruction owns approval authority.
python3 tools/decision-bench/run.py --provider rules --output /tmp/rules.json
HF_HUB_OFFLINE=1 TRANSFORMERS_OFFLINE=1 /reviewed/venv/bin/python tools/decision-bench/run.py --provider multilingual --model-dir /reviewed/minilm --output /tmp/minilm.json
HF_HUB_OFFLINE=1 TRANSFORMERS_OFFLINE=1 /reviewed/venv/bin/python tools/decision-bench/run.py --provider english --model-dir /reviewed/deberta --output /tmp/deberta.json
/reviewed/venv/bin/python tools/decision-bench/onnx_check.py --model-dir /reviewed/minilm --onnx onnx/model.onnx --output /tmp/onnx.json
SYNAPSE_DECISION_CONFIG=/reviewed/config.json SYNAPSE_TEST_LOCAL_NLI=1 python3 tests/decisions_smoke.py target/debug/syn
Thresholds use training cases only. Threshold 1.01 in a report means disable all model predictions; it is not a deployable production threshold. Candidate recall concerns the explicitly scoped synthetic subset, not corpus-wide search completeness. Bootstrap intervals resample whole heldout families (2,000 samples, seed 731); sparse synthetic families limit external validity. Raw reliability bins/ECE characterize uncalibrated scores; they are not fitted or field-calibrated confidence.
See measured artifacts and limitations. Before any local default, require heldout gates, domain/human-reviewed validation, permissive artifacts and the native ONNX/runtime and repository gates. The optional Python ONNX compatibility probe is recorded separately and does not claim native Rust ONNX validation.
Primary contracts: TypeSafe API, TypeSafe confidence, MiniLM card, DeBERTa card.
Memory CI
Compare explicit base/head knowledge snapshots using approved syn verify --deep
and syn state verify --profile contracts. The POSIX runner supports macOS/Linux
and requires Python 3.10+, Git, an approved
native Synapse binary and explicit existing Git knowledge workspaces. No models,
connector, daemon or mandatory account is needed for local/basic verification.
The executable SHA-256 and exact raw policy-file SHA-256 are independently reviewed pins. They are not taken from a PR-controlled manifest. Review the executable’s release/source authenticity separately; a matching hash alone does not establish it. Use this runner/action from a reviewed immutable revision, not PR-edited runner code.
python3 tools/memory-ci/memory_ci.py \
--binary /approved/bin/syn --binary-sha256 <reviewed-executable-sha256> \
--policy /approved/memory-policy.json --policy-sha256 <reviewed-raw-file-sha256> \
--base /prepared/base --base-revision <exact-base-40hex-sha> \
--head /prepared/head --head-revision <exact-head-40hex-sha> \
--output /private/artifacts/memory-ci
policy.basic.json is an explicit example, not an implicit default. It requires
INV-1 and deep INV-13, permits honest legacy receipt/witness gaps and does not prove
general semantic truth. All deterministic Fail results deny the current head,
including pre-existing failures; the diff describes changed evidence for review.
Policy fields: schema, profile (basic, strict-local, strict-team),
required_invariants, required_surfaces, max_snapshot_bytes, max_report_bytes,
timeout_ms. Unknown fields, floats, duplicate requirements and wrong pins deny.
Surfaces: storage, transitions, receipts, trust_freshness, identity, witness.
Strict profiles add their native mandatory surfaces even if the list is empty.
Required Skip/Partial/missing/Fail deny. Strict-team currently denies missing complete
team authority/source integration; a separately verified historical grant does not
silently enable the profile. A passed basic result keeps strict_requirements_met:false.
External --base-evidence / --head-evidence optionally name JSON descriptors with
absolute public-file paths and independent pins:
{
"trust": "/approved/local-public-trust.json",
"trust_sha256": "<normative-trust-sha256>",
"witness": "/independently-retained/state-witness.json",
"witness_key": "<canonical-public-key-base64>",
"audit_witness": "/independently-retained/optional-audit-witness.json"
}
Omit unused fields. Trust and witness keys are never inferred from workspace or
receipt contents. Base/head witnesses may differ; each describes its own retained
range. A new limited baseline leaves earlier history unknown, including a legacy
base. Different initialized governed GUIDs deny rather than comparing unrelated
ledgers. same_governed_namespace:null means legacy/new-baseline identity comparison
was unavailable, not that namespace equality was proven.
The runner validates Git HEAD labels, freezes the executable, copies bounded raw Markdown/control metadata into private snapshots, backs up an existing SQLite index read-only, and checks source hashes/ref stability. Symlinks, private key files, interrupted source transactions and exceeded budgets deny. It never recovers the original workspace. A missing disposable index is rebuilt only inside the copy; a mismatching existing index is reported, not repaired. Caller source files and accepted evidence stay unchanged. The raw snapshot digest includes files not necessarily tracked by Git; it is separate from the Git label/logical projection.
Outputs: report.json and comment.md, outside both source workspaces. Reports
separate deterministic checks, raw source/drift, declared baseline range, receipts,
trust, identity and witness. They export aggregate fingerprints/status/reason codes,
not native evidence text, titles, fact ids, raw source paths or source contents.
Current INV-13 aggregate output is bounded to 50 details by the native verifier;
a changed fingerprint is labelled new/changed aggregate evidence, not invented
individual findings. SYN-319 adds the finer typed diagnostic surface separately.
INV-13 checks indexed structured temporal facts; assertions in unindexed/malformed
documents are not semantically assessed. Raw source hashing does not supply that
missing assessment. General natural-language truth, opaque agent attention and unobserved revocations
remain excluded. No automatic repair or comment posting occurs.
The composite action .github/actions/memory-ci accepts the same inputs, displays
its redacted preview in the step summary and retains only these artifacts for seven
days. Call it under contents: read; it needs no pull-request write permission.
Install/verify the approved binary and stage snapshots/trusted inputs yourself.
No private-repository credential is introduced by this action. A separate caller
may post a reviewed preview only under its explicitly configured permissions.
Required repository native test/pre-push gates run:
python3 scripts/check-native-contracts.py
This shared CI/pre-push runner builds syn and the native rejection core probe
with all features, then runs the 15 guarded rejection cases and Memory CI tests.
Cargo metadata selects the configured target directory, including a custom
CARGO_TARGET_DIR; Cargo’s build messages supply the actual executable paths.
Python fixtures receive private HOME/XDG directories. Rust unit gates keep their
existing enrichment configuration. Empty suites, skipped required cases and
expected failures fail the native contract gate.
Real-binary cases cover partial legacy basic, required missing receipts, strict missing trust, signed strict-local migration/witness, unjournaled drift, introduced inverted validity, source immutability, wrong pins, pending transactions and symlinks. Adversarial runner cases cover malformed/oversized/timed-out outputs and invalid policies. Missing actual binary fails validation; tests never silently skip it.
Architecture Decision Records
Registry of non-obvious architectural decisions made in synapse.
| ADR | Title | Status |
|---|---|---|
| 001 | Rust 1.85 + edition 2024 as floor | Accepted |
| 002 | MCP via official rmcp 1.7 | Accepted |
| 003 | rusqlite 0.39 with bundled feature | Accepted |
| 004 | YAML via serde_yaml_ng (serde_yaml/serde_yml banned) | Accepted |
| 005 | Git hooks via core.hooksPath | Accepted |
| 006 | MSRV policy: piso 1.85, subida via ADR | Superseded by 009 |
| 007 | Release pipeline: git-cliff + dist + cargo-release | Accepted |
| 008 | Embedding backend (fastembed-rs + BGE-M3 default) + vector storage (sqlite-vec) + NER backend (GLiNER-multi) for v0.5: supersedes the tentative release-pipeline reservation from ADR-007 §Consequences | Accepted |
| 009 | Subir MSRV para 1.88 (ecosystem floor real) | Accepted |
| 010 | Workspace federation: leve, opt-in, registry-based | Accepted |
| 011 | Workspace freshness: advisory git remote drift detection | Accepted |
| 012 | Provenance frontmatter (x-ai-*) on AI-authored entities | Accepted |
| 013 | .synapse/audit.ndjson format + retention policy | Accepted |
| 014 | Portable identity bundle (me.synapse) | Accepted (prototype) |
| 015 | File-size threshold + modular split policy (yellow 500-800 / red 800+) | Accepted |
| 016 | Portable identity v2: mandatory ed25519 + revocation | Accepted (supersedes ADR-014 §v0.3 non-goals) |
| 017 | Curriculum schema: attested_involvement + skills + CV format | Accepted |
| 018 | LSP companion via tower-lsp-server 0.23 (active fork) | Accepted |
| 019 | Terminal styling via anstream + anstyle; rich-by-default, --plain opt-out | Accepted |
| 020 | Self-identity: capture once globally, seed the self person per workspace | Accepted |
| 021 | TTY progress via indicatif, gated by is_rich() (stderr, hidden off-TTY) | Accepted |
| 022 | Evolve the propose/accept gate (BR-12.3) into a confidence-tiered write policy | Accepted |
| 023 | Read-only query tool: structured aggregation, no raw SQL (injection/exfiltration-proof) | Accepted |
| 024 | SYN-185: batch approval over syn pending (accept/reject --all), not a syn agent runner | Accepted |
| 025 | SYN-220 syn capture --from-git: harvest people+project (not decisions) as proposals; bot-filtered, idempotent | Accepted |
| 026 | SYN-239 two-tier tamper-evident audit: git-versioned per-author segments → Merkle DAG; unversioned → self-chained + ed25519 signed checkpoint | Accepted |
| 027 | SYN-223 bitemporal: store valid-time only (x-valid-* + x-supersedes); transaction-time from git; supersession = invalidation | Accepted |
| 028 | SYN-234 open entity types via .synapse/types.toml validation registry (closed-world, ADR-023-safe); EAV for indexed fields | Accepted |
| 029 | SYN-241/245 deterministic per-fact trust (WPM: Confidence × Freshness × Supersession × Provenance); mutual-kNN edge weight | Accepted |
| 030 | SYN-249 git as integrity substrate + ed25519 identity bound to SSH-format git commit signing; recommend-and-verify default | Accepted |
| 031 | SYN-223 belief-time axis (x-recorded-at/x-invalidated-at): third axis distinct from git transaction-time; deterministic supersession (invalidation-not-deletion). Amends ADR-027 | Accepted |
| 032 | SYN-254 per-type identity fields → deterministic duplicate detection routed to the gate (detect-don’t-merge); never derives the id from the hash; identity ⊆ indexed. Extends ADR-028 | Accepted |
| 033 | SYN-248 per-type policy reshaped to the profile-vs-log decay axis ([policy.<kind>] decay); advisory only, never bypasses the gate. Extends ADR-029/028 | Accepted |
| 034 | SYN-249C scope: doctor reports HEAD commit signature posture (dep-free); cryptographic SSHSIG verification (ssh-key dep) deferred to multi-user/Commercial, not smoke-testable here. Scopes ADR-030 | Accepted |
| 035 | SYN-262 syn verify foundation: canonical logical dump (hash the reproducible logical manifest, not the non-deterministic .db; exclude FTS/pending/ledger/vectors/mentions:* enrichment) + signed synapse.verify-report/1 in-toto schema (reuses SYN-247 ed25519). Companion to ADR-026/027/030 | Accepted |
| 036 | SYN-266 verify-first: audit-log replay-to-store is unsound (hash-only log, non-journaled wall-clock, state-based editable-Markdown store). Reframe INV-4 to replay determinism (provable; clock seam via pin_clock) + reconstruction is INV-1/INV-7, not replay. verify --deep = bounded journal-integrity check, not store reconstruction. Amends ADR-027 | Accepted |
| 037 | SYN-270 syn audit witness (signed synapse.audit-witness/1: git HEAD + per-author segment prefixes) + verify --since INV-12: prove history is an append-only extension (git ancestry + prefix stability), offline, dep-free. Closes the force-push/rewrite gap ADR-026/030 left to a remote. Tamper-evident, not tamper-proof | Accepted |
| 038 | SYN-276: ship v1.0 on ort = 2.0.0-rc.12 (transitively forced by fastembed + the gliner fork) with a tracked exit to ort 2.0.0 final once upstream supports it; the “stable” promise scopes the Synapse surface (schema/CLI/MCP), not the transitive ML runtime. Scopes ADR-008 | Accepted |
| 039 | SYN-287 syn undo: Phase 1 reverses a capture_fact create by removing the entity (payload-derived, works uncommitted); state-mutating kinds refuse until Phase 2 (git-restore / pre-image). Never replays the audit log (ADR-036); CLI-only (human corrective, not an agent self-undo tool); atomic + audited + conflict-guarded | Accepted |
| 040 | SYN-290 benchmark methodology contract (checksummed corpus, median/p95/p99, machine stamp, stub=latency / real=quality, publish raw + reproduce) + charts via a hand-rolled deterministic SVG writer (no charting dep; only sha2 added); poloto/vl-convert/hyperfine documented alternatives. Extends synapse-bench | Accepted |
| 041 | SYN-298 agent-in-the-loop benchmark driver: drive with raw claude -p headless (stream-json trajectory + total_cost_usd, SYN-297 three-gate recipe, zero new dep) now; adopt the Claude Agent SDK later (a non-Rust sidecar) when a fully-unattended fleet / programmatic connect-status is needed. Owner-run; reliability via N≥5 runs → pass@1 + pass^k keyed on the syn verify dump digest. Companion to ADR-040 | Accepted |
| 042 | SYN-301 retrieval quality: attack the measured recall@1 ~0.78 with (1) deterministic structure-aware chunked embeddings (passage-granular vectors, best-chunk pooling; no ML segmenter) and (2) a cross-encoder reranker (bge-reranker-v2-m3 on the shared ort/fastembed copy, no new backend) over the RRF shortlist. Ranking-only (never trust/gate/supersession); disposable-index; benchmark-gated (ship only on a persona-bench --features real recall@1 win). Rejects TF-IDF/LSA (redundant), TextTiling/semantic-chunk (non-deterministic), external rerank API (local-first), K-Means (opaque). Extends ADR-008, composes ADR-029 | Accepted |
| 043 | SYN-303 governed capture from agent-supplied context: Synapse composes the MCP ecosystem, it does not build calendar/Slack/ticket connectors. Steer the agent (via tool descriptions + instructions, not auto-firing prompts; no server sampling: deprecated/SYN-219) to enrich proposals from context it already has, and govern the enriched write with hard invariants: enriched ⇒ never auto_apply=safe/accept --all (closes the auto-linked-mislink hole), structured-field whitelist, no raw external data persisted, provenance = agent-asserted source-class only, redactable, verify-honest. Reframes SYN-206 as compose-not-integrate; complementary/defensive, sequence after SYN-271/SYN-254; v1.3 or fold into SYN-284. Composes ADR-025/022/012 | Accepted (MVP) |
| 044 | SYN-306 composite/parallel search + deterministic relevance calibration: independent legs (FTS, vector) run concurrently on their own connections (deterministic fusion), the SYN-304 per-hit signal becomes a bounded [0,1] compatibility score (leg agreement + rank position). persona-bench --features real k-sweep {20,40,60} showed recall@1 identical at every k (0.85 final) → kept the canonical k=60; weighted-RRF stays equal (no per-leg bias). Rejects RSF/DBSF, global title>body field weighting, FTS5 k1/b tuning, learned LTR. Extends ADR-042, composes the SYN-86 RRF | Accepted |
| 045 | SYN-307 on-device retrieval-model policy: add models only behind a permissive-licence gate + ort/fastembed substrate gate + a persona-bench win. E5-class needs an asymmetric query:/passage: embed seam (fastembed does not auto-prefix); EmbeddingGemma-300M is opt-in-only (Gemma licence + gated repo); Qwen3-Embedding deferred (no ONNX); MRL truncation is per-model, BGE-M3 exempt; the discarded BGE-M3 sparse output is a separate spike. Rejects SPLADE (non-commercial) and a second native backend. Disposable-index only. Extends ADR-008/042 | Proposed |
| 047 | #288 multi-project brain: the catalog (federation registry, syn ws) + syn mcp --workspace (#287) + attributed federated reads ship today (phases 1–3). The always-on resident daemon (OS auto-start, persistent process, catalog-driven discovery) is deferred behind its own future ADR: large OS-specific infra + a new security/IPC model (composes SYN-235), not a v1.x code drop. Follow-up SYN-317 | Accepted |
| 048 | #160 (SYN-194) keep enrichment synchronous (BR-13 enrich-before-commit). Async / eventual-consistency stays trigger-gated: build only on a measured p95 write regression past the budget. Disposability backfill (reindex --relink + embed --all, ADR-046) lowered the pressure. No async scheduler ships now | Accepted |
| 046 | SYN-315 (#289) bitemporal field corrections: an update_status in-place edit records valid-time at the field level (x-field-history frontmatter → entity_field_history table), so --as-of <before> reconstructs the prior value while the entity stays visible on both sides. Rejects stamping x-valid-until on the corrected entity (breaks post-correction visibility), a shadow-entity snapshot (id-namespace pollution), and reframe-only. Disposable (reindex rebuilds it from Markdown), verify-honest. Extends ADR-027/031 | Accepted |
| 049 | Composable knowledge framework and source revision adapters | Accepted (design) | | 050 | Typed probabilistic diagnostics with abstention | Accepted (design) | | 051 | Sealed transitions, source drift and independent witnessing | Accepted (design) | | 052 | Persisted receipts and explicit verification profiles | Accepted (design) | | 053 | OIDC subject/key binding and scoped authority | Accepted (design) |
New ADR? Copy template.md, name NNN-kebab-case-title.md,
keep one decision per file, link it in this index.
| 054 | Maintained YAML backend and compatible security patches (SYN-330) | Accepted |
| 055 | Cryptographic Git signatures and pinned local trust | Accepted | | 056 | Durable workspace prepare/commit and bounded recovery | Accepted | | 057 | Governed Markdown suffix and independently retained witnesses | Accepted |
| 058 | Recoverable native receipts and explicit local assurance; team authority stays pending | Accepted |
| 059 | Shared pure scoped device evidence and explicit offline identity verification | Accepted (binding component) |
| 060 | Current event-bound shared admission and expected heads | Accepted (protocol component) |
| 061 | Pinned Memory CI policy and separate proof dimensions | Accepted |
| 062 | Scoped exact diagnostics and unambiguous supersession | Accepted |
| 063 | Versioned bounded knowledge process contract | Accepted |
| 064 | Independently pinned commit roster revisions | Accepted |
| 065 | Native shared source sealing through an authenticated host | Accepted (native component) |
| 066 | Historical team assurance and original seal witnesses | Accepted (verifier component) |
| 067 | Frozen native source candidates before shared review | Accepted (native component) | | 068 | Scoped read-only provider execution and failed initial model default gates | Accepted (experimental adapters) | | 069 | Explicit current Git/Markdown and GitHub revision acquisition with access boundaries | Accepted | | 070 | Access-filtered source lineage, explicit review candidates and guarded local native annotation acceptance | Accepted (shared reads/local acceptance) | | 071 | Guarded retained pending rejection and exact operator review | Accepted |
| 072 | Bundled pinned Mermaid preprocessing and local runtime for documentation diagrams | Accepted (browser acceptance pending) |
ADR-001: Rust 1.85 + edition 2024 como floor
Date: 2026-05-24 Status: Accepted Author: Rômulo Febasi
Context
Synapse é um projeto novo (maio 2026). Decidir agora o MSRV é decidir quem consegue compilar a partir do código-fonte (Homebrew, nixpkgs, Debian stable, dev de máquina antiga). Decidir agora a edition (2021 vs 2024) fixa convenções de macro, idioms de async, e capacidades de bibliotecas que dependem da edição.
Decision
edition = "2024"(Cargo.toml erustfmt.toml).rust-version = "1.85": primeira stable que suporta edition 2024 (lançada em fev/2025).- Não pinar abaixo de 1.85: pinar mais baixo nos custa edition 2024 e ganhamos nada (sem dep crítica abaixo desse).
- Não pinar mais alto agressivamente (como
uvfaz com 1.93): corta packagers que ainda não pegaram a versão.
Consequences
- + acesso a
let-elseem mais posições, async closures, melhorias noasync/await,genblocks (quando estabilizado), trait upcasting. - + alinhamento com ripgrep,
jj, e a maioria dos CLIs modernos. - − dev em máquina com Rust < 1.85 precisa rodar
rustup update. - Revisitar: quando Rust 2.0 (hipotético) for anunciado, ou quando metade do top-100 do crates.io tiver subido o MSRV para 1.90+.
Alternatives considered
edition = "2021": ecossistema migrou. Não há razão para um projeto novo nascer em edition antiga.- MSRV “stable - 3” rolling: bom para libs amplamente consumidas, ruim para CLIs distribuídas por packagers: quebra de MSRV em patch release queima reputação.
- MSRV 1.85 exato (sem
+): clippy e cargo desencorajam pin exato para crate aplicações: usamos"1.85"que significa ≥ 1.85.
ADR-002: Adotar rmcp 1.7 como SDK MCP
Date: 2026-05-24 Status: Accepted Author: Rômulo Febasi
Context
Synapse expõe sua base de conhecimento via Model Context Protocol. Em maio de 2026 existem três caminhos:
rmcp: SDK oficial Anthropic sobmodelcontextprotocol/rust-sdk. Atualmente em 1.7.0 (lançado 2026-05-13), 78 releases, 1.x estável semver, edition 2024.mcp-protocol-sdk/mcp-sdk-rs: alternativas comunitárias.- Implementar JSON-RPC sobre stdio na mão usando
tokio+serde_json.
Decision
Usar rmcp = "1.7" com features ["server", "transport-io", "macros"].
Consequences
- + alinhamento com o que Claude Code consome: menos surpresas em atualizações de protocolo.
- + macros
#[tool]reduzem boilerplate. - + ferramenta versionada em lockstep com a especificação MCP.
- − vendor-lock leve no ecossistema Anthropic (mitigado pelo MCP ser spec aberta).
- − ciclo de release rápido (~2 semanas). Pinar com til (
~1.7) por 6 meses, então afrouxar para caret. - − puxa
tokio; mitigado por feature flagmcp(default-on no release; quem só usa CLI pode opt-out).
Alternatives considered
- Implementar JSON-RPC na mão: rejeitado. Reinventaria parsing,
tracing, validação de schema, e ficaríamos sem as macros que mantêm
tools.rsenxuto. O custo é alto e o ganho (independência de SDK) é baixo enquanto a spec MCP é estável. mcp-protocol-sdk: muito menos baixadas, sem macros, trail de features em relação ao oficial.
ADR-003: rusqlite 0.39 com feature bundled
Date: 2026-05-24 Status: Accepted Author: Rômulo Febasi
Context
Synapse usa SQLite como índice (FTS5 obrigatório; possivelmente
sqlite-vec em fase 2). Em Rust as opções viáveis para acesso a SQLite
são rusqlite (síncrono), sqlx (async), libsql (fork da Turso).
Synapse roda como processo único, local, sem concorrência alta. A maioria das chamadas é leitura ad-hoc; escritas são raras e curtas.
Decision
rusqlite = "0.39" com features:
features = ["bundled", "modern_sqlite", "blob", "backup",
"functions", "load_extension"]
bundledcompila libsqlite junto: elimina dependência da versão de SQLite no host (importante: macOS 14 ainda manda SQLite 3.43, antigo para nossas necessidades de FTS5/vec).load_extensionnecessário para futurosqlite-vec(fase 2).
Em chamadas vindas do MCP (que roda em tokio), envolver com
tokio::task::spawn_blocking.
Consequences
- + mesmo binário, mesmo SQLite, em macOS/Linux/Windows.
- + FTS5 sempre disponível.
- + API síncrona simples mapeia bem para um processo single-user.
- + binário ~2 MB maior; aceitável.
- −
tokioprecisa de wrapperspawn_blockingnas bordas async.
Alternatives considered
sqlx: overkill: toda sua complexidade (query macros, pool, compile-time check) endereça problemas de server multi-conexão que Synapse não tem.libsql: pertinente só se quiséssemos replicação Turso, o que não é o caso. Comunidade menor, menos extensões funcionam.rusqlitesembundled: causa “funciona-na-minha-máquina” clássico: versão da libsqlite varia por SO.
ADR-004: YAML via serde_yaml_ng (serde_yaml e serde_yml banidos)
Date: 2026-05-24 Status: Superseded (backend selection) by ADR-054 Author: Rômulo Febasi
Context
Frontmatter de Markdown em Synapse é YAML. Em maio de 2026 o ecossistema Rust YAML está fragmentado e perigoso:
serde_yaml(dtolnay): arquivado upstream, última release0.9.34+deprecated(mar/2024). Sem manutenção.serde_yml: fork inicialmente popular via search/Google. Possui RUSTSEC-2025-0068: “unsound and unmaintained, AI-generated changes are complete nonsense or unsound”.cargo deny checkrejeita.serde_yaml_ng(acatton): fork mínimo do último commit do dtolnay, mantido ativamente, ~686k downloads/mês, em transição paralibyaml-safer.yaml-serde(yaml org): replacement “oficial” em desenvolvimento, ainda não ecossistema-default.
Decision
- Usar
serde_yaml_ng = "0.10". - Banir
serde_yamleserde_ymlemdeny.toml. - Isolar todas as operações YAML em
crate::store::frontmatterpara que swap futuro (parayaml-serdequando maturar) seja um one-file change.
Consequences
- + sem advisory bloqueando releases.
- + API idêntica à do
serde_yamloriginal: sem custo de migração. - −
gray_matter(usado para split de frontmatter) pode trazer YAML parser próprio. Auditar viacargo tree -dperiodicamente; se trouxer um banido, considerar fork ou substituir por split manual---serde_yaml_ng.
Alternatives considered
- JSON ou TOML no frontmatter: rejeitado. YAML é o padrão de fato para frontmatter em PKMs (Obsidian, Logseq, Hugo, Jekyll); divergir fricciona usuário que já tem hábito.
- Implementar parser YAML mínimo: não é nosso problema resolver YAML para o ecossistema; custo alto, ganho zero.
ADR-005: Git hooks via core.hooksPath = .githooks/
Date: 2026-05-24 Status: Accepted Author: Rômulo Febasi
Context
Os repos da Febasi (Soma, Eved, Auth) usam Conventional Commits com
escopo (type(SCOPE): subject), mas não têm hooks configurados, a
convenção é manual. Para Synapse queremos automatizar a validação sem
introduzir dependência de outro ecossistema (husky/lefthook são Node;
pre-commit é Python).
Decision
- Versionar hooks em
.githooks/(commit-msg,pre-commit,pre-push). - Ativar via
git config core.hooksPath .githooksnoscripts/bootstrap.sh. - Hooks são scripts shell POSIX simples, sem dep externa obrigatória.
- Validação de commit message via regex no
commit-msg:^(feat|fix|docs|refactor|perf|test|chore|build|ci|revert|style)(\([a-zA-Z0-9_\-]+\))!?: …$ pre-commitfazcargo fmt --check(rápido) etypos(se instalado).pre-pushfazcargo clippy+cargo nextest run.
Consequences
- + zero dependência de runtime externo (Rust-only).
- + versionado em git: qualquer dev tem a mesma proteção.
- + funciona idêntico em CI e local.
- − primeiro setup exige rodar
./scripts/bootstrap.sh(não é automático após clone: limitação do próprio git). - − sem UI bonita de
lefthookpara reportar quais hooks rodaram.
Alternatives considered
lefthook(binário Go): excelente, mas adiciona uma dep binária externa apenas para hooks que cabem em 50 linhas de shell.pre-commit(Python): idem; pior ainda: exige Python e YAML config em projeto Rust.cargo-husky: instala hooks nocargo test. Surpresa para devs que rodamcargo builde não veem hooks ativos. Rejeitado.- Nada (manual): o que Febasi tem hoje; lapsos acontecem. Synapse é a chance de melhorar o padrão.
ADR-006: Política de MSRV (Minimum Supported Rust Version)
Date: 2026-05-24 Status: Superseded by ADR-009 Author: Rômulo Febasi Supersedes: consolida e formaliza o que ADR-001 introduziu
Histórico: esta ADR fixava o piso em 1.85. Ao rodar a primeira CI real, deps transitivas (
darling 0.23,etcetera 0.11) provaram que o piso verdadeiro do ecossistema 2026 é 1.88. ADR-009 documenta a subida.
Context
Há confusão recorrente entre MSRV e versão de uso:
- MSRV (
rust-versionnoCargo.toml) é o piso: a versão mais antiga que ainda compila o projeto. - Versão de uso é a stable que o desenvolvedor de fato tem
instalada (
rustup show); cargo simplesmente usa essa, desde que seja ≥ MSRV.
Em maio de 2026 a stable corrente é 1.95.0. Synapse declara
rust-version = "1.85". Isso não significa “estamos usando Rust
antigo”: significa “compila a partir de 1.85”. Quem tem 1.95 compila
com 1.95 normalmente, com acesso a todas as APIs estabilizadas até essa
versão (desde que não sejam usadas no código).
A questão de política é: quando subir o MSRV, e por quê.
Decision
-
Manter
rust-version = "1.85"na v0.x. É o piso de edition 2024, o limite inferior natural. Não baixar. -
Não pinar no exato latest stable. Pin no
"1.95"em maio/2026 significa que em junho (1.96 sai) o projeto está travado uma versão atrás do querustup updateinstala: fricção sem benefício. -
Subir o MSRV é uma decisão consciente, registrada em ADR. O gatilho é necessidade real: uma API estabilizada acima de 1.85 que resolve um problema concreto. Não subir “porque há versão nova”.
-
Job
msrvno CI valida que o código ainda compila no piso declarado. Esse job é o único que protege contra “alguém usou um recurso pós-1.85 sem perceber”. Quebrou? Ou se reverte o uso, ou se sobe o MSRV com ADR. -
Quando subir. Cenários que justificam:
- API/feature de std/cargo necessária e sem fallback razoável.
- Pelo menos uma das deps críticas (clap, rmcp, rusqlite, serde, tokio) subiu MSRV acima do nosso, forçando.
- Marco de release maior (v1.0, v2.0): oportunidade de limpar.
Consequences
- + desenvolvedor com qualquer Rust ≥ 1.85 consegue compilar.
- + packagers conservadores (Debian, Homebrew formula clássica) permanecem viáveis sem fricção.
- + subir MSRV vira evento documentado, não acidente.
- − o código não pode usar livremente o que estabilizou pós-1.85 (let chains em mais posições, gen blocks, alguns combinators). Quando precisar, abrir ADR.
- − o job
msrvno CI é mais uma matriz para manter.
Alternatives considered
- Manter
rust-version = "1.95"(exato). Rejeitado: auto-fura o pé toda vez querustup updateinstala a próxima minor: pesquisa recente em GitHub mostra que isso é o anti-padrão #1 de MSRV em projetos pessoais. - MSRV rolling “stable - N” (estilo cargo/rust-analyzer). Bom para ferramentas oficiais com base massiva; overkill para um projeto novo. Volta a discussão se chegar v2.0.
- Omitir
rust-version. Significa “compila no que você tiver”, ambíguo, sem garantia, atrapalha pacote em distros que checam. - MSRV 1.90 ou 1.93. Discutido em conversa antes desta ADR; rejeitado em favor de 1.85 porque (a) nenhum recurso pós-1.85 é load-bearing no MVP e (b) 1.85 é o piso lógico (edition 2024). Subir é decisão de uma ADR futura quando houver demanda concreta.
ADR-007: Reviewed release inputs and verified distribution
Date: 2026-05-24 Status: Accepted original decision and merged SYN-340 amendment; SYN-343 amendment under review Author: Rômulo Febasi Amendment: 2026-10-17, SYN-340 (#343)
Original context and decision
The original decision separated three responsibilities: git-cliff generated
Conventional Commit notes, cargo-release coordinated version/tag changes, and
Axo dist was proposed for cross-platform packaging and installers. The intended
result was a readable changelog, consistent package/tag versions and downloadable
platform archives. The first workflow generated notes after a tag and committed
them back to main. The implementation now uses native Cargo build jobs rather
than an installed/configured Axo distribution pipeline. Neither dist nor
cargo-release is required by the checked-in release workflow.
The original promise of fully reproducible binaries exceeded the evidence: checksums and signatures authenticate selected bytes, but do not establish independent build reproducibility. This amendment narrows that claim.
Observed problems
At source baseline 21242ae, the workflow can package a tag’s old changelog,
then create a different changelog commit on main as github-actions[bot].
That source write bypasses review and the required author/committer identity.
Manual dispatch has no tag input and derives archive names from its branch.
The public mirror infers a tag from workflow_run.head_branch, which is not the
selected build tag for a manual run. Mirror retries delete the existing release
and tag before recreating them. The release job also lacks an exact platform
asset inventory check.
SYN-340 amendment
Prepare before tagging
Review the version, lockfile package version and generated CHANGELOG.md in
ordinary source control. Generate notes with the inspected, already installed
git-cliff 2.12.0 and the repository’s cliff.toml. Commit author and committer
are both Rômulo Febasi romulo@febasi.com.br. Automation creates no source commit,
changes no source branch and does not invent a human author for bot work.
For the current candidate, 73 commits after v1.4.0 add public capabilities such
as the bounded knowledge process contract, Memory CI and governed source/shared
state operations. No breaking commit marker was found, and git-cliff recommends
v1.5.0. This supports a minor candidate, subject to public-contract review and
exact-source CI. The user’s existing release-update authorization applies once
the actual review and validation gates pass; this preparation is not evidence
that tagging or publication occurred.
Stable 1.x command, MCP and frontmatter contracts retain Semantic Versioning;
a breaking change requires explicit review and a major version. The changelog
marks breaking Conventional Commits with BREAKING rather than hiding them.
Bind publication to reviewed inputs
A tag push or a manual dispatch with an explicit existing tag runs preflight.
Manual release dispatch must also select that tag as its workflow ref, rather
than main. Checking out a tag alone does not change the OIDC signing workflow
identity; preflight rejects a branch-based dispatch or a different tag ref.
The selected tag must resolve to checked-out HEAD. The committed Cargo manifest,
lockfile root package and first changelog section must match the tag. Preflight
extracts exactly that reviewed section and records the source commit plus input
and notes SHA-256 digests. Every native build checks out that exact commit and
uses cargo build --locked --release --features network_backends with defaults.
--all-features remains excluded because it enables the deterministic
test_backend, which must not ship as production inference.
The source job does not run git-cliff, rewrite notes or push main. Source CI,
MSRV and independent review must pass before the authorized release tag is created.
Tagging and publication remain separate from this preparation change.
flowchart TB R[Reviewed version and generated notes] --> C[Committed source] O[Authorized release after gates] -. tag or explicit dispatch .-> P[Preflight] C --> P P -->|commit and input receipt| B[Four native builds] B -->|platform archives| V[Exact inventory and checksums] P -->|source and notes receipt| V V --> S[Sigstore manifest signing] S --> G[Source release] G -->|unchanged authenticated assets| M[Public mirror] classDef source fill:#f2efff,stroke:#5B3EE0,color:#1A1A1A classDef artifact fill:#F6F4EF,stroke:#5F596B,color:#1A1A1A classDef evidence fill:#fff0d3,stroke:#FFB454,color:#1A1A1A class R,C,O,P source class B,G,M artifact class V,S evidence
The dotted edge is an owner’s control action. Solid edges carry source, receipts or artifacts. None represents proof of factual truth, universal supply-chain safety, detached process containment or independent build reproducibility.
Preserve actual platform limits
The matrix remains Linux x86_64 and ARM64 on Ubuntu 24.04, Apple Silicon macOS, and Windows x86_64 MSVC. ORT’s downloaded runtime requires modern glibc and libstdc++; these are GNU/Linux archives, not generic musl/Alpine binaries. Intel macOS remains unavailable because the selected ORT release candidate has no corresponding prebuilt. SYN-174 tracks restoration; this amendment adds no unsupported target and makes no macOS notarization or Windows signing claim.
Publication requires exactly those four nonempty archives and the release input
receipt. SHA256SUMS covers the archives and receipt. The existing keyless
Sigstore workflow identity signs that manifest. The receipt binds declared
source and notes to the manifest; it is not an independent SLSA build attestation.
Mirror without destructive replacement
Automatic mirroring reads release-metadata from the exact successful originating
workflow run, with explicit actions: read; it does not infer a tag from a branch.
Before selecting the tag, the receipt’s source commit must equal the authenticated
originating workflow_run.head_sha. Downloading an artifact by run ID alone does
not establish that commit binding.
Manual mirroring requires an explicit tag. Both compare downloaded source notes,
receipt, checksums and original tagged workflow signature before publication.
The public mirror preserves the original manifest and bundle; it does not re-sign
assets under a different identity.
The publishing process refuses an already existing release, including an identical rerun. API failures cannot establish release absence. There is no release/tag delete, force update or asset replacement path. This is a workflow policy, not permanent immutability: repository administrators can still change or delete GitHub objects. A failed partial publication requires owner inspection; a rerun does not silently erase it. The public mirror’s tag is a distribution reference, not a claim that its repository contains the private source commit.
Consequences and alternatives
The reviewed changelog, package versions, native archives and signed receipt share an explicit source boundary. Local dependency-free controls reject wrong tags/commits, dirty or inconsistent versions/notes, incomplete inventories, changed hashes and attempted replacement. These are validator controls, not proof that GitHub Actions permissions or all platform builds have executed.
Preparation and release now require a deliberate source review, replacing the old promise of zero manual steps. The four native builds and publishing services remain external prerequisites. Keeping post-tag bot writes or deleting existing releases on retry was rejected because both replace reviewed state implicitly. Using a new distribution tool is deferred: this amendment hardens the actual native Cargo pipeline without a tool installation or product MSRV change.
See release verification for exact signing identity, selected-archive checks and the separate best-effort installer limitations.
Platform archive layout
Unix packaging sets COPYFILE_DISABLE=1 for tar. macOS otherwise adds AppleDouble
entries for host provenance attributes, outside the declared binary/README/license/
changelog layout. The consumer keeps its exact-member policy. An independent
actual-shell test observes this environment setting, invokes the real local tar
and checks exact archive members and bytes. Representative ZIP fixtures preserve
Windows layout coverage; no local Windows 7z execution is claimed.
The metadata-free producer receipt keeps the actual shell regression, local normalized tar compatibility and platform-build limits separate.
SYN-343: Canonical documentation bytes before signing
The published Windows v1.5.0 archive contains a CRLF-converted full changelog. Converting its 1,317 CRLF sequences to LF reproduces the tagged Git blob exactly. The strict downstream npm workflow correctly stopped before publication because the archive bytes do not match the source receipt. Source and mirror publication success did not prove that inner documentation matched the receipt.
The forward repair packages
README.md, LICENSE and CHANGELOG.md from git cat-file blob HEAD:<document>.
A checkout’s line-ending conversion cannot redefine these selected bytes. Release
metadata retains schema version 1 and the same field set. Its input digests now
read exact committed blobs after the existing dirty-input check, so uncommitted
manifest, lockfile or changelog edits still fail preflight.
Before writing the signable checksum manifest, the source publishing job reads the full changelog in each of the four staged archives without extracting files. It requires one bounded regular member at the declared platform path and an exact SHA-256 match to the verified source receipt. Missing, duplicate, malformed or changed changelogs stop this job before Sigstore signing or release creation. This narrow producer check complements the downstream archive checks; it does not replace them or establish independent build reproducibility.
The repair targets a new v1.5.1 patch release. Existing v1.5.0 tags, assets, notes and manifests remain unchanged. The npm consumer remains strict. Local evidence uses an actual Git CRLF checkout, the actual packaging Bash stage and local tar, plus representative ZIP archives. Windows 7z execution and all four new hosted platform builds remain pending. See the bounded verification receipt.
ADR-008: Embedding backend, vector storage and NER backend for v0.5
Date: 2026-06-02 Status: Accepted (locks the v0.5 brief decisions; consumed by SYN-81 + SYN-83 + SYN-164; supersedes the v0.4-era placeholder reservation) Author: Rômulo Febasi
Context
Slot 008 was reserved at the start of the v0.1 cycle for “the embedding
backend choice” with the explicit caveat that it would be filled “after a
benchmark spike, when v0.5 lands”. The v0.5 brief
(docs/researchs/2026-06-02-v0.5-semantic-layer-and-second-brain.md)
decided the four load-bearing questions on 2026-06-02:
- Embedding inference stays on-device. The local-first invariant from BR-1.4 and BR-12 binds; Ollama as a runner is a configurable alternative, not the default.
- Vector storage lives in the same
.synapse/index.dbto keep BR-1.5 (“one workspace, one source of truth”) intact. - Hybrid ranking uses RRF in v0.5: no labels yet, no place for convex combination.
- Entity linking at ingestion is mandatory (BR-13). It needs a small zero-shot NER model so that pt-BR-grammatical content like “reunião com a Carina sobre a flag do Remote Config” does not require the user to pre-create every proper noun as a slug.
The remaining unknown (and the thing the spike had to settle) was whether the chosen tooling actually composes:
- Does
sqlite-vecintegrate with arusqlite-backed connection without breaking thebundledSQLite that ADR-003 mandates? - Does
fastembed-rs5.x produce usable embeddings on a CPU-only laptop, and at what latency? - Does the Carina/Firebase canonical query (brief §1.1) actually surface the right notes when the corpus is small enough to read by eye?
The spike at spike-syn-89/ answered all three with numbers. This ADR
records the answers, locks the tooling choices, and registers the
issues SYN-81 + SYN-83 + SYN-164 have to navigate when production
wiring begins.
Decision
Adopt the following stack for the v0.5 semantic layer. Each row of the table is the default; the opt-down column is a workspace-level config override (per §1.3 of the v0.5 brief) for users who explicitly value smaller footprint over recall.
| Slot | Default (best) | Opt-down | Crate |
|---|---|---|---|
| Embedding inference | BGE-M3 multilingual quantised | BGE-small-EN-v1.5-Q | fastembed 5.x |
| Vector storage | sqlite-vec vec0 virtual table | (none: single backend) | sqlite-vec 0.1.x |
| NER (entity linking) | GLiNER-multi-base-Q | GLiNER-base-en-Q | also via fastembed-rs (ships GLiNER as ner_model) |
| Hybrid fusion | RRF with k = 60 | (none: v0.5 ships only RRF; convex combination is the v0.6 polish) | implemented in core::search |
The configuration knob lives in <workspace>/.synapse/config.toml:
[enrichment]
language = "pt-BR" # or "en-US"
embedding_model = "bge-m3" # or "bge-small-en-v1.5-q"
ner_model = "gliner-multi" # or "gliner-base-en"
mode = "heuristic+ner" # or "heuristic", "off"
Why these and not the alternatives
| Candidate | Why rejected as default |
|---|---|
| Ollama as embedding runner | ~700 MB binary + ~280 MB model = ~1 GB on disk, plus a separate daemon the user has to start. Violates “one binary, one workspace” UX even though it does not violate BR-1 strictly. Kept as configurable alternative (provider = "ollama") so a user who already runs Ollama for chat can reuse it. |
candle (pure Rust ML) | Strong on flexibility (any HF model, no ONNX conversion) but slower per-query than ort on CPU. For an ingestion-time hot path the throughput matters more than the flexibility. |
| OpenAI / Voyage / Cohere embedding APIs | Best raw quality but each write becomes a network call that leaves the workspace. Breaks BR-12 privacy posture without an explicit opt-in. Kept as stretch SYN-165 behind provider = "openai" | "voyage". |
lance for vector storage | Full ANN + HNSW out of the box, columnar Arrow-native storage. But ~50 MB of extra code in the binary and a separate mental model from “everything lives in .synapse/index.db”. The spike confirmed sqlite-vec is fast enough for the v0.5 ceiling of 10k entities. |
| Hand-rolled flat scan + SIMD cosine | Tempting (no extra dep). Rejected because sqlite-vec ships rank fusion + chunked storage + future HNSW for ~1.5 MB of native code; reinventing the storage layer just to avoid one dep is the wrong axis to optimise on. |
| Full Microsoft-style GraphRAG with community summaries | 3–5× ingestion cost ([GraphRAG vs Vector RAG 2026]). Requires LLM calls during ingestion which breaks local-first. The brief substitutes the lightweight heuristic + GLiNER combo and reserves the GraphRAG-class polish for v0.6+. |
Spike findings (the numbers behind the choices)
Ran 2026-06-02 on aarch64-apple-darwin (Apple M1 Pro, macOS 25.4),
cargo run --release, model: BGE-small-EN-v1.5-Q (the lightest variant:
the spike did not need the full BGE-M3 download to validate the
integration; the production-default BGE-M3 numbers are cited from the
public MTEB report referenced below).
[fastembed] model loaded in 11.12 s (cold start, first-time download)
[fastembed] embedded 8 samples in 69.48 ms (avg 8.69 ms/text, dim=384)
[sqlite-vec] registered, version = v0.1.9
[sqlite-vec] inserted 8 rows in 433.12 µs
[sqlite-vec] vec0 query (8-row corpus) ~130 µs / query
[footprint] release binary size ~21 MB
[footprint] model cache (BGE-small-EN-v1.5-Q) ~33 MB
[total wall-clock] 11.23 s (dominated by first-time fetch)
Carina/Firebase regression (brief §1.1 canonical query). Eight planted notes; query was the multi-hop concept “flag do Firebase para o checkout”. Top-3 cosine ranking (lower distance = closer):
project/checkout-v2 dist=0.6859
note/firebase-stack dist=0.7440
note/meeting-carina-2026-04-20 dist=0.7869
All three relevant notes surface in the top-3 with no tuning. This validates the v0.5 thesis: vector retrieval alone bridges the “Remote Config ↔ Firebase” concept gap even before the entity-linking heuristic + NER pipeline adds a graph layer on top.
Reverse query (“Remote Config feature flag eligibility”) was also
clean: firebase-stack + meeting-carina + api/firebase-remote-config
in the top-3.
Findings that survive the spike: issues for v0.5 production wiring
-
rusqlite version conflict: AMENDED 2026-06-02 during SYN-81. The spike flagged thatsqlite-vec0.1.9 declaresrusqlite ^0.31while the main crate runs onrusqlite 0.39(ADR-003). On closer inspection ofsqlite-vec’sCargo.toml, therusqlitedep is in[dev-dependencies]only: it backs the upstream crate’s own test suite, never its public API. SYN-81’s integration goes through the SQLite C ABI’ssqlite3_auto_extension, which is ABI-stable and version-agnostic. Decision: skip path B (vendor the C source) entirely. We depend onsqlite-vec0.1 directly as a runtime dep behindfeature = "vector"and register it viarusqlite::ffi::sqlite3_auto_extension. Total cost: ~10 lines insrc/index/vector.rsand oneOnceLock. The original path A (downgrade) was never viable; path B was real engineering work the spike’s surface read mis-attributed.unsafeexception. Wiringsqlite3_auto_extensionrequires a singletransmuteof a C function pointer. The crate’s#![forbid(unsafe_code)]is relaxed to#![deny(unsafe_code)]so thatsrc/index/vector.rscan scope a documented#![allow(unsafe_code)]. Every other file in the crate still fails the build onunsafe. This is the onlyunsafesite introduced by v0.5 and the only one allowed by the project’s architectural rules going forward.
-
First-run model download is 11 s + ~33 MB for BGE-small (~570 MB for the production-default BGE-M3 and an additional ~150 MB for GLiNER-multi). This download cannot land on the hot path of
syn init: UX-blocking. Decision: lazy initialise theEmbeddingBackendandNerBackendon the first write call, not on workspace open. Print an explicit “downloading model…” banner on the first fetch (SYN-83 + SYN-164 acceptance).Correction (SYN-214, 2026-06-07): the size estimates above were wrong. Measured from Hugging Face: the production-default BGE-M3 the code wires (
fastembed::EmbeddingModel::BGEM3→BAAI/bge-m3) is fp32, ~2.2 GB (the “~570 MB” was the int8gpahal/bge-m3-onnx-int8, offered as a choice in SYN-237), and GLiNER-multi quantised is ~350 MB, not ~150 MB. Real default footprint: ~2.5 GB. The English opt-downs are ~65 MB (BGE-small) + ~260 MB (GLiNER-base) ≈ ~325 MB. Canonical figures live indocs/MODELS.md. -
sqlite-vecis flat-scan only in v0.1.9: no HNSW index yet. For Synapse’s≤ 10k entitiesv0.5 ceiling this is fine (the spike measured ~130 µs per query in an 8-row table; even O(N) scaled to 10k stays well under the 150 ms search budget). Document the ceiling explicitly indocs/CONFIGURATION.md; revisit in v0.7 if a real user exceeds it. -
Fastembed cache discovery: the spike’s
duheuristic reported 0 MiB becausefastembed-rs5.x writes under~/.cache/fastembed_cache/(lowercase, no leading dot) rather than the~/.fastembed_cache/the spike probed. Cosmetic for the spike, butcore::embeddings(SYN-83) must readFASTEMBED_CACHE_PATHfirst, then fall back to the platform-correct path (XDG on Linux,~/Library/Caches/fastembed_cacheon macOS,%LOCALAPPDATA%on Windows), and Synapse should surface the chosen path insyn embed --checkso the user can sweep it manually.- AMENDED 2026-06-03 during SYN-83. Synapse owns the cache
directory and passes it to fastembed via
InitOptions::with_cache_dir. Precedence:[enrichment] model_cache_dir→SYNAPSE_MODEL_CACHE→ platform default (~/Library/Caches/synapse/models/fastembedon macOS, XDG on Linux,%LOCALAPPDATA%on Windows). The fastembed-ownedFASTEMBED_CACHE_PATHenv var is not read; one knob, one owner.syn embed --checkwill print the resolved path so users candu -shit themselves.
- AMENDED 2026-06-03 during SYN-83. Synapse owns the cache
directory and passes it to fastembed via
-
GLiNER via
gline-rsblocked byortRC pinning, NEW 2026-06-03 during SYN-164. The v0.5 brief asserted “fastembed-rs ships GLiNER via ORT: no separate ORT dep”. Verified false at wiring time: fastembed-rs 5.x carries embedding + reranker catalogues only, no NER. The canonical NER crategline-rs1.0.1 pinsort = "=2.0.0-rc.9"exactly while fastembed 5.15 pinsort = "=2.0.0-rc.12"exactly: Cargo cannot resolve two pinned RCs of the same crate. Three paths were considered:- A. Vendor / fork
gline-rsourselves and bump its ORT to rc.12. Owner of the fork; ORT RC API drift between rc.9 and rc.12 is non-trivial. - B. Use
kreuzberg-gliner-rs0.2.0: Kreuzberg’s Apache-2.0 fork ofgline-rswhose published crate re-pins ort to=2.0.0-rc.12and ships an alignedkreuzberg-orp 0.1.1. Public API surface (GLiNER<SpanMode>,Parameters,TextInput,SpanOutput) is identical to upstream, so the consuming code ports back the moment upstream re-aligns. - C. Ship v0.5 with heuristic-only NER via the existing
RegexNeruntil upstreamgline-rsre-aligns ort.
Initial reading (2026-06-03): path C, on the assumption that path B would land a second ORT copy in the binary (+50 MB).
Amended 2026-06-03: path B chosen. A build probe (
/tmp/probe-kg,cargo build --release) withkreuzberg-gliner-rs = "0.2"+fastembed = "5.15"resolved to a singleort 2.0.0-rc.12inCargo.lock. Both backends share the same ORT copy: no binary bloat. The earlier +50 MB claim came from misreading the fork’sCargo.toml: the published 0.2.0 carries ort=rc.12, even though the fork’smasterbranch on GitHub still shows=rc.9. With the real cost at zero MB, the only meaningful trade-off vs. path A is dependency hygiene, the Kreuzberg org’s main project (kreuzberg-dev/kreuzberg, 8.4k stars, 200 releases, last May 2026) is active enough to amortise that risk. SYN-164 lands in v0.5 againstkreuzberg-gliner-rs; we revisit when upstreamgline-rsre-aligns its ORT pin (issue #156’s exit criterion). - A. Vendor / fork
Consequences
- +1 dep family:
fastembed5.x pullsort(ONNX Runtime statically linked, ~25 MB compressed),tokenizers,hf-hub, and the BGE / GLiNER ONNX models on first use. Acceptable: the local-first invariant survives and the binary growth lands in the ~50 MB envelope the v0.5 brief §7 advertised. +1 dep:AMENDED 2026-06-02. Per the Findings amendment above, SYN-81 addssqlite-vecdoes not get added tosynapse’sCargo.tomlsqlite-vec = { version = "0.1", optional = true }as a normal optional runtime dep gated byfeature = "vector". No vendoring; the upstream Cargo dep ships the C source the build links in. The dev-deps-onlyrusqlite ^0.31constraint insidesqlite-vecis irrelevant to consumers.- Per-workspace language config: workspaces are now meaningfully
different from each other in what model they load. A user with
three pt-BR repos pays the ~720 MB cache once (shared); a mixed
install pays twice. Documented in
docs/IDENTITY.mdanddocs/CONFIGURATION.md. - Migration story: opening a v0.4 workspace with a v0.5 binary
triggers
syn embed --allautomatically on the first write (or runs it explicitly when the user typessyn embed --all); thevectormigration ships asmigrations_vec/0001__entities_vec.sqlper SYN-80. - Upgrade path documented in advance (added 2026-06-02 after the
owner-prompted critical review at
docs/researchs/2026-06-02-vector-storage-options-vs-sqlite-vec.md): if a workspace ever exceeds ~50k indexed entities and the flat-scan latency becomes visible, switch tovectorlite(HNSW-based SQLite loadable extension by1yefuwang1, ~10× faster searches than sqlite-vec’s flat scan, sameload_extensionshape, same SQL surface). The swap costs ~30 lines of FFI changes and zero business-logic change. Pre-staged ticket: SYN-203 in v0.7+. LanceDB / SurrealDB / DuckDB+VSS were evaluated in the same review and rejected as v0.5 migration targets: they would force rewritingWorkspace::tx, FTS5 callers, the pending queue and the audit-trail SQL (estimated ~2,400 lines + ~100 tests), which is a v1.0-shaped commitment, not a v0.5 spike. - Reverse compatibility: the schema additions are append-only;
removing the
vectorfeature flag at build time omits theentities_vectable cleanly. v0.4 binaries opening a v0.5 workspace ignoreentities_vec(FTS5 path still works). Documented in SYN-80. - MSRV stays at 1.88 (ADR-009); no fastembed-imposed bump in 5.x.
Alternatives considered (recorded for completeness)
The “why rejected” table above covers Ollama, candle, OpenAI/Voyage,
lance, hand-rolled flat scan, and full GraphRAG. Two more decisions
deserve explicit recording so the next reader does not have to relitigate
them:
- Embed model =
nomic-embed-text-v1.5instead of BGE-M3: Nomic is English-strong, 768-dim, ~280 MB cached, MTEB EN 62.39. The brief rejected it as default because Synapse is per-workspace multilingual per BR-13 §1.3; Nomic would force every pt-BR workspace to opt-up to a different model. BGE-M3 covers both languages competently at one model. - NER backend =
RelikorspaCyover an inference sidecar: both outperform GLiNER on top-tier benchmarks but require either a Python sidecar (Relik) or a separate runtime (spaCy). GLiNER is the Rust-native ONNX option. The 2026 Neo4j blog ([Entity Linking and Relationship Extraction With Relik]) explicitly recommends GLiNER for the “ingestion-time, no Python” branch of the decision tree.
Amendment 2026-06-04 (SYN-168): opt-out, not opt-in
The original decision shipped the semantic layer behind --features vector (off by default), deferring the default flip to “v0.6 if
adoption pulls it.” That was reversed inside v0.5: the layer is the
product, not an experiment, so it now ships in the default binary
(default = ["mcp", "v05_backends"]). Two constraints shaped how:
- Binary cost is small; model cost is not. Compiling fastembed +
GLiNER adds ~25 MB of statically-linked ONNX Runtime: acceptable as
a default. The ~920 MB of model weights are a different category, so
they are never pulled implicitly. A per-model readiness sentinel
(
core::embeddings::readiness, written the moment a backend loads) is the single source of truth;run_on_write, hybrid search, andsyn embed --checkall gate on it and degrade gracefully (skip embedding / fall back to FTS+graph) instead of triggering a download. The only consent paths that fetch are thesyn initonboarding prompt andsyn embed. Without the models the tool is a fully functional keyword + graph brain; the models add meaning-search and NER discovery on top. network_backendsandtest_backendstay opt-in. Hosted providers send content to a third party (privacy: local-first is the default per BR-13). The deterministic stub returns fake embeddings and must never reach a production binary; the release pipeline therefore builds--features network_backends(additive to the default set), not--all-features, which previously leakedtest_backendinto shipped binaries.
References
- v0.5 brief:
docs/researchs/2026-06-02-v0.5-semantic-layer-and-second-brain.md§2.1, §2.2, §2.4 - BR-13:
docs/business/BUSINESS_RULES.md - ADR-003:
rusqlite bundled(the constraint sqlite-vec collides with) - ADR-009: MSRV 1.88 (unchanged by this ADR)
- ADR-013: audit trail (the BR-13 enrichment pipeline writes to it on enrichment failures, but never on success: atomicity is preserved by the surrounding
Workspace::tx) - Spike crate:
spike-syn-89/(reproducer for the numbers above) fastembedcratesqlite-veccrate and Alex Garcia’s Rust integration walkthrough- Best Embedding Model for RAG 2026: Milvus (BGE-M3 multilingual MTEB numbers)
- Hybrid Search BM25 + Vector + Reranking 2026 (RRF baseline)
- GraphRAG vs Vector RAG 2026 (the v0.6 polish path we are not taking yet)
- Entity Linking and Relationship Extraction With Relik: Neo4j Developer blog (the GLiNER recommendation we are taking)
ADR-009: Subir MSRV para 1.88 (supersede ADR-006)
Date: 2026-05-24 Status: Accepted Supersedes: ADR-006 Author: Rômulo Febasi
Context
A ADR-006 fixou rust-version = "1.85" como
piso da v0.x, com o argumento “1.85 é o piso da edição 2024, ganho
zero em subir”. Esse raciocínio não bateu com a realidade do
ecossistema:
etcetera 0.11.0(única opção XDG decente para edição 2024) requer Rust 1.87.darling 0.23.0(puxado pelorust-embed/vergen-gix) requer Rust 1.88.vergen-gix 1.0.9,time 0.3.47+,wit-bindgen 0.51+,wasip3: todos exigem 1.87+ ou 1.88+.
Manter 1.85 obrigaria pinar versões antigas dessas deps no
Cargo.lock, e cada cargo update reabriria a discussão. A primeira
verificação real (CI rodando build on MSRV (1.85)) falhou
imediatamente:
error: rustc 1.85.1 is not supported by the following packages:
darling@0.23.0 requires rustc 1.88.0
etcetera@0.11.0 requires rustc 1.87.0
Decision
- MSRV = 1.88 em
Cargo.toml(rust-version = "1.88") e emclippy.toml(msrv = "1.88"). - CI job
msrvinstala 1.88 (não 1.85). - README e ADRs atualizados.
- A política de “não subir o MSRV agressivamente; subir só com justificativa registrada” da ADR-006 permanece válida: esta ADR-009 é justamente a justificativa formal para esta subida.
Consequences
- + Build passa com as deps recomendadas em ADR-004 e o dep-set inicial.
- + Compatível com Rust stable 1.88+ (lançada fev/2026; 7 versões atrás do atual 1.95 em maio/2026). Folga saudável para packagers.
- − Devs com Rust < 1.88 precisam
rustup update stable. Não há uso real disso em 2026. - − Primeira ADR retratada: registra que
rust-versionprecisa ser checado contra o resolver real, não inferido.
Alternatives considered
- Pinar versões antigas das deps no
Cargo.lock(manter 1.85): rejeitado. Cada bump puxado porcargo updatereabriria a discussão; debate sem fim para ganho marginal (Debian stable já tem Rust 1.88). - Subir só para 1.87 (mínimo necessário para
etcetera): rejeitado.darling 0.23exige 1.88; ficar entre 1.87 e 1.88 imediatamente reabriria a discussão na próxima minor dodarling. - Trocar
etceterapordirectories/dirs: tecnicamente possível, masetceteraé a escolha certa para a UX de paths em macOS (ver ADR-001 e crítica §5). Não vamos trocar arquitetura para evitar bump de MSRV.
Emenda 2026-06-04 (SYN-168): MSRV sobe para 1.89
Mesma lógica deste ADR (acompanhar o piso real do ecossistema), nova
ocasião. O backend de NER do v0.5 (kreuzberg-gliner-rs 0.2.0, SYN-164)
puxa wide 1.4.0, que declara rust-version = "1.89". Como o
kreuzberg-gliner-rs não aceita wide < 1.4 e não vamos forkar uma
dep de ML só para segurar o MSRV, o piso passa a ser 1.89.
Detectado de-riscando o cargo release do v0.5: um build em 1.88 exato
(--locked) aborta na resolução com wide@1.4.0 requires rustc 1.89,
e o job msrv do CI (que fixava @1.88 e buildava --all-features)
quebraria assim que o gliner_backend entrasse no grafo padrão.
Atualizado: Cargo.toml rust-version = "1.89", job msrv do CI para
@1.89 (em ubuntu-24.04), e este ADR. O bump não tira nenhuma
plataforma de baixo da mesa (toolchains 1.89+ amplamente disponíveis).
ADR-010: Workspace federation: leve, opt-in, registry-based
Date: 2026-05-24 Status: Accepted Author: Rômulo Febasi
Context
The product premise (README, line 1) is “for the developer who, alone and AI-augmented, juggles more projects than a team used to”. v0.1 ships a perfect single-workspace experience but is silent about the plural: when the same user maintains three or four workspaces (per client, per domain, per audience), there is no way to:
- discover them (“which workspaces exist on this machine?”),
- query across them (“who owns the payments API: in any of my workspaces?”),
- expose them as one surface to the AI assistant via MCP.
syn today behaves like git: command runs in the workspace it can find
walking up from cwd. Outside any workspace, every command but
init/setup errors out with Error::NoWorkspace. That is correct for
the workspace-local use case and wrong for the multi-workspace one.
The user’s proposal was: have syn init register the workspace in a
global SQLite owned by syn, expose CLI + MCP queries over all of
them, and add syn remote add <git-url> for remote integration.
This ADR decomposes that proposal critically and locks in the chosen shape before issues are opened.
Decision
Federation lands in v0.2 as seven layered tickets (SYN-75 … SYN-79
plus SYN-90 … SYN-92 for the identity card). The architecture is
leve por design: the global database stores metadata pointers
to workspaces, never the content of their entities; the identity of
each workspace lives in a versioned SYNAPSE.md in its own root.
F1: Workspace registry (SYN-75)
A SQLite file at the etcetera-resolved global path
(~/Library/Application Support/synapse/workspaces.db on macOS,
$XDG_DATA_HOME/synapse/workspaces.db on Linux) tracks every workspace
the user has ever syn init-ed on this machine.
Schema (single table; small, append-friendly):
CREATE TABLE workspaces (
id TEXT PRIMARY KEY, -- short slug derived from root path
name TEXT NOT NULL, -- display name (defaults to dir basename)
root TEXT NOT NULL UNIQUE, -- absolute, canonicalised path
git_remote TEXT, -- auto-detected from .git/config on register
federated INTEGER NOT NULL DEFAULT 0, -- privacy gate (SYN-79)
last_seen TEXT NOT NULL, -- ISO-8601, refreshed on every syn command
registered_at TEXT NOT NULL,
notes TEXT
) STRICT;
syn init writes the row (idempotent on root). Every command that
runs successfully refreshes last_seen. git_remote is auto-detected
from .git/config on registration (the workspace root is often a git
repo; if not, the column stays NULL).
F2: Federated read commands (SYN-77)
--all flag on read commands (search, info, graph) iterates the
registry, opens each workspace in turn, runs the existing
core::* use case, and merges results. Output always carries the
workspace identifier so the consumer (human or AI) knows where each hit
came from.
$ syn search "api pagamentos" --all
client-alfa/ api/pagamentos API Pagamentos
client-beta/ note/decisao-pgto Decisão de Pagamentos
personal/ (no hits)
Federated queries are read-only. Writes always target the local
workspace (the one Workspace::find resolves from cwd).
F3: MCP federation (SYN-78)
The MCP server exposes three new tools alongside the existing four:
list_workspaces: registry contents.federated_search:search_knowledgeacross opt-in workspaces.get_entity_from(workspace, type, id): read a specific entity from a non-current workspace.
The existing search_knowledge, get_entity, list_by_type,
project_graph continue to act on the current workspace only.
Adding federated_* cousins instead of overloading existing tools
keeps the v0.1 contract intact (BR-11.1).
F4: Git remote awareness (folded into SYN-75)
Captured automatically on registration. No syn remote add in v0.2, that command is reserved for F6 (cross-machine federation), which
stays in the backlog (SYN-210).
F5/F6: Cross-machine sync and remote federation
Kept in the backlog (SYN-209, SYN-210). They require design decisions this ADR explicitly defers:
- Sync model (CRDT? git-driven push/pull? merge conflict handling?)
- Authentication for cloning remote workspaces
- Cache invalidation strategy
- Performance bounds for N-workspace federation across N-machine sync
Privacy gate (SYN-79)
Federation is opt-in per workspace. Default federated = 0 on
register. Activation is explicit:
syn ws include <name> # set federated = 1
syn ws exclude <name> # set federated = 0
This addresses the “client A workspace cannot leak into client B
queries” concern. --all skips workspaces where federated = 0.
F7: Workspace identity card SYNAPSE.md (SYN-90/91/92)
The registry knows that a workspace exists. It does not know what
it is about. Without that, list_workspaces over MCP returns paths
and slugs: useless for routing a question.
Each workspace ships a single identity file at its root:
<workspace>/
├── SYNAPSE.md ← identity card (NEW)
├── .synapse/ ← tool area (existing)
├── projects/ ← entities (existing)
├── people/
└── …
Why root, not .synapse/
.synapse/is the “tool area”: disposable index, config, caches. The manifest is content: versioned, diffed, shared, owned by the human.- A root-level
SYNAPSE.mdmirrors theREADME.mdconvention, obvious, discoverable, hand-editable. .gitignorerules that hide.synapse/(recommended) do not accidentally hide the manifest.
Shape
YAML frontmatter for queryable fields, free Markdown body for the human/AI brief. Same parser, same atomicity, same i18n discipline as every other entity in the codebase:
---
# identity
name: Client Alfa Workspace
description: |
Knowledge base for Cliente Alfa: payments integration, vendor
contracts, team contacts. Focus on B2B payments and webhook reliability.
# categorisation (consumed by MCP list_workspaces routing)
topics: [payments, webhooks, b2b, vendor-management]
tags: [client-alfa, pagamentos]
# stakeholders (referenced as person slugs IF they exist locally)
maintainer: maria-silva
language: pt-BR
# advisory privacy hints: the real privacy gate is registry.federated
visibility: private # private | shared | public
shareable: false
# meta
last_curated: 2026-05-24
---
## Quick brief
This workspace covers the Client Alfa engagement: the payments
integration, the focal contacts, and active migrations. Use this
workspace when asked about Alfa, Acme Corp (the vendor), or webhook
v2 work.
## Out of scope
- Personal projects (those live in `~/personal-brain`)
- Cliente Beta work (separate workspace, `~/work/client-beta`)
Cache, not source of truth
SYNAPSE.md is the source of truth: same rule as BR-1.4 for
entities. The registry caches name, description, topics,
tags, language on:
syn init(initial create + write the file)syn ws refresh [<name>](manual; or auto when the file mtime beats the cachedlast_seen)- the watcher (SYN-65), when it lands
workspaces.db gains columns: description TEXT, topics TEXT
(JSON array), tags TEXT (JSON array), language TEXT,
manifest_updated TEXT. Everything optional, NULL on legacy rows
from before this ticket.
Read and write surfaces
CLI (SYN-91):
syn ws edit [<name>]: openSYNAPSE.mdin$EDITOR. On save, re-cache.syn ws refresh [<name>]: re-readSYNAPSE.md, update cache. No args = refresh all known workspaces.syn ws topic add|rm <topic>: append-only mutation of thetopicslist, atomic viaWorkspace::tx.syn ws tag add|rm <tag>: same fortags.
MCP (SYN-92):
get_workspace_manifest(workspace): returns the full document (frontmatter + body) for an opt-in workspace.add_workspace_topic(workspace, topic): proposes an append. Lands in the pending queue (BR-12.3, SYN-60). Same for tag.list_workspaces(cousin of SYN-78) now returns{id, name, description, topics, tags, language}per row: the routing signal the AI needs.
Why “AI can curate the card”
The AI assistant (interacting with the user across sessions) is in
the unique position to notice what a workspace is actually
becoming about. If, over weeks of Claude Code sessions, an audit
topic emerges in 80% of the conversations rooted on a workspace, the
AI proposes add_workspace_topic(workspace, "audit"). The user
sanctions (BR-12.3) and the next session benefits from the sharper
routing. The manifest evolves with use, not with one-shot curation.
Shareable in the future
Since SYNAPSE.md lives in the workspace folder, it travels with
the directory. When SYN-210 (remote federation via git) lands, a
clone of team/brain-shared.git immediately exposes its manifest, no extra protocol needed. The card is the contract.
Trade-offs
- +
list_workspacesover MCP becomes routing-useful: the AI picks the right workspace before even searching. - + Identity is versioned in git alongside content. Diffs of
SYNAPSE.mdare part of the project history. - + Same parsing/validation discipline as the rest: no new format, no new tooling.
- − One more file per workspace. Mitigated by
syn initbootstrapping it automatically. - − Cache invalidation. Mitigated by mtime check on every
command that touches the registry, plus explicit
syn ws refresh.
Consequences
- + Solves the user-stated problem with a small surface (one table, one flag, three MCP tools, four CLI flags).
- + Preserves BR-1.4 (“nada vive só no índice”): the global DB
has no content of its own, only pointers. Wiping
workspaces.dbis recoverable by re-runningsyn initin each root. - + Preserves the v0.1 CLI/MCP contract: federation lives behind
--alland behind new MCP tools; existing behaviour unchanged. - + Privacy by default: opt-in flag keeps confidential workspaces out of cross-queries.
- − Federated query cost is O(N workspaces). Acceptable for realistic N (≤ ~20); above that needs a persistent index, that’s a separate ticket if/when demand surfaces.
- − Two databases to reason about (
workspaces.db+ each workspace’sindex.db). Mitigated by giving the registry a single, trivial schema. - − Moved-or-deleted workspaces leave stale entries. Handled by
syn ws gc(sweep paths that no longer exist; non-destructive, warns before removing).
Alternatives considered
- Federation pesada (content mirror in global DB). Rejected. Doubles storage, reintroduces sync/consistency between workspace and global, violates BR-1.4, and a deleted workspace orphans its rows. The leve registry has none of these problems and the query-on-the-fly path is fast enough.
- No registry at all; scan
~for.synapse/. Rejected. Slow, fragile (skips workspaces outside~), no place to storefederatedflag orgit_remoteauto-detected. - Federation always-on (no opt-in). Rejected. Clients with
confidential data deserve a default of “do not leak”.
--alldefaulting to “all you opted in” is a safer default than “every workspace I find”: even on a single-user machine. syn remote add <git-url>in v0.2. Rejected. The user proposed it; it’s a great idea, but it pulls in clone caches, auth, sync semantics. Tracked in SYN-210 (backlog) and revisited when local federation has flown in production for a while.
ADR-011: Workspace freshness: git remote drift detection
Date: 2026-05-24 (revised 2026-05-25) Status: Accepted (Phase 1 in SYN-93; Phase 2 implemented in SYN-93b, 2026-06-04) Author: Rômulo Febasi
Context
ADR-010 (workspace federation) lets one machine register many
workspaces and ADR-010 §F4 captures git_remote automatically on
register. Once workspaces start being shared via git between
team-mates, the file you read locally can be silently behind the
remote, and Synapse, by default, returns confidently wrong data.
For a tool whose value is “the AI consults your world with your facts”, returning stale facts is the worst failure mode: confident, silent, undetectable.
The user proposed: every N minutes, check whether the local workspace
is in sync with origin. If behind, warn (in the CLI and through the
MCP) and recommend git pull. Crucially, never block the user.
This ADR locks the design so SYN-93 lands opinionated, not improvised.
Decision
Synapse exposes a freshness signal per workspace: an advisory status derived from a periodic comparison between local HEAD and the configured git remote. The signal is advisory, never authoritative and never blocking. It surfaces in two places:
- CLI: a one-line note on stderr before the regular output of
commands that read from a workspace whose freshness is
behindordiverged. - MCP: every tool response that returns data sourced from a
workspace embeds a
freshnessfield; the AI assistant decides whether to surface it to the human.
Design rules
- Never blocking. A freshness check that fails (network error,
timeout, auth problem) does not fail the command. Status falls
back to
unreachable; the command runs normally. - Bounded latency. The async dispatcher has a hard 3-second timeout per remote check. Past that, the check is aborted; the stored status keeps its previous value.
- Async after the work. A command that reads a workspace triggers a background check after the command has already responded to the user. The current command sees the previous check’s result, which is acceptable because the goal is detection, not blocking.
- Rate-limited. A check fires at most once per workspace per
freshness.interval_minutes(default 30). Two parallel commands seeing a stale stamp may both fire, the race is benign (last writer wins). - Local-only by default. CLAUDE.md forbids shelling out to git
from production code, and the simplest signal (drift since the
user’s last
git fetch) already covers the most common failure mode. Phase 1 (SYN-93) compares the localrefs/heads/<branch>SHA with therefs/remotes/origin/<branch>SHA: both read directly from the filesystem, no subprocess, no network, no auth surface. Phase 2 (SYN-93b, deferred) layers a realgix-protocolls-refs call on top to detect drift the user hasn’t fetched yet, with the 3s timeout andunreachablefallback. The two phases share the same status enum and the same registry columns. - Network path is opt-out, not opt-in. Default is enabled when a
workspace has a
git_remote. Disable per-workspace withsyn ws freshness off <name>or globally in config:[freshness] enabled = false. TheSYNAPSE_NO_FRESHNESS=1env var disables for the current process only (CI-friendly). - No fetching. Synapse never runs
git fetch. Recommendation output suggests it to the user; execution belongs to the user. - Privacy: the check uses whatever git credentials are already
available (SSH agent, credential helper). Synapse stores no
credential and exposes no remote URL beyond what
git_remotealready cached.
Registry schema delta (lands in SYN-93)
Three columns added to the workspaces table:
ALTER TABLE workspaces ADD COLUMN last_remote_check TEXT;
ALTER TABLE workspaces ADD COLUMN remote_status TEXT;
ALTER TABLE workspaces ADD COLUMN remote_behind INTEGER;
remote_status is one of:
| value | meaning |
|---|---|
unknown | check never ran, or no git_remote |
up_to_date | local HEAD matches remote HEAD of the tracked branch |
behind | remote has commits the local doesn’t (count in remote_behind) |
ahead | local has commits not pushed (often fine for solo workflows) |
diverged | both have commits the other doesn’t |
unreachable | last check timed out, hit auth error, or network failed |
MCP payload shape
Every tool that returns workspace-sourced data adds a freshness
field:
{
"result": "...",
"freshness": {
"workspace": "client-alfa",
"status": "behind",
"behind_count": 5,
"last_check": "2026-05-24T19:42:00Z",
"advice": "git -C /Users/.../client-alfa pull"
}
}
advice is always a concrete command the user can copy; the AI is
encouraged to surface it verbatim. The field is omitted entirely
when status is up_to_date or unknown: silence is the polite
default.
CLI advisory
When a command opens a workspace whose remote_status ∈ {behind, diverged}, the binary writes one line to stderr before the
normal output:
syn: workspace 'client-alfa' is 5 commit(s) behind origin (last check 12m ago).
syn: run `git -C /Users/.../client-alfa pull` to refresh.
Suppression: SYNAPSE_NO_FRESHNESS=1, --no-freshness CLI flag, or
the [freshness] silent_cli = true config.
unreachable status produces no stderr: too noisy on flaky
networks. The status is still visible in syn ws list.
Phasing
- Phase 1 (SYN-93): filesystem-only. Reads
.git/HEADto find the current branch, then comparesrefs/heads/<branch>withrefs/remotes/origin/<branch>.behind_countis reported as a precise count when the local tip is reachable from the remote tip (or vice versa); when histories have diverged, status isdivergedand the count is set to-1(not computed in Phase 1 to keep the implementation walk-free). No subprocess, no network, no credentials. - Phase 2 (SYN-93b, shipped 2026-06-04):
freshness::check_remotelayers a real ls-refs handshake over Phase 1 viagix(no shelling out). It runs on a worker thread under a hard 3s timeout; any failure folds intounreachable, and the runner then retains the previously known status on the row (only bumpinglast_remote_check) so a transient offline moment never erases a real “behind”.behind_countis precise when the remote commits have been fetched (a bounded 1000-commitgix::rev_walk), and a count-lessBehindwhen the drift is brand-new (objects not yet fetched). Credentials come from the system (SSH agent / credential helper); Synapse stores none. The CLI advisory + MCP enrichment surface the richer status with no changes: except one new string,fresh-behind-unknown, for the count-less case.
Sub-features deferred to backlog
syn ws pullthat orchestratesgit pull(SYN-209 already covers the broader cross-machine sync question).- Auto-fetch (vs. only ls-remote): rejected. Pulling refs without user intent is closer to acting than informing.
- Cross-VCS support (jj, fossil, hg): out of scope; revisit when someone asks.
Consequences
- + Solves the silent-staleness failure mode without surprising the user (advisory only, opt-out at three levels).
- + Adds usefulness to MCP: the AI can frame answers as “this was last refreshed 14m ago; the source may be 5 commits behind”.
- + Discoverable via
syn ws list(the column ships in SYN-93 too). - − Background async needs care: the dispatcher must not leak
child processes when
synexits. Mitigated by a 3s timeout and a detach-on-exit policy. - − Workspaces with auth-locked remotes will sit at
unreachableforever for users with no credential. Documented; also produces no noise.
Alternatives considered
- Synchronous check inside every command. Rejected: adds 100-2000ms latency to every read. Async background gives the same signal with negligible foreground cost.
- Cron-style daemon (
synd) that polls in the background. Rejected: adds a moving part (process lifecycle, restart, logging) for marginal win over the in-process spawn-after-command pattern. - Detect via webhook from GitHub. Rejected: only works for GH, requires server setup, leaks workspace identity.
git fetchinstead ofgit ls-remote. Rejected: pulls refs the user didn’t ask for; counts as acting on the user’s repo.- Always-on, no opt-out. Rejected: corporate networks rate-limit
outbound git over time; a CI machine that runs
synin scripts would spam ls-remote.
ADR-012: Provenance frontmatter for AI-authored entities
Date: 2026-05-25 Status: Accepted Author: Rômulo Febasi
Context
EU AI Act Article 50 enforcement starts on 2026-08-02 with penalties up to €15M or 3% of global turnover. Article 50 requires machine-readable provenance on AI-authored content; the C2PA norm has been propagating from images to text, and the practical standard emerging in the wild (Glean audit logs, Notion AI compliance) is to stamp the content itself with a small set of fields recording who asked for the change, what prompt produced it, when it was sanctioned, and who sanctioned it.
Synapse’s pending queue (BR-12.3) already gates AI writes behind a human accept. What is missing is the per-file record on each accepted entity. Without it, an exported Markdown file leaves no intrinsic trace of having been AI-authored: fine for a personal note, a problem for any regulated context.
The audit trail in .synapse/audit.ndjson (ADR-013, SYN-110) covers
the workspace-wide story. ADR-012 covers the file-level story.
Decision
Every entity created or mutated through the pending-queue accept path
(crate::core::pending::accept → apply_capture_fact or
apply_update_status) carries five (or six) x-ai-* frontmatter
fields:
x-ai-source: mcp:capture_fact # how the proposal entered the queue
x-ai-decision-trace-id: pending:7 # stable, queryable trace id
x-ai-model: anthropic/claude-opus-4-7 # best-effort, optional
x-ai-prompt-hash: sha256:f8e2c1… # sha256 of pending.payload
x-ai-accepted-at: 2026-05-25T15:30:00Z
x-ai-accepted-by: romulo@febasi.com.br
Names live under the x-ai- namespace so existing YAML readers, the
doctor lint (BR-9.1), and any third-party tool treat them as forward-
compatible extensions (BR-3.4 preserves order; BR-3.1 ignores unknown
keys). They are stamped after the caller-supplied per-type fields so
they read as a trailing banner in the markdown.
An escape hatch lives in cascade config:
[provenance]
inline = true # default; emit stamps inline
accepted_by = "user@org" # override $USER; defaults to env $USER, then "unknown"
When inline = false, the audit trail in .synapse/audit.ndjson
remains the only provenance record: useful when the user prefers
clean entity files but still needs the regulatory receipt.
The mutators that touch auxiliary data (link_entity,
add_workspace_topic, add_workspace_tag) do not stamp anything
into entities: they mutate links / manifest fields where per-row
stamps do not fit the model. The audit trail still records them.
Consequences
- + Every AI-authored entity is self-describing as such.
git diffshows the stamps without any tool. - + The decision-trace-id links the file back to a pending row and
to an audit.ndjson entry; reproducibility for
syn audit replay(SYN-113) is one query away. - + Hash of payload, not the payload itself, in frontmatter, the body stays clean and the audit trail keeps the full payload for replay until it is pruned.
- − Workspaces in regulated industries with
inline = falselose the file-level guarantee. Documented; the audit trail compensates. - − A user who manually edits the markdown can strip the stamps.
By design: the user owns the workspace. The audit trail in
.synapse/audit.ndjsonis append-only and lives outside the entity file, so it survives.
Open question (closed in v0.4: SYN-137): when an entity is mutated
twice via the pending queue, the second accept overwrites the
stamps with its own values. v0.3 lost the prior six fields on every
re-mutation; v0.4 keeps them. Before stamping the new values,
apply_to_raw snapshots the existing x-ai-* six-tuple into a
mapping and prepends it to the x-ai-history array (newest first).
The array is capped at [provenance] history_cap entries (default
10) so a hot-edited entity does not balloon its frontmatter
indefinitely.
Alternatives considered
- C2PA manifest sidecar files. Rejected: the markdown is the source of truth (BR-1.4). Sidecars duplicate state and drift.
- Stamps in a
meta:sub-mapping. Rejected: breaks BR-3.4 insertion order discipline and is harder to grep. - Audit-trail-only (no inline stamps). Rejected as the default, loses the AI-Act-grade file-level receipt that downstream tools
(publishers, exporters) can read without parsing the audit log.
Kept as the opt-out via
inline = false. - PGP-signed entity files. Rejected: disproportionate to the
threat model, breaks
git differgonomics, ed25519 is reserved for the portable identity bundle (ADR-014) where the threat model actually warrants it.
ADR-013: audit.ndjson format + retention policy
Date: 2026-05-25 Status: Accepted Author: Rômulo Febasi
Context
The pending queue (SYN-60, BR-12.3) gives the human gate over AI
writes. What it does not give is an immutable record: the user
can syn pending reject --prune a row out of existence. For
regulatory contexts (EU AI Act Article 50, internal compliance audits,
post-hoc investigations) we need a tamper-evident, append-only log of
every decision that ever was: distinct from the queue, which is
working state.
Synapse already commits markdown writes inside Workspace::tx (BR-6.1
atomicity). The audit log is a different beast: it must be writable
when the markdown write succeeds, and the user must not be able to
silently edit it.
Decision
Every pending-queue decision (accept / reject / prune) writes one JSON
object to .synapse/audit.ndjson before the corresponding queue
mutation, except for accept where the workspace apply happens first
(see the ordering trade-off below). The schema:
{
"id": 7,
"kind": "capture_fact",
"decision": "accepted",
"proposed_at": "2026-05-25T15:00:00Z",
"decided_at": "2026-05-25T15:30:00Z",
"source": "mcp:capture_fact",
"reason": "user-supplied justification",
"payload_sha256": "sha256:f8e2c1…"
}
Why ndjson and not a JSON array: append-only with O_APPEND, no tail bookkeeping, line-grep friendly, robust to crashes (a half-written trailing line never corrupts the rows above it).
Rotation kicks in on the next append after the live file ages past
[audit] retention_days (default 90). Rotation moves stale
entries into .synapse/audit-archive/<yyyy-mm>.ndjson then rewrites
the live tail atomically (temp file + rename). The live file is
never auto-deleted; syn audit prune --before YYYY-MM is the
only path to physical removal, and it only touches the archive
directory.
Ordering trade-off: “incomplete trail” beats “lying trail”
The trail must never claim a workspace mutation happened when it didn’t:
- accept:
apply_*(workspace mutation) →audit::append→mark accepted. If the append fails after a successful apply, the queue row stayspending. Nextsyn pending listshows it; the user retries. The workspace is mutated but the trail is shorter than reality; that is recoverable. The inverse (trail says “accepted” but the workspace mutation failed) is not. - reject and prune:
audit::append→mark. There is no workspace mutation to fail; the trail comes first.
Payload hash, not payload
The audit row carries sha256:<hex> of the proposal payload, not the
payload itself. The payload lives on pending.payload until the
user prunes the queue row; the hash forever links the trail entry to
its original content. syn audit replay <id> (SYN-113) joins the two
when the pending row still exists; otherwise it reports unavailable
and the hash alone proves what was decided without revealing it.
Unparseable rows are preserved, not dropped
Rotation reads lines, attempts to deserialise each one, and keeps anything it can’t parse in the live file. The audit trail must never silently lose bytes: a corrupted line stays around as a forensic artifact, not as a deletion.
Consequences
- + Tamper-evident audit history available as a single
git diff .synapse/audit.ndjson(the file lives inside.synapse/but is not excluded from git: workspace owner can choose to commit or gitignore based on their policy). - + Rotation keeps the live file small without bloating the workspace; archives are partitioned by month for cheap grep.
- + Independent of the queue:
syn pending reject --prunecannot erase the historical record. - − Two write surfaces (queue row + ndjson line) introduce a small window where one succeeds and the other fails. Mitigated by the explicit ordering above; never silently incorrect.
- − Concurrent processes appending to the live file at the same millisecond rely on O_APPEND atomicity guarantees of the OS. POSIX guarantees this for line-sized writes (< PIPE_BUF). Documented; callers should not write multi-MB lines (BR-7.3 caps proposal size anyway).
- − No cryptographic chaining yet (each line is independent).
A v0.5 extension can add a
prev_hashfield for a Merkle-tree audit trail if downstream tools demand it.
Alternatives considered
- JSON array file. Rejected: needs tail bookkeeping, breaks on crashes, harder to grep.
- SQLite table. Rejected:
pendingis already SQLite; the audit trail needs to be readable by non-Synapse tools (compliance reviewers runningjq, archiving systems doing daily backups). A flat ndjson is the universal interchange. - Auto-purge on retention. Rejected: the archive must survive
until the user says otherwise.
syn audit pruneis explicit. - Cryptographic Merkle chain in v0.3. Deferred: adds complexity without a current consumer asking for it. Slot reserved for a future ADR.
ADR-014: Portable identity bundle (me.synapse)
Date: 2026-05-25 Status: Accepted (prototype shipped in v0.3) Author: Rômulo Febasi
Context
A consultant or senior IC running across N employers / clients
re-types the same person entity in every workspace and loses their
professional memory (projects touched, decisions signed,
expertise validated) every time an engagement ends. Glean’s Personal
Graph solves this conceptually but is SaaS tied to an employer’s
subscription and dies at exit. LinkedIn is coarse, public, and not
typed.
The v0.3 research brief argues this is one of the few moats SaaS structurally cannot copy: the bundle belongs to the human, not the org. We need a wire format for the bundle, a clear trust model, and an explicit list of what does and does not ship in the v0.3 prototype.
Decision
Wire format: single JSON file
The bundle is a single JSON document, conventionally named
me-<name>.synapse.json. Schema:
{
"synapse_protocol": "synapse/v1",
"kind": "identity-bundle",
"exported_at": "2026-05-25T...",
"person": {
"id": "romulo-febasi",
"title": "Rômulo Febasi",
"frontmatter": { ... },
"body": "..."
},
"attestations": [
{
"type": "project",
"id": "api-pagamentos",
"role": "tech-lead",
"since": null,
"until": null,
"title": "API Pagamentos"
}
],
"signature": null
}
synapse_protocol is the version marker: bump on any breaking shape
change. kind is identity-bundle (more kinds reserved for the
future when we share other artifacts: expertise-bundle,
curriculum-bundle).
Tar+zstd was considered and rejected: the bundle is tiny (low KB), and
the user benefit of being able to jq / open it in a text editor
outweighs any compression saving.
Source: the self: true person
Export reads the workspace’s person entity that has self: true in
its frontmatter. Exactly one is required: zero or more than one is
a clear error. Attestations are derived automatically from the
person’s memberships array (project, role, optional since /
until). Each attestation carries the referenced entity’s title
at export time so the bundle reads naturally without re-joining to
the original workspace.
Import: read-only consumption
Importing a bundle into another workspace creates / updates a
person entity. Imported persons carry an imported_from_bundle
frontmatter marker (timestamp) so they are visibly external.
Attestations attach under attested_involvement: readable but
deliberately distinct from native memberships so the consuming
workspace’s graph stays unambiguous.
The import strips incoming x-ai-* frontmatter stamps before writing:
those reference the source workspace’s audit chain, not the
consumer’s.
Signature: reserved field
signature is always null in v0.3 and the import reports the
bundle as unverified. v0.4 introduces ed25519-dalek signing:
syn identity keygencreates the user’s keypair under~/.config/synapse/identity/.syn identity exportsigns the canonical JSON (bundle minus thesignaturefield) with the configured key.syn identity importverifies against a pinned public key declared in[identity] trusted_keys = ["<base64>"].
v0.3 explicit non-goals
- No mandatory crypto:
signaturefield is reserved but unused. - No attestation revocation surface. Adding it requires either a transparency log or a re-published bundle; v0.4 picks one.
- No Sigstore-style transparency log. Reserved for v0.5+ when the user demand for cross-employer reputation justifies the operational cost.
- No HTTP discovery (
https://me.example.com/.well-known/me.synapse). Backlog. - No multi-bundle merge into one identity. v0.4.
Future direction (owner-flagged, 2026-05-25)
The owner flagged the bundle as the natural home for a curriculum-like surface built from workspace activity: “syn experience add …”, auto-derived skill counts from tag frequency, exportable as a CV. Out of v0.3 scope but recorded here so the prototype’s schema stays open to that direction:
attestationsschema is intentionally extensible (open record); future commands addskill,accomplishment,certificationkinds.- Bundle root
kindis namespaced (identity-bundle); a futurecurriculum-bundlecan layer on top with shared schema. - The
person.frontmatterfield accepts arbitrary keys today, a futureexperiencearray fits there without a schema break.
Consequences
- + Human owns their professional graph; survives any employment change.
- + Bundle is inspectable in
jqand any text editor: no vendor lock-in even on the artifact format. - + Federation with an employer workspace stays opt-in: the workspace consumes the bundle (read-only), never owns it.
- + Curriculum / CV evolution has a documented home (see above).
- − Without crypto in v0.3, an attacker who can write to the user’s filesystem can also forge bundles. Real threat for v0.4, the current threat model is “user is honest with themselves”.
- − Schema versioning lives on a single
synapse_protocolfield. When breaking changes accumulate, importer needs to learn migration. Acceptable for the prototype; ADR follow-up ifsynapse/v2arrives.
Alternatives considered
- Tar + zstd archive. Rejected: too small to benefit from
compression, kills
jqergonomics. - Sign every entity individually with ed25519. Rejected as v0.3, threat model does not warrant per-entity signing; bundle-level signature in v0.4 carries the necessary trust.
- Federate a remote bundle URL instead of a file. Rejected for
v0.3:
local-first(BR-12.1) wins; HTTP discovery is a backlog candidate when there is real demand. - Store inside the workspace
.synapse/. Rejected: the bundle belongs to the human across workspaces. Per-workspace.synapse/defeats the purpose.
ADR-015: File-size threshold + modular split policy
Date: 2026-05-25 Status: Accepted Author: Rômulo Febasi
Context
src/mcp/server.rs crossed 870 lines during the v0.3 sequence
(tool args + tool handlers + resource enumeration + prompt rendering
- freshness enrichment + federation helpers, all in one file). The
trajectory is clear: every new MCP feature lands here, and the file
keeps growing. Beyond a certain point the file stops being readable:
code review costs explode, regressions multiply, new contributors
spend hours mapping responsibilities, and the temptation to “just add
one more
if” wins over proper layering.
The project already has clear architectural principles (BR-1.4 source-of-truth, BR-6.1 atomicity, the edge/core/data layering in ARCHITECTURE.md). What it lacks is an operational rule that triggers refactor before a file becomes a problem, not after.
Decision
Adopt a two-tier size threshold for source files:
| Tier | Lines | Action |
|---|---|---|
| Yellow | 500-800 | A reviewer must call out the file in the next PR that touches it. Refactor proposal welcome but not forced. |
| Red | 800+ | A refactor issue must be opened and linked from any PR that adds further code to that file. New code in a red file blocks merge unless it (a) already removes lines, or (b) is part of the refactor itself. |
The threshold is heuristic, not dogmatic, a 600-line file of
schemars-derived argument structs is fine; a 600-line file of
braided business logic is not. Reviewers apply judgement informed by
cohesion, not just line count.
Refactor playbook
When a file enters the red tier, the refactor follows this order:
- Inventory. List the responsibilities the file currently
carries. One bullet per concern (e.g. for
mcp/server.rs: tool arg shapes, tool handlers, resource list, resource read, prompt list, prompt render, freshness enrichment, federation routing, blocking-task helper). - Group. Cluster the bullets into 3-5 cohesive modules. Each module exports a public surface that the others depend on; the inter-module graph must be a tree, not a cycle.
- Open an issue + ADR in the same milestone. The issue lists the new module tree; the ADR records the architectural intent so the next maintainer knows why the split landed where it did.
- Land in one commit per module where possible, smallest first.
Each commit must pass
cargo clippy --all-targets -- -D warningsandcargo test. No big-bang. - Document. Update
docs/ARCHITECTURE.md(module tree) anddocs/CONTRIBUTING.md(file-size policy section) so the rule propagates beyond this ADR.
Architectural principles to respect
- Single Responsibility per module: a file does one thing well.
- Dependency Inversion at boundaries: handlers receive
&AppDeps, never construct the concreteServerHandler. - Use-case isolation: each MCP tool / each CLI command / each core operation lives in a file you can read without scrolling.
- Edge / core / data layering remains the spine (ARCHITECTURE.md §1). Modular splits land within a layer; never across.
Consequences
- + New code lands in small, reviewable files. Cohesion stays high without anyone having to remember the rule mid-PR.
- + Onboarding gets cheaper: the module tree maps directly to the feature list.
- + The “red tier blocks merge” rule prevents the death-by-1000- ifs failure mode that big files trend toward.
- − More files to navigate. Mitigated by clear naming and the module tree in ARCHITECTURE.md.
- − Some refactors will touch a lot of files at once. Mitigated by the “smallest first” landing order: bisect stays meaningful.
- − The tiers are heuristic and will need revisiting. ADR supersedes itself when the project’s median file size shifts.
Immediate follow-up
A v0.4 issue tracks the mcp/server.rs split as the first
beneficiary of this policy. Other current candidates (audit-listing
in cli/audit.rs, the federation fan-out helpers) sit in yellow
tier and stay as-is until the next PR touches them.
Playbook: what to do when the tier moves
The four-row patterns covered so far. Add a row when a new shape shows up.
| Symptom | Pattern | Reference |
|---|---|---|
A subcommand dispatcher (syn at, syn audit, syn ws, …) crosses 500 lines because the verbs keep growing | Convert to a mod.rs + one file per verb under cli/<name>/. Helpers shared by ≥ 2 verbs go in a sibling <helpers>.rs. Helpers shared by 1 verb live with their caller. | cli/at/ (SYN-152), cli/audit/ (SYN-153) |
A macro-bound impl block (rmcp #[tool_router], #[tool_handler]) blows the budget because each new tool has to land in the same impl | Dispatcher pattern: keep the #[tool] shim thin (3–15 lines, parse args → call core → wrap result); move all logic into core::<facet>::* modules outside the macro’s reach. When the impl ServerHandler block also has to leave the file, use #[tool_router(vis = "pub(crate)")] so its companion macro in a sibling module can still see the generated method. | mcp/server.rs + core/mcp/* (SYN-151) |
| A use case grows because it gained an optional async branch (progress, cancellation, sampling) | The async/transport-bound work stays in the shim at the transport boundary (it imports rmcp / tokio / signal types). The pure business work moves to core::<facet>::* returning Result<serde_json::Value> or a small Synapse type. | core::mcp::{reindex,federated_search,replay_proposal} (SYN-151) |
| The yellow-tier file has tests that exercise an internal helper | The tests stay next to the helper they exercise (keep test files small too: same tiers apply). When the helper moves, the test moves with it. | cli/at/tree.rs host of count_delta_between_two_commits (SYN-152) |
Enforcement (SYN-154)
A CI job file-size (in .github/workflows/ci.yml) and the
local .githooks/pre-push hook both run
scripts/check-file-sizes.sh. Yellow tier emits a GitHub Actions
warning annotation; red tier emits an error annotation and exits
non-zero so the PR cannot merge until the file is split.
The lint exists because a human-only enforcement of this ADR was
silently violated within two weeks of being written: SYN-150
reduced mcp/server.rs from 1380 → ~550, and by v0.4 ship two
new MCP features had brought it back to 844. Automation is the
fix that survives the next contributor.
Alternatives considered
- Hard line-count gate enforced by CI (e.g.
tokei+ fail). Rejected: too rigid; punishes legitimate cases (long arg-struct files) and misses cohesion-based bloat. - No policy, refactor when it hurts. Rejected; that is the
status quo and it just produced the 870-line
mcp/server.rs. The threshold exists precisely so the conversation happens before pain. - Refactor on every PR that touches the file. Rejected: too expensive; the yellow tier is the right calibration: visible but not blocking.
References
src/mcp/server.rs: the file whose growth triggered this ADR.docs/ARCHITECTURE.md§Module tree: primary documentation site for the resulting splits.docs/CONTRIBUTING.md§File-size policy: contributor-facing summary of the same rule.
ADR-016: Portable identity v2: mandatory ed25519 + revocation
Date: 2026-05-25 Status: Accepted (supersedes ADR-014 §v0.3 explicit non-goals) Author: Rômulo Febasi
Context
ADR-014 shipped the me.synapse bundle as an unsigned JSON artefact and
explicitly deferred the cryptographic layer to “v0.4 if there is demand”.
The demand showed up immediately: any consumer who imports a bundle has
no way to prove the person frontmatter or the attestation list have not
been edited mid-flight, and any human who realises an attestation is
wrong (employer dispute, role title change, security incident) has no
way to retract it without re-issuing every downstream copy by hand.
The EU AI Act Article 50 timeline (force of law on 2026-08-02) is the external pressure: a regulator looking at the bundle must be able to verify the chain of custody. v0.3’s “trust the file” model is not a defensible answer.
The relevant constraints:
- Single-user keypair. Synapse is local-first; there is no central issuer. The user owns the key, period.
- No PKI. A regulator-grade chain via Sigstore or X.509 is out-of-scope for v0.4 (kept as ADR-014 §Future direction).
- Backward compatibility. v0.3 bundles in the wild must still
import, behind an explicit
--allow-unverifiedflag that stampsverified: falseon the imported person. - Self-describing. The verifier must not need a side-channel to find the signer’s key: the bundle carries the public key.
Decision
Adopt an ed25519 signature scheme with a workspace-local trusted-keys allow-list and a revocation list embedded in the bundle.
Key generation. syn identity keygen [--dir PATH] writes a raw
ed25519 keypair under ~/.config/synapse/identity/ (etcetera XDG
strategy; per-platform). Files: private.key (base64 of 32 bytes,
chmod 600 on Unix) and public.key (base64 of 32 bytes). The CLI never
moves the private key off-disk.
Canonical JSON. Both the signer and the verifier serialise the bundle to JSON with:
- Recursively sorted object keys (BTreeMap).
- The
signaturefield omitted. - The
public_keyfield present (so the verifier and the signer agree on the byte sequence over which the signature is computed).
This canonicalisation is byte-stable across Rust versions because the
implementation lives in core::identity::canonicalise_value and never
delegates to serde_json::to_string for ordering.
Signing. syn identity export --key PATH loads the private key,
computes the canonical bytes, signs them with ed25519, and writes
signature (base64 of 64-byte signature) + public_key (base64 of
32-byte verifying key) into the bundle. When no key is configured, the
bundle ships unsigned: the importer rejects it unless given
--allow-unverified.
Verification + trust. syn identity import PATH runs
verify_signature(bundle, trusted_keys):
- If
signatureis absent: succeed in “unverified” mode iff--allow-unverifiedwas passed; otherwise error. The imported person stampsverified: false. - If
signatureis present:public_keymust also be present and appear in the[identity] trusted_keyslist configured in~/.config/synapse/config.toml(or the per-workspace cascade). Verification usesed25519_dalek::VerifyingKey::verify_strict. - On success the imported person stamps
verified: true.
The trusted_keys allow-list is the user’s explicit “this signer is
me / this team / this employer”. An empty allow-list is allowed and
short-circuits the membership check (matches v0.3 behaviour for
unsigned bundles): kept on the table because v0.4 must not break the
single-user case where the bundle’s only consumer is the same person.
Revocation. The bundle carries a revoked: ["project/cliente-alfa", …] array. On import, attestations whose {type}/{id} (preferred) or
bare id appear in revoked are filtered out before being written to
attested_involvement. syn identity revoke <bundle> --target project/cliente-alfa --key PATH appends to the list and re-signs the
bundle so revocation propagates the next time a consumer imports.
Re-signing drops the old signature first so an incompletely-resigned
bundle never lies about being verified.
Backward compatibility. v0.3 bundles (no signature, no
public_key) still import behind --allow-unverified. The imported
person carries verified: false. syn identity import --trusted KEY
inlines a one-shot trusted key for cases where the cascade config has
not been edited.
Consequences
- Regulators and downstream consumers can answer “did the human sign
this bundle?” by running
syn identity importand reading theverifiedflag on the materialised person entity. - The user is responsible for the private key: losing it forfeits the ability to sign or revoke; bundles already in the wild remain verifiable but no new revocations can attach to them.
- Re-signing is cheap (canonical JSON + ed25519 signature in
microseconds) and the implementation lives entirely in
core/identity(mod.rs + experience.rs). No new crate dependencies beyond the v0.4-onlyed25519-dalek,base64,rand_core. - Importers without
trusted_keysconfigured default to accepting any embeddedpublic_key(single-user mode). This is documented in docs/IDENTITY.md and is what--allow-unverifiedis not: the latter accepts unsigned content too. - The format is byte-stable: a bundle exported on day N and re-verified on day N+M produces the same hash. Two implementations of canonical JSON would diverge; that risk is contained because Synapse owns the only producer and the only consumer today.
Alternatives considered
- Sigstore (cosign). Solves the chain-of-trust problem properly but requires Sigstore infrastructure (Fulcio CA, Rekor transparency log) and OIDC identity. v0.4 is local-first; defer to v0.5+ if Synapse ever ships a public exchange.
- X.509 / PKI. Same problem at higher cost; rejected for v0.4.
- PGP via gpgme. GPG dependency, key-server semantics, and a UX every user dreads. Rejected.
- HMAC with a shared secret. Would work for the single-user case but does not generalise to “verify a bundle a stranger handed you” , and the v0.4 brief calls out cross-org exchange as the goal.
- JWS / detached signature. Standard but requires JOSE plumbing and a header schema we would not otherwise use. ed25519 over canonical JSON keeps the bundle a single readable artefact.
- No backward compat (v0.4 hard break). Rejected: v0.3 bundles
exist and
--allow-unverifiedis a one-line escape hatch.
ADR-017: Curriculum schema: attested_involvement, skill derivation, CV export
Date: 2026-05-25 Status: Accepted Author: Rômulo Febasi
Context
ADR-014 §Future direction flagged a “curriculum surface” as the follow-up to the identity bundle: turn the workspace’s accumulated context into something a human can hand to an employer. v0.3 shipped the bundle without the surface. v0.4 closes it.
The surface has to satisfy three constraints simultaneously, and any two of them in isolation produce a bad design:
- Auditable. The output must be reconstructible from the workspace
without an LLM in the loop. A regulator or hiring manager who reruns
syn cv exporton the same inputs gets identical bytes back. - Human-controlled. The user, not Synapse, decides which
involvement counts as part of their CV. There is no auto-promotion
from “you touched this project” to “this is on your CV”. The
decision is an explicit
syn experience add. - Useful as a CV. The output is something a recruiter or collaborator can read end-to-end without first learning the data model. That rules out raw frontmatter dumps.
The line that is easiest to blur in implementation: syn experience add writes a signed claim about the human’s past; capture_fact
(BR-12.3) writes a free-form fact the LLM proposed. Conflating them
would make the bundle’s attested_involvement array unreliable.
Decision
Adopt three coupled artefacts: an explicit attestation array on the self person, a tag-frequency skill ranking, and a deterministic Markdown renderer.
attested_involvement array. Lives on the self: true person
entity’s frontmatter. Each entry has the shape:
attested_involvement:
- type: project # or api, entity, task, note
id: cliente-alfa # slug
role: tech-lead # free-text
since: 2024-01-01 # optional ISO date
until: 2024-12-31 # optional; omitted = present
title: "Tech lead at Cliente Alfa" # display title at the time
The array is the bundle’s source of truth for past involvement; the
v0.3 fallback that derived attestations from memberships still runs
when the explicit array is empty so users who have not migrated do
not lose data.
syn experience add|list|rm. The CLI surface is intentionally
narrow:
add <title> --target {type}/{id} --role R [--since DATE] [--until DATE]appends one entry. Idempotent on(type, id); a duplicate add errors out and points the user atsyn experience rmto replace.listrenders the array (text or JSON).rm {type}/{id}removes every entry whose target matches.
These commands rewrite the markdown of the self person atomically
(tx.write_file + commit). They never touch other entities.
Skill derivation (v0.4 algorithm). syn skills counts the
occurrences of each tag across every entity in the workspace and
ranks count DESC, name ASC. The top N (default 20, capped 200)
ships in the bundle’s skills array and renders in the CV. This is
the simplest defensible signal: a tag that appears on many entities
is a topic the human spends real time on.
The v0.5+ direction (not in scope here, recorded as Future direction):
- Project depth. Weight tags by the lifecycle phase of the
project they appear on (a tag on
status: activecounts more than one onstatus: archived). - Decision count. Boost tags that recur in note
kind: decisionfrontmatter (the person is making calls in this space, not just observing). - Owner-resolved attribution. Filter to entities where the self
person appears in
membershipsorowner. v0.4 counts every tag occurrence regardless of who created it; that is fine for the single-user case but inflates totals for shared workspaces.
CV renderer (syn cv export). A pure function from (workspace, unsigned identity bundle) to Markdown bytes:
# {person.title}heading.- Optional headline (frontmatter field
headline). - Contacts line (
email,github,linkedin,website). ## Summaryfrom the person body, trimmed.## Skills:detailedshowsname (Nx);minimalshows comma list. Top N from the bundle’sskills.## Experience:attested_involvementnewest-first bysince, each entry### {title}: {role}+ date range +ref: {type}/{id}.## Recent decisions(detailed only): last 5 notes withkind: decision, sortedupdated DESC, id ASC. Capped, the list ends with the comment marker<!-- cv: capped at 5 most-recent decisions -->.
--format json emits the structured Cv intermediate so other tools
(Pandoc, a custom Hugo template) can render their own layout without
having to re-extract the data.
Determinism is non-negotiable: same inputs → identical bytes. That is
why the renderer never sorts by created (which fluctuates with
imports), never includes a timestamp in the output, and never touches
the LLM.
PDF export is not in v0.4. Adding it would pull in a heavy
dependency (typst or headless chromium). The user can pipe the
Markdown through pandoc if they need PDF today; a first-party
renderer lands in v0.5+ if the demand justifies the dep.
Consequences
attested_involvementbecomes the canonical place to record what the human wants to claim. The bundle’sattestationsarray serialises from it.- The CV renders without any model invocation. The model is welcome to polish the output afterwards: that path is documented in docs/IDENTITY.md, but the canonical CV is reproducible.
syn skillsexposes a number the user can argue with. A surprising top tag is a hint to clean up the workspace or rename a tag, not a hint to call ranking broken.- The v0.4 skill algorithm is intentionally crude. The followups are flagged as Future direction so a reader six months out knows the team has not forgotten about them.
- The line between
syn experience add(human signs a past) andcapture_fact(LLM enqueues a write proposal) stays sharp: the former runs atomically, the latter goes through the pending queue for review.
Alternatives considered
- LLM-generated CV. Rejected at the start: the value of a reproducible CV is exactly that two different model runs cannot produce two different “you”. The model can polish; it cannot author.
- Auto-derive
attested_involvementfrommemberships. Kept as a fallback for v0.3 bundles but not the default. Explicit is better than implicit for something the user will hand to an employer. - Embed PDF rendering directly. Postponed. The Markdown surface is the contract; a renderer is a layer above and can ship separately.
- Carry skills outside the bundle. Rejected: a bundle is self-contained or it is not portable. The N=20 cap keeps it tiny (<2KB typical).
- First-class “endorsements” from other workspaces. Tempting and also a privacy minefield. Federation (BR-12.4) is opt-in workspace-side; cross-workspace endorsements would need a separate exchange protocol. Reserved for v0.6+.
ADR-018: LSP companion via tower-lsp-server
Date: 2026-05-25 Status: Accepted Author: Rômulo Febasi
Context
Adoption blocker for Synapse outside the dedicated syn terminal: the
user is editing markdown in Cursor / VS Code / Helix / Neovim / Zed
and has to switch out to syn search foo to find the right slug
before writing [[foo]]. Anything that reduces that friction, inline completion, hover, jump-to-definition: pays back immediately.
Two design vectors mattered for the v0.4 choice:
- One binary, every editor. A per-editor plugin would have to be maintained for each marketplace, learn each editor’s extension shape, and re-implement the slug index against the same data. The Language Server Protocol short-circuits all of that: a single binary that speaks LSP plugs into Cursor, VS Code, Helix, Neovim, Sublime, Kate, and Zed with a 4-line client config per editor.
- Reuse the local index. The MCP server reads from
.synapse/index.db; the LSP server reads from the same place. No new data path, no schema duplication.
rmcp provides MCP; the LSP equivalent is tower-lsp. The catch:
tower-lsp last released 0.20.0 on 2023-08-11 and has been
inactive ever since. The active fork is tower-lsp-server, currently
0.23.0 (released 2025-12-07, ~335k recent downloads on
crates.io). The fork tracks LSP 3.18 via lsp-types 0.98.
Decision
Adopt tower-lsp-server 0.23 behind a new lsp Cargo feature
(off by default), expose the server as syn lsp reading + writing
LSP over stdio.
Architecture.
src/lsp/mod.rs: module documentation, re-export.src/lsp/server.rs:Backendstruct implementingLanguageServer. Single file because tower-lsp-server’s macro, likermcp’s, wants oneimplblock.src/cli/lsp.rs:syn lspsubcommand. Workspace is optional, the LSP still serves type/tag completions on a brand-new workspace where the index does not yet exist.
Surface (v0.4).
textDocument/completion: three contextual lists:- Inside
[[<cursor>(wikilink): every entity slug, ranked by substring match against the partial query. - After
type: <cursor>: the sixEntityTypevariants. - Inside
tags: [<cursor>: distinct tags from the workspace.
- Inside
textDocument/hover: over a[[slug]]: bold title, type/id reference, first paragraph of the body.textDocument/definition: over a[[slug]]: jump to the entity’s markdown file viafile://URI.textDocument/publishDiagnostics: ondidOpen,didChange,didSave: dangling[[slug]]references are flagged as Warning withsource: "synapse".
Editor integration. Documented in
docs/EDITOR-INTEGRATION.md with copy-paste configs for Helix,
Neovim (nvim-lspconfig), VS Code (with a thin “Synapse” extension
stub), and Zed.
Why a separate feature (and not on by default).
- LSP transitively pulls
tower-lsp-server+ parts oftokio. Users who only want the CLI shouldn’t pay the link-time cost. - The MCP feature already gates
tokio; LSP shares the runtime when both are on, so it costs nothing extra inmcp lspbuilds.
Consequences
- The LSP server is local-first and offline by construction: same invariant as the CLI. No tokens, no network.
- Cross-editor consistency: every editor sees the same completions, the same hover, the same diagnostics. New editors that speak LSP work out of the box.
- The crate dep is a tower-lsp fork. That carries the risk that the fork stops being maintained: the mitigation is that our Backend is ~400 lines and could in extremis be ported to a hand-rolled JSON-RPC layer in a few days.
- We chose tower-lsp-server over the original tower-lsp despite the
Anthropic-bias-toward-canonical-crates because the canonical crate
has had no releases in nearly three years. The user’s
modern-stable-stack preference (memory
feedback_modern_stable) applies: pick the maintained fork over the abandoned upstream.
Alternatives considered
tower-lsp 0.20(original crate). Rejected: inactive since 2023-08-11, no LSP 3.18 support, no fix forlsp-typessecurity advisories shipped after the freeze. The fork (tower-lsp-server) is the active continuation.- Hand-rolled JSON-RPC over stdio. Doable (~500 lines) but duplicates well-tested code in the ecosystem and forces us to own Content-Length framing, async I/O, and the LSP schema mapping. Reserved as the fallback if the fork goes inactive too.
- Per-editor plugins (VS Code extension + Neovim plugin + …). Rejected: N editors × M maintenance cost. LSP is the standard exactly because the industry already paid that price.
- Server-side MCP-only (no LSP). Considered: MCP already exposes entities as resources. Rejected because MCP clients are not editors; the user editing markdown does not have an MCP client attached to that buffer.
- Web-based editor (Synapse Web). Out of scope for v0.4 and arguably out of scope for Synapse at all (local-first is core).
ADR-019: Terminal styling via anstream + anstyle, rich-by-default with a --plain opt-out
Date: 2026-06-05 Status: Accepted Author: Rômulo Febasi
Context
Audited in the v0.5 code: the CLI output is essentially monochrome plain text.
The only styled output in the whole tool is the red error: label in
src/output.rs::print_error (gated on stderr.is_terminal()); everything else (success messages, search, list, info, doctor, graph) is bare
println!. There is no colour, no glyphs (✓/✗/▲), no tables, and none of the
clig.dev colour-disable signals (NO_COLOR, TERM=dumb,
--no-color) are honoured. The brief
docs/researchs/2026-06-05-cli-ux-and-agent-ergonomics.md set the target: a
premium (“AAA”) modern-CLI experience by default, while staying safe for
pipes and agents.
This requires a styling library and a clear policy. Two constraints shape the choice: (1) no new dependency without an ADR (CLAUDE.md), and (2) the output must never regress the data a piped consumer or an AI agent already parses.
A relevant finding: anstream, anstyle, anstyle-query and colorchoice
are already in Cargo.lock: clap pulls them through its default color
feature. Adopting them as direct dependencies adds zero new transitive
weight; it only promotes what is already compiled in.
Decision
Adopt anstyle (the styling vocabulary) + anstream (the
auto-stripping output stream) as direct dependencies for all terminal styling.
They are the clap/cargo ecosystem stack and are already in the tree.
Rendering is rich by default (opt-out model) behind a single predicate,
output::is_rich(), true only when all of:
- stdout is a TTY (checked per-stream;
anstreamstrips ANSI on non-TTY); - the output format is
text(never style--format json); - no opt-out is set:
--plain/--raw,SYNAPSE_PLAIN,--no-color,NO_COLOR(non-empty),SYNAPSE_NO_COLOR, orTERM=dumb.
--plain/--raw (and SYNAPSE_PLAIN) force raw/minimal output: no
colour, no tables/trees/panels, no animation: even on a TTY, for token economy
(an agent reading the human terminal) and minimalists. --no-color kills only
colour; --quiet only suppresses non-essential lines. A SYNAPSE_AGENT=1
convenience flips --plain + --no-input. Animations never run off a TTY
(clig.dev). This contract governs the rich renderers (tables, unicode trees,
panels, progress, animation) tracked under SYN-186/190/191/192/193/195.
Consequences
- Better: one centralised gate; a genuinely premium default UX; full
clig.dev compliance; agents/scripts keep stable plain output via auto-strip +
explicit
--plain. No new dependency weight. - Worse: every styled call site must route through
output::(not rawprintln!) so the gate is honoured, a discipline to enforce in review.anstreamreplaces directprintln!for styled output. - Open / revisit: table and tree rendering may want a dedicated crate
(
comfy-table/tabled): decided per SYN-190/193, not here. Progress animation crate (indicatif) is a separate, later decision (SYN-191/192) and will get its own ADR if it lands as a dependency.
Alternatives considered
owo-colors: ergonomic, zero-dep, but BYO TTY/NO_COLORdetection and not already in the tree; we would re-implement whatanstreamgives for free.termcolor: solid (used by ripgrep/cargo) but a heavier API and not the clap stack; would be a new dependency.colored/console:coloredmutates global state and lags onNO_COLOR;console(fromindicatif) is heavier and oriented at full TUI.crossterm: full terminal control (raw mode, events); overkill for styled line output and a large new dependency. Reserve for a future TUI (SYN-203, backlog), not the CLI line renderer.- Hand-rolled ANSI (the status quo, one
\x1b[31m): does not scale to a styling system and cannot honour the five clig.dev disable signals cleanly.
ADR-020: Self-identity: capture once globally, seed the self person per workspace
Date: 2026-06-05 Status: Accepted Author: Rômulo Febasi
Context
The Synapse self: true person is load-bearing: syn identity export, syn cv export and syn experience all locate exactly one self person and refuse to run
otherwise. Yet through v0.5 there was no way to set the flag: the user had to
hand-edit YAML to make any of those features work. Onboarding also left identity
implicit: syn setup only captured locale and output format.
The v0.6 headline is to close that gap and make the tool know its owner: capture name, birthdate, profession and employers, and have every workspace carry a canonical self person derived from that. Three forces shape the design:
- One owner, many workspaces. The identity is a property of the human, not of any single workspace. Capturing it per workspace would force re-entry and risk drift between copies.
- CI/agents must never block.
syn initruns in pipes, scripts and agent loops. Making setup compulsory cannot introduce a path that waits on stdin (BR-14.1, the hard invariant inherited from SYN-187--no-input). - One stable shape.
core, MCP context, cv export and federation must depend on a single self-person schema, not each re-deriving fields ad hoc.
Decision
Capture once, seed everywhere. The self-profile (name, optional birthdate,
optional headline, optional employers) is captured a single time during syn setup
and persisted to a global profile.toml, kept separate from config.toml
(config describes tool behaviour and cascades; the profile describes the human and
does not). On syn init, Synapse materialises/refreshes the canonical self: true
person from that profile.
- Compulsory setup (SYN-170):
syn initrequires a global config. Absent + TTY runs the guided setup inline; absent + non-TTY falls through on defaults without writing or blocking. - Canonical schema (SYN-173, BR-14.3):
self: true,birthdate(ISO 8601),headline(consumed by cv),affiliations[] = { org, role? }.affiliationsis deliberately distinct frommemberships(BR-4.4 reserves the latter for person→project). Employers materialise asentity(kind=org). - Idempotent seed (SYN-172, BR-14.4): reuse the existing self id (slug never
churns), preserve
created/memberships/body on refresh, guarantee exactly one self, and skip BR-13 enrichment so seeding never forces a model download.
SYNAPSE_PROFILE overrides the profile path (tests pin it to keep workspaces
hermetic; advanced users can point at an alternate profile).
Consequences
- Better.
identity/cv/experiencework out of the box after onboarding. The assistant gains the owner’s name/role/employers for routing and federation context. Identity is entered once and propagates to every workspace. - Worse. A second global file (
profile.toml) to reason about alongsideconfig.tomland the registry. The seed adds work toinit’s hot path (bounded: one person + N org entities, no enrichment). - Open. Rich person→org graph edges are deferred: v0.6 records affiliations in
frontmatter and materialises org entities but does not wire a typed membership
link. Birthdate validation is a lenient
YYYY-MM-DDshape check, not a full calendar. Revisit both if/when employer-scoped project organisation lands.
Alternatives considered
- Store identity inside
config.toml. Rejected: conflates tool behaviour (which cascades system → global → workspace) with personal identity (exactly one, global, no cascade). Mixing them muddies precedence and federation redaction. - Capture per workspace. Rejected: forces re-entry, invites drift between copies, and contradicts “the identity is the human’s, not the workspace’s”.
- Persist a global config on non-TTY init. Rejected: writing files from CI/pipe runs is a side effect, and the silent-default fallback (BR-14.1) only requires behaving as defaults, not materialising them.
- Reuse
membershipsfor employers. Rejected: BR-4.4 reservesmembershipsfor person→project;collect_links/cv export already assume aprojectkey. A separateaffiliationsfield keeps both semantics clean. - Run BR-13 enrichment during seed. Rejected:
initruns before the semantic models may exist; embedding the self person then would force a ~920 MB download or fail. The self person embeds on its next edit orsyn reindex.
ADR-021: TTY progress via indicatif, gated by is_rich()
Date: 2026-06-05 Status: Accepted Author: Rômulo Febasi
Context
Three commands do visible work with no feedback: syn embed --all downloads
~920 MB of models then embeds the whole workspace, syn reindex rebuilds the
SQLite index over every entity, and syn watch reindexes on each change. Today
embed --all streams one [n/total] line per item to stderr; reindex shows
nothing until its final summary. On a slow embed run the tool looks hung.
The CLI-UX brief (docs/researchs/2026-06-05-cli-ux-and-agent-ergonomics.md)
set a premium (“AAA”) bar: a real progress indicator on a TTY, but no
animation when stdout is not a TTY (progress bars become “christmas trees” in
CI logs), and nothing that pollutes the data a pipe or an agent reads.
Two constraints, as with ADR-019: (1) no new dependency without an ADR, and (2) never regress the machine-readable path.
A relevant finding: indicatif (0.17 and 0.18) and its console backend are
already in Cargo.lock: fastembed/hf-hub pull them for the model
download bar. Adopting indicatif 0.18 as a direct dependency adds zero
new transitive weight to the default build; it shares the copy fastembed
already compiles.
Decision
Adopt indicatif 0.18 as a direct dependency and wrap it in
output/progress.rs behind a single gate: a Progress draws only when
output::is_rich() and not --quiet: i.e. a TTY, text mode, no
--plain/SYNAPSE_AGENT. Otherwise it is a ProgressBar::hidden() no-op.
embed --all: a determinate bar on a rich TTY; the existing one-line-per-item stream is kept for non-TTY text (readable in CI logs, no\rspam).reindex/watch: a steady-tick spinner with the current stage + counts, cleared on completion so the final summary stands alone.
Progress draws to stderr, so stdout (and --format json) is never touched.
Colour is left to console, which already honours NO_COLOR/CLICOLOR.
Consequences
- Better. Long operations show live progress on a TTY; the tool no longer
looks hung. The bar is auto-suppressed in CI/pipes/agents and under
--quiet. - Worse. A direct dep on a non-trivial crate. On a core-only build (no
v05_backends)indicatifis newly compiled (small); in the default build it is already present.indicatif0.17 and 0.18 coexist in the tree (via hf-hub 0.4 vs 0.5/fastembed): a pre-existing duplication this change does not add to (we pin 0.18, fastembed’s copy). - Open. The bar templates are fixed; per-command tuning can come later.
embed --all’s model-download phase still uses fastembed/hf-hub’s own bar.
Alternatives considered
- Hand-roll a spinner. Rejected: doing TTY width, steady ticks, redraw and
NO_COLORcorrectly is exactly whatindicatifalready does, and it is already compiled in. Reinventing it is strictly worse and more code to own. - A heavier TUI (
ratatui). Rejected: massive new surface for a few progress bars; Synapse is a line-oriented CLI, not a full-screen app. - Keep the line stream everywhere. Rejected: no live feedback on a TTY, and the brief explicitly asked for a premium indicator there.
- Draw to stdout. Rejected: would corrupt piped output and
--format json. Progress is stderr-only.
ADR-022: Evoluir o gate propose/accept (BR-12.3) para política em camadas por confiança
Date: 2026-06-07 Status: Accepted (owner, 2026-06-07) Author: Rômulo Febasi
Context
A BR-12.3: “IA propõe, humano sanciona”: é hoje uma regra dura e
absoluta: toda ferramenta de mutação via MCP grava em fila pendente e só é
aplicada após syn pending accept. Foi a decisão certa para a v0.2–v0.6:
estabeleceu o write-path governado que é a única posição defensável do
produto (proveniência auditável + propose/accept + identidade assinada),
contra um substrato: Markdown-como-verdade, índice descartável, busca
híbrida on-device: que já é commodity (basic-memory, o Memory MCP de
referência da Anthropic, e qualquer servidor MCP de fim de semana fazem o
mesmo).
Uma auditoria estratégica (2026-06-07, cinco lentes: competitiva, IA/ML, comportamental, econômica, arquitetural) expôs duas pressões independentes que convergem sobre o caráter absoluto do gate:
-
Comportamental: o gate vira carimbo no volume. A literatura de automação revisada por pares (Parasuraman & Manzey, Human Factors, 2010; revisão sistemática PMC3240751) é categórica: quando uma fila de decisões imperfeitas cresce, o revisor desenvolve automation complacency:
syn pending acceptvira reflexo. O efeito atinge especialistas igual a novatos e não se treina para fora. Um gate que o usuário aprende a carimbar deixou de ser um gate; a garantia de segurança degrada silenciosamente exatamente onde mais importa. -
IA/ML: o gate absoluto bloqueia autonomia. O
syn agent(SYN-185) pressupõe um loop que roda desacompanhado; um gate que para em cada escrita é incompatível com isso. Ao mesmo tempo, a literatura de write-path de 2026 (mem0 State of Memory; Graphiti/Zep) diz que o problema real não é impedir escrita, é impedir que um erro se torne durável, e a resposta do campo é reversibilidade + proveniência + escopo, não um portão humano universal.
A síntese das duas lentes é a mesma frase: o fosso é proveniência + reversibilidade, não o gate em si. O gate é uma política sobre esse núcleo, não o núcleo.
Mexer na BR-12.3 é decisão de dono: é a regra que define a identidade do produto. Aceito pelo dono em 2026-06-07. A implementação é rastreada por SYN-218 (#209, v0.7); até ela landar, o comportamento em código permanece o gate absoluto.
Decision
Reposicionar a BR-12.3 de regra dura para política em camadas por risco/confiança, mantendo o princípio “IA propõe, humano sanciona” como default, não como invariante universal:
- Cada proposal recebe um score de risco/confiança.
- Escrituras de baixo risco e reversíveis (ex.: linkar duas entidades que já existem) podem ser auto-aplicadas sob política, sempre com undo git-backed e carimbo de proveniência.
- Asserções novas, irreversíveis ou de identidade continuam exigindo
syn pending accepthumano: sem exceção no default. - O núcleo inegociável passa a ser proveniência + reversibilidade: nada
é auto-aplicado que não seja carimbado e desfazível por
syn audit replay. - A política é configurável, com default conservador (no default, nenhuma asserção nova é auto-aplicada).
Implementado em SYN-218 (config [proposals] auto_apply + core::policy;
default manual, opt-in safe para link_entity entre entidades existentes
não-self, carimbado auto-accepted na trilha). Destrava SYN-185 (#177,
syn agent) com segurança.
Consequences
- Melhora: mata o rubber-stamping (a fila deixa de ser uma notificação
descartável; o accept humano só aparece onde ganha o seu custo).
Destrava autonomia (
syn agent) sem abandonar a garantia. Alinha o produto à direção de 2026 (reversibilidade > prevenção universal). - Piora: aumenta a superfície de raciocínio: agora há um scorer de risco que pode errar; um auto-apply indevido é uma regressão de confiança séria. Mitigação: default conservador + reversibilidade universal + testes adversariais do scorer.
- Piora (narrativa): “IA nunca escreve direto” era uma frase de marketing limpa; “IA escreve coisas reversíveis de baixo risco e propõe o resto” é mais fiel mas menos slogan. O reframe de posicionamento (SYN-229) precisa abraçar “auditável e reversível”, não “nunca escreve”.
- Em aberto: a calibração exata dos tiers (o que conta como “baixo risco”) e a UX do scorer. Revisitar após SYN-218 ter dados de uso real e antes de SYN-185 ir a default-on.
Alternatives considered
- Manter o gate absoluto (status quo). Rejeitado se a autonomia for
perseguida: incompatível com
syn agente exposto ao carimbo no volume. Continua sendo a opção correta se o dono decidir que single-user human-gated é o escopo permanente: caso em que SYN-185 deve ser reescopado ou cortado. Esta é a decisão que o ADR pede. - Auto-aplicar tudo com undo (modelo basic-memory/Letta). Rejeitado: abandona o único fosso do produto; iguala o Synapse ao gêmeo já mais adiantado e melhor financiado.
- Tiers fixos hard-coded sem score. Rejeitado: rígido demais; a fronteira entre “linkar entidades existentes” e “asserção nova” precisa de avaliação por proposal, não de uma tabela estática.
- Confiar no modelo do agente para se auto-moderar. Rejeitado: a taxa de sucesso de memory-injection reportada (>95%) mostra que o agente é justamente o vetor; a política tem de ser do store, não do agente.
ADR-023: Read-only query tool: structured aggregation, no raw SQL
Date: 2026-06-07 Status: Accepted Author: Rômulo Febasi
Context
SYN-183 asks for a read-only query MCP tool so an agent can compute
out of context (e.g. “how many tasks are blocked?”, “count projects by
status”, “which tags are most used?”) without pulling every entity into
the model’s context window. The ticket gates implementation behind a
mandatory security spike (this ADR).
The obvious shape (“let the agent send SQL, run it read-only”) is unsafe even though the data is the user’s own and local:
- Exfiltration of internal tables. The index holds more than entities:
pending(un-accepted AI proposals + theircontext_snapshot), the audit-adjacent rows,embeddings_meta, and (viaATTACH) the globalworkspaces.dbregistry of every workspace on the machine, including confidential, non-federated ones (BR-12.4). A rawSELECT * FROM pendingor anATTACHlets an agent read data no read tool exposes, defeating the federation redaction boundary. - Injection. String-built SQL from model output is the canonical injection vector; a single quote in a “filter value” rewrites the query.
- Denial of service. Cartesian joins, recursive CTEs, or
load_extensioncan hang or crash the process, or worse. - Read-only is not enough. Opening the connection read-only stops
writes but not exfiltration,
ATTACH,pragma, or expensive scans.
A SQL allow-list parser (single SELECT, one sanctioned view, no
ATTACH/pragma/CTE/subquery) was considered and rejected: it is a
large, fragile, security-critical parser to maintain, and SQLite’s surface
makes “prove this SELECT is safe” a losing game.
Decision
The query tool takes no SQL. It exposes a structured aggregation
over a hard-coded allow-list:
group_by: exactly one oftype | status | owner | priority | due | tag. The value maps to a hard-coded column identifier: never a string from the request, so no identifier injection is possible.filter(optional): equality ontype | status | owner | priority | tag. Every filter value is a bound SQL parameter, never interpolated.metric:countonly (v0.7).- The query only ever reads
entitiesandtags. It cannot namepending, the audit tables,embeddings_*, orATTACHthe registry. limitis an integer clamped to[1, 1000].
So the entire SQL string is assembled from constants; the only request-
derived data are bound parameter values. Injection, internal-table
exfiltration, registry reach, and ATTACH/pragma/CTE are impossible by
construction, not by validation.
Consequences
- Improves: a genuinely useful out-of-context aggregation (counts by
type/status/owner/priority/due/tag, optionally filtered) with zero new
attack surface. Stays inside the same
entities/tagssurface the existing read tools already expose: no new data is reachable. - Limits: no arbitrary analytics: only the whitelisted group/filter
dimensions and
count. Joins across entities, free-form expressions, and other metrics (sum/avg over a numeric field) are out of scope until a concrete need justifies extending the allow-list (each addition is a reviewed column, not a new SQL capability). - Open: if richer analytics are ever needed, extend the allow-list (new group/filter columns, new bounded metrics): never open a raw-SQL path. Revisit only with a concrete use case.
Alternatives considered
- Raw SQL, read-only connection. Rejected: exfiltration of internal
tables +
ATTACHof the registry + DoS, none of which read-only stops. - Raw SQL against a sanctioned view + an allow-list SQL parser. Rejected: a large, fragile, security-critical parser; SQLite’s surface makes a sound allow-list impractical to maintain.
- No query tool; tell agents to page entities and count themselves. Rejected: that is exactly the out-of-context cost SYN-183 removes, and it scales badly on large workspaces.
ADR-024: SYN-185: batch approval over syn pending, not a syn agent runner
Date: 2026-06-08 Status: Accepted (owner, 2026-06-08) Author: Rômulo Febasi
Context
SYN-185 was filed as “syn agent: autonomous loop with batch approval”,
with the acceptance line: “BR-12.3 is a hard criterion: nothing applies
without human sign-off; batch approval means the human approves N proposals
at once, never the agent auto-applying.”
Two things changed between filing and implementation, making the literal “autonomous loop” framing the wrong thing to build:
-
The autonomous loop already exists, and it is the external agent. Synapse is local-first: it ships no LLM, holds no API keys, and (since SYN-219) no longer uses MCP server-initiated sampling. A loop in which “the AI works a task across multiple steps and queues writes” is exactly what an MCP client (Claude Code, etc.) does today over
syn mcp. Asyn agentcommand that ran a model itself would contradict the product’s foundations and duplicate the MCP session. The genuinely missing piece is the human side: sanctioning many queued proposals in one action instead of onesyn pending accept <id>at a time. -
ADR-022 superseded the “never auto-apply” line. When SYN-185 was filed, BR-12.3 was an absolute gate. ADR-022 (accepted 2026-06-07, implemented in SYN-218) evolved it into a confidence-tiered policy: the opt-in
safetier auto-applies low-risk, reversible writes (alink_entitybetween two existing non-selfentities), stampedauto-accepted. So “never the agent auto-applying” is no longer the rule, the rule is “the human sanctions everything the policy does not auto-apply.”
Decision
Deliver SYN-185 as batch approval on the existing syn pending queue, not
as a new syn agent command and not as an LLM orchestrator:
syn pending accept <id>...accepts one or more ids;syn pending accept --allsanctions every pending proposal in one action.syn pending reject <id>... | --all [--prune]mirrors it.- Each proposal applies independently and atomically (SYN-216): the batch
is best-effort: a failure on one is reported, it never half-applies and
never blocks the others. Cross-proposal all-or-nothing remains the job of a
single
propose_batchrow (SYN-202), which is the unit when atomicity across several writes is wanted. - There is no
syn agentcommand. The autonomous loop is the external MCP agent, unchanged.
Consequences
- Scope change (recorded): SYN-185’s
syn agentnoun and any “Synapse-runs-the-agent” reading are dropped. The issue’s deliverable, “human approves N at once”: is met bysyn pendingbatch flags. The milestone item is satisfied; the name is not. - Business-rule implications (recorded):
- BR-12.3 is intact and unchanged. A human still sanctions every
non-auto-applied write;
--allis one human action over the queue, not a bypass. The provenance + audit trail is unchanged. - The issue’s original “never the agent auto-applying” clause is
superseded by ADR-022 / SYN-218: the
safetier (opt-in, defaultmanual) auto-applies the low-risk, reversible class. Batch approval covers exactly what the policy leaves queued.
- BR-12.3 is intact and unchanged. A human still sanctions every
non-auto-applied write;
- Improves: the high-friction part of an agent-driven session (reviewing many queued writes one id at a time) collapses to one command, without weakening the gate.
- Open: if a richer “review the agent’s run” UX (showing auto-applied + pending together, interactive select) is ever wanted, it layers on top of these flags: revisit on demand.
Alternatives considered
- A
syn agent runorchestrator that shells out to an external agent CLI and drives a task loop. Rejected: depends on an external LLM/CLI, breaks local-first / no-API-key, and the loop already runs viasyn mcp. - A dedicated
syn agent reviewcommand with run transparency (auto-applied + pending, interactive). Reasonable, but more product than the deliverable needs; deferred (owner chose the minimal form). - Cross-proposal all-or-nothing for
accept --all. Rejected: one bad proposal would block every good one in a review-many flow; per-proposal atomicity + a clear failure report is the better UX.propose_batchalready provides all-or-nothing when that is the intent.
ADR-025: SYN-220 ambient git capture: scope and filtering
Date: 2026-06-08 Status: Accepted (owner, 2026-06-08) Author: Rômulo Febasi
Context
SYN-220 (behavioural lens) attacks the category’s fatal trait: capture cost is paid up front, retrieval value arrives later, so the corpus never fills and the habit never forms. The fix is to invert the value-timing: let the graph seed itself from artefacts the developer already produces (git), so a fresh workspace is populated before any manual capture is asked for.
The issue proposed harvesting “authors → people, repos/dirs → projects, merge commits / commit conventions → decision notes”. The risk, flagged at design time, is the opposite of the goal: a harvester that floods the pending queue with noise makes the propose/accept review worse, not better. The scope and the filtering are therefore the load-bearing decisions, not the git plumbing.
Decision
syn capture --from-git [PATH] harvests people + project only in v1,
as proposals (never silent writes: BR-12.3):
- People = commit authors, deduplicated by email, bot-filtered
(
[bot],noreply,dependabot,github-actions,renovate), ranked by commit count, capped by--max(default 20). Each becomes apersonproposal carrying acontributormembership to the project, so the graph link is durable (materialised from frontmatter on reindex), not an index-only edge. - Project = the repo’s working-directory name → one
projectproposal, queued first sosyn pending accept --all(ascending, SYN-185) creates it before the people that reference it. - Idempotent: anything already in the index or already queued as a
pending
capture_factis skipped, so re-runs never duplicate. - Uses
gix(bounded to ~5000 commits); never shells out to git (CLAUDE.md). Everything lands in the pending queue for one batch review withsyn pending accept --all.
Decision notes from commit messages are deferred. They are the noisy
part (merge commits, conventional-commit prefixes: high false-positive
rate), and the cold-start value is in seeding the who/what. A future
opt-in --decisions can add them once a precise, low-noise heuristic
exists.
Consequences
- Improves: a fresh
syn initin (or pointed at) a real repo yields a populated graph: the retrieval-before-capture moment the behavioural analysis says creates the habit: without weakening BR-12.3 (all queued) and composing with SYN-185 (accept --allto apply the lot). - Limits (recorded scope): no decision notes, no per-directory
sub-projects, no commit-message mining in v1. People are only as good as
gitauthor metadata (shared/again-bot emails slip through if not in the filter list: extend the list, not a parser). - Open:
--decisions(opt-in, needs a vetted heuristic); a git hook for continuous ambient capture; mapping monorepo sub-dirs to projects. Revisit on demand.
Alternatives considered
- Harvest decisions from commits now. Rejected: high noise; would make the queue review worse, defeating the purpose. Deferred behind a future opt-in flag.
- Write the entities directly (skip the queue). Rejected: violates BR-12.3 and removes the human’s chance to prune bot/duplicate authors.
- One atomic
propose_batchfor project + all people + links. Rejected: one bad author slug would block the whole seed; independent proposals let the human batch-approve and let partial failures be re-tried (SYN-185 semantics).propose_batchstays the tool when cross-write atomicity is actually wanted.
ADR-026: Tamper-evident audit log: git-native primary, self-chained fallback
Date: 2026-06-12 Status: Accepted (owner-delegated review, 2026-06-12) Author: Rômulo Febasi Revision: rewritten 2026-06-12 after the multi-user / git-versioned review (the original draft assumed a single writer with no external witness: wrong for the common case; see Context).
Context
.synapse/audit.ndjson is append-only and every record carries a
payload_sha256, but syn audit verify only re-hashes each record’s payload, it cannot detect deletion, reordering, or truncation of records. The first draft
of this ADR fixed that with a single linear hash-chain (seq + prev) plus one
ed25519-signed checkpoint. That design assumed one writer, one key, one reader,
no external witness.
A Synapse workspace is created locally and offline, but in practice it is very likely a git repository with more than one person editing the same versioned project. That breaks every assumption of the single-writer draft:
- whose key signs the single checkpoint, when Alice and Bob both append?
- a linear
prevchain assumes total order; git is a DAG (partial order). Two clones extend the chain from the same parent → divergent chains the merge cannot linearize. - an append-only
prev-chained NDJSON is the worst possible format for git merges: every concurrent append conflicts and the conflict breaks the chain.
Crucially, git is itself a Merkle DAG: every commit hashes its tree and
parent(s), giving ordered, tamper-evident, branch/merge-aware, distributed
history for free, which a hand-rolled linear chain cannot. Today Synapse only
reads git (gix in at.rs/capture_git.rs); it never commits, the user
versions the workspace, so git commits are coarser than Synapse mutations and the
audit.ndjson is the fine-grained record committed alongside them.
The conclusion: in a versioned workspace the linear hash-chain + single checkpoint is both redundant with git’s Merkle DAG and harmful to merges. The audit log should be a semantic layer that rides on whatever integrity substrate exists. This ADR covers the log format and verification; the identity/signing/multi-user trust model it relies on is ADR-030.
Decision
A two-tier integrity model, selected by whether the workspace is a git repo
(gix discovery):
Tier 1: git-versioned (primary, multi-user).
- Delegate ordering and sequence tamper-evidence to git’s Merkle DAG. Drop
the global linear
prevchain and the single signed checkpoint: git already provides ordered, tamper-evident, distributed history, and signed commits provide attribution (ADR-030). - Make the audit log merge-safe via per-author segments:
.synapse/audit/<author-id>.ndjson, where<author-id>derives from the writer’s Synapse identity (ADR-016). Each author appends only to their own segment → concurrent appends never conflict, and there is no global chain to break.seqis monotonic per author; the global order is the git commit topology (recordtimestampis an advisory tiebreaker, not authority). Retention (ADR-013) applies per segment. - Each record stays the semantic unit: mutation kind, proposal id,
confidence tier (ADR-022), model/agent attribution, accepting identity
(BR-12.3), and
payload_sha256(content integrity vs the Markdown). It does not carry a cross-record chain. syn audit verifybecomes git-aware: (a) re-hash each record’s payload vs the Markdown blob at the commit that introduced the record, not the working tree, which legitimately changes under later mutations; (b) assert the git commit DAG is intact (gix); (c) delegate signature/identity checks to ADR-030 (commits signed by known, non-revoked identities); (d) optionally compare against the remote to detect a force-push/history rewrite (reuse ADR-011 git-drift).git blameon a segment already attributes each line to a commit + author: verification leverages that.
Tier 2, not versioned (fallback, single local user).
- Here there is no Merkle DAG to lean on, so the original design is correct and
necessary: keep the self-chained log (
seq+prev= SHA-256 of the prior record’s canonical bytes, genesis = zeros) plus an ed25519-signed.synapse/audit.head.jsoncheckpoint ({origin, size, head_hash, timestamp, sig}) refreshed at the end of every mutating command.verifywalks the chain, verifies the checkpoint signature, and assertscount/head_hash, catching edit, reorder, mid-delete, and end-truncation. syn doctorwarns when a workspace is unversioned: “not under git: audit integrity is local-only;git init(and a remote) is recommended.”
Naming and boundary (both tiers): this is tamper-evident, not
tamper-proof. Tier 1’s strength is real only with a shared/pushed remote or
multiple clones (a witness); a single local clone, like Tier 2, is only as strong
as what its owner can rewrite. A key-holder who deliberately rewrites both the
content and its attestation is out of scope. Zero new dependencies (SHA-256 +
ed25519 + gix already in tree). Implemented in SYN-239 (log + verify); the
signing/identity half is SYN-249 / ADR-030.
Consequences
- Improves: the common case (versioned, multi-user) gets ordered,
tamper-evident, distributed history from git’s battle-tested Merkle DAG
instead of a fragile hand-rolled chain; the audit log is conflict-free under
concurrent edits (per-author segments) and
git blame-friendly; the unversioned single-user case keeps full self-contained tamper-evidence. - Worsens: two code paths (versioned vs not) to maintain; per-author segment
layout is a format change with a migration (fold the existing single
audit.ndjsoninto the current author’s segment, or keep it read-only as a pre-split tail); Tier-1 attribution depends on ADR-030 signed commits being adopted, elsegit logauthor is forgeable. - Open: exact
<author-id>derivation (identity fingerprint vs short hash), and its default when no identity is seeded yet (a local anonymous id untilsyn setupruns); whetheraudit verifyshould require a clean git DAG or only warn; bootstrapping which identities are “known” for a shared workspace (an allowlist: ties to ADR-010 federation and ADR-030). Revisit if an external auditor must verify membership without reading the files (then a Merkle history tree earns its keep: still deferred).
Alternatives considered
- The single-writer linear chain + one signed checkpoint (the original draft). Redundant with git’s Merkle DAG and merge-hostile in the common multi-user case. Demoted to the Tier-2 fallback, correct only when the workspace is not versioned.
- A multi-writer CRDT / hash-DAG inside
audit.ndjson. Reinvents git’s commit DAG, badly, inside a file git already version-controls. Rejected: lean on git. - Merkle history tree (Crosby–Wallach) in v1. O(log n) proofs only matter for a remote auditor who won’t read the files; the local verifier reads them anyway. Deferred.
- RSA/bilinear accumulators; Certificate-Transparency witness networks. Trusted setup / multi-party equivocation defense: no surface here. Rejected (full rationale retained in ADR-030).
- Calling it “tamper-proof”/“immutable.” False for a local file. Rejected.
Research grounding
Git object model as a Merkle DAG; Crosby & Wallach, Efficient Data Structures for Tamper-Evident Logging (USENIX Security 2009); Schneier & Kelsey, Secure Audit Logs (1999) and Ma & Tsudik, FssAgg (the truncation result) (both grounding the Tier-2 fallback; RFC 9162 (Certificate Transparency) and Syta et al. CoSi (a single signer cannot self-prevent equivocation) which is why Tier 1 relies on git/remote as the witness). Signing/identity specifics: ADR-030.
ADR-027: Bitemporal facts: valid-time + supersession
Date: 2026-06-12 Status: Accepted (owner-delegated review, 2026-06-12) Author: Rômulo Febasi
Context
Synapse facts are atemporal. The system already has transaction-time for
free: git commit time plus the x-ai-history chain and audit.ndjson form an
append-only “known-to-system” log, and syn at <ref> already time-travels along
it. What is missing is valid-time (when a fact was true in the world,
which is independent of when Synapse learned it) and an explicit way to record
that one fact superseded another.
This is the half of bitemporality that the frontier (Zep/Graphiti) owns and that mem0/basic-memory lack; combined with Synapse’s signed provenance it becomes something even Graphiti cannot do: prove what was believed true, and when. The constraint: Markdown frontmatter is the source of truth, the SQLite index is disposable, and the design must stay deterministic and small. Extends ADR-012.
Decision
Store valid-time only; derive transaction-time from git. Add to a fact’s
frontmatter, closed-open [from, until) per the SQL:2011 convention:
x-valid-from: 2024-03-01 # world-time the fact became true (required)
x-valid-until: 2025-06-01 # exclusive; omitted/null = open ("still true")
x-supersedes: <fact-id-or-hash> # optional: this fact replaces that one
Transaction-time is not stored in frontmatter; it is recovered from git
(syn at <ref>) and x-ai-*/audit.ndjson. Storing it twice would duplicate
the source of truth and risk drift (XTDB’s invariant: transaction-time is
system-assigned and immutable; git already provides exactly that).
Supersession = invalidation, not deletion (Graphiti’s rule). When a new
fact F2 contradicts F1: write x-supersedes: F1 on F2 and set F1’s
x-valid-until to F2’s x-valid-from. F1 is never deleted or edited
destructively: closing its interval is itself a new append. A pure
retraction (the fact was never true) is an empty interval
(x-valid-until == x-valid-from) plus an x-ai-history note. x-supersedes is
the world-time analogue of what x-ai-history already does in system-time.
Two orthogonal time axes, each reusing an existing mechanism:
| Axis | Mechanism | Surface |
|---|---|---|
| Transaction-time (“what did we know at ref R”) | git ref (existing) | syn at <ref> -- … (unchanged) |
| Valid-time (“what was true in the world at date D”) | x-valid-* filter in the index | new --as-of <date> |
They compose: syn at <ref> -- search --as-of <date> = “using only what we knew
at R, what was true at D”: the full bitemporal query, nearly free because each
axis is a separate, already-existing filter. The index gains valid_from,
valid_until, supersedes columns (a pure reindex: no stored-data migration).
The default query predicate becomes the SQL:2011 “current row” filter, hand-coded:
valid_from <= now AND (valid_until IS NULL OR valid_until > now) at the
supersession-chain tip.
audit replay stays correct because valid-time lives in the frontmatter
payload it already replays: provided two code invariants hold: replay
reconstructs frontmatter fields generically, and supersession edits (closing
F1’s interval) are appended as ordinary mutations, never written in place.
Migration is deterministic and lazy: existing facts get x-valid-from =
their first git-commit date, open-ended (the only honest default: world-validity
isn’t knowable retroactively); x-supersedes absent; a one-shot syn reindex
populates the new columns; frontmatter is stamped lazily on next mutation (no
bulk rewrite of Markdown). Implemented in SYN-223 (split: schema+migration /
write+supersession / time-aware replay+at).
Consequences
- Improves: matches Graphiti’s strongest feature locally and with
per-fact signed provenance it lacks; enables a “belief-lineage” view (walk
x-supersedes); the minimal shape is 2 interval fields + 1 pointer (AeonG reports ~10% overhead for full bitemporal); the index stays 100% rebuildable. - Worsens:
audit replayandatmust become time-aware (the hard part, gated by the append-only-supersession invariant); the migration default (valid_from= first-commit-date) is an approximation of world-validity. - Open: free-text contradiction detection is deferred to SYN-242
(structured fields first, NLI later); decay (SYN-224/ADR-029) must operate on
the valid-time axis as a derived salience score and must never delete
superseded facts or touch transaction-time; the belief-lineage edge surfaces in
the live graph (SYN-244). Verify before building: that
audit replayreconstructs arbitrary frontmatter fields generically today.
Alternatives considered
- Full TSQL2 / a temporal query algebra. TSQL2 was never adopted; SQL:2011
won by stripping to two columns +
PERIOD FOR. Sequenced/non-sequenced semantics and coalescing are months of work the single-user case never exercises. Copy the column convention, not the algebra. - An Allen interval-algebra reasoning engine. Full-algebra satisfiability is NP-complete (Vilain & Kautz; only ORD-Horn-style subclasses are polynomial). Synapse needs the 13 relations as vocabulary for point-in-time filters (two comparisons), not constraint propagation. Borrow the words, refuse the engine.
- Store transaction-time in frontmatter. Duplicates git, invites drift,
violates “one place per fact.” Derive from
<ref>/audit instead. - JUXT “bitemporality must live in the DB engine.” That targets multi-writer RDBMS whose planners can’t see temporal columns. Our index is single-writer and hand-queried: we can hand-roll correctly. Take JUXT’s failure-mode list as a checklist, reject its conclusion.
- Closed-closed intervals / a custom Period datatype. Exactly what SQL:2011 removed from TSQL2. Use closed-open ISO strings.
- Datomic-style retraction datoms. Redundant with
x-ai-history; encode retraction as an empty valid interval instead.
Research grounding
Snodgrass/Jensen TSQL2; SQL:2011 application-time + system-versioned tables;
Allen’s interval algebra + Vilain & Kautz / Nebel & Bürckert (NP-completeness);
Fowler, Bitemporal History; Datomic (transaction-time only); XTDB/Crux
(immutable transaction-time); Zep/Graphiti t_valid/t_invalid invalidation
(arXiv 2501.13956); AeonG bitemporal extension (ADBIS 2025, ~10% overhead);
temporal/bitemporal RDF; AGM belief revision (Gärdenfors & Makinson).
ADR-028: Typed-but-extensible entity types: a validation registry
Date: 2026-06-12 Status: Accepted (owner-delegated review, 2026-06-12) Author: Rômulo Febasi
Context
Synapse has six hard-coded entity types (project, person, entity/org, api,
task, note) wired across CLI, MCP, store, and index. The query tool (ADR-023)
deliberately hard-codes the columns its group_by/filter may touch and binds
all values, making SQL injection and internal-table exfiltration impossible by
construction. Users and agents increasingly want new entity types (e.g.
book, meeting, decision, client) without a code change.
The tension: add extensibility now, yet still freeze a stable schema at v1.0 (SYN-104), and without reopening the injection surface ADR-023 closed. This is a one-time cross-cutting refactor; doing it before bitemporal (ADR-027) and before the freeze avoids paying for it twice. Implemented in SYN-234.
Decision
Build a closed-world validation registry, not an open-world ontology. A
type definition lives in .synapse/types.toml. Conceptually a type is a
row-polymorphic open record over a closed core: a small fixed set of
indexed/queryable columns (the part ADR-023 protects) plus an open frontmatter
bag validated on read. “A function can only project fields it explicitly
names”: enforced at the DB boundary.
schema_version = "1.0"
[types.book]
display = "Book"
extends = [] # reserved for v1.x composition; [] = no-op in v1
store_dir = "books"
indexed = ["title", "author"] # WHITELIST → these become bindable query keys
[types.book.fields.title]
type = "string"; required = true
[types.book.fields.author]
type = "ref"; target = "person"; required = false
[types.book.fields.isbn]
type = "string"; pattern = "^[0-9-]{10,17}$"
Injection safety is preserved by construction:
- The SQLite
entitiestable keeps its fixed columns; user fields never become new columns. Every entity is one row regardless of type. - A query’s
kindis checked against the registered slug set (aHashSetlookup) and then bound as a parameter: never string-interpolated. Unknown kind → rejected before SQL. indexedfields project into a narrow whitelistedentity_fields(entity_id, field_name, value)table;group_by/filterresolvefield_nameagainst the registry’sindexedlist for that kind (a closed in-memory set) and bind it as a value parameter (WHERE field_name = ?1 AND value = ?2). Column identifiers stay static; only values are dynamic. ADR-023’s invariant is not reopened.- Non-indexed fields are schema-on-read only: validated on accept, never queryable via SQL, zero injection surface.
Validation rules (deterministic, offline, pure): reserved names (the six
built-ins + self/tag/audit/relation); slug grammar
^[a-z][a-z0-9_]{0,31}$ (also a safe SQL/path token by construction); closed
field-type enum (string|int|float|bool|date|ref|list<T>); ref target must
be a registered type or *; constraints limited to deterministic ones (regex
under a step budget, numeric/length bounds, enum value lists: no executable
validators, no network); indexed ⊆ fields, |indexed| ≤ 16. Validation is a
pure function of (types.toml, frontmatter), stamped like provenance (ADR-012)
so accept/replay is reproducible.
Migration = expand-contract (Avro/Protobuf/Confluent discipline): adding a
type is additive → MINOR, forever-legal post-1.0 (user-type space never bumps
the major); a new field on an existing type must be optional-with-default; a
retired slug is never reused (reserved). The v1.0 freeze covers only the
closed core: the six built-in kinds’ semantics, the fixed entities columns,
the query contract, and the types.toml format version. Freeze the spine,
not the skin.
Consequences
- Improves: extensibility now and a clean v1.0 freeze; nominal safety (kinds are validated names) + structural extensibility (open frontmatter) + determinism + a clean SemVer story; injection safety unchanged. Pays the enum→registry refactor once, before bitemporal and the freeze.
- Worsens: one cross-cutting refactor (replace the hard-coded
Kindenum with a registry-resolved kind loaded at startup); an EAV join for indexed fields; reserved-slug discipline to maintain. - Resolved (2026-06-13, SYN-234 slice 2): EAV vs per-type generated columns
for
indexedfields → EAV (entity_fields(type, id, field_name, value), indexed on(field_name, value)). The spike (docs/design/04-syn234-eav-spike.md) confirmed both query patterns (point filter, group-by) are index-backed at 10k scale and that bindingfield_nameas a value preserves ADR-023 more strongly than columns would. EAV keepstypes.tomlpurely additive (no DDL on a new type) and fits the disposable/rebuildable index; generated columns only win at a scale Synapse does not target and are a later, backward-compatible optimisation if SYN-227 ever shows a need. - Open: ref validation eager (target must exist) vs advisory
(dangling allowed, flagged by
doctor --lint-provenance): recommend advisory, consistent with current LSP dangling-ref diagnostics and bulk-import ordering;extendscomposition stays a reserved no-op in v1, single-level last-wins in v1.x; per-type governance policy (a type declaring its own write-policy /piiflag, composing with ADR-022) is split out as SYN-248.
Alternatives considered
- OWL / Description-Logic reasoner. Open-world inference adds facts to conform rather than reporting errors: the opposite of what a bounded local KG needs. SHACL-style closed-world validation is the right paradigm. Rejected.
- Arbitrary user SQL / a query DSL over dynamic columns. Exactly the surface
ADR-023 closed.
field_namenever reaches SQL as an identifier. Rejected. - Executable / Turing-complete validators (user lambdas, plugin code, network-fetching constraints). Break determinism and the offline guarantee. Fixed constraint vocabulary only.
- RDF triple-store rewrite. Markdown-as-truth + disposable SQLite is the architecture; a TOML registry suffices for typing without a triple store.
- Full structural typing / multiple-inheritance diamonds at v1. Defer; linear
single-
extendsis 90% of the value at 10% of the cost. - An Avro-grade automated data-migration engine. Expand-contract by
convention +
reindexis enough for a single-user local KG.
Research grounding
SHACL (closed-world validation) vs OWL (open-world inference); row polymorphism /
extensible records (arXiv 1707.07872; ACM Rows by Any Other Name); nominal vs
structural typing; Confluent/Avro/Protobuf schema-evolution rules; SemVer 2.0.0;
Tana supertags (composable types, the extends north star); Anytype/Capacities
(local-first user-defined types ship); Obsidian properties + Propsec (the
freeform-drift cautionary tale).
ADR-029: Per-fact trust & freshness: a deterministic, explainable score
Date: 2026-06-12 Status: Accepted (owner-delegated review, 2026-06-12) Author: Rômulo Febasi
Context
The most-cited unsolved failure mode in agent memory is “stale facts served
with high confidence” (a production mem0 audit found 97.8% junk over 32 days).
Synapse should surface a per-fact trust/freshness signal in show, search,
and MCP so stale-but-confident memory is visibly flagged. It must be
deterministic and explainable (a closed-form arithmetic over data already
stored (x-ai-* provenance, confidence tier from ADR-022, timestamps,
supersession from ADR-027, ed25519 signing)) never an opaque ML confidence
number, which would manufacture exactly the false authority that memory-poisoning
exploits. The same paper’s edge-weighting findings ground the semantic-edge
weight that SYN-245 needs. Implements SYN-241 (trust) and the weight half of
SYN-245 (edges).
Decision
Per-fact trust = multiplicative composition (Weighted Product Model: it is dimensionless, needs no weight tuning, and lets any single collapsing dimension veto the whole, which is the correct behaviour for “stale OR contradicted ⇒ low regardless of the rest”):
TRUST(fact) = Confidence × Freshness × SupersessionPenalty × ProvenanceGate
All four factors ∈ (0, 1]; output ∈ [0, 1]; each is independently inspectable, that is the explanation.
- Confidence: Beta / subjective-logic projected probability. With
rindependent corroborations,scontradictions, prior weightW = 2, and base rateakeyed to the ADR-022 tier:Confidence = (r + a·W) / (r + s + W), witha = 0.60auto-accepted,0.85human-accepted,0.95signed/identity. A new fact sits at its tier prior with full uncertainty; corroboration raises it, contradiction lowers it. The raw opinion(b, d, u),u = W/(r+s+W),b = r/(r+s+W): is surfaced as a decision aid (see below). - Freshness: ACT-R base-level activation, squashed. With reinforcement-event
ages
t₁…tₙ(days) and decayd = 0.5:B = ln(Σⱼ tⱼ^(−d)),Freshness = σ((B − B₀)/s). The O(1)-state approximationB ≈ ln(n/(1−d)) − d·ln(L)(store only countnand first-seen ageL) is permitted when per-access timestamps aren’t kept. Recency and frequency compose in one validated formula. - SupersessionPenalty:
γ^k,γ = 0.5, wherekis supersession depth (the current head fact hask = 0). Superseded facts sink fast but never hit zero (they remain auditable history). - ProvenanceGate: a coarse, honest, discrete multiplier (not a fake
continuous knob):
1.0cryptographically signed,0.9human-accepted unsigned,0.8auto-accepted (ADR-022 safe tier).
Display is banded (one of five bands, or ≤2 decimals) never 3-decimal false
precision. All constants (a, γ, d, gate buckets, B₀, s) are
config-exposed defaults to calibrate on real data, not laws; B₀/s are set
from a histogram of actual fact ages before locking.
Semantic-edge weight (SYN-245). Do not threshold raw cosine: in high
dimensions cosines concentrate, so absolute thresholds drift and aren’t
comparable across regions. Gate edges by mutual-kNN (k ≈ 10: keep (i,j)
iff j ∈ kNN(i) and i ∈ kNN(j)), then weight by a local-baseline rescale:
weight(i,j) = max(0, (cos(i,j) − μ_local)/(1 − μ_local)), where μ_local is
the mean cosine of i to its k neighbours. For co-occurrence edges (two
entities in the same note, no embedding) use clamped NPMI ∈ [0,1], which
discounts ubiquitous entities exactly like IDF, formalizing “high-value,
high-recurrence” as recurring together more than chance, not merely frequent.
Consequences
- Improves: a deterministic, per-term-explainable trust signal that directly
answers “stale served as confident”; the ACT-R activation
Bdoubles as a declarative-memory retrieval prior for ranking (relevance × Freshness); surfacing(b, d, u)distinguishes “uncertain: go corroborate” from “disputed: go resolve”. Edge weights are calibration-free (rank-based), so the live graph (SYN-244/245) stays meaningful across embedding models. - Worsens: constants need calibration on real data; the σ centering (
B₀,s) needs a quick spike (naiveσ(B)clusters near 0.5); freshness needs either per-access timestamps or the(n, L)approximation stored per fact. - Open: exact
B₀/sfrom an age histogram; per-access vs optimized state; the moment multiple signing identities co-assert a fact, the Beta(r, s)model extends cleanly to a real trust graph: revisit then (not now).
Alternatives considered
- EigenTrust / PageRank over facts. Need a population of mutually-rating agents; a single offline user makes the trust matrix rank-1 and the eigenvector trivial. (PageRank is fine for entity centrality: importance, not trust, and must never be silently multiplied into TRUST.) Rejected for trust.
- Additive weighted sum (
w₁·conf + w₂·fresh + …). Hides that a stale-but-confident and a fresh-but-dubious fact can score identically, and demands normalization that silently reorders results. Multiplicative composition is veto-respecting and tuning-free. Rejected. - Continuous provenance score. “Signed” is binary information; a smooth
provenance ∈ [0,1]is false precision. Three buckets. Rejected. - Absolute cosine thresholds for edges (e.g.
cos > 0.8). Drift with model, dimension, and local density. Rank-based mutual-kNN has no magic float. Rejected. - More than ~4 factors / 3-decimal display. Each extra multiplicand drags scores toward 0 and dilutes explainability; the math doesn’t support 3 significant figures. Rejected.
Research grounding
Jøsang, Subjective Logic; Jøsang & Ismail, Beta Reputation System; ACT-R base-level activation (Anderson & Schooler 1991); Ebbinghaus forgetting curve; SM-2 / SuperMemo; BM25 IDF (Robertson/Spärck-Jones); Pointwise Mutual Information / NPMI; Wilson score interval (Miller, How Not to Sort by Average Rating); concentration of measure + mutual-kNN (curse of dimensionality); Weighted Product Model / composite-index theory.
ADR-030: Git as the integrity substrate + signed commits + identity binding
Date: 2026-06-12 Status: Accepted (owner-delegated review, 2026-06-12) Author: Rômulo Febasi
Context
A Synapse workspace is local and offline, but very likely a git repository with more than one person editing the versioned project. ADR-026 establishes that, in that case, git’s Merkle DAG (not a hand-rolled chain) is the right integrity substrate for the audit trail. But integrity of what changed is only half of governance; the other half is who changed it, non-repudiably, across multiple users. Two facts make this urgent:
- The git author field is forgeable.
git config user.emailandgit commit --author=…are unauthenticated. Plaingit logproves nothing cryptographically, so “git versioning is enough for attribution” is false unless commits are signed. - Synapse already has the missing primitive. ADR-016 gives every user a
portable ed25519 identity with signing and in-band revocation: today used
for identity bundles and federation, but not bound to the workspace’s own
history. ed25519 is exactly an SSH key, and git signs commits natively in
SSH format (
gpg.format=ssh).
Today Synapse only reads git (gix); it never commits. This ADR decides how
identity, signing, and the multi-user trust model bind to git. It is the
companion to ADR-026 (which covers the audit-log format).
Decision
1. Git’s Merkle DAG is the integrity substrate for versioned workspaces. Do not reinvent ordering/tamper-evidence; rely on the commit graph (ADR-026 Tier 1).
2. Attribution = signed commits, bound to the Synapse identity. A user’s
ed25519 identity (ADR-016) doubles as their git SSH signing key. Synapse
recommends and verifies that commits touching the workspace are signed:
syn audit verify (and syn doctor) validate commit signatures against the
workspace’s set of known, non-revoked identities (revocation from ADR-016
finally pays off), and flag unsigned or unknown-signer commits. Non-repudiation
and multi-user attribution come from the signature, not the forgeable author
field.
3. Recommend-and-verify by default; drive-and-sign is opt-in. Synapse stays
non-intrusive: it writes Markdown + per-author audit segments to disk, and the
user commits (Synapse references the resolving commit post-hoc: git blame
on the audit segment already maps each line to a commit). As an opt-in
convenience ([git] auto_commit / syn commit), Synapse may create signed
commits for its own mutations via gix. It never takes over a user’s git
workflow by default: many manage their own.
4. The remote is the witness. A single local clone is not tamper-evident
against its own owner (rebase/force-push). Pushing to a shared/protected remote
makes a history rewrite detectable by anyone holding the old refs. This composes
with ADR-011 (advisory git-remote drift): verify/doctor can warn on a
local-only workspace or on remote divergence. The “free witness” property is real
only in the multi-user/remote case: which is the common one.
5. Multi-user trust model. The accepting identity (BR-12.3 “a human accepts”)
is recorded in the audit record and carried by the signed commit. This sharpens
the rest of the governance stack: ADR-029 corroboration r counts distinct
signed identities (the multi-witness case it deferred); contradiction detection
(SYN-242) becomes cross-user, not just cross-time. The set of identities
trusted for a shared workspace is an explicit allowlist (ties to ADR-010
federation), not “whoever can push”.
Implemented in SYN-249. Zero new dependencies (gix signs/verifies; ed25519
in tree).
Consequences
- Improves: real, multi-user-safe attribution and non-repudiation; leans on git’s battle-tested DAG instead of bespoke crypto; ADR-016 identity + revocation finally protect the workspace itself; the remote becomes a free witness; the trust/contradiction layers (ADR-029, SYN-242) gain genuine multi-party meaning.
- Worsens: signing setup/UX (key configuration,
gpg.format=ssh); users who don’t sign get only the weaker unsigned-git guarantee (detectable history, not attributable); Synapse-driven commits are a behavior change, hence opt-in; team key distribution / the trusted-identity allowlist is new surface to manage. - Open: enforce vs merely recommend signing (recommend first; enforcement is
a per-workspace policy later); how the trusted-identity allowlist is
bootstrapped and updated for a team (a signed
.synapse/identitiesfile?); SSH-format signing vs a future sigstore/gitsign path. Revisit identity bootstrapping alongside ADR-010 when a real multi-writer workspace ships. Feasibility finding (2026-06-13,gix0.71 /gix-object0.48): gix reads commit signatures (viaCommitRef::extra_headers, which carries thegpgsigheader) but does not verify them: its onlyverifyisverify_checksum(object-hash integrity). Native SSH-signature verification would need a new dependency (ssh-keyto parse/verify SSHSIG) + its own ADR. Decision: SYN-249 Tier-1 attribution uses the identity fingerprint recorded in the audit (the author of each entry) plus the already-signed per-author checkpoint (SYN-239), verified out-of-band against the trusted-key set with the existing ed25519 path. Git-commit-signature verification is a later opt-in behind anssh-keyADR, not required for attribution.
Alternatives considered
- Trust the unsigned git author field. Forgeable; provides no non-repudiation. Rejected: signing is the whole point.
- A hand-rolled multi-writer chain / CRDT inside the audit file. Reinvents git’s commit DAG inside a git-tracked file, with merge pain and no distribution. Rejected: lean on git.
- Mandatory Synapse-owned git workflow (auto-commit by default). Too intrusive; conflicts with users who manage their own commits/branches/CI. Opt-in instead.
- Sigstore / gitsign (keyless OIDC signing). Needs online OIDC + Fulcio/Rekor: breaks offline-first and reintroduces a vendor in the trust path, the exact property the product rejects. Keep user-held ed25519/SSH signing; leave sigstore as an optional future for users who want a public transparency anchor.
- A central server to arbitrate multi-user writes. Contradicts local-first and single-source-of-truth-on-disk. Rejected.
Research grounding
Git object model (Merkle DAG of commits/trees); git SSH commit signing
(gpg.format=ssh, ed25519); the forgeability of unsigned git author/committer
fields; gitsign / Sigstore (rejected for offline/local-first); internal:
ADR-011 (git-remote drift), ADR-016 (portable ed25519 identity + revocation),
ADR-010 (federation/allowlist), ADR-026 (audit-log format), ADR-029 (trust).
ADR-031: Belief-time axis: explicit x-recorded-at / x-invalidated-at
Date: 2026-06-14 Status: Accepted (owner-delegated review, 2026-06-14) Author: Rômulo Febasi
Amends ADR-027 (bitemporal facts). Companion to the SYN-223 valid-time work it ships alongside.
Context
ADR-027 set up two time axes and deliberately stored only valid-time
(x-valid-from/x-valid-until) in frontmatter, deriving transaction-time
from git (syn at <ref>). Its “Alternatives considered” explicitly rejected
“store transaction-time in frontmatter” as duplicating git and inviting drift.
Implementing supersession (SYN-223 slice 3) exposed a gap in that framing. ADR-027 conflated transaction-time with git-commit-time and assumed they are interchangeable. They are not, at the granularity we need:
- Git-commit-time is per-commit. A single
git committhat accepts a batch of proposals stamps every fact in it with one timestamp. - Belief-time is per-fact: the moment we decided to record a fact, or the moment we learned a fact stopped being true. Two facts invalidated in the same batch commit have the same git time but can represent decisions made at different logical moments, and, crucially, the moment we learned a fact became false (belief) is independent of the world-time it became false (valid-until). That independence is the whole point of bitemporality.
So “when did we stop believing F1” cannot be recovered faithfully from git under batch commits. It needs an explicit, per-fact stamp.
Decision
Add a third, belief-time axis as two frontmatter keys, stamped at decision (accept) time by the governed write path:
x-recorded-at: 2026-06-14T... # belief-time the fact entered the store
x-invalidated-at: 2026-06-14T... # belief-time we learned the fact stopped being true
x-recorded-atis stamped on every governedcapture_factwrite.x-invalidated-atis stamped on the superseded fact F1 when a new fact F2 closes its interval (x-supersedes: F1).
This does not reintroduce what ADR-027 rejected. ADR-027 forbade duplicating
git’s system storage time (immutable, system-assigned: git owns that, and
syn at still reads it). The belief axis is a distinct semantic: the
human/agent decision time, which git cannot represent per-fact under batching.
Three axes, each with its own home: valid-time (frontmatter x-valid-*),
transaction-time (git, via syn at), belief-time (frontmatter
x-recorded-at / x-invalidated-at).
Supersession stays invalidation-not-deletion (ADR-027 / Graphiti). Three
deterministic rules on an explicit x-supersedes pointer (free-text
contradiction detection remains SYN-242):
- new-after-old (F2 starts at/after F1): close F1 at F2’s
valid-from; stamp F1’sx-invalidated-at. F1 is never deleted, the close is an append. - older-than-known (F2 starts before F1): F2 is born expired at F1’s start; F1 untouched.
- disjoint (F1 already ends at/before F2 begins): keep both.
Consequences
- Improves: the bitemporal matrix is now complete and honest: when was X true in the world (valid), what did the store hold at ref R (git), and when did we decide/learn it (belief) are three separable answers. Supersession is auditable (the belief stamp records the decision moment) without trusting batch-commit granularity.
- Worsens: two more
x-*keys per superseded fact; a second per-fact stamp the reader must understand. The keys are advisory metadata, not load-bearing for--as-of(which filters on valid-time only). - Open: supersession-close currently mutates F1 via the EntityType-keyed
set_field, so custom-typed F1 is not closed yet (same fork asupdate_statusfor custom: SYN-234 contract finish). A custom or danglingx-supersedespointer degrades to “write F2, skip the close”. Temporal replay of supersession remains v0.10.
Alternatives considered
- Derive belief-time from git (ADR-027 as written). Fails under batch commits: distinct decisions collapse to one timestamp, and learned-false-time is unrecoverable. This ADR’s whole reason to exist.
- Reuse
x-ai-accepted-atas the belief stamp. It exists only on AI-authored facts and only records the recorded side, never invalidation. The belief axis must cover human facts and the invalidation moment too. - A separate belief-time ledger (not frontmatter). Splits a fact’s truth across two files; violates “one place per fact”. Frontmatter keeps the fact self-describing and git-diffable.
Research grounding
Snodgrass/Jensen TSQL2 (the valid/transaction distinction); SQL:2011
application-time vs system-versioned tables; Fowler, Bitemporal History (the
“we believed X about world-time W” matrix); Zep/Graphiti t_valid/t_invalid
invalidation. Extends ADR-027; companion to ADR-026/030.
ADR-032: Identity fields: deterministic dedup, detect-don’t-merge
Date: 2026-06-15 Status: Accepted (owner-delegated review, 2026-06-15) Author: Rômulo Febasi
Extends ADR-028 (open types) and ADR-022 (write gate). Companion to ADR-031/SYN-242 (contradiction → gate).
Context
mem0’s #1 unsolved bug is duplicate facts: “My name is X” then “My name is Y” stores both (exact-hash dedup only). In Synapse a re-capture of the same real-world thing under a different title produces a different slug (the id = slug = filename) and so a different entity: a silent duplicate the human gate can’t easily spot. SYN-242 catches structured contradictions (same subject+predicate, different object) but not “same thing, captured twice.” We need a deterministic way to recognise that two captures denote the same identity: without an opaque entity-resolution model and without breaking the pillars.
Decision
A custom type may declare identity fields in types.toml: the
fields whose combined values denote the same real-world thing:
[types.book]
indexed = ["isbn", "author"]
identity = ["isbn"] # two books with the same isbn are the same book
At propose-time, a capture_fact whose identity-field values match an
existing entity of the same kind (with a different id) is flagged as a
duplicate-identity collision and routed to the human gate: exactly
like a contradiction (SYN-242), surfaced with a contradicts/collision
signal so the agent can self-correct (re-capture as a x-supersedes update,
or drop the duplicate). Three hard constraints:
- Never auto-merge. Detect, don’t resolve: the human decides
(consistent with
core::contradiction; auto-merge is the <26%-correct stage HaluMem measures). - Never derive the id/slug from the identity hash. The id stays the slug = filename = human-editable, git-diffable, stable. The identity is a comparison fingerprint, not the key. This protects Markdown-as-truth + git-native.
identity ⊆ indexed. Every identity field is already a bound query key (ADR-023 invariant): the match is a parameterised lookup over the core columns +entity_fields, never interpolation.
Detection is a deterministic set-intersection of candidate ids, one identity
field at a time (bound values), bailing the moment the intersection is empty.
Opt-in per type (empty identity = no dedup). Fuzzy/MinHash/LSH matching is
deferred: single-user corpora don’t have the volume that makes blocking
pay, and fuzzy matching introduces false-merge risk.
Consequences
- Improves: the deterministic first tier of entity resolution that the
field’s best systems use; sharpens SYN-242 (a duplicate identity is a kind
of contradiction); keeps the gate’s incoming quality high (the gate’s value
is the scarce reviewer attention). Zero new deps; reuses
entity_fields. - Worsens: one more
types.tomlknob; the O(n) candidate scan per propose (fine at single-user scale; revisit only under a SYN-227 budget breach). Identity fields must beindexed, slightly constraining the declaration. - Open: cross-kind identity; the merge action (today it’s detect→gate→manual supersession): deferred until real demand. The merge-safe multi-author angle is a Commercial/team concern, not single-user.
- Update (SYN-254 expansion, 2026-07-06): identity matching now folds
case, accents (Unicode NFD + combining-mark strip), and whitespace before
comparison (
model::slug::normalize_for_match), so a re-capture differing only in those is detected: while punctuation is preserved (C++≠C#) to avoid over-merge. This is safe here precisely because the design is detect-don’t-merge: widening detection only routes more candidates to the human gate, never auto-merges. Fuzzy/similarity (MinHash/LSH/edit-distance) stays rejected: it is over-engineering at single-user scale and would trade the deterministic guarantee for a similarity threshold to tune.
Alternatives considered
- Derive the entity id from
hash(identity fields). Rejected: it makes the id unstable under any identity-field edit, fightinggit blame,syn at <ref>, human editability, and diffability. The hash is a comparison fingerprint, never the key. - Fuzzy/MinHash/LSH blocking. Over-engineering at single-user scale and a false-merge hazard; a flat exact-match scan over a few thousand candidates is correct and cheap.
- Auto-merge on collision. Violates the moat (the human gate) and is the exact stage the field gets wrong (<26%). Detect-don’t-merge.
- A full entity-resolution model. Opaque, non-deterministic, off-thesis.
Research grounding
mem0 #4896 (duplicate facts); HaluMem auto-update correctness <26%;
Zep/Graphiti + mem0 LLM reconciliation as the hard tier; classic
deterministic-first / blocking entity resolution. See
docs/researchs/2026-06-15-v0.11-issue-direction-study.md §3 (SYN-254).
ADR-033: Per-type policy: the profile-vs-log decay axis
Date: 2026-06-15 Status: Accepted (owner-delegated review, 2026-06-15) Author: Rômulo Febasi
Extends ADR-029 (trust/decay) and ADR-028 (open types). Reshapes the original SYN-248 “per-type governance policy” idea.
Context
SYN-248 was scoped as a full per-type governance policy
(write_policy/pii/link_policy). For a single curator who already
reviews every diff, a write-policy/PII matrix is gold-plating, the
governance the gate provides is uniform, and a per-type write policy is a
multi-actor affordance the single-user thesis doesn’t exercise (the v0.11
direction study, §3). The one genuinely useful axis the field surfaces is
profile vs log: some facts are current-state (your name, your role, they don’t go stale), others are append-and-enrich logs (notes, decisions:
they do). Today syn stale (ADR-029 decay) treats every AI fact the same,
so a stable profile fact eventually gets flagged as “decayed”: noise.
Decision
Ship only the decay axis, as a [policy.<kind>] table in types.toml:
[policy.person]
decay = false # profile: current-state, never flagged stale
[policy.note]
decay = true # log (the default): participates in decay
A [policy.<kind>] block (not a field under [types]) so it covers the
built-ins too, which are RESERVED in [types] and cannot be redefined.
decay = false makes syn stale skip entities of that kind. Default
(true, or no block) is unchanged behaviour. Advisory only: policy can
never bypass or weaken the gate (BR-12.3 / ADR-022); it only changes what the
advisory stale list reports. Deterministic; no engine, no DSL.
The broader write_policy/pii/link_policy surface is deferred (Open
Decision #4 in the research): it is a multi-actor / Commercial concern, and
adding it now would be a config surface a single curator rarely varies.
Consequences
- Improves: profile facts stop appearing as false-positive “stale”; the stale list becomes higher-signal (the decay-as-daily-value-moment idea); a clean, tiny, deterministic policy surface that future per-type knobs can extend without a redesign.
- Worsens: one more
types.tomlsection to learn; a second config surface beyond[types](justified by the built-in RESERVED constraint). - Open:
write_policy/pii/link_policy(multi-actor governance); whether decay-exempt should also pin trust at its initial confidence in the ranking (SYN-250) rather than only exempting the stale list: deferred until there’s evidence it matters.
Alternatives considered
- Full per-type write/PII/link policy. Over-built for single-user; a policy matrix nobody fills in. Defer to Commercial/team.
- A
mutabilityfield on[types.<kind>]. Can’t express policy for the RESERVED built-ins (e.g. markpersonas a profile). The[policy.<kind>]table is the surface that covers both. - Exempt by an entity-level frontmatter flag (
x-no-decay: true). Per-entity is more granular but pushes the decision onto every write; the profile/log distinction is naturally a type property. - A policy engine (OPA/Cedar/Casbin). Deliberately over-powered for a single-user TOML; borrow nothing.
Research grounding
docs/researchs/2026-06-15-v0.11-issue-direction-study.md §3 (SYN-248); the
profile-vs-log (“overwrite current state vs append-and-enrich”) distinction
from the 2026-06-14 field study §2.3; ADR-029 decay curve.
ADR-034: Commit-signature posture: detect now, cryptographic verify deferred
Date: 2026-06-15 Status: Accepted (owner-delegated review, 2026-06-15) Author: Rômulo Febasi
Scopes SYN-249C. Companion to ADR-030 (git substrate + signed commits).
Context
ADR-030 designed binding the Synapse ed25519 identity to SSH-format git
commit signing, with verify/doctor checking signatures against known
non-revoked identities: because the git author field is forgeable, so only
a verified signature makes multi-user attribution non-repudiable. SYN-249C is
the “C” slice: actually verify.
Two facts shape the scope:
- The value is multi-user. On a single-user, single-disk vault there is one identity and no adversary forging your own authorship to yourself. The 2026-06-15 direction study (4 independent reviewers) is unanimous: cryptographic commit-signature verification is a team / shared-repo feature, the lowest single-user-value item in v0.11.
- The crypto path is not smoke-testable in this environment. Full SSHSIG
verification needs the
ssh-keydependency, reconstruction of the exact signed commit payload, an allowed-signers set bound to the identity registry, and (to validate it honestly) a real SSH-signed commit, which this build environment cannot produce. Shipping an unverifiable crypto verifier would violate the project’s “never fake green” principle (a half-correct signature check is worse than none).
Decision
Split SYN-249C:
- Ship now (dep-free, single-user-useful, testable):
syn doctorreports the signature posture of the workspace’s HEAD commit: signed vs unsigned: by scanning the raw commit object for agpgsigheader (covers GPG and SSH; gix-version-independent). AdvisoryInfo: unsigned is fine for a solo vault, flagged so a shared vault knows to opt into signing. - Defer (to multi-user / Commercial, where the value lives): the
cryptographic verification:
ssh-keySSHSIG verify of the signature against the non-revoked identity allowlist (ADR-016 revocation), withaudit verify/doctorrejecting unsigned/unknown-signer commits. This carries the newssh-keydependency and the allowed-signers bootstrap; it belongs in the team wedge (SYN-231), gated by a real shared-repo need and a way to test it against genuine signed commits.
Consequences
- Improves: a real, tested, dep-free increment that nudges shared-repo users toward signing; the moat claim (“verifiable attribution”) stays honest: we report posture, we don’t pretend to verify what we can’t yet test; no premature dependency.
- Worsens: the headline “verify signatures” capability is not delivered for single-user; an attacker forging a signed commit by an unknown key is not yet caught (posture only reports presence, not validity).
- Open: the full SSHSIG verify (the
ssh-keydep + ADR was pre-authorised by ADR-030) moves to the multi-user milestone; pull it with a test harness that produces real signed commits.
Alternatives considered
- Implement full SSHSIG verification now. Adds
ssh-keyfor a feature with ~no single-user value and no way to smoke-test the crypto path here, exactly the unverifiable-green the project forbids. Defer to where it’s testable and valuable. - Do nothing for SYN-249C in v0.11. Leaves zero increment; the dep-free posture check is cheap and genuinely useful (it tells a shared-repo user their commits are forgeable today).
- Sigstore/gitsign. Already rejected by ADR-030 (online OIDC breaks offline-first).
Research grounding
docs/researchs/2026-06-15-v0.11-issue-direction-study.md §3 (SYN-249C: all
four reviewers: multi-user value, opt-in, last); ADR-030; ssh-key SSHSIG
support (RustCrypto). Verification harness blocked on producing real
SSH-signed commits in CI.
ADR-035: syn verify report schema + canonical logical dump
Date: 2026-06-18 Status: Accepted Author: Rômulo Febasi
Foundation for SYN-262 (the de-risking spike of the v1.0
syn verifyheadline, SYN-253). Companion to ADR-026 (audit), ADR-027 (bitemporal), ADR-030 (git substrate). Grounded indocs/researchs/2026-06-16-provable-non-corruption-syn-verify.md.
Context
The v1.0 thesis is provable non-corruption: prove the AI memory was not
silently corrupted, offline and per-fact. The keystone invariant (INV-1) is
that the disposable SQLite index is a reproducible build of the Markdown, digest(reindex(markdown)) == digest(live index). A hosted competitor
cannot offer this, because there is no user-side source to reproduce from.
The research brief flagged one real prerequisite: the .db file is not
byte-deterministic. Rowids, FTS5-internal pages, WAL framing, and float
vector blobs all vary between two indexes that are logically identical. So we
cannot hash the file; we must hash a canonical logical manifest. SYN-262
is that spike; it must produce a dump(ws) that is byte-stable across calls
and across a reindex --full, or every downstream invariant is built on sand.
The brief also fixed the proof-artifact shape: an in-toto Statement
(subject digest + predicate invariant table) signed with the user’s
existing ed25519 identity (reuse SYN-247 receipts: no new crypto). The
schema must be frozen before SYN-263 populates it.
Decision
1. Canonical logical dump. core::verify::dump(ws) emits a sorted,
line-oriented manifest. Each line is
"<table>\t<primary-key…>\t<canonical-json-of-columns>"; lines are sorted
lexically, so the output groups by table then primary key and any divergence
is a localized line diff that names the offending fact (the basis for the
SYN-269 rebuild attestation). JSON is canonicalized with recursively sorted
object keys, independent of serde_json’s preserve_order feature. The digest
is inputs_hash = sha256:<hex> of the dump bytes.
The dump covers exactly the rows that reindex deterministically reproduces
from the Markdown (plus git for last_modified_commit): entities, tags,
the deterministic frontmatter-projected links, and entity_fields.
It excludes, by construction:
entities_fts: FTS5-internal storage, fully derived fromentities;pending: the proposal queue; mutable statereindexdoes not reproduce, with a non-contentAUTOINCREMENTrowid;reindex_ledger: a machine-local(mtime, size)skip cache;sqlite_*internals and the feature-vectorfloat embedding blobs, which are model-dependent and not bit-reproducible;- enrichment links (
mentions:*): see §Consequences.
2. Report schema synapse.verify-report/1. An in-toto Statement:
{
"schema": "synapse.verify-report/1",
"subject": { "workspace": "...", "git_ref": "HEAD", "inputs_hash": "sha256:…" },
"predicate": {
"tool_version": "1.0.0",
"invariants": [ { "id": "INV-1", "title": "…", "status": "pass|fail|skip", "evidence": "…" } ],
"checkpoint": "…|null",
"trust_root": "local_only|checkpoint_witnessed|remote_witnessed",
"verified_at": "RFC-3339"
},
"signature": "base64|null",
"public_key": "base64|null"
}
trust_root is mandatory and always surfaced: the audit log is
tamper-evident, not tamper-proof: a lone local clone cannot detect its
own owner’s rewrite without a witness. The Rust data model
(core::verify::Report et al.) is defined in SYN-262; the invariant
evaluation, signing, and the syn verify command itself land in SYN-263.
Consequences
- Improves: the index becomes a provable reproducible build of the
Markdown; the digest is reproducible across machines (anyone with the repo
at
git_refrecomputes a bit-identicalinputs_hash); a divergence is localized to a named fact, not “the.dbdiffers”. - Worsens / limits:
syn verifyproves the integrity of the deterministic projection of the Markdown, not of model-derived enrichment (embeddings, NER edges). Covering enrichment would require pinningmodel@precisionand is deferred. - Real drift surfaced (open): building the dump exposed that the create
path writes
mentions:*enrichment links thatreindexdoes not regenerate, so the live index and areindex --fullindex diverge on those edges (and--fullsilently drops them). We excludementions:*from the dump (same class as float vectors: heuristic/model-derived, not a pure function ofreindex(markdown)). The underlying disposability gap,reindexnot regenerating enrichment: is a genuine finding to track as a follow-up (SYN-271), not fixed here. - Open: the
mentions:prefix is the current discriminator for enrichment edges; if future enrichment adds non-mentions:rels, the exclusion predicate must grow with it.
Alternatives considered
- Hash the
.dbbytes. Rejected: non-deterministic (rowids, FTS pages, WAL, float blobs): would report corruption on every benign rebuild. - Include enrichment links / embeddings in the dump. Rejected: they are
model-derived and not reproduced by
reindex, so the dump would never be a fixpoint. Pinmodel@precisionlater if they must be covered. - Make
reindexre-run enrichment so the dump can include it. Out of scope for the canonicalization spike, and NER output is not bit-stable across model versions regardless. Tracked separately (SYN-271). - A bespoke binary manifest / protobuf. Rejected: the line-oriented text manifest is human-inspectable and diffable (SYN-269) with zero new deps.
- A non-signed report. Rejected: the brief requires a portable, signed attestation; reusing SYN-247 receipt signing costs no new crypto.
ADR-036: Deterministic replay model + clock/identity seam
Date: 2026-06-18 Status: Accepted Author: Rômulo Febasi
SYN-266 (INV-4). The verify-first investigation the issue mandated: “ADR-027 assumes but never verified that audit replay reconstructs the store.” It does not. Companion to ADR-026 (audit), ADR-027/031 (bitemporal), ADR-035 (verify report). Grounds:
docs/researchs/2026-06-16-provable-non-corruption-syn-verify.md.
Context
The brief’s INV-4 read: “replaying every accepted audit payload into an empty workspace reconstructs the current store.” Before building it the issue required verifying that assumption. It is false as stated, for three reasons found in the code:
- The audit log is hash-only.
AuditEntrystorespayload_sha256, not the payload. The replayable payloads live in thependingtable: part of the disposable index, and prunable (syn audit prune). So the audit log is not an event journal you can replay. - The wall clock is baked in, not journaled.
applystampscreated/updated(viaFrontmatter::now_iso) and the belief axis (x-recorded-at) fromnow()/ the accept-time stamps, andaccepted_byfrom the environment. Two of these are freshnow()calls recorded nowhere a replay can read, so a replay cannot reproduce them byte-for-byte. - Direct CLI writes are not audited.
syn project add,tag,rm, and manual Markdown edits never enter the queue. The decision journal therefore covers only the governed (AI-proposed → human-accepted) path.
The deeper point (3) exposes: Synapse is state-based, not event-sourced. The Markdown is the user-editable source of truth; the decision journal records what was proposed and accepted (provenance), not the current content (which the human may have since edited: legitimately). “Replay the decision journal == current store” is the wrong model: it would flag every legitimate post-accept edit as corruption.
Decision
1. Reframe INV-4 to what is sound and provable. Two separable claims:
- Replay determinism (provable, shipped).
applyis a deterministic function of(payload, stamps, clock). Given the same journal and the same pinned clock, replay reproduces the store exactly. This is proven by a property test (core::replay, ≥48 generated cases incl. a supersession). - Store reconstruction is NOT journal replay. It is INV-1 (the index
is a reproducible build of the Markdown: ADR-035) and INV-7 (any past
state is reconstructible from git via
syn at: SYN-258). Those, not audit-replay, are Synapse’s reconstruction guarantees.
2. Introduce a clock/identity seam. Frontmatter::now_iso consults a
per-thread pinned value (pin_clock, returning an RAII guard); production
never pins, so behaviour is unchanged. Replay pins the recorded instant per op
so timestamps are reproducible. This is the seam the issue asked for.
3. syn verify --deep is a bounded journal-integrity check, not store
reconstruction. It replays the governed journal (accepted, still-present
pending rows) through the same mutation engine the accept path uses, in two
throwaway scratch workspaces, and confirms every op applies cleanly and the
two replays agree (determinism on the real data). It catches a tampered or
corrupt pending payload. It does not compare against the live store
(unsound for an editable-Markdown store). The bound (pruned payloads and
CLI-direct writes are outside the journal) is intrinsic and stated in the
report evidence.
Consequences
- Improves: an honest, sound INV-4 ships today; the clock seam makes the
governed write path reproducible; the deep check catches journal corruption
with no false positives on legitimate edits;
core::replayreusespending::applyso the replay and accept engines cannot drift. - Worsens / limits:
--deepdoes not prove “the store equals its decision history”: that is not a property of a state-based store and is deliberately not claimed. Exact store reconstruction from a complete, durable op-journal would need the journal to record the payloads and the wall-clock stamps (today they are hash-only / fresh-now()); that is a larger event-sourcing refactor, not pursued because Markdown-as-truth + git already reconstruct state (INV-1/INV-7). - Open: if a future “shared/governed team” edition wants audit-log-only reconstruction (no shared Markdown), it must durably journal payloads + a clock/identity seam in the log: pull this then.
Alternatives considered
- Replay the audit log into an empty workspace and compare to the store. Rejected: the audit log is hash-only (no payloads), and the comparison is unsound: a human editing a governed fact afterwards is legitimate, not corruption.
- Replay the
pendingjournal and compare a timestamp-normalised dump to the live store. Rejected: still false-positives on any legitimate post-accept Markdown edit (Markdown is the truth, not the journal); the normalisation needed to hide that is broad enough to risk fake-green. - Thread explicit
created/updated/accepted_atparameters through everycreate_*andapply. Heavier and more invasive than a single thread-local clock seam, for the same determinism. Rejected in favour ofpin_clock. - A full event-sourcing rewrite (the store is a fold over an op-log). Rejected: it contradicts Markdown-as-truth (ADR foundation) and git-native history; reconstruction is already INV-1/INV-7.
ADR-037: Offline witness checkpoint + append-only consistency proof
Date: 2026-06-18 Status: Accepted Author: Rômulo Febasi
SYN-270. Closes the gap ADR-026/030 explicitly leave to remote comparison. Companion to ADR-026 (tamper-evident audit), ADR-030 (git substrate), ADR-035 (verify report). Grounds:
docs/researchs/2026-06-16-provable-non-corruption-syn-verify.md(O3).
Context
ADR-026 makes the audit log tamper-evident (per-author hash-chain + a signed head checkpoint) and ADR-030 makes git the integrity substrate. Both note an honest ceiling: a lone local clone cannot detect its own owner’s rewrite: a force-push or a mid-log edit-then-recheckpoint: because the attacker controls every local artifact. ADR-026/030 leave that to a remote witness (a push-protected remote sees the old history).
The research (O3, after Go sumdb / Sigsum / RFC 9162 consistency proofs) identifies a local analogue: cache a signed tree-head and later prove the current state extends it. The verifier holding a prior checkpoint is the witness: exactly the offline single-user case.
Decision
syn audit witness emits a tiny ed25519-signed synapse.audit-witness/1
checkpoint: the git HEAD, and per author the segment size + a prefix_hash
(sha256 over the canonical bytes of the first size entries), plus the total
count and a timestamp. It is signed with the user’s existing identity (reuses
the receipt/report signing path: no new crypto, no new deps). The user
publishes it (commit it, post it, email it).
syn verify --since <witness> adds INV-12: prove the current audit
history is an append-only extension of the witness,
- git ancestry (when versioned): HEAD is the witnessed commit or descends
from it (
gixrev-walk). A force-push that drops the witnessed commit fails here. - segment prefix stability: for every witnessed segment, the current
author segment has
>= sizeentries and its firstsizeentries re-hash to the witnessedprefix_hash. A truncation shrinks the count; a mid-log rewrite moves the hash. Either fails.
Both checks are offline and dep-free. The check runs over read_all
(live + archive), so retention rotation does not weaken it.
Consequences
- Improves: force-push / history-rewrite is now detectable locally, without a remote: the gap ADR-026/030 deferred. A published witness lets a third party hold the owner to their history (the witness property a remote used to be required for). Zero new dependencies.
- Worsens / limits: still tamper-evident, not tamper-proof: the
witness is only as trustworthy as where it was published. A witness an
attacker can also rewrite (e.g. an unpublished local file they control) is no
stronger than the chain it summarises; the value is in publishing it
somewhere the owner cannot quietly change.
verifysurfaces this in the trust-root caveat. - Open: a future
audit witness --publishcould push the checkpoint to a conventional location (a gist, a transparency endpoint); deferred, the file- manual publication is enough for single-user and shared-git today.
Alternatives considered
- Require a remote to detect rewrites (ADR-026/030 status quo). Works only online and only when a push-protected remote exists; the witness file gives the same property offline and for a third party.
- Run a transparency log (Rekor/Trillian/Sigsum server). Rejected by the governing discipline (borrow the invariant, refuse the engine): a network service contradicts offline-first and single-user. The consistency proof is adopted; the server is not.
- Byte-compare the whole audit file against a stored copy. Brittle across retention rotation and legitimate appends; the per-segment prefix hash + git ancestry is rotation-stable and proves the precise append-only property.
- A Merkle tree over entries for inclusion proofs. Overkill for single-user (no third party requesting selective inclusion); a prefix hash over the witnessed range is sufficient and simpler.
ADR-038: Ship v1.0 on ort 2.0.0-rc.12 with a tracked exit
Date: 2026-06-20 Status: Accepted Author: Rômulo Febasi
Scopes SYN-276 (#264). Companion to ADR-008 (embeddings/NER backend). The v1.0 milestone freezes the Synapse surface (schema / CLI / MCP); this ADR states what that “stable” claim does and does not cover for the transitive on-device inference stack.
Context
Synapse’s on-device embedding + NER runs on ONNX Runtime via the ort crate,
pinned to =2.0.0-rc.12 (a release candidate). This pin is transitive
and forced, not a free choice (Cargo.toml §inference, lines ~186-189):
fastembed 5.15(the embedding backend) pinsort = "=2.0.0-rc.12"exactly.- canonical
gline-rs(the NER crate) pinsort = "=2.0.0-rc.9", which conflicts; thekreuzberg-gliner-rsfork re-pins to rc.12 to align.
So the whole inference stack sits on rc.12, and ort cannot be moved to a final
release until both fastembed and the NER fork move first: Synapse cannot
unilaterally bump it. The stack is built, tested, and working on rc.12 (373 lib
tests green; cargo deny check clean: no advisory blocks it).
Tagging a release branded “stable” (v1.0) on an RC dependency is a real maturity question that should be decided explicitly rather than left as an undocumented pin.
Decision
Ship v1.0 on ort = 2.0.0-rc.12 (Option A), with a tracked exit.
- The v1.0 “stable” promise covers the Synapse public surface: the Markdown/frontmatter schema, the CLI, and the MCP tool surface: under semver (BR-11.2). It does not assert that the transitive ONNX Runtime is a final release; that is a documented caveat, not a broken promise.
- The exit criterion: when
fastembed(andkreuzberg-gliner-rs) publish versions depending onort 2.0.0final, adopt it in a 1.0.x patch: bump the three together, runcargo build --release --all-features && cargo test --all-features && cargo deny check, re-runsynapse-benchto confirm no embedding/NER regression, and ship. Until then, no action (the pin cannot move). - Do not bump
Cargo.toml/Cargo.lockspeculatively: a partial bump that desyncs fastembed and the NER fork breaks the build.
Consequences
- Improves: an honest, written posture instead of a silent RC pin; the “stable” claim is correctly scoped to the surface semver actually governs; no premature dependency churn on the inference path.
- Worsens / limits: v1.0 ships with an RC ONNX Runtime. If
ort 2.0.0final differs from rc.12 in a way that changes embedding/NER behavior, a 1.0.x patch is needed once upstream supports it, but the semantic models download on consent and the index is disposable, so any behavior change is reproducible and re-derivable (syn embed --all/syn reindex), never silent data loss. - Open: the bump is gated on upstream (
fastembed+ the NER fork adoptingort 2.0.0final). Track it with the dependency-health cadence; this ADR is the exit criterion of record.
Alternatives considered
- Option B: wait for
ort 2.0.0final before tagging v1.0. Rejected for now: adoption is not in Synapse’s hands (it requiresfastembed+ the NER fork to move first), and blocking the v1.0 tag on an upstream release with no committed date would hold the whole milestone hostage to a transitive pin. Revisit only if upstream ships final and the bump tests clean on all targets. - Swap the inference stack to a final-release ONNX binding now. Rejected: the backend choice (fastembed + BGE-M3 + GLiNER via the fork) is settled by ADR-008; replacing it to dodge an RC is a far larger, riskier change than the RC itself warrants for a single-user local-first tool.
- Pretend the RC is irrelevant and say nothing. Rejected; that is exactly the undocumented-pin the “never fake green / honesty” discipline forbids; a “stable” release should state what its stability covers.
ADR-039: syn undo reversal semantics
Date: 2026-06-21 Status: Accepted Author: Rômulo Febasi
Scopes SYN-287 (#275). Companion to ADR-026 (audit log), ADR-027/031 (supersession), ADR-030 (git as integrity substrate), and especially ADR-036 (audit-log replay-to-store is unsound). Defines how
syn undoreverses a governed accept.
Context
The product promises reversibility (“undo any of it”, “reversible against git”),
but there is no syn undo command. Today reversal is manual: git revert
the workspace, or syn pending reject (which only works before acceptance).
After a proposal is accepted, undoing the specific change is a hand-rolled git
operation. That is a real gap between the narrative and the binary.
Two hard constraints shape any design:
- ADR-036: replaying the hash-only audit log to reconstruct store state is
unsound (the log stores
payload_sha256, not payloads; the store is hand-editable Markdown; there is no journaled wall-clock). Soundomust not be “apply the logical inverse from the audit log.” - The prior value is not recorded. The only per-decision snapshot is
pending.context_snapshot, which captures the LLM’s view at proposal time (what it read), not a pre-image of the files the apply touched. So for a decision that mutated an existing fact, the prior value lives only in git history: nowhere else.
Decision
syn undo <decision-id> reverses a governed accept by the soundest mechanism
available for that decision’s kind, in phases.
Phase 1 (this iteration): structurally-invertible creates. Undo a
capture_fact that created a new built-in entity by removing that entity
(file + index, atomically, via the same remove_atomic path as syn rm). The
inverse is computed deterministically from the recorded payload
(type + slugify(title)): no prior state and no git commit are required, so
it works on a just-accepted, uncommitted write. This is the dominant AI write
(a new fact), so Phase 1 closes the “no undo command exists” gap for the common
case.
Guards (Phase 1 refuses rather than clobber):
- The decision row must be
accepted/auto-accepted(notpending,rejected, or alreadyundone). - The created entity must still exist and not have been re-mutated after the
accept (a non-empty
x-ai-historyor a recorded supersession means a later change: undo refuses, pointing the user at git/manual reversal). This is the conflict guard: undo never overwrites work done after the decision. - The created type must be built-in (custom-typed and supersession-bearing creates are Phase 2).
undo is CLI-only by design, not an MCP tool. Reversing an accepted
decision is a human corrective action; exposing it to the agent would let the
agent erase its own accepted history, inverting the gate (the agent proposes,
the human disposes: ADR-022). The port-parity manifest (SYN-264) records this
as a deliberate single-door capability, not a gap.
Every undo is itself a governed, recorded event: it runs in one
Workspace::tx (atomic: a crash leaves consistent state, BR-6), reindexes the
touched dirs, flips the decision row to undone, and appends an undone
audit entry. History is append-only: undo never deletes the original decision
record (ADR-026).
Phase 2 (SYN-292): state-mutating kinds, shipped for link + status. Chose
option (b) pre-image capture over git-restore: it works on uncommitted writes
(the common case) and is sound (stored data, not log-replay). Migration 0008 adds
pending.undo_image; accept captures the pre-image before mutating.
link_entity→ delete the edge (payload-derived; no pre-image needed).update_status→ restore the prior status fromundo_image; refuses if the status changed again after the decision (conflict guard). Still refuse (revert via git): supersessions (reopening F1), custom-typed creates, and batches: their pre-image/interval logic is a later increment. No audit-log replay anywhere (ADR-036).
Consequences
- Improves: the reversibility promise becomes a real command for the common
case; the #1 code-vs-pitch gap is closed honestly; undo is atomic, audited,
and conflict-guarded; nothing in the binary’s integrity model changes (it
reuses
remove_atomic+ reindex). - Worsens / limits: Phase 1 undoes only built-in creates; mutations and links refuse (with guidance) until Phase 2. The command’s help text states exactly what is reversible now, so the limit is visible, not silent.
- Honesty:
undois not advertised as universal until Phase 2 lands; the README/whitepaper claims stay scoped to what ships.
Alternatives considered
- Audit-log replay to reconstruct prior state. Rejected by ADR-036: unsound for a state-based, hash-only-logged, hand-editable store.
- Reconstruct prior state from
context_snapshot. Rejected: that snapshot is the LLM’s read-view, not a pre-image of the touched files. - Ship a universal git-based undo now (all kinds). Deferred to Phase 2: it requires the decision to be committed (a just-accepted write usually isn’t), so as a sole mechanism it would refuse the most common “I just accepted this, undo it” case: a poor first cut. Phase 1’s payload-derived create-undo works uncommitted and covers that case; git-restore is the right tool for the mutation cases Phase 2 adds.
ADR-040: Benchmark methodology + chart pipeline
Date: 2026-06-21 Status: Accepted Author: Rômulo Febasi
Scopes SYN-290 (#278). Extends the
synapse-benchharness (SYN-162/163). Records the methodology contract that makes the numbers honest and the decision on how charts are produced for the README andsynapse-releases.
Context
synapse-bench already generates a deterministic corpus, scores retrieval
(P@k / MRR / nDCG), and measures search latency (median / p95) with PASS/FAIL
gates. It is manual (owner decision: no CI) and produces a results.json + a
.md report, but it has no charts, no machine/version stamp, no corpus
checksum, and only p50/p95 (no tail). The README needs credible, reproducible
performance visuals; a “premium chart” with a bare mean and no provenance is
marketing, not evidence.
Market research (2026) on credible benchmarking converged on: fixed,
checksummed corpora; report distributions (median + p95 + p99 + error
context), never bare means; stamp every result with the machine + versions;
keep cold/warm and stub/real separate; publish the raw data + a
one-command reproduce; and never present shared-CI wall-clock as a headline.
The gold standard for a Rust CLI is Astral’s uv, whose charts come from a
pure-Rust pipeline (hyperfine JSON → poloto SVG → resvg).
Decision
Adopt the methodology contract below as binding, and render charts with a small in-crate SVG writer: no new charting dependency.
Methodology contract (binding on synapse-bench):
- Checksummed corpus. The corpus is seeded/deterministic; every results
file carries its SHA-256 (
chart::corpus_sha256) so a number is tied to exact bytes. - Distributions, not means. Report median + p95 + p99 (added here). Never a bare mean.
- Machine/version stamp. Every results file carries a
SpecStamp(os, arch, cpus, backend,synapse-benchversion, git commit): printed in the chart footnote. - stub vs real stays separate.
stub= mechanics + latency (real timings, non-semantic vectors);real= the only configuration whose quality numbers (P@k/MRR/nDCG) are meaningful. Charts committed to the repo render latency only (honest understub); quality charts require arealrun. - Publish raw + reproduce. The
results.jsonis committed next to the SVG, with the exact command to regenerate it. - No shared-runner headline wall-clock. CI numbers are relative/illustrative unless produced on a pinned/dedicated runner.
Charts: hand-rolled SVG (synapse-bench/src/chart.rs), no charting dep.
A render subcommand turns a results.json into a deterministic, premium-styled
SVG (Synapse palette, value labels, machine-stamp footnote). Rationale:
- Determinism + zero new toolchain. Same JSON → byte-identical SVG; no
Python, no Node, no headless browser; honors the house rule “no dependency
without an ADR.” The only new dep is
sha2 = "0.10"(already the parent crate’s pin) for the corpus checksum: justified by contract item #1. - SVG (not PNG) so the README stays crisp and the chart diffs in git.
Consequences
- Improves: the README/releases get reproducible, stamped, distribution-aware charts with published raw data; the harness gains p99 + a corpus checksum + a machine stamp; no new toolchain or charting dependency.
- Worsens / limits: the hand-rolled SVG is intentionally simple (horizontal
bars); richer chart types would want a real library. Committed charts show
latency only (quality needs a
realrun on a dedicated machine). - Deferred (follow-ups, not blockers): warmup + N-sample-per-query for
tighter percentiles; explicit cold-vs-warm series; a
real-backend quality chart from a dedicated runner; PR-time regression gating (bencher.dev / CodSpeed, relative-only). If the hand-rolled SVG outgrows its remit, revisitpoloto(uv’s choice) orvl-convert(Vega-Lite → SVG, Rust-native), each a new dep needing its own ADR amendment.
Alternatives considered
poloto+resvg(uv’s stack). Strong and proven, but adds 2+ deps for a styling upgrade we don’t yet need; the hand-rolled SVG covers the current charts deterministically with zero charting deps. Revisit when chart types grow.hyperfinefor the latency track. Excellent for CLI wall-clock, but it is an external binary the harness would shell out to; the in-harness sampler (already present, now with p99) keeps the measurement self-contained and pure-Rust.hyperfinestays a documented “also run this” option.- matplotlib / Vega via Node. Rejected: drags a non-Rust toolchain into CI, against the project’s dependency discipline.
ADR-041: Agent-in-the-loop benchmark driver
Date: 2026-07-04 Status: Accepted Author: Rômulo Febasi
Scopes SYN-298 (#286), plan 022. Records how the agent-in-the-loop benchmark drives a real AI agent through the governed-memory loop over MCP. Companion to ADR-040 (the retrieval/scaling methodology contract) and depends on SYN-297 (#285), which made Synapse’s MCP tools loadable by strict clients at all.
Context
The agent-in-the-loop benchmark (plan 022) must drive a real agent, not a
mock: through insert / correct / contradict / retrieve, then grade the resulting
memory state (via syn verify’s canonical dump). “Real agent” concretely
means Claude Code: it speaks MCP, ships headless, and is the client we position
against. The state grader (synapse-bench loop) already exists and is
deterministic. The open question is the driver: the component that, per user
turn, spins a fresh zero-context session, lets the agent choose and call Synapse
MCP tools, and returns the trajectory + end state.
Two viable driving mechanisms, both verified to work after SYN-297:
- Raw
claude -p(headless CLI). A fresh session per invocation (no cross-turn bias),--output-format stream-jsonyields the tool trajectory +total_cost_usd. Needs the three-gate recipe: register the server local-scoped (claude mcp add --scope local),ENABLE_TOOL_SEARCH=false,--allowedTools "mcp__synapse__*". Zero new dependencies: the harness shells out to a binary the developer already has. Proven end-to-end on this machine (a headless run calledcapture_factand enqueued a real pending proposal). - Claude Agent SDK (Python/TS). Takes
mcp_servers+allowed_toolsin code, bypasses the CLI trust/tool-search machinery, and lets the harness assertinit.mcp_servers[].status == "connected"before the first call: the robust path for a fully unattended fleet. But it adds a non-Rust sidecar (a Python or Node process + package) to a project whose discipline is “no dep without an ADR” and whose harness is otherwise pure-Rust.
The benchmark is owner-run, not CI (credentials + token cost + agent non-determinism), so operational robustness matters less than it would for an unattended service; simplicity and zero-toolchain-drift matter more right now.
Decision
Drive the benchmark with raw claude -p (headless CLI) for now; adopt the
Claude Agent SDK later, when the harness needs a fully-unattended fleet or
programmatic PreToolUse/connection-status control.
Concretely:
- The
synapse-benchdriver shells out toclaude -p … --output-format stream-jsonper user turn, using the SYN-297 three-gate recipe, and parses thestream-jsonstream fortool_useevents (trajectory) and the finalresultevent (answer +total_cost_usd+ tokens). No new dependency. - The driver is isolated behind a small internal interface (a
Drivertrait / one module) so swapping in an Agent-SDK-backed driver later is a localized change, not a rewrite of the harness. - Every run records the exact
claudeversion and model snapshot in the results, because the CLI’s behavior (and its gates) are version-sensitive: SYN-297 is the standing proof of that.
Switching to the Agent SDK is a future ADR amendment (it adds a sidecar dep),
triggered by any of: needing assertable connect-before-call in an unattended fleet,
needing PreToolUse hooks / programmatic permission control, or the CLI’s
approval/tool-search gates shifting again in a way the recipe can’t absorb.
Consequences
- Improves: zero new dependencies; the harness stays pure-Rust + a binary the
developer already runs; a fresh
-psession per turn gives clean zero-context isolation for free;total_cost_usdcomes straight from theresultevent, feeding the cost-vs-accuracy Pareto (plan 022 Phase 4). - Worsens / limits: the driver is coupled to CLI surface that is
version-sensitive and lightly documented (the SYN-297 saga): mitigated by
stamping the
claudeversion per run and by the strict-schema conformance test. No programmatic connect-status assertion (the CLI just fails the turn); the driver must detect “0 tools available” defensively rather than pre-flight it. No temperature/seed control → reliability must come from N runs, not a single pass (see below). - Open / revisit: the Agent-SDK amendment above. Also: if a future Claude Code
release removes
ENABLE_TOOL_SEARCHor changes local-scope auto-approval, the recipe breaks and the SDK path likely becomes mandatory sooner.
Reliability protocol (binding, since there is no seed control): every task is
run N ≥ 5 times; report pass@1 (capability) and pass^k (reliability, identical correct end state across all k trials, the headline for a
“can’t-silently-corrupt” tool), with the syn verify canonical dump digest as the
equality key. Single-run numbers are not reported.
Alternatives considered
- Claude Agent SDK now. The robust unattended path, but it drags a non-Rust sidecar into a pure-Rust, ADR-disciplined harness for robustness the owner-run benchmark does not yet need. Deferred to a future amendment with explicit triggers, not rejected.
- A mock/scripted “agent”. Cheap and deterministic, but it measures the harness, not Synapse-under-a-real-agent: it cannot surface a real tool-selection or contradiction-handling failure (SYN-297 is exactly the class of bug a mock would hide). Rejected as the primary driver; a canned-trajectory replay stays useful for grader unit tests.
- Another agent framework / a different model client. Out of scope now: Claude Code is the positioning target and the client that exposed the real MCP-conformance bug. Cross-client grading is a later extension.
ADR-042: Retrieval quality: chunked embeddings + cross-encoder reranking
Date: 2026-07-05 Status: Accepted (2026-07-06) Author: Rômulo Febasi
Measured outcome (owner-run
persona-bench --features real, M3 Pro). Track A (chunking) shipped default-on: full-haystack (6 personas, 463 entities)recall@10.75 → 0.85, norecall@10regression (a clean 1.0 across the whole curve); averagerecall@1across the six tiers 0.783 → 0.842. Track B (cross-encoder reranker) shipped opt-in, off by default: on this corpus chunking already captured therecall@1gain, so the reranker was neutral at scale (0.85 → 0.85 at the headline; average 0.842 → 0.850, its only edge being the small-N tier and a clean recall@10). It is viable, tested, and available via[enrichment] rerank, but did not clear the “measured win” bar as a default. Honest per §3 of this ADR: a change that does not move the number does not ship as a default.Reranker input: the snippet, not the full body (tested). A follow-up measured feeding the cross-encoder each candidate’s full entity body instead of its query-focused snippet. It was worse: recall@1 at the small tier dropped to 0.75 (vs 0.85 with the snippet) (the long body dilutes the relevance signal with unrelated text) and it pushed a single search to ~45 s (reranking the top-k over ~512-token inputs on CPU). So the shipped reranker uses the snippet: faster and better. This is the counter-intuitive but measured result; the pool size (top-k) remains a latency knob for anyone enabling it on large workloads.
Scopes SYN-301 (v1.2 “Retrieval quality”). Extends ADR-008 (embedding stack) and composes with ADR-029 (trust) and the SYN-250/255 rankers. Motivated by the agent-in-the-loop benchmark:
persona-benchmeasured recall@1 ~0.78 (docs/BENCHMARKS_EXPLAINED.md), the clearest measured weakness in the product.
Context
Retrieval today is a three-leg RRF fusion (k = 60, src/core/search/hybrid.rs:49):
FTS5/BM25 + vector cosine over entities_vec + one-hop graph neighbourhood, with
the ADR-029 trust score folded in as a bounded [0.5, 1.0] multiplier (SYN-250)
and an optional --near graph-distance reranker (SYN-255, anchor_rerank). Two
structural limits cap its precision:
-
One vector per whole entity.
src/core/embed.rsembeds each entity as a singleentities_vecrow (entity_type, entity_id, embedding). A long note or runbook is averaged into one point, so a specific fact buried in a large document dilutes against the surrounding text. The needle loses rank. -
No re-scoring of the top candidates. RRF is a rank-fusion heuristic; it never compares the query against a candidate jointly. The bi-encoder (BGE-M3) scores query and document independently, which is fast but coarse. The community-standard fix for exactly this: a strong
recall@10, weakerrecall@1: is a cross-encoder reranker over the shortlist.
persona-bench confirms the symptom: recall@10 holds at 1.0 while recall@1
sits at ~0.78 (docs/BENCHMARKS.md §3). The agent masks this end-to-end by
issuing multiple searches, but the raw retrieval quality is the honest lever, and
recall@1 is the number that matters when a single ranked answer is consumed
(digests, search --limit 1, a small-context agent).
The user also asked which classic NLP techniques (TF-IDF, TextRank, YAKE, RAKE, KeyBERT, LSA, K-Means, TextTiling) apply. Most are superseded by the current stack (BM25 already is global TF-IDF; dense BGE-M3 subsumes LSA) or violate the deterministic/explainable ethos (K-Means topic clustering is opaque ML). The two that map to a real, measured gap are chunking (the structure-aware cousin of TextTiling) and, outside the list, cross-encoder reranking.
Decision
Adopt two independent, benchmark-gated retrieval upgrades, both confined to the disposable index (Markdown truth and the governance gate are untouched):
1. Chunked embeddings (structure-aware, deterministic)
Embed entities at passage granularity, not whole-entity. A deterministic,
dependency-free Markdown chunker splits an entity by structure (headings →
paragraphs → token-bounded windows with a small fixed overlap); each chunk gets
its own vector in a chunk-keyed vec0 table with a parent link
(entity_type, entity_id, chunk_ord). Short entities produce a single degenerate
chunk, so their behaviour is unchanged. At search time, chunk hits map back to
their parent entity (best-chunk pooling into the existing RRF), and the snippet is
taken from the winning chunk. This is a reindex/re-embed migration on the
disposable index; no source file changes.
The chunker is structure-aware and deterministic on purpose: same bytes → same chunks, no ML segmentation model. TextTiling and embedding-based semantic chunking are considered and rejected below.
2. Cross-encoder reranker over the RRF shortlist
After RRF (and the SYN-250 trust fold) produce the top-k candidates, a
cross-encoder rescoring pass reorders that shortlist before the results are
returned. The model is bge-reranker-v2-m3 (the reranker paired with BGE-M3,
multilingual), run through fastembed 5.15’s text-reranking API on the same
ort copy already shared by BGE-M3 and GLiNER (ADR-008): no new backend, no
new ADR-008 amendment, no new heavy dependency. Reranking touches only the
final ordering of the shortlist: it never changes what is stored, never feeds
the trust score, and never bypasses or informs the write gate. It is a ranking
aid, not a governance signal, so ADR-029’s “no opaque ML in governance” line holds.
The reranker is model-gated and opt-in like semantic search: available only
when its model is present (guided download, like BGE-M3), enabled via
[search] rerank = true in config, defaulting on when the model is present
and a silent no-op otherwise. --no-rerank forces it off for a query.
3. Ship each only on a measured win
Both are benchmark-gated: each is merged only if persona-bench
(--features real) shows recall@1 up with no recall@10 regression and no
p95 latency budget break (ADR-040 methodology). If a change does not move the
number, it does not ship: the benchmark that exposed the gap is the same one that
approves the fix.
Consequences
- Improves: directly attacks the one measured retrieval weakness
(
recall@1 ~0.78). Chunking sharpens recall on long documents; the reranker sharpens the top of the list for every query. Both reuse the existingort/fastembed/sqlite-vecstack: zero new charting/runtime deps. - Worsens / limits: chunking multiplies
entities_vecrows (more embed time at reindex, more index size, both linear and one-time); the reranker adds a per-query cross-encoder pass over the shortlist (bounded,O(k), top-k only, measured against the p95 budget). The reranker model is another ~hundreds-of-MB download on top of BGE-M3, so it is opt-in, not forced. - Boundary held: neither feature crosses into governance. The index stays
disposable and rebuildable; a bad chunker or reranker is fixed by
rm .synapse/index.db && syn reindex, never by touching a source file. - Open / revisit: chunk size + overlap are tunables to sweep empirically; per-type chunking policy (a runbook chunks differently from a person) is a follow-up; a distilled/quantised reranker for lower latency is a follow-up if the p95 pass is tight. Query expansion / HyDE is a separate future lever, not in this ADR.
Alternatives considered
- TF-IDF / LSA (from the technique list). Rejected as redundant: FTS5/BM25 is already a global TF-IDF ranking, and dense BGE-M3 subsumes LSA’s latent-topic role. Neither adds signal over the current stack.
- TextTiling / embedding-based semantic chunking. Rejected as the default: both are non-deterministic or model-dependent, which fights the “same bytes → same index” property and adds a segmentation model to maintain. Structure-aware Markdown chunking captures most of the benefit deterministically. Revisit if a measured recall gap on long prose survives structural chunking.
- KeyBERT / YAKE / RAKE / TextRank keyphrase extraction. Not a reranking lever;
deferred to a separate “auto-tags + facets” line of work (they improve metadata
and the digest, not
recall@1directly). Out of scope here. - K-Means topic clustering. Rejected for governance-facing use: opaque ML that tensions ADR-029’s explainability. Acceptable only as an advisory navigation view later, never in ranking or the gate.
- An external reranker API (Cohere/Voyage rerank). Rejected: breaks local-first and the offline guarantee (ADR-008 already rejected hosted embeddings for the same reason). The reranker must run on device.
- A learned/weighted RRF (train fusion weights). Rejected for now: introduces a trained artifact and a training pipeline; the cross-encoder gets more precision for less machinery and stays a drop-in over the existing fusion.
- Bump HNSW first (SYN-225). Orthogonal: HNSW is an ANN speed index for
large N, not a quality lever; it is correctly gated on a corpus-size trigger
(ADR / plan 015) and does nothing for
recall@1at current scale.
ADR-043: Governed capture from agent-supplied context (Synapse composes, does not integrate)
Date: 2026-07-06 Status: Accepted (2026-07-06) Author: Rômulo Febasi
MVP shipped (SYN-303). The steering clause lives on the always-on MCP surfaces (server
instructions+ thecapture_fact/link_entitytool descriptions); the agent declares the source class viaenriched_from(validated against a closed whitelist: RI-3/RI-4). The load-bearing RI-1 guard is enforced store-side: an enriched proposal never auto-applies (core::policy::should_auto_apply) and is never swept bypending accept --all(core::pending::enriched_pending_ids, filtered in the CLI). No new MCP capability, no server sampling (SYN-219). Follow-ups (not in the MVP): RI-2 structured-field whitelist enforcement, RI-5 redactable/verify wiring, RI-7 diff-highlight of enriched fields at review: all deferred behind demand; until then RI-1 (human gate) is the containment.
Scopes SYN-303 (proposed v1.3 “capture quality”; sequence after the deterministic linker fixes SYN-271/SYN-254 and reconcile with the v2.0 SYN-284 enrichment-backfill line: do not file as an orphan). Records a scope + governance decision. Composes ADR-025 (ambient git capture), the SYN-252 MCP proposal-contract prompts, the BR-12.3 propose/accept gate (ADR-022), and ADR-012 provenance. Hardened by a three-lens review (MCP-protocol, security red-team, product).
Amendment 2026-10-12: ADR-049 permits optional source-revision acquisition/monitoring adapters at edges. Agent-composed capture remains supported by default; credentials/network acquisition stay outside pure core. This supersedes the blanket native-connector exclusion, not the governed acceptance requirement.
Context
Synapse’s measured weakness is on the write side (insertion pass@1 80%, CI
55–93%; entity linking is only as good as the context at capture time). The
disambiguating context: which decision, which meeting, who was there,
which project: often lives in the user’s other tools (calendar/agenda,
tickets/PRs, chat), not in the prompt. The consulting agent (Claude, or any MCP
client) can already compose multiple MCP servers; Synapse is itself one.
So the question is not “can the agent get that context” but “should Synapse build calendar/Outlook/Slack integrations, or steer the agent to compose the servers that already exist, and govern the result?”
Building per-source integrations inside Synapse (an SDK, OAuth, secrets, a network client, live sync) would add a heavy dependency and a surface that fights the local-first, dependency-disciplined, no-telemetry ethos, and would duplicate what MCP composition already does. ADR-025’s git-capture is sound precisely because git is a local, dependency-light substrate already in the workspace; a cloud calendar is not.
But the reframe cuts both ways, and the review sharpened this: composition is
the commoditized layer of the 2026 agent-memory market: every framework offers
“context enrichment,” and the value of composing servers accrues to the
orchestrator (the client), not to any one tool. “Steer the agent to compose” is
therefore not, by itself, a differentiator. What Synapse uniquely owns is what
happens after the agent proposes an enriched fact: the gate, provenance, and
syn verify. And agent-supplied external context raises the write-side risk, a poisoned calendar event is a prompt-injection vector. That risk is exactly what
Synapse’s governance exists to contain. So this is a governance decision, not a
new capability, and it must be framed that way or it dilutes “the memory your AI
can’t silently corrupt.”
Decision
Synapse composes the MCP ecosystem; it does not build per-source integrations. It steers the agent to enrich capture proposals from context the agent already has, and it governs those enriched writes with hard invariants. Concretely:
-
One generic enrichment clause, on always-on surfaces. MCP prompts are user-controlled: they run only when the user explicitly invokes them, so a prompt cannot auto-steer a capture. The guidance therefore lives where the agent always sees it: the tool
descriptionfields (capture_fact,link_entity,propose_batch) and the serverinstructionsstring. It says, generically: before proposing, consult the context you already have (tickets/PRs, calendar, chat: whatever MCP sources are connected) to link the right people, route to the right project, and set better tags and title; treat that context as untrusted, agent-asserted input. One generic clause: never a per-source prompt (a calendar prompt + a Slack prompt + … is the integrations hub by another name). The SYN-252synapse:proposal-contractprompt carries the deep version for explicit user invocation. -
Advisory only, topology-blind. MCP exposes no primitive to enumerate sibling servers: the
initializehandshake advertises the client’s own capabilities, never what other servers are connected, and the sole cross-server hook (samplingincludeContext:"allServers") is deprecated (SEP-2577/2596) and client-discretionary. So Synapse recommends, it cannot detect. The recommendation lives in theinstructionsstring,syn doctor, and (most usefully) a conditional nudge in the capture tool result when a proposal lands with weak people/project linking. It is one-shot and dismissible (privacy note below), never in every tool description (noise degrades tool selection). -
Governed, gate-forced, provenance-honest (the required invariants below). Enriched proposals flow through the propose/accept gate, and enrichment overrides the auto-apply policy: see §Required invariants. Everything is a capture-time, human-reviewed act; once accepted it is plain Markdown in the
syn verifydump. No drifting model, no hidden state, no new determinism/verify hole: the opposite of the rejected LTR/continuous-learning direction.
Zero net-new MCP surface. This is implementable by editing existing tool
descriptions, the instructions string, and the capture tool result: no new
capability. Server-initiated sampling is explicitly rejected: it is
deprecated (SEP-2577), client-discretionary, and Synapse already removed it
(SYN-219); re-introducing it to pull calendar context server-side would contradict
a shipped decision and build the marquee flow on a deprecated primitive.
Required invariants (load-bearing: do not ship without these)
The red-team found the draft’s “the gate contains injection” claim false against
the code: auto_apply=safe auto-applies exactly link_entity
(src/core/policy.rs:84-111), which is enrichment’s primary output. So the
easiest injection payload (a mislink from a crafted calendar event) would bypass
the only backstop. Therefore:
- RI-1 (gate-forced). A proposal whose fields were derived from external
context is marked
enrichedand is never eligible forauto_apply=safeand never applied bypending accept --all; it requires an individual, deliberate human accept, regardless of[proposals] auto_apply. (Theenrichedmarker is an agent self-declaration; it earns more scrutiny, never less, a poisoned agent that omits it is the pre-existing “poisoned agent proposes anything” risk the manual gate already covers, and this ADR pressures thesafe-tier auto-link surface enough that revisiting that tier is a noted follow-up.) - RI-2 (structured whitelist). Enrichment may populate only people-slug
links, project routing, tags, and title: never the free-form body,
status, identity/selfedges, and may link only to entities that already exist (an attacker-named attendee is never auto-created into a person). - RI-3 (no raw external data persisted). No verbatim calendar content is ever
stored, not in the entity, the pending
payload, thecontext_snapshot(src/core/pending.rs:42), or the append-only audit trail (ADR-012, which is unredactable). Only minimised, structured fields land. - RI-4 (provenance is a self-report, not an attestation). The stamp records
that the agent asserted it used a
calendar-class source: source class only, never the event title/attendees, never a truth claim: worded exactly likex-ai-source(ADR-012). Synapse never saw the source and attests only that the agent claimed context. Free text in existingx-ai-*/reason fields; no typed integration registry (that is the camel’s nose toward a hub). - RI-5 (redactable + verify-honest). Enriched fields are
Redactable(ADR-010) so federation strips them from shared workspaces. Enriched links are normal frontmatter links (present in the ADR-035 dump), nevermentions:*enrichment edges (excluded from the dump): otherwise the enriched edge is verify-invisible. Docs must state:syn verifyclean ≠ the enrichment was legitimate (verify proves faithful reproduction of the Markdown, ADR-035 §limits). - RI-6 (privacy nudge). The “connect a source” advisory is one-shot, dismissible, non-repeating, states the privacy cost (“the model will read your calendar”), and is never presented as recommended-by-default.
- RI-7 (review makes the untrusted bytes visible). The accept diff highlights which fields are agent-enriched (untrusted-derived) vs user-stated, to counter automation bias (ADR-022 §Context).
Consequences
- Improves (honestly bounded). Capture quality when the agent has and reads
external context: a defensive, non-deterministic gain, not the
highest-leverage write-side fix. The deterministic linker fixes rank above it and
should ship first: SYN-271 (
mentions:*reindex regeneration gap) and SYN-254 (identity/dedup expansion) improve entity linking with zero agent-dependence and full determinism. This ADR is complementary, not a substitute, and should not precede them. - Strengthens the brand: if led with governance. More agent-supplied context
= more attack surface = more need for the gate. Framed as a capability it muddies
“the memory your AI can’t silently corrupt”; framed as
governance-of-enriched-writes it reinforces it. The durable win is the
non-goal: Synapse composes the MCP ecosystem instead of maintaining N
connectors. Zero new dependency; local-first, no-telemetry,
syn verifyintact. - Limits. Synapse cannot detect a connected source (no topology primitive) →
advisory only. Synapse cannot verify enrichment truth → the (gate-forced) human
review + provenance-as-self-report is the backstop. A connected source MCP that
also has write/egress verbs, plus Synapse read tools, plus
git push, completes a lethal-trifecta exfiltration channel: document and warn; RI-2’s whitelist + length caps bound what can land, but Synapse cannot stop the agent reading its own (user-authorised) data. - Open. Whether the
safeauto-apply tier should keep auto-applying links at all once enrichment is encouraged; exact clause wording; howsyn doctorsurfaces the advisory without nagging.
Alternatives considered
- Fix the deterministic linker first (SYN-271 / SYN-254). Preferred ordering,
not an alternative to reject: regenerating
mentions:*on reindex and expanding identity fingerprints improve entity linking with full determinism and zero agent-dependence. This ADR is complementary and should be sequenced after them. - Synapse integrates the source directly (
syn capture --from-calendar, Synapse-side). Rejected: a heavy external dependency + OAuth + secrets + a live-data surface, duplicating the agent’s MCP composition. Distinct from ADR-025 git-capture, whose substrate is local and already present. - A Synapse “integrations hub” (Slack/Linear/Notion/calendar connectors), or a per-source prompt per source. Rejected as scope creep off the moat and “the hub by stealth”; the MCP ecosystem already provides those servers.
- Server-to-server calls or server-initiated
samplingfrom Synapse. Rejected: makes Synapse a credentialed network client (the surface local-first avoids); the only in-protocol path (sampling+includeContext:"allServers") is deprecated (SEP-2577/2596) and was removed in SYN-219. The consulting agent is the correct place for cross-source composition. - Do nothing. The agent can already read a connected source but is not steered to, so enrichment is inconsistent. A single generic clause is the cheap, high-leverage change, but only once RI-1..RI-7 are in place.
ADR-044: Composite parallel search + deterministic relevance calibration
Date: 2026-07-06 (accepted 2026-07-06 after the persona-bench k-sweep) Status: Accepted (SYN-306 shipped) Author: Rômulo Febasi
Scopes SYN-306 (v1.2 retrieval quality). Extends ADR-042 (chunking + reranker) and the SYN-86 RRF hybrid. Motivated by an owner request (“run more than one kind of search at once, async not chained, return a precision/compatibility signal, weighted variations are fine”) and a 2026 IR research pass.
Outcome (SYN-306 shipped)
Shipped, unconditionally (no ranking change):
- Parallel legs. The two independent legs: FTS (lexical) and vector (semantic): run concurrently: the vector leg on its own thread + own SQLite connection, the FTS leg on the caller’s connection meanwhile; the graph leg follows (it seeds from both). Fusion is a pure function of the per-leg orders, so the fused result is identical and deterministic regardless of thread timing: proven by a repeat-run test and by three byte-identical real-model search runs. The win is architectural clarity + overlap of the two SQLite legs; at single-user scale the embedding forward pass dominates latency, as predicted.
- Compatibility score. The SYN-304 band is refined into a bounded
[0,1]score,0.6·agreement + 0.4·position(agreement = fraction of the three legs that surfaced the hit; position = mean1/(1+rank)over those legs). Surfaced per hit over both doors. Deterministic; explicitly not a calibrated probability.
Measured, then held at the default (evidence-gated):
-
RRF-k sweep.
persona-bench --features real(real BGE-M3, the 6-persona haystack growing 77 → 463 entities, 20 fixed needles) sweptk ∈ {20,40,60}:k recall@1 (final tier) recall@10 (final tier) recall@1 across tiers 60 0.85 1.00 0.75/0.90/0.85/0.85/0.85/0.85 40 0.85 1.00 identical to k=60 20 0.85 1.00 identical to k=60 recall@1is identical for every k at every tier;recall@10differs only at the smallest, noisiest tier (k=20: 1.00 vs 0.95, a single needle out of 20) and never on the headlinerecall@1. Decision: keep the canonical k=60. The sweep is not a systematic win; RRF’s well-known k-robustness holds at this scale.SYNAPSE_RRF_Kremains a bench-only knob. -
Weighted RRF. No per-leg bias surfaced in the sweep (both legs already drive
recall@1 = 0.85/recall@10 = 1.00), so weights stay equal: the honest default. The knob is not shipped; a future bench that shows a systematic per-leg bias would revisit it.
Context
Search today (src/core/search/hybrid.rs) already runs three retrieval legs (FTS5/BM25, dense vector (BGE-M3 chunks), and graph one-hop) but it runs them
sequentially in one function and fuses them with unweighted RRF (k=60),
returning a rank-only list. SYN-304 added a per-hit relevance signal
(matched_by + a strong/good/partial band from leg agreement), which is a
first step, but two owner asks remain:
- Parallelism. The independent legs (FTS, vector) should run concurrently, not chained: at single-user scale search is already fast, but the composite design should be genuinely parallel where the legs are independent.
- A relevance/compatibility score, so the agent can judge whether results are on-topic, from multiple search variations (weighted variations are acceptable) rather than one path.
A hard prior from the field research (see SYN-305 findings): BM25 k1/b are
not tunable in FTS5 (compiled constants), calibrated-score fusion (RSF/DBSF)
is a poor fit because the graph leg has no calibratable score and there is
no labelled eval set to choose a distribution, and global title>body field
weighting is wrong (the owner noted a query is often about the body). So the
design space is: parallel execution, weighted RRF variations, a deterministic
compatibility signal, and an RRF-k sweep, not learned fusion.
Decision (proposed: evidence-gated)
Evolve the hybrid search into a composite, parallel retriever with a deterministic relevance calibration, keeping RRF’s rank-only robustness.
- Parallel legs. Run the independent legs (FTS, vector) concurrently, each
on its own SQLite connection (rusqlite
Connectionis notSync), then the graph leg (which seeds from FTS+vector) after. Deterministic: the fused order is a pure function of the per-leg orders, independent of thread timing. - Deterministic compatibility score, not calibrated fusion. Extend the
SYN-304 signal into a bounded, explainable score derived from **leg agreement
- rank position within each leg** (e.g. how many legs ranked it and how high), surfaced per hit. Reject RSF/DBSF and any trained ranker, the graph leg has no score to normalise and there is no eval set (keeps ADR-029’s no-opaque-ML line).
- Weighted RRF as an optional, bench-gated knob. Allow per-leg weights in
the fusion (
wᵢ · 1/(k+rank)) (deterministic, no artifact) but ship weights ≠ 1 only ifpersona-benchshows a systematic per-leg bias. Default stays equal weights (the honest prior). No global title>body field weighting. - RRF-k sweep. Treat
k(currently 60) as a bench-tuned global constant; sweep {20,40,60} onpersona-benchand pick the precision-best. One constant, never per-query.
Every change is benchmark-gated (ship only on a measured recall@1/precision
win with no recall@10 regression, ADR-042 discipline) and disposable-index only.
Consequences
- Improves: the agent gets a real “how compatible is this result” signal from multiple deterministic search variations; independent legs run concurrently; fusion can be tuned within RRF without a trained artifact. All on-brand (deterministic, local, explainable).
- Worsens / limits: parallel legs add per-thread connections + coordination (modest at single-user scale: the win is architectural clarity, not latency); a compatibility score risks over-trust if presented as a probability: it must stay a banded, explainable signal, never calibrated.
- Open: exact score formula (leg agreement × normalised rank); whether parallelism is worth the complexity at this scale (measure first); per-leg weight values (only if the bench demands).
Alternatives considered
- Relative-score / distribution fusion (RSF / DBSF). Rejected: the graph leg emits a link-count with no calibratable scale, and there is no labelled eval set to choose a distribution: RRF’s rank-only design is why the three heterogeneous legs fuse soundly today.
- Global title>body field weighting (BM25F-lite). Rejected per owner: a query is often about the body; a global title bias hurts those. Field weighting, if ever, belongs inside a weighted variation, not the default.
- Tune FTS5
k1/b. Not possible (compiled constants); a C extension is a native dep not worth ≤15% for. - A trained learning-to-rank / calibrated confidence model. Rejected: opaque, off-ADR-029, and single-user data is too sparse (see the SYN-305 LTR note).
ADR-045: On-device retrieval-model support policy (prefix, MRL, sparse, licensing)
Date: 2026-07-06 Status: Proposed Author: Rômulo Febasi
Scopes SYN-307 (retrieval-model options, v1.3+). Extends ADR-008 (BGE-M3 via fastembed + sqlite-vec) and ADR-042 (chunked embeddings + reranker). Motivated by a 2026 model-landscape pass (HuggingFace MTEB, trending sub-500M multilingual encoders) against Synapse’s hard local-first + permissive-licence constraints.
Context
The default embedding backend is BGE-M3 (dense, 1024-d, ~2.2 GB fp32 /
~2.5 GB with the ORT runtime; int8 onboarding option, SYN-237), served through
fastembed on the shared ort copy (ADR-008/038). A field pass surfaced newer
sub-500M multilingual encoders and index-shrinking techniques that could cut
footprint and cold-start without a new native backend, but each carries a
license, quality, or determinism catch that has to be gated, not assumed:
- multilingual-e5-small/base (MIT, ~120–280 MB): strong for the size, but
E5 is asymmetric: it requires
query:/passage:prefixes, andfastembeddoes not auto-prefix them. Shipping E5 through the current symmetric embed path would silently degrade quality, a real code change (an asymmetric embed seam), not a config swap. - EmbeddingGemma-300M (best-in-class sub-500M multilingual on MTEB, ~300 MB) , but Gemma-licensed and gated on HuggingFace: not a drop-in permissive dep, and auto-download behind a gate is a distribution problem.
- Qwen3-Embedding-0.6B: competitive, but no maintained ONNX export yet,
so it does not run on the
ort/fastembed substrate without a backend we have ruled out adding. - Matryoshka / MRL truncation: a dense vector trained with MRL can be truncated (e.g. 1024→256) for a ~4× smaller index at a small recall cost. BGE-M3 is not MRL-trained, so this only unlocks with an MRL-capable model.
- BGE-M3 sparse output: the model already emits a learned-sparse (SPLADE- like) representation that Synapse currently discards; it is the one honest learned-sparse leg available under a permissive licence (SPLADE proper is non-commercial: rejected). Adding it is a retrieval-quality spike, not a model swap.
Decision (proposed: evidence-gated)
**Adopt a licence-and-determinism-gated policy for on-device retrieval models; add options only behind a benchmark win and only when they respect local-first
- permissive-licence + deterministic constraints. No default change without a measured win.**
- Permissive-licence gate. A shippable default/opt-in model must be permissively licensed (MIT/Apache-class) and auto-downloadable without a gated-repo token. Reject SPLADE (non-commercial). Gemma-licensed models (EmbeddingGemma) are opt-in-only and never the auto-downloaded default: the licence + gating make them a documented alternative, not the spine.
- ONNX/
ortsubstrate gate. A candidate must run on the existingort/fastembed copy (ADR-008/038). No second native backend for a retrieval model without its own ADR. Qwen3-Embedding is deferred until a maintained ONNX export exists. - Asymmetric embed seam for E5-class models. Supporting E5
(multilingual-e5-*) requires a real feature: an asymmetric embed path that
prefixes
query:on the query andpassage:on stored chunks. Ship E5 only with that seam and apersona-bench --features realwin: never as a bare fastembed enum swap (which would degrade silently). - MRL truncation is a model property, not a global knob. Offer dimension
truncation only for an MRL-trained model, as a per-model bench-gated option
(index size vs
recall@10). BGE-M3 is exempt (not MRL-trained). - BGE-M3 sparse leg = a separate spike, its own ADR. Wiring the discarded sparse output into a fourth retrieval leg (or replacing FTS/BM25) is a retrieval-quality experiment gated by the ADR-042 discipline: tracked, not bundled here.
Every model addition is disposable-index only (a model swap is a reindex,
never a source-of-truth change) and benchmark-gated (ship on a measured
recall@1/footprint win with no recall@10 regression).
Consequences
- Improves: a clear, defensible path to a smaller/faster footprint (E5-small ~120 MB vs BGE-M3 ~2.2 GB) and a smaller index (MRL): if the bench earns it. Keeps the door open to best-in-class encoders without betraying local-first or permissive-licence promises.
- Worsens / limits: more model plumbing (asymmetric seam, per-model dims); the most capable sub-500M option (EmbeddingGemma) stays opt-in behind a licence gate, so it cannot be the friction-free default; Qwen3 is blocked on upstream ONNX.
- Open: whether E5-small’s quality at a fraction of the footprint justifies the asymmetric seam; the sparse-leg fusion weight (composes ADR-044); whether to make a smaller model the default or keep BGE-M3 default with smaller models opt-in (bench + footprint call).
Alternatives considered
- Swap the default to a sub-500M model now. Rejected as premature: no bench
on Synapse’s own
persona-bench, and E5 needs the prefix seam first, a swap today would be an unmeasured quality regression. - Add a second native backend (e.g. Candle) for Qwen3. Rejected: a native ML
backend is a heavy dep requiring its own ADR; the
ort/fastembed substrate is the ADR-008 decision, and Qwen3 will likely land an ONNX export. - Adopt SPLADE for learned-sparse. Rejected: non-commercial licence. The BGE-M3 sparse output is the permissive alternative already in the binary.
- Ship EmbeddingGemma as the default. Rejected: Gemma licence + gated repo make auto-download a distribution/licence problem; it stays a documented opt-in, not the spine.
ADR-046: Bitemporal field corrections (update_status valid-time history)
Date: 2026-07-07 Status: Accepted (SYN-315 / #289) Author: Rômulo Febasi
Fixes #289. Extends ADR-027 (bitemporal facts: store valid-time, supersession = invalidation-not-deletion) and ADR-031 (belief-time axis). Surfaced by the SYN-298 benchmark scenario B.
Context
A field correction via update_status (both doors) overwrites the field in
place: apply_update_status_in_tx calls set_fields_kind_in_tx with the new
value only. The entity’s index row keeps valid_from = created,
valid_until = NULL, so the valid-time --as-of axis cannot reconstruct the
pre-correction value:
capture aurora (status=active, created 2026-01-01)
update_status → archived (accepted 2026-02-01)
syn list project --as-of 2026-01-10 # a date BEFORE the correction
# → aurora with status=archived (the CURRENT value), not active
The old value survives only in git + the audit trail (syn at, syn blame,
syn diff), not in a valid-time-queryable form. This scopes the “correct it and
--as-of <before> returns the old value” story narrower than the pitch implies.
The existing bitemporal machinery (ADR-027) works only for supersession: a
new fact F2 (separate entity, its own id) carries x-supersedes: F1 and closes
F1’s interval. An update_status is an in-place, same-entity, single-field
edit: there is no F2, and the field, not the whole entity, changes over time.
Rejected shapes
- Naïve patch: stamp
x-valid-untilon the corrected entity. Breaks visibility: the entity now holds the new value but its interval closes at the correction, so--as-of <after>(the common case) drops it entirely, and--as-of <before>still returns the new value. Wrong on both axes. - Shadow-entity supersession: write the prior value as a derived-id
companion (
aurora~<until>). Pollutes the id namespace, leaks snapshots intolist/search, and breaks “one entity, referenced once” (BR-1). Rejected. - Reframe only: document that in-place corrections are transaction-time
(
syn at), not valid-time. Honest but under-delivers: #289 asks for a working--as-of, and the entity (not just its history) stays valid throughout, so a whole-entity interval is the wrong model anyway.
Decision
Record valid-time history at the FIELD level, not the entity level. The
entity stays visible for every date >= created (it existed throughout); only
the field value is reconstructed per valid-time interval.
- Frontmatter is the source of truth (disposable-safe). On a correction
that actually changes the value,
update_statusappends the prior value to a reservedx-field-historylist in the entity’s frontmatter:{field, value, valid_from, valid_until}wherevalid_fromis the field’s prior start (the entity’sx-valid-fromorcreated) andvalid_untilis the correction’s belief-time. The live frontmatter field holds the current value over[valid_until, ∞). Nothing is deleted; history lives in the Markdown, sosyn reindexrebuilds it (disposability, BR-1.4). - Index projection. Migration 0009 adds
entity_field_history(type, id, field, value, valid_from, valid_until). Reindex parsesx-field-historyinto it (a pure function of the Markdown, so INV-1/verify stays honest). - Read overlay. A read with
--as-of Doverlays the field: if a history row coversD(valid_from <= D < valid_until), its value replaces the current column; otherwise the current value stands. The entity’s own row keepsvalid_from = created,valid_until = NULL: visibility is unchanged. - Scope. Applies to the fields
update_statusgoverns (the indexedstatus, extensible to other corrected core fields). Full per-field temporal tables for arbitrary custom fields are out of scope; this closes the headlinestatusgap without a schema explosion.
Consequences
- Improves:
--as-of <before>reconstructs the pre-correction value deterministically; the entity stays visible on both sides of the correction; the model is disposable (reindex rebuilds it) andverify-honest (history is a pure function of the Markdown). Composes withsyn at(transaction-time) and supersession (whole-fact valid-time) rather than duplicating them. - Worsens / limits: a correcting write now carries a small frontmatter list
and one extra index table; the
--as-ofread path gains an overlay step (only for entities that have history). Custom non-statusfield corrections are not yet reconstructable (documented; follow-up if demanded). - Migration: additive (new table). Existing corrections made before this ADR are not retro-reconstructable (their prior value is only in git): documented.
Alternatives considered
See Rejected shapes above (naïve interval stamp, shadow entity, reframe-only).
A general per-field temporal table for every field was rejected as a schema/query
explosion disproportionate to the one real gap (status).
ADR-047: Multi-project brain: catalog-served today, resident daemon deferred
Date: 2026-07-07 Status: Accepted Author: Rômulo Febasi
Scopes the #288 epic (“always-on multi-project brain”). Seeds from #287 (SYN-300, the
--workspaceflag). Composes ADR-010 (federation) and ADR-011 (remote-as-witness).
Context
The desired product flow: “one brain, many projects, ambient access”, an agent in any repo can reach the user’s Synapse brain(s) elsewhere on the machine. The #288 epic sketched several phases; this ADR records what already exists and draws the line on what stays deferred.
What already ships (v1.2):
- User-level catalog. The federation registry (
src/registry,workspaces.dbat a platform-standard path) IS the per-user catalog: everysyn initregisters the workspace (id, name, root, git remote, cached SYNAPSE.md identity).syn ws list/show/include/exclude/refresh/forget/gcmanage it. - Serve any brain, from anywhere.
syn mcp --workspace <path|name>resolves a brain by path or by its catalog name (SYN-300);syn mcp install --workspacebakes that into the client config (#287), so a client in any directory reaches the pinned brain with nocwddependency. - Attributed cross-workspace reads.
list_workspaces,federated_search,get_entity_from,get_workspace_manifestreturn workspace-attributed results, gated by the opt-infederatedflag (BR-12.4). Redaction is per-workspace.
So phases 1–3 of the epic (catalog, serve-from-catalog, attributed reads) are done. The remaining phase is the “always-on resident service”.
Decision
The multi-project brain is served through the catalog + --workspace +
federation, which ship today. The always-on resident daemon (OS auto-start,
persistent process, lifecycle) is DEFERRED and gated behind its own future ADR, it is not built in v1.x.
Rationale for deferring the daemon rather than building it now:
- It is large, OS-specific infra: launchd (macOS), systemd (Linux), a Windows service: plus crash-restart, upgrade handling, and a login hook. None of that exists today, and MCP clients already spawn the stdio server on demand, so the daemon is an optimization, not a capability gap.
- It needs a security/IPC model of its own: a resident server implies a transport beyond per-client stdio (an HTTP/SSE or socket gateway: SYN-235, already Commercial-scoped), an authorization story (today the user owns the machine and each client spawns its own process), and resource limits. Those are ADR-level decisions, not a code drop.
- MCP has no “enumerate sibling servers” primitive: catalog-driven auto- discovery would need either an MCP protocol extension (Anthropic’s call) or an external registry file kept in sync: a design question, not a bug.
Consequences
- Improves: the epic’s user-visible value (reach my brain from any project) is
available now with zero new infra:
syn mcp install --workspace <name>. The catalog + federation give attributed multi-brain reads today. - Worsens / limits: there is no single always-on process; each MCP client starts its own stdio server (cheap, but not “resident”). Auto-start on login and catalog-driven discovery wait for the deferred daemon ADR.
- Follow-up: a future ADR (when demand is shown) decides the resident server transport + autostart + discovery, composing SYN-235 (HTTP/SSE gateway) and the multi-user roster (SYN-285 spike). Filed as SYN-317.
Alternatives considered
- Build the daemon now. Rejected: large cross-platform infra + a new security model for a convenience over the working on-demand spawn; premature without demand and its own ADR.
- Catalog-driven auto-discovery via a synced registry file. Deferred: needs a
sync + staleness design; the manual
--workspaceinstall already covers the concrete workflow.
ADR-048: Keep enrichment synchronous; async is trigger-gated, not now
Date: 2026-07-07 Status: Accepted Author: Rômulo Febasi
Resolves the #160 (SYN-194) placeholder. Confirms BR-13 (insertion-time enrichment). Companion to ADR-008 (backends) and ADR-046 (disposability).
Context
BR-13 runs enrichment (chunk embeddings + mentions:* NER/gazetteer links)
synchronously inside Workspace::tx, before commit, under a latency budget
(median ≤ 500 ms, p95 ≤ 800 ms on 10k entities, Apple M1 Pro). #160 registered a
possible v0.6+ async path: run enrichment after tx.commit() returns, make the
entity searchable on FTS immediately, and reach vector/NER consistency within a
documented window (~2–5 s). It was filed as a placeholder: “do not start work”
until real usage shows the synchronous write penalty is the wrong trade-off.
Decision
Keep enrichment synchronous. The async path stays deferred and is
trigger-gated: build it only when a measured p95 write regression on real usage
crosses the BR-13 budget. No async scheduler, no [enrichment] schedule config
ships now.
Rationale:
- The budget holds. No evidence the synchronous penalty is a problem; the
synapse-benchwrite path (SYN-293/298) stays within budget, and enrichment already runs its embed + NER legs in parallel threads inside the tx. - Async contradicts the enrich-before-commit principle. An eventual-
consistency window means a fact is briefly committed without its semantic
edges/embeddings: searchable by FTS but not by meaning, and momentarily
missing
mentions:*. That weakens the “a write is complete and findable when it lands” guarantee for a latency win nobody has asked for. - Disposability already covers the backfill case. The one real gap async was
partly meant to soften (entities written before models were downloaded) is
handled by
syn reindex --relink(heuristic edges, SYN-271) +syn embed --all(embeddings), both shipped in v1.2 (ADR-046 disposability line). So the pressure for async is lower than when #160 was filed.
Consequences
- Improves: the write contract stays simple and honest, a committed fact is fully enriched and findable, no consistency window to reason about. No new scheduler/retry/queue surface to maintain or verify.
- Worsens / limits: very fast capture flows pay the full enrichment latency per write. If that becomes real (a measured p95 breach, or a user report on a large corpus), the async path is revisited.
- Trigger to revisit: a
persona-bench/write-path p95 above the BR-13 budget on a real corpus, or a concrete user report. At that point: design the opt-in[enrichment] schedule = asyncwith a documented consistency window and a crash-safe queue: its own change, not a default.
Alternatives considered
- Ship async now (opt-in config). Rejected: adds a scheduler + eventual- consistency semantics + crash-safety for a latency problem not observed; against the enrich-before-commit principle.
- Close #160 as won’t-do. Rejected: the trade-off is legitimate at scale; keep it registered as trigger-gated so it is revisited on evidence, not forgotten.
ADR-049: A composable operational knowledge framework
Date: 2026-10-12 Status: Accepted (design; implementation tracked separately) Author: Rômulo Febasi
Context
Synapse stores typed Markdown, indexes it in disposable SQLite and exposes one core through CLI/MCP. It is used as a knowledge dependency, not a replacement for note tools, Git, MCP or other memory/retrieval engines. Agents and Soma need budgeted source-linked queries, source evolution and reviewable updates to shared knowledge.
Positioning: Operational memory for agents, with governed changes, preserved history, and explicit integrity checks.
ADR-043 keeps acquisition in agent-composed MCP tools. Explicit source-revision monitoring is now requested, so an optional ingestion adapter seam amends that decision without making connectors part of pure core.
Decision
Expose a versioned KnowledgeClient contract over the existing core, initially through CLI JSON and MCP. Keep acquisition/retrieval/decision adapters outside the core, and represent external sources as revisioned, access-scoped references. Automatic detection proposes changes; acceptance remains governed.
Consequences
- Positive: Soma and third parties consume the same knowledge surface, with existing tools remaining canonical.
- Negative: lineage, source ACL changes and reconciliation are additional obligations.
- Scope amendment: optional adapter workers are allowed at edges; default agent-composed capture remains supported.
- Limit: cached remote data cannot be made safely available offline without an explicit access lease/policy.
Alternatives considered
- Own a new all-purpose documentation UI: duplicates established products.
- Replicate every remote document as accepted truth: creates stale copies and loses authority.
- Embed every connector in core: breaks layering and burdens local users.
- Treat another memory engine’s output as verified Synapse evidence: a provider bridge must disclose provenance/trust differences.
Implementation contract
Read contract: query + workspace/project/source scope + purpose + time/version/environment + token/latency budget; return bounded evidence passages with source revisions, freshness, confidence class, omissions and verification profile. ACL filtering occurs before candidate retrieval/model calls and again before output. Cache keys include policy/ACL revision, model revision and source digest. Optional grounded summaries cite retrieved passages and abstain when evidence is missing.
Write contract: propose with expected revision, source digests, run/context references and idempotency key; review/accept verifies current authority and stale-source preconditions. Credentials stay in connector secret storage. Read-only adapters precede write-back; write-back requires a scoped capability, explicit review and optimistic concurrency.
Implement Git/Markdown plus one remote connector first. Represent Notion/Jira/Confluence/Obsidian as adapters, not promised ready integrations. QMD/Mem0/Zep can be retrieval/memory providers with explicit trust mapping; Synapse-owned acceptance evidence remains separate.
References
- Knowledge framework and trust design
- Framework design
- Source lineage/impact
- Source adapters
- Soma embedding
- ADR-002, ADR-023, ADR-043, ADR-047
ADR-050: Typed probabilistic decisions with abstention
Date: 2026-10-12 Status: Accepted (design; implementation tracked separately) Author: Rômulo Febasi
Context
The existing contradiction detector operates on structured status/link/identity changes; INV-13 proves interval/cycle properties. Prose disagreements require a different, probabilistic diagnostic. Jev returns typed decisions through a hosted API; that architecture is useful, but no local weights/parameter size or Synapse benchmark was established by this research.
Decision
Add a replaceable, opt-in DecisionProvider for narrow triage/classification after scoped candidate retrieval. Benchmark local NLI against rules and optional Jev. Keep model outputs diagnostic; exact checks, permission decisions and accepted transitions remain deterministic and separately verified.
Consequences
- Positive: small classifiers can reduce manual triage without generating a response for every decision.
- Negative: thresholds, language/domain calibration, candidate recall and review load must be measured.
- Limit: neither typed output nor confidence proves correctness; no semantic verdict becomes a cryptographic invariant.
- Offline: absent/disabled/failed model means unknown/review, never a fabricated clean bill of health.
Alternatives considered
- Embedding similarity alone: semantically similar statements may contradict one another.
- Reuse the retrieval reranker as an NLI classifier without evaluation: its training objective is different.
- Require a hosted Jev call for every operation: defeats offline operation and source egress boundaries.
- Train a new Jev-scale architecture: unsupported by a demonstrated data/compute need.
- Generate every routing decision with a large chat model: unnecessary for a bounded label space.
Implementation contract
DecisionRequest pins rubric/schema, candidate facts/source revisions, environment/API version and optional time. DecisionResult carries relation, evidence spans, probabilities or uncalibrated scores, model/provider revision, input/rubric digest, abstention reason and proposed action. Pairwise entailment is directional; evaluate both relevant directions. Distinguish contradiction, entailment, independent, temporal_change and insufficient_context.
The code executes actions from a reviewed policy; the model only recommends. NLI’s three labels must be mapped explicitly without pretending it understands every business action. Thresholds require held-out labeled cases and calibration. Local defaults need permissive artifacts and existing ONNX/runtime compatibility. Jev egress requires source/operator consent, budgets, credentials, redaction and failure fallback. Never pin production behavior to an unrecorded latest alias.
See the research protocol for EN/PT corpus, option-order tests, numeric/date deterministic handling and proposed evidence gates. No model is installed or benchmarked by this ADR.
References
- Knowledge framework and trust design
- Decision evaluation
- Research protocol
- TypeSafe introduction
- Known limitations
- ADR-008, ADR-029, ADR-042, ADR-045
ADR-051: Authenticated transitions and explicit source drift
Date: 2026-10-12 Status: Accepted (design; implementation tracked separately) Author: Rômulo Febasi
Context
INV-1 compares a deterministic projection of Markdown with a rebuilt index. INV-5-lite checks declared supersession edges, not every historical mutation. Existing git/hash-chain/checkpoint primitives preserve useful evidence, but a faithful rebuild of edited files cannot prove that an edit was authorized. ADR-036 correctly rejects reconstruction from a hash-only log.
Decision
Record governed changes in a versioned transition journal binding full source pre/post manifests and retrievable content images to proposals, actor keys and policy/context revisions. Verify current Markdown against the sealed accepted head, and require external signed witnessing for stronger rewrite/truncation detection.
Consequences
- Positive: changes without a recorded transition become explicit Unattested drift.
- Negative: journal recovery, content retention/encryption and migration add complexity.
- Limit: the promise covers a sealed suffix from an acknowledged baseline; history before that boundary remains unknown.
- Threat model: an attacker holding every signing key and controlling all witnesses may forge a consistent history. This remains tamper-evident under stated trust assumptions, not universally tamper-proof.
Alternatives considered
- Hash current Markdown only: authenticates bytes but does not establish a sequence of authorized changes.
- Replay the legacy hash-only audit as a complete store: contradicts ADR-036 and lacks content/preimages.
- Blockchain for every write: adds availability, cost and privacy obligations without proving fact truth or authorization.
- Silently reconcile direct edits by reindexing: erases the distinction between accepted transitions and arbitrary source drift.
Implementation contract
A transition binds schema/algorithm, workspace UUID, operation/event ID, actor/device key ID, previous accepted head/producer sequence, proposal/rule/context digests, canonical before/after manifests, content references and observed timestamps. Keys include entity type plus ID/path; cover create, update, delete and rename. Define domain-separated canonical encodings with test vectors; use exact integers/digests, not free-form ML floats.
Markdown stays the content source for reindex. Journal/receipts are authoritative evidence of governed transitions, not a hidden replacement database. Legacy events are not retroactively attested. Snapshot the explicit baseline under owner acknowledgment and begin the sealed suffix.
Use a writer lock and durable prepare/commit recovery record across filesystem/index/journal/receipt boundaries. A success acknowledgment follows a durable committed transition. On crash, recover idempotently or quarantine; fail closed on digest mismatch. Test power-loss boundaries, not merely Rust Drop rollback.
An independent witness retains signed workspace head/count/checkpoint evidence. Merkle inclusion/consistency proofs are an optional scaling primitive inspired by RFC-9162, not a claim of implementing Certificate Transparency. Fork/equivocation detection needs witness comparison/retained prior heads. A later blockchain anchor adapter may commit an approved privacy-preserving checkpoint commitment only; no raw document/identity or blindly hashed low-entropy secret is published.
References
- Knowledge framework and trust design
- Transition journal
- Merkle consistency reference
- Canonical JSON reference
- ADR-026, ADR-035, ADR-036, ADR-037
ADR-052: Persisted receipts and explicit verification assurance
Date: 2026-10-12 Status: Accepted (design; implementation tracked separately) Author: Rômulo Febasi
Context
INV-8 currently skips because receipts are export-only. Report signing is optional when no key exists; skipped checks do not fail the basic report. A dashboard/CI must not collapse this into a universal green assurance verdict.
Decision
Persist signed governed-transition receipts and introduce explicit basic, strict-local and strict-team verification profiles. Preserve compatible v1 readers/behavior and introduce a versioned report extension/schema only with migration tests. Strict profiles fail when any required assurance component is absent, skipped or invalid.
Consequences
- Positive: operators can see exactly which surfaces/ranges were verified.
- Negative: receipt persistence, historical trust evidence and migration require sustained tests.
- Compatibility: ordinary local/basic checks remain useful with honest partial coverage.
- Limit: integrity, identity/authority and semantic diagnostics are different report dimensions; none guarantees world truth.
Alternatives considered
- Make every legacy check mandatory immediately: breaks existing unsigned/local workspaces and v1 consumers.
- Keep treating skipped checks as universally sufficient: incompatible with a team assurance claim.
- Put ML outputs into deterministic signed verdicts: confuses reproducible evidence with probabilistic assessment.
- Add a standard envelope only: serialization does not create missing receipts or trust evidence.
Implementation contract
Receipt contents include transition/payload/source/context digests, actor/device binding, authorization/roster revision, accepted time, algorithm/key ID and signature. Preserve retrievable signing/witness material under policy. Authenticate against pinned trusted keys/authorities; a public key supplied with a report alone is not a trust root.
Basic: report actual checks, missing evidence and limited covered range. Strict-local: require sealed transition coverage, receipts and trusted local signing evidence from the baseline. Strict-team: additionally require scoped subject/key bindings, historical acceptance grant, valid revocation/freshness evidence and selected independent witness/checkpoint. Current authority for a new approval is revalidated online by the collaboration authority; old-signature validity is a separate historical question.
Any missing required evidence fails strict mode with a stable reason/exit code. Report explicit exclusions for pending, embeddings/enrichment and opaque agent behavior. Support a policy-pinned verifier and golden canonical vectors. A receipt-store check replaces INV-8 Skip only when actually implemented; older workspaces show migrated coverage, not invented completeness.
Use native receipts plus an in-toto predicate envelope when useful; DSSE/in-toto encoding and verification require conformance fixtures. C2PA remains a separate evidence-gated option under existing SYN-320, not a required change in this design.
References
- Knowledge framework and trust design
- Receipts/strict profiles
- Standard envelopes
- in-toto Statement
- ADR-034, ADR-035, ADR-037
Standard serialization implementation
SYN-320 adds separate opt-in in-toto Statement v1 and DSSE export/verification. The subject digest identifies actual immutable exported native artifact bytes; head hashes and filenames are not substituted for byte digests. Collection and native policy verification share one writer lease. Public selected trust/witness material, originals, skips and exclusions remain explicit. Native v1 consumers and their default behavior are preserved.
The independent conformance proof computes SHA/PAE in Python and verifies the actual CLI signature with OpenSSL, checks mutation/overwrite denial, and compares the CLI/MCP payloads. Outer authenticity and subject integrity are separate from native source revalidation, organizational authority and general semantics.
ADR-053: OIDC subject bindings and independent witnesses
Date: 2026-10-12 Status: Accepted (design; implementation tracked separately) Author: Rômulo Febasi
Context
Portable ed25519 identity proves possession of a key, not an enterprise subject’s current membership. ADR-034 only reports commit signature posture; SYN-261 owns genuine SSHSIG validation. Shared approval needs authentication, key binding and scoped authority. These functions can be supplied without a mandatory network dependency in the local core.
Decision
Keep local keys as the baseline and consume optional signed subject/device bindings and scoped roster snapshots from an external identity authority. Use OIDC for online authentication, proof of key possession for binding and separate membership/policy checks for approval. Witness adapters may anchor authenticated checkpoints independently.
Consequences
- Positive: an offline verifier can check previously exported identity/evidence without consulting a live IdP.
- Negative: device enrollment, issuer trust, revocation timing and evidence freshness need explicit policy.
- Limit: an offline client cannot know an unobserved revocation; stale evidence is reported and new shared acceptance waits for server revalidation.
- Privacy: bearer/refresh/ID tokens and private keys never enter Markdown, journal, receipts or public witnesses.
Alternatives considered
- OIDC login as proof of every action: does not bind an artifact signature or authorize a project operation.
- Wallet/blockchain address as enterprise identity: identifies a key; employee membership and approval rules still need an authority.
- Public Sigstore as mandatory runtime: changes offline/privacy assumptions and may expose identities/log metadata.
- Trust any self-signed key: establishes possession but no organizational identity or approval authority.
Implementation contract
The authority validates issuer/audience/nonce and native PKCE login outside Synapse core. Enroll a device with a one-use scoped challenge signed by its key, then issue an authenticated binding: (issuer,sub), key fingerprint, organization/workspace scope, validity window, authority key ID, revocation reference and signature. Do not use mutable email as the subject key.
The verifier uses trusted authority roots, exported binding/roster revisions and timestamp/witness evidence. Distinguish revoked-now from compromised-at/signing-time validity and new-action permission. The authority owns membership; agents get delegated/scoped execution identity, never implicitly the approver’s authority.
SYN-261 validates real SSHSIG signatures/allowlists; detection of gpgsig remains only posture until then. An optional Sigstore bundle can provide signing identity and timestamp/inclusion evidence, but public publication requires consent and is never forced for private knowledge. A private witness or independent retained signed checkpoint may be a better initial fit.
Blockchain anchoring is an optional checkpoint publication transport if a buyer requires an independent trust domain. It does not resolve correctness, availability of retained payloads, identity mapping or membership revocation. Start with existing git/ed25519 and independently retained witnesses; no token/chain is introduced.
References
- Knowledge framework and trust design
- OIDC binding
- SSHSIG verification
- OIDC Core
- Sigstore bundle
- ADR-016, ADR-030, ADR-034, ADR-037
Implementation clarification (2026-10-13)
The initial Soma adapter authenticates through actual febasi-api-auth/SDK with
configured tenant febasi. It does not implement OIDC and does not copy its JWT
signing secret to Synapse. OIDC remains an optional outside-core adapter when its
protocol is implemented. ADR-059 supplies the
shared pure device/scope wire contract and offline pins; current new-event approval
remains SYN-231. This clarification supersedes any inference that the initial
Febasi account flow is already OIDC or that an offline binding grants new approval.
ADR-054: maintained YAML backend for governed Markdown
Date: 2026-10-12 Status: Accepted Author: Rômulo Febasi Supersedes: ADR-004 backend selection (frontmatter contract retained) Tracking: SYN-330 (#316)
Context
The locked serde_yaml_ng 0.10.0 pulls unsafe-libyaml 0.2.11, which is unmaintained (RUSTSEC-2024-0449). The CI advisory gate rejects that graph. An ignore would hide the blocker instead of replacing the unsupported implementation. The current backend is used by frontmatter, typed values, history, provenance, proposals and export; the old promise of a one-file swap is not true in the current code.
Decision
Use yaml_serde 0.10.7, the maintained YAML Organization fork, with libyaml-rs 0.3.0. Rename imports explicitly, retain ordered Mapping/Value and the existing Error::Yaml boundary. Ban the retired serde_yaml_ng and unsafe-libyaml packages. No advisory exceptions or safety claims based only on the package name are added. libyaml-rs still contains translated unsafe Rust; this migration restores maintenance, not a proof of memory safety.
Apply compatible patched lockfile updates to anyhow, crossbeam-epoch, h2, rustls and the yanked der 0.8.0. Keep Rust 1.89, the ONNX runtime and model stack unchanged. The independently modified developer benchmark lockfile is outside this change.
Compatibility and rollback
No workspace format, database migration or bulk Markdown rewrite occurs. Existing readers and writers retain YAML semantics, unknown fields, mapping order, tags and multiline strings. Serialization already normalizes YAML formatting; comments and original quoting were never byte-preserved. Body bytes with a trailing newline are preserved. Test before/after the swap, plus full/incremental index invariants. Untouched files remain byte-identical. Rollback is a binary rollback with the same Markdown format; do not redeploy an advisory-affected binary as an operational security fix.
Alternatives
- serde_norway: compatible API, but the frontend/backend repositories have older maintenance activity than the official fork inspected on 2026-10-12.
- serde_yaml_bw: active hardening but still an unsafe libyaml lineage and a wider API/behavior change.
- serde-saphyr: pure Rust and Rust 1.89 compatible, but no Value/Mapping DOM matching the public in-memory contract; a larger domain migration deserves its own scope.
- noyalib: not selected without an independent safety/maintenance audit; API compatibility alone is insufficient.
- Adding an advisory ignore or disabling the gate: rejected.
Verification
Regression coverage preserves ordered unknown nested fields, scalar types, YAML tags, multiline values and Markdown bytes through disk writes. Existing CLI snapshots and rebuild invariants check canonical behavior. Run fmt, Clippy/tests/doc with all features, cargo deny and the Rust 1.89 build before merge. Record actual results in the PR, not anticipated passes.
Sources
- YAML Organization fork
- libyaml-rs
- unsafe-libyaml advisory
- anyhow advisory
- crossbeam-epoch advisory
- h2 advisory
- rustls advisory
ADR-055: Cryptographic Git signatures and pinned local trust
Status: accepted. Implements SYN-261; external authority bindings remain SYN-326.
A Git signature header is not proof of valid attribution. The verifier removes
exactly the gpgsig header and continuation lines from raw commit bytes, verifies
SSHSIG in the git namespace with RustCrypto ssh-key (Ed25519, SHA256/SHA512),
and matches the signing key against an explicitly pinned local issuer/subject
roster. OpenPGP, alternate SHA256 headers and unsupported algorithms fail explicitly.
No shell, global Git configuration, key agent or network is used to verify.
The roster supplies signing-time validity and compromise/revocation boundaries.
Historical validity is separate from current key eligibility. Committer time is
signed but signer-controlled: this cannot defeat backdating without independent
timestamp evidence. Current team authority and freshness require SYN-326/SYN-231.
A self-supplied key is unknown_signer; email and Git author text are never trust roots.
commit-verify --roster roster.json and MCP commit_verify use the same core.
Native report v1 remains compatible. Ordinary single-owner commands stay advisory.
Strict profiles will consume this explicit verdict rather than signature presence.
ADR-056: Durable workspace prepare/commit and bounded recovery
Status: Accepted Date: 2026-10-13 Task: SYN-324 (#309)
Context
The previous transaction deleted overwrite backups before SQLite COMMIT. A failed COMMIT could delete the new file without restoring its old bytes, while a failed later rename could strand earlier renames. Process death had no durable instruction describing what to recover. A hash chain cannot repair those losses.
Decision
All workspace transactions acquire an advisory file writer lock, recover prior intent, and then start SQLite with FULL synchronization. Stage writes and deletes without altering current source files. A versioned, fsynced prepare document binds a random transaction ID, database incarnation and ordered relative paths to before/after tree fingerprints. Save the old source under the transaction directory before installing each new file. Preserve replacement permissions.
An additive SQLite migration records its database incarnation and committed transaction ID with the exact prepare digest in the same SQLite transaction as the index changes. A durable matching marker means roll forward and finalize; its absence means restore preimages. Keep backups until that decision is durable. Recovery repeats safely after another crash. A missing/different-incarnation database, modified committed prepare, path escape or unexpected source content fails closed and retains recovery evidence rather than overwriting someone else’s bytes.
Workspace discovery and transaction entry recover under the same writer lock. Concurrent cooperative writers receive a retryable busy result. Co-operating leases explicitly unlock before closing their descriptor, so a descriptor inherited briefly by a subprocess does not retain a finished writer’s lock. Co-operating reindex also acquires this lock. These rules do not make advisory locks constrain external editors or make a filesystem/SQLite pair physically atomic to external readers: observers may see a short installation window before recovery/commit.
Proof boundary
This is the recovery foundation of SYN-324. It does not authenticate actor
identity, preserve an indefinite historical suffix, detect later Markdown edits
or independently prove non-truncation. Governed sealed transitions, retained
payloads and independent witnesses build on this protocol in a separate slice.
Preimages are private recovery material retained until finalization. A local
transaction .gitignore excludes them from ordinary Git staging; forcing a Git
add bypasses that safeguard. Backups must retain a consistent encrypted copy of
source, index and pending journal to retain recovery capability. Existing
Markdown remains the content source and old indexes upgrade additively.
Directory fsync is required on Unix for power-loss ordering; other platforms retain process-crash recovery without a directory durability guarantee. Strict assurance must reject an unsupported platform rather than claim this guarantee. Hardware/filesystem dishonesty, complete disk loss and privileged concurrent rewrites of source plus journal plus database require independent retained evidence and are outside this protocol. Restoring an old database snapshot with the same incarnation is indistinguishable from an uncommitted marker without independent retained evidence; this foundation does not certify historical non-rollback. No blockchain network is involved.
Verification
Deferred foreign-key COMMIT failure, overwritten/create/delete/directory cases, two writers, path and symlink rejection, changed database incarnation and subprocess death around prepare, backup, installation and SQLite commit. Reopen the workspace after each death and verify both exact source bytes and index state, then repeat recovery to establish idempotence.
ADR-057: Governed Markdown suffix and independently retained witnesses
Status: Accepted Date: 2026-10-13 Task: SYN-324 (#309)
Decision and scope
Opt into a limited baseline through an explicit acknowledgement of unknown
preceding history and plaintext historical retention. Markdown remains current
content. Scope workspace-markdown/1 covers regular .md files recursively,
excluding .git and .synapse directory components regardless of ASCII case.
Reject symlinks, non-UTF8/unportable names, excessive depth and budgets rather
than silently omit ambiguous content. Indexes, keys, attachments and arbitrary
non-Markdown files are outside the evidence claim.
Persist raw SHA-256 content-addressed images under private metadata permissions. Retention is manual with no automatic GC; filesystem encryption/access controls are the operator’s responsibility. Deleted content remains retrievable history. No encryption, confidentiality from a privileged host or historical source permission proof is inferred from a hash. No raw content or commitment is published to a blockchain or remote service by this slice.
A fixed-order compact UTF-8 JSON transition binds workspace/event identity,
monotonic sequence, parent digest, baseline/mutation/reconciliation kind, local
observation time, explicit actor/input claims and complete before/observed/after
manifests. Prefix canonical bytes with synapse.governed-transition/1 and NUL.
All fields, including nulls, are present. Map keys sort by exact UTF-8 bytes;
strings are not Unicode-normalized. Unsigned integer values fit exact JSON
integers; floats, unknown fields and invalid digests are rejected. Actor claims
remain unverified until the separate authority/receipt layers check them.
Store a prospective transition in prepare-v2 before filesystem installation. The exact SQLite prepare digest commits index and transition intent together. After COMMIT, publish immutable record then accepted head before acknowledging success. Recovery repeats that publication if interrupted. Read old prepare-v1 intents as ungoverned; an old binary refuses v2 rather than discard evidence.
In an opted-in workspace, a mutation requires current source to match the last accepted complete manifest. Reindex cannot erase the discrepancy. Explicit reconciliation records prior accepted state and the actual externally edited source separately. Expected parent checks and unique event IDs reject conflicting or repeated transitions. Crash recovery repeats an identical decision safely.
An exported Ed25519 witness binds baseline and accepted prefix tip/count. Retain it independently and pin its public key from a trusted source; its embedded key alone is insufficient. Comparing the retained prefix detects truncation, rewritten suffixes, reorder or forks relative to that observation. Absence of a retained witness is explicitly local consistency, not non-rewrite proof.
Limits and budgets
Advisory locking cannot constrain external editors; source may change after a check. Future verification detects differences from the accepted head. The evidence proves bytes/causal ordering, not semantic truth, comprehensive natural language consistency or current organizational permission. Local timestamps are not trusted timestamps; signatures do not prove an agent followed instructions. Strict-local/team verification and authenticated shared admission are subsequent SYN-325/326/231 layers, not claims of this native record format.
Bound snapshots to10000 files/64MiB total,16MiB per file; prepare/record JSON to 16MiB; suffix verification to10000 records/64MiB metadata. Missing/purged images lose reconstruction coverage and fail verification explicitly. Larger workspaces require a reviewed scope/version/budget change rather than silent truncation.
Verification
Golden canonical vectors, all create/update/delete/rename manifests, actual crashes including record/head publication, repeated recovery, external edits followed by reindex, explicit reconciliation, tampering/missing payloads, independent witness/pin and predecessor/event conflicts. CLI and MCP call the same core operations; no network/model inference is part of acceptance.
ADR-058: Recoverable native receipts and explicit local assurance
Date: 2026-10-13 Status: Accepted (local implementation; team authority remains SYN-326/SYN-231) Author: Rômulo Febasi
Context
A source hash chain authenticates neither its actor nor its original receipt metadata. Re-signing a receipt with the same transition can evade a source-only checkpoint. Optional signatures/skips must not become a universal green report.
Decision
Prepare-v3 binds the complete native receipt into the same exact SQLite decision as the source record. A strict-local signer is loaded/validated before source installation; missing keys/policy, unknown/revoked/expired signing keys or stale trust deny. After COMMIT, receipt publication precedes source record/head success and recovers from prepared bytes without needing the private key. V1/v2 prepare formats remain recoverable; older binaries refuse v3. Basic new records retain explicitly unsigned native receipts; old coverage holes remain holes.
Native receipt bytes are synapse.state-receipt/1 + NUL + fixed-order compact
struct JSON, signature null while public key/key ID/public write profile/trust
and policy hashes remain covered. Key ID is full SHA-256 of canonical raw
Ed25519 public bytes. Verification is strict Ed25519. Public policy digest is
synapse.receipt-write-policy/1 + NUL + compact JSON array [profile,trust_hash];
key paths/private material are excluded. The ordered receipt snapshot is
SHA256("synapse.receipt-set/1" + NUL + [[transition_hash,receipt_or_null],...]).
No floats/unknown fields/noncanonical base64 are accepted. Python independently
computes the receipt/trust digest vectors used by native tests/stdio smoke.
V1 source witnesses remain byte-compatible. Opt-in witness v2 signs the source prefix and exact original receipt-set digest. Receipt changes/re-signing then fail the retained checkpoint. Strict-local requires its witness to cover the selected complete tip, all receipts, independently selected public trust/pin and reviewed freshness. Basic success permits explicit gaps. Strict-team fails closed until authority-issued subject/device scope and current admission exist; a local reviewed key is never represented as employee/team authority.
Explicit syn state verify --profile ... reports synapse.state-assurance/2.
Storage/source, transitions, receipts, pinned trust, identity, witnesses and
semantic diagnostics/exclusions are separate. Semantic diagnostics are explicitly
not run by this source/receipt verifier; use the separate deterministic verifier.
V1 syn verify still exists: INV-8 verifies stored signature round-trip when
available, skips absent/unsigned evidence and fails invalid receipts. A v1 pass
with skips is not complete assurance or independently authenticated identity.
Local strict policy is configured before baseline, with conditional replacement against an exact reviewed public policy digest for rotation/trust refresh. Retained original receipts/public trust revisions are never re-signed. An interrupted initial configuration can be retried explicitly; a missing active policy with retained strict configuration denies source writes. Policy pointer updates are atomic private evidence configuration, outside Markdown/index state. Their trust comes from separately reviewed verifier inputs, not file ownership.
Boundaries
- Current new-signature approval differs from historical validity at a claimed time. Revoked-now can preserve earlier evidence; compromised-at can invalidate it. Both clocks and unsigned local trust reviews are operator assertions.
- Offline freshness is the reviewed snapshot deadline and verifier clock; unobserved later revocations, trustworthy historical time and team grants require external authority evidence. Strict-team cannot pass this delivery.
- Plaintext private history/manual retention, bounded Markdown scope and Unix durability remain ADR-056/057. A disk owner can replace all local artifacts; only separately retained pins/checkpoints constrain that adversary.
- No global trust/account configuration is changed, and no public blockchain, key material or knowledge content is published. Native receipts are not an in-toto/DSSE standard envelope; that adapter remains SYN-320.
Verification
Signed/unsigned/missing/tampered receipts; wrong/stale trust pins; administrative revocation versus compromise; key rotation with stale-policy conflict; missing policy/key and private-file mode; independent receipt re-signing mismatch; legacy v2 COMMIT recovery and byte-compatible v1 witness; actual subprocess termination at prepare, SQLite, receipt, record, head and finalization. Recovery runs twice without the signing key. Actual CLI/stdio MCP assert equal evidence beyond verifier observation time and explicit strict-team failure.
ADR-059: Shared pure authority contracts and offline scope verification
Date: 2026-10-13 Status: Accepted (binding component; new shared admission remains SYN-231) Author: Rômulo Febasi
Context
State/receipt signatures authenticate bytes and key possession, not a company’s subject/device/scope binding. Soma already verifies real Febasi accounts and proof-enrolled device keys. Sharing one normative contract prevents issuer and verifier from assigning different meanings to signed fields.
Decision
A small pure Rust synapse-evidence package owns versioned Ed25519 bindings,
selected-key revocation snapshots, reviewed issuer roots and offline verification.
Synapse reexports its public types in model; Soma’s Rust issuer consumes an exact
Git revision. It imports no filesystem, HTTP, database or account SDK. Authentication,
issuance/key handling, scope authorization and current admission stay in adapters.
Both CLI syn state binding-verify and MCP state_binding_verify use the same core.
Signed artifacts, reviewed root snapshot/digest and expected subject/tenant/device/
resource scope are distinct inputs. Embedded keys do not choose their own roots or
scope. Core checks the actual limited-baseline workspace GUID; source hashes/images
are verified separately. source_integrity_checked:false and
shared_acceptance:unknown_without_online_event_bound_admission prevent identity
success from becoming permission to accept new shared knowledge.
Normative contract
Schemas: synapse.authority-binding/1, synapse.authority-snapshot/1,
synapse.authority-trust/1. Bytes are schema + NUL + fixed-order compact UTF-8 JSON,
explicit null signature/embedded issuer public key. Maps/strings are not normalized;
unknown fields, floats, noncanonical base64, oversized arrays and invalid namespace
identities deny. IDs are exact lowercase UUIDs; knowledge GUID is exactly 32 lowercase hexadecimal characters.
Fingerprints/key IDs are full raw-public-key SHA-256. Root-review digest is selected
independently. Canonical field order is the package’s versioned struct declaration.
Authority timestamps are integer Unix microseconds, bounded to the exact JSON integer range. This avoids rounding a concurrent revocation before issuance; existing native source/local-receipt time fields remain their documented seconds. Binding lifetime is at most one day; snapshot lifetime at most five minutes. Local clock drift/future/stale snapshots deny; offline later revocations remain unknown.
A snapshot declares exact sorted covered_key_ids; complete_key_observations
means all requested keys’ known lifecycle states were returned. It never claims all
device history when exporting a bounded subset. Missing/gap observations fail; no
silent truncation. Historical enrollment/revocation/owner-reported compromise are
separate from current observed membership and new action approval.
Trust and identity
The account issuer is the actual configured API namespace with tenant/user UUIDs, not mutable email and not an invented OIDC claim. OIDC remains an optional external adapter when implemented. Authority URI and account URI are separately selected. Issuer-key validity/revocation/compromise, exact scope and complete selected-key state are verified against pinned reviewed roots; an old signature is not current membership. Newly issued bindings are not reusable event approval grants.
The issuer authenticates accounts and locks scoped membership/device/key rows before signing/persisting. The database has a single pinned account issuer; adopting old unattested namespace metadata needs an explicit deployment acknowledgement, and a later different provider cannot alias existing UUIDs. Enrollment/rotation/revocation proofs and reported compromises are owned by Soma, not by this offline library.
No blockchain/public Sigstore is mandatory. They can transport reviewed checkpoint commitments in a later witness adapter; they establish neither content truth nor membership. Public publication/private data retention remain separate decisions.
Verification and boundaries
- Independent OpenSSL signing plus Python canonical vector; pure library adversarial issuer/tenant/subject/device/key/scope, stale/expired/partial/revoked/compromised tests.
- Core checks real governed GUID and separately selected pins; CLI/MCP output parity.
- Real local Febasi account/device proof -> Soma signed scope -> actual Synapse binary/stdio, wrong scope and historical revoke/compromise/re-enrollment cases.
- Real PG concurrent permission/device revocation waits for the signed observation transaction; a new issuance after revocation denies.
- Existing SSHSIG verification remains SYN-261, source/receipt checkpoints remain ADR-057/058. Artifact identity verification alone closes neither strict-team assurance nor SYN-231’s current event/proposal/policy-bound acceptance.
References
Alternatives checked against primary specifications
Pinned local keys establish selected key possession without authenticating an enterprise account. An OIDC adapter additionally needs ID Token issuer/audience, signature, lifetime and nonce validation; we do not label Febasi’s current API/SDK as that protocol. OpenID Connect Core defines those client checks. Scope membership and event approval remain application policy even after successful account authentication.
Sigstore provides its own artifact bundle and certificate/issuer/identity verification workflow. It is a later explicit adapter with its own roots/privacy/deployment assumptions, rather than a substitute for private project admission. See Sigstore bundle format and identity-based verification. Native private authority contracts here do not claim Sigstore conformance.
ADR-060: Event-bound shared admission with an independently tracked head
Date: 2026-10-13 Status: Accepted (protocol component; integrated source publishing remains SYN-231) Author: Rômulo Febasi
Decision
The collaboration adapter accepts a bounded digest-only candidate through an authenticated, current, device-proved review. A proposal and its review are distinct operations. Their server-issued, one-use challenges bind the exact candidate, account/device/key, scope, issuer and policy digest. Device keys sign the canonical challenge bytes in their host; private keys never enter Synapse or the server.
The first Soma adapter uses an explicit conservative policy: current scoped writers may propose; a different current scoped administrator must review. Each candidate binds a stable event ID, expected accepted head, next sequence, transition digest, proposal/rule/context digests and resource identities. Accepting it atomically compares and advances the server head and retains a signed admission before HTTP success. Concurrent candidates from the same parent stay pending after a conflict. No last-write-wins and no offline acceptance.
A fresh login is insufficient by itself. Each consuming operation rechecks current membership and active device/key under database locks. Challenge policy changes, expiry, signature substitution, cross-scope use and replay for a new event deny. An exact successfully committed retry returns its original decision only after current authorization; it never repeats effects. The authority’s decision clock uses integer Unix microseconds. Account validation precedes the database transaction; the adapter cannot promise atomic revocation across independent identity services.
The initial remote head is an explicit scoped administrator acknowledgement of a limited baseline digest. It does not attest source contents or pre-baseline history. The shared service stores no paths, facts, prompts, bearer tokens or private keys. Search/citation ACL, actual source transfer, host prepare/sign/commit and retained independent witnesses remain required for the complete SYN-231 flow.
Portable evidence
synapse.shared-change/1, synapse.operation-challenge/1 and
synapse.shared-admission/1 are owned by the pure evidence package. Fixed-order
compact JSON, schema plus NUL, explicit nulls and strict Ed25519 follow ADR-059.
The server grant covers the exact candidate, proposer, reviewer device proof,
policy and observed role-input digest. A caller separately selects expected scope,
subject, candidate and reviewed root pin. A historic admission proves the authority
recorded that exact decision; it is not a reusable capability for a new publication.
Later source changes still require a new event and current server admission.
Alternatives and limits
- A reusable binding or five-minute roster cache cannot authorize a specific change.
- A TTL-only permit cannot serialize two reviewers of the same accepted head.
- Exporting the macOS Keychain key to a CLI file would weaken the existing device lifecycle. Sign public normative intent bytes through the host instead.
- Server digest acceptance alone does not prove source installation, semantic truth or strict-team completeness. Keep incomplete verification visibly failing until the source receipt, binding, grant and witness are composed.
- A database administrator controlling the issuer and all independently retained witnesses can rewrite evidence. This remains an explicit trust boundary.
Validation
Independent signing vectors, hostile mutations, real scoped PostgreSQL concurrency, two authenticated identities and real CLI/MCP verification exercise the protocol. Expiry, replay, role/device revocation, stale head, wrong tenant/scope and changed policy fail without advancing a head. A recovered response returns the same signed artifact. Human pilot results are tracked separately and are not synthetic tests.
References
- SYN-231
- ADR-051/052/059; Soma ADR-0051
ADR-061: Pinned Memory CI policy and separate proof dimensions
Date: 2026-10-13 Status: Accepted Author: Rômulo Febasi
Decision
Memory CI orchestrates the existing deterministic verify --deep and version-two
state verify doors using a separately pinned executable and reviewed policy.
It compares explicit base/head knowledge snapshots and emits bounded redacted JSON
and a Markdown comment preview. It neither repairs source nor automatically posts
comments. The policy names basic, strict-local or strict-team and optional additional
required invariants/proof surfaces. Required Skip, Partial, missing and Fail deny;
the native strict profile cannot be weakened by omitting a required surface.
Read from private bounded snapshot copies, including public retained governance evidence and a consistent disposable SQLite backup. Never recover a pending source transaction in the caller’s workspace. Reject symlinks, private key files, changing input snapshots, interrupted transactions and exceeded budgets. Missing indexes are built only in the private copy; an existing index is checked, never silently repaired. Git metadata is a private local clone pinned to the caller-selected revision, with read-only object sharing. Clear inherited Git repository variables. No network/model download or original workspace mutation is part of the runner.
The policy SHA-256 pins exact reviewed file bytes. Executable SHA-256 pins exact approved bytes, snapshotted before invocation. A digest by itself is not release authenticity; the operator must review its source/release trust. External trust and per-snapshot witness files/key pins remain separately supplied inputs. Workspace files and embedded receipt keys cannot choose their own trust roots.
Findings and coverage
Expose deterministic checks, source/drift, transition range, receipts, identity, trust freshness and witness separately. Base/head git labels, logical projection digests and frozen source-snapshot digests describe exactly what was checked. Limited baseline coverage never becomes proof of pre-baseline history. Basic may pass with explicitly incomplete evidence. Strict-team remains visibly failing until the complete authority/source/receipt/witness flow is integrated.
Current legacy invariant evidence is bounded aggregate text. Compare its fingerprints without exporting text, titles, paths, prompts or identifiers from knowledge contents. A changed aggregate is labelled changed/new evidence, not a fabricated list of individual new facts. Individual scoped semantic diagnostics remain SYN-319. General natural-language correctness, model enrichment and agent attention stay excluded.
Validation and integration
Test partial legacy basic coverage, required skipped receipts, strict missing trust, valid strict-local baseline migration, introduced deterministic contradiction, wrong pins, private-copy source immutability and malformed/oversized/timed-out subprocesses. Run the real pinned binary as well as adversarial runner fixtures. The repository’s existing required test job and native pre-push gate run these Python standard-library tests; no separate paid job or connector is required.
The composite action needs prepared explicit base/head workspaces, approved executable and policy pins. Its permissions remain read-only. The comment preview is an artifact for a caller to review/post under its own explicit permissions, not an automatic publication or a universal green integrity badge.
References
- SYN-318
- ADR-035/051/052/058/060
ADR-062: Scoped exact diagnostics and unambiguous supersession references
Date: 2026-10-13 Status: Accepted Author: Rômulo Febasi
Decision
Expose existing structured interval/history/supersession checks through one shared core used by CLI and MCP. Return versioned typed findings, stable rule IDs, exact entity/field/interval evidence references, selected/checked scope and omissions. An empty result means only that the checked structured classes had no findings; general prose semantics stay unknown. Model assessments are separate advisory inputs with explicit model/rubric revision, confidence and abstention. They never change deterministic results, permissions or source. No automatic review/write.
Use the same typed scanner for INV-13 to prevent diagnostic/invariant drift. Keep its compatible aggregate rendering while correcting bare-id target ambiguity. Supersession targets currently identify facts by bare ID and may cross entity types. Resolve only a unique target across the complete indexed graph. A colliding ID in multiple types is an exact ambiguity finding, not an arbitrarily chosen edge or claimed cycle. The acceptance gate rejects ambiguous targets before committing any source mutation. Typed target syntax/migration is a later explicit decision.
Scope filters apply after whole-graph cycle/reference resolution; filtering before traversal could hide a cycle crossing the selected facts. Bound input rows and pair comparisons, diagnostics and source references; exceeded scan budgets fail rather than claim consistent memory. C1 preserves the existing disjoint-history invariant, including duplicate/overlapping assertions. C3 checks inverted closed intervals.
Compatibility and limits
INV-13 remains deterministic and index-based. Existing unambiguous bare references and output schema remain supported. Previously accepted ambiguous references now fail explicitly; no past source or timestamp is silently corrected. Missing/dangling targets are reported as unavailable evidence. Unindexed/malformed Markdown, general language, opaque agent behavior and disabled probabilistic adapters are excluded.
The proposal detector continues to check status/relationship flips and supported identity collisions. Unsupported proposal kinds are unavailable, not declared semantically consistent. External model assessments are caller-asserted diagnostics, with typed provenance; they do not claim this binary ran the model.
Verification
Golden exact-positive/negative, boundary intervals, cross-type same-ID ambiguity, legitimate cross-type targets, cycles crossing selected scopes, truncation and unknown-prose fixtures. Prove ambiguous acceptance leaves both targets/source unchanged. Real CLI/MCP output parity and complete native gates are required.
References
ADR-063: Versioned bounded knowledge process contract
Date: 2026-10-13 Status: Accepted Amends: ADR-049 implementation contract
Context
Existing CLI/MCP tools share core behavior but do not provide an explicit embedding handshake, stable process envelopes or a single budgeted source-revision contract. Soma needs reusable knowledge without copying its operational history into notes.
Decision
Add a version-1 request/response envelope through a CLI stdin JSON door and an MCP tool, both executing one core contract. Describe exposes actual protocol/build target and supported operations. Requests select an exact canonical workspace and acknowledged knowledge GUID; local owner scope is not remote authorization. Shared authority remains unsupported until the native source admission bridge is complete. An unsupported shared request fails instead of falling back to local.
Read uses scoped lexical retrieval without models/network, before candidate selection. Returned raw Markdown passages bind typed IDs, source paths, whole-file SHA-256, byte/line ranges, accepted head and explicit omissions. Source drift and mixed snapshots fail; basic source integrity does not prove authorship, all index semantics, independent history or model attention.
Budgets bound request/results/output bytes and elapsed core work. Optional token budgets use explicitly selected cl100k_base via a pinned offline tokenizer; counts apply to supplied passage text, not an arbitrary target model or its hidden prompt. The process client independently bounds stdout and enforces deadline/cancellation.
Proposals always queue for explicit review, including when an old auto-apply policy would allow a direct tool write. Capture links run/context/source references, an idempotency key, exact accepted head/source manifest and effective native policy. Review returns exact retained payload and metadata; the local operator CLI acceptance rechecks these preconditions inside the native source transaction. Old pending accept doors use the same guard, so switching clients cannot bypass it. Shared acceptance is denied. The MCP caller can query/propose/review but cannot mint operator acceptance; it gets local_operator_acceptance_required. This preserves the existing agent/human gate. Queue idempotency lasts while its operational index records are retained; explicit prune/index destruction is not claimed to preserve a durable request ledger.
Clients independently pin executable digest and architecture; managed release metadata additionally requires a separately selected publisher key. A hash or self-reported build/version does not establish release identity. No daemon, FFI, global MCP editing, automatic model download or latest-version upgrade is required.
Verification
Exercise scoped query/citations/omissions, real token counts and budgets, source drift/scope errors, exact proposal retries, changed request conflict, stale head, source and native policy, old-client acceptance guards and CLI/MCP parity. Soma must prove the pinned-binary read/run/review/propose/accept/reuse loop, signature and architecture checks, malformed responses and deadline/cancel behavior before the integration issues are closed.
References
ADR-064: Independently pinned commit roster revisions
Status: Accepted Date: 2026-10-13 Task: SYN-261 (#249)
Context
The genuine SSHSIG verifier already checks exact Git bytes and the pinned caller key list, but that list has no explicit revision/freshness digest and Git author text is not an external subject. Retaining a fixture alone does not prove that current Git/OpenSSH implementations can generate an accepted signature.
Decision
Preserve the existing array roster and v1 result for compatibility. Add an explicit versioned roster snapshot with bounded revision, observation/expiry and unique canonical signing keys. Its domain-separated normative digest must match an independently supplied pin; changing a workspace roster cannot select a new trusted snapshot implicitly. Separate signing-time validity from currently eligible keys, administrative revocation and compromise timing. Claim-time remains signer-controlled without an independent timestamp witness.
CLI and MCP dispatch into the same typed core. Strict snapshot verification denies missing/wrong pins, stale observations, ambiguous keys, unknown/unsigned commits and malformed intervals. Current-key requirements are explicit; historical administrative revocation does not silently rewrite a prior signature. The snapshot remains local reviewed policy and grants no team publication permission. Authenticated bindings/current admission are still SYN-326/SYN-231.
Verification
Create real Git/OpenSSH Ed25519 signed commits in an isolated temporary repository in CI. Verify signature/tampered payload/wrong key/unsigned required commit, revision pin substitution, stale snapshot, rotation and administrative revocation versus compromise, with equal CLI/MCP canonical core results. Run native full checks, dependency denial, documentation and MSRV gates.
ADR-065: Native shared source sealing through an authenticated host
Status: Accepted (native protocol foundation; paired runtime and strict-team profile pending) Date: 2026-10-13 Tasks: SYN-231 (#222), SYN-325 (#310), SYN-326 (#311)
Context
The authority currently admits exact digests, and Synapse verifies historical bindings/grants separately from source installation. That historical success must not become permission to publish after membership, policy or key changes. Soma keeps device keys in Keychain and authentication tokens in its SDK boundary; Synapse must not acquire either secret through Markdown or global configuration.
Decision
Use a bounded per-operation host bridge: Synapse stages native source/index changes under its writer lease and supplies the exact prospective source record and detached receipt bytes. Soma signs with the currently acknowledged device, obtains a fresh scoped operation decision from its Rust authority, and returns public signed evidence. Synapse verifies scope, exact candidate/receipt/policy, issuer roots, subject/device binding and decision lifetime before installation. The default transaction path refuses shared writes without that evidence.
Preserve the exact public seal in a new durable prepare version. Source, index, receipt and authority evidence recover from the same committed decision marker; recovery of an already committed decision does not request new permission or re-sign history. Uncommitted decisions roll back. A signed historical digest grant alone never satisfies the live publication door.
Explicit shared configuration pins an independent authority trust revision and resource identity. Initial shared bootstrap requires current administration, device possession and acknowledged limited source/history retention. Existing unsigned/local histories are not silently re-signed or relabeled as a team baseline. Ordinary independently selected local workspaces remain usable.
Scoped short-lived read evidence constrains shared retrieval, citations, pending review, retained evidence and impact fan-out. No source content, prompt, private key or bearer is sent to the metadata authority. Disconnected proposals can be retained locally; shared reads/publication require current host verification. Metadata signatures assert authenticated decisions about exact bytes, not truth, agent attention, trusted client clocks or instant revocation on disconnected machines. Finite operation leases must be visible in reports.
Strict-team assurance composes actual source/receipt inclusion with separately selected authority/binding/revocation and an independently retained witness. Every required unknown/missing/stale component denies; basic/v1 results remain explicitly partial and compatible. Git SSHSIG verification remains genuine and cannot be replaced by the presence of a signature header or an author email.
Verification
Two real local authenticated accounts submit conflicting API changes; a distinct current reviewer authorizes one candidate, source/index/evidence commit together, and the competing head fails. Test revoked account/device/key/policy after reconnect, scope/candidate/receipt substitution, all older mutation/read doors, missing/stale roots and witnesses, exact retry and crash recovery at each prepare, install, commit and publication boundary. Validate native Keychain-host signing without exporting private material or tokens to Synapse.
Implementation status
The native protocol and public current-operation types, prepare-v4 source/index/ receipt sealing, protected API reads and fixture-host crash/stdio proof are implemented. Actual Soma issuance/Keychain and independent shared-seal witnessing/ strict-team profile remain acceptance work; this component does not close them.
ADR-066: Historical team assurance and original seal witnesses
Status: Accepted (verifier component; shared host staging and issuer checkpoint service pending) Date: 2026-10-13 Tasks: SYN-325 (#310), SYN-326 (#311), SYN-231 (#222)
Decision
Compose strict-team verification from actual native source manifests/images, original detached receipts and original source/issuer evidence. Select current issuer roots, their normative pin, exact resource scope and fresh complete historical-device observations explicitly outside the workspace. A retained embedded key, policy or actor claim cannot select its own verifier trust.
Assess historical device validity at original issuer permit issuance in Unix
microseconds, separately from the source preparation observation in seconds.
A historical source decision has a distinct result type with
authorizes_new_operation:false. Expiry and later administrative retirement
preserve preceding decisions; compromise at or before the original decision,
stale current observations, wrong scope or missing coverage fail. New source
publication still requires fresh current account/key/role/policy admission.
Retain byte-compatible witnesses v1 and v2. Add opt-in v3 with ordered original receipt-set and complete original SharedCommitEvidence-set commitments. A signed replacement of old issuer/binding metadata changes the retained seal commitment and fails against an independently retained checkpoint, even if its individual signatures remain valid. Independent witness pins remain external inputs.
Strict-team requires every retained transition from the acknowledged limited shared baseline, original receipt/seal/admission composition, fresh selected identity evidence and a v3 witness covering the selected tip. Report storage, transitions, receipts, identity, witness, semantic diagnostics and exclusions separately. Basic and strict-local behavior remain available; no automatic account dependency is added to local owner workflows.
Evidence and limits
Pure tests distinguish expired historical decisions from new operation permits and exercise administrative retirement versus compromise. Native tests cover full strict-team baseline composition, missing actors, scope substitution, current revocation preserving history, compromise and authentic rewritten metadata invalidating the seal witness. Actual CLI/MCP38 parity covers positive team assurance and root-pin denial. Real local febasi-api-auth/PostgreSQL with two accounts supplies issuer/device evidence for a native limited baseline and strict-team v3 verification; device signing uses an isolated OpenSSL fixture.
This component does not complete the SDK/Keychain/UI proposer/reviewer handoff, interrupted-baseline reconciliation, issuer-produced checkpoint service or conflicting shared API update loop. Those remain explicit SYN-231/SYN-326 and SOMA-45 acceptance work. No blockchain, semantic truth, independently trusted source-installation time, hidden agent behavior or instantaneous disconnected revocation guarantee is inferred from successful cryptographic checks.
ADR-067: Frozen native source candidates before shared review
Status: Accepted (native staging and portable witness component) Date: 2026-10-13 Tasks: SYN-231 (#222), SYN-325 (#310), SYN-326 (#311), SOMA-45
Problem and decision
A source record generated at installation has a new event ID, source preparation time and provenance bytes. It cannot equal a digest previously approved by a separate identity. Issuer acknowledgment before native prepare durability also leaves an interrupted limited bootstrap without its original resumable inputs.
Stage an immutable synapse.source-candidate/1 before requesting issuer review.
Retain exact native record, complete observed/after source manifests, private
content-addressed images and exact queued review guards under the writer lease.
Persist the candidate before returning its independently selected digest. Release
the lease and roll back SQLite without installing source or accepting the queue.
Shared review uses the public exact synapse.shared-change/1 metadata derived
from the retained candidate; source body/private queue context do not enter the
control plane. The reviewer resumes only an explicitly selected candidate.
Project the same retained Markdown bytes into SQLite, check current source/head,
queued bytes/context, current review policy and fresh scoped host authority, then
commit source/index/original receipt/issuer evidence through the existing v4
journal. Never regenerate accepted timestamps or implicitly select latest data.
The preparation timestamp remains an untrusted historical source claim; current issuer permit issuance separately gates installation. An interrupted attempt retains the original candidate but receives a fresh current authorization. External source edits, changed heads/policies and missing input coverage refuse installation without repairing or overwriting the user’s edits.
Scope and evidence
The initial typed path covers built-in capture/status/link mutations and supported batches. Custom/NER mutation kinds remain pending with an explicit unsupported reason. Graph-only leaves use their exact guarded typed payload; no arbitrary serialized SQL replay or replacement of an entire live index is introduced. Exact original undo preimages join the queue/source transaction.
Native tests cover staged baseline retry, changed source/policy denial, frozen queued fact bytes, pending-to-accepted commit and exact reindex projection. Actual local Auth/PostgreSQL two-account proof kills the native child after issuer acknowledgment before native WAL prepare, resumes the same candidate with fresh authority, reviews incompatible API constraints at the same expected head, installs only the selected candidate and retains the conflicting proposal.
Finish complete SDK/Keychain host composition and issuer-produced checkpoints before closing the paired integration issues. Existing local owner knowledge contracts cannot claim owner authority over a configured shared source; generic scoped reads and the explicit shared bridge remain separate supported doors.
The portable witness and original public source-evidence codecs live in the pure synapse-evidence crate. Their byte order is checked against genuine native stored artifacts; a witness issuer compares those artifacts to its immutable decision store rather than signing an unchecked client hash. No signing key or source body is part of these portable inputs. Actual native SDK/Keychain baseline publication has passed in the paired Soma harness; the issuer checkpoint service and shared desktop review surface remain separate acceptance work.
ADR-068: Scoped advisory provider execution
Date: 2026-10-13 Status: Accepted (experimental adapters; no model default) Author: Rômulo Febasi
Decision
Implement ADR-050 with a pure typed core port and separately reviewed outer adapters shared by CLI/MCP. Select only exact authorized source facts; integrity, deterministic checks, explicit entity/API version/environment and validity precede inference. Compare accepted snapshot and head again after external execution. Model output has no source mutation capability. Evidence spans identify the exact supplied UTF-8 bytes, not generated explanations.
Default to deterministic checks and a conservative exact-text baseline. A pinned local Python safetensors runtime is an optional research adapter, isolated from source and credential configuration. Hosted Jev uses its official closed Choice schema with per-scope egress consent. Neither adds a mandatory runtime/network dependency. Enforce call, byte and wall-clock budgets and unknown confidence.
Evidence and limits
The first grouped synthetic EN/PT evaluation failed the proposed conflict recall gate. Keep both models experimental; do not download or enable a default. Record empirical reliability separately from provider claims. Optional Python ONNX load probes do not establish native Rust runtime compatibility. Jev quality remains unmeasured without credentials. Synthetic engineering labels require domain and human review before field adoption.
Alternatives
Reusing embeddings as contradiction proof or granting authority from model confidence conflates retrieval with governance. A native ONNX default before evaluation adds distribution/runtime cost without established value. The pure port preserves that future option without imposing it today.
See provider and benchmark contract, measured results, ADR-049 and ADR-050.
ADR-069: Explicit read-only revision adapters
Date: 2026-10-13 Status: Accepted Author: Rômulo Febasi
Decision
Implement SourceAdapter outside core with Git/Markdown and optional GitHub first. Host-private mappings select exact documents, access subjects and credentials. Shared actors come from verified Root read capabilities; tool arguments cannot impersonate another subject. Recheck native access and current host policy around bounded upstream reads. Return exact revision/digest/access provenance and explicit unavailable status; retain no passage cache.
Notifications are hints. Resolve current revision for every poll; reconcile gaps to latest state without claiming unobserved intermediate history. Keep source evolution/dedup/impact semantics separate in SYN-327. Write-back is a closed disabled capability; broader token scopes cannot unlock it.
Consequences and limits
This yields a usable revision-monitoring slice without implementing an authoring app or mandatory cloud. Each read pays for fresh checks, and source availability depends on upstream access. No offline cached remote text is returned under an obsolete access policy. Local host mappings are capabilities, not a replacement for Febasi authentication; shared mode uses the established authenticated read protocol. Notion/Jira/Confluence/unversioned Obsidian monitoring remain separate adapter work.
See source configuration and coverage, ADR-049 and SYN-335 (#96).
ADR-070: Scoped source evolution and explicit impact acceptance
Date: 2026-10-13 Status: Accepted (shared impact reads; local native acceptance) Author: Rômulo Febasi
Decision
Compose fresh revision adapters with a metadata-only, per-actor lineage and explicit dependency map. Filter source and consumer grants, version and environment before retrieving native consumer identities or generating recipients. Notifications deduplicate exact revisions; unknown gaps reconcile current state without fabricated history. A source change proposes review, not accepted truth.
Pin candidate/patch/native annotation review to exact upstream revision, content, access policy and mapping. Local source-derived note acceptance uses fresh adapter evidence and exact queue digest inside the native transaction; generic/MCP acceptance cannot bypass it. Retain full source/dependency citation in the accepted historical annotation. Shared source-derived publication needs a dedicated authenticated Root host workflow and remains disabled in this slice. All adapters remain upstream read-only.
Consequences and limits
Two authorized squads can see reviewable API-contract impacts through the same library/CLI/MCP surface while restricted/unrelated consumers are excluded. The lineage ledger retains no source bodies or credentials and is operational metadata, not a signed truth proof. Accepted local annotations remain historical records; fresh external passages come from the source API. Host grants must be maintained explicitly; no automatic account-membership inference, notification sending or cross-system atomic transaction is promised.
See contract and verification, ADR-049, ADR-069, SYN-327 (#312) and SYN-335 (#96).
ADR-071: Guard retained pending rejection with exact review
Date: 2026-10-17 Status: Accepted Author: Rômulo Febasi Amends: ADR-063 operator decision contract
Context
Soma retains interrupted knowledge intentions and needs to discard an exact reviewed proposal without publishing it. An ID-only rejection cannot attest which payload was displayed. The legacy path appends audit before checking whether the row is still pending, producing a false rejected entry when an accepted or rejected row is selected. Its status update already restricts the pending state, but does not coordinate that check and the audit with other native writers.
Decision
Add reject to the version-one local-owner knowledge descriptor. The existing
scoped and bounded request envelope carries
{"op":"reject","pending_id":42,"review_sha256":"<64 lowercase hex>"}.
Return {pending_id,status:"rejected",review_sha256,retried}. Existing descriptors
without this capability remain valid; clients retain the intention and report an
unsupported decision instead of falling back to an ID-only legacy command.
The CLI and MCP knowledge doors call the same core contract. The agent MCP door
refuses this operator action with local_operator_rejection_required, just as
acceptance requires an explicit local operator. Shared authority remains unsupported
by this local-owner contract. Legacy pending reject shares the native writer and
pending-state guard but makes no exact displayed-review claim.
Hold the existing writer lease and an immediate SQLite transaction while reading
and comparing the retained row, recording the decision and committing its state.
Compare the reviewed digest over ID, kind, payload, context, reason and source.
A changed review fails without an audit append. Only pending rows can transition;
accepted, auto-accepted and undone rows cannot be rejected. A repeated exact
knowledge rejection of an unchanged rejected row succeeds with retried:true
without another audit entry or timestamp change. Legacy repeated ID-only rejection
refuses without appending. No new request ledger or schema migration is needed;
status and decision timestamps are deliberately outside the existing review digest.
Rejection discards a retained proposal and publishes no source. It can therefore discard a stale proposal after source, accepted-head or proposal-policy changes. It does not relax acceptance checks, revalidate stale evidence for publication, change accepted seals, prune the queue or remove prior audit entries. Current local-owner workspace selection and the existing shared-authority boundary still apply. Advisory locking excludes cooperative native writers, not external editors.
Consequences
The row, exact reviewed payload and rejection audit remain available for recovery. Changed or missing retained rows cannot support an exact retry. Explicit pruning, index destruction or direct database rewriting remain outside the retained-index idempotency claim in ADR-063.
Audit and SQLite are separate durability domains. The conditional status update is staged inside SQLite before the audit append; audit failure rolls back that update. The append precedes SQLite commit. A crash or commit failure after append can leave an audit entry while the row remains pending, and a later retry can add another entry. The writer lease and transaction prevent contradictory cooperative live decisions, but do not make the audit file and SQLite physically atomic or provide crash-safe exactly-once audit delivery. Source transaction durability in ADR-056 is not extended to this operational audit file by this decision.
Alternatives considered
- Invoke legacy reject after a separate review: a race can discard different bytes.
- Require current publication preconditions: prevents discarding stale proposals even though rejection publishes nothing.
- Add a durable decision ledger: broader recovery work than this retained-index contract; revisit if crash-safe exactly-once audit delivery becomes required.
Verification
Real workspace assertion tests cover accepted-state audit preservation, exact rejection and retry, changed digest/payload/context/metadata, stale-source discard, writer contention, retained rows and unchanged source/seals. MCP smoke verification checks the operator rejection boundary and CLI review parity. Core tests exercise writer contention without relying on process startup. An independent mutation challenge and the repository quality gates accompany the implementation evidence.
References
ADR-072: Bundle Mermaid with the generated documentation
Date: 2026-10-18 Status: Accepted Author: Rômulo Febasi Issue: SYN-342 (#347)
Context
The published release-pipeline chapter contains a Mermaid code fence but no Mermaid runtime. Plain mdBook syntax highlighting cannot render that diagram. Canonical tracked Markdown already serves GitHub and the generated book, so maintaining separate hand-rendered copies would introduce drift.
Decision
Keep mdBook 0.5.4 and use official mdbook-mermaid 0.17.1, which uses the mdBook 0.5 preprocessor API. Verify each pinned release archive SHA-256 before running its executable. Configure the generated book with the standard preprocessor and run its standard installer to bundle the unmodified runtime and initializer. Serve these assets from the generated site, with no runtime CDN.
flowchart TB
A[Tracked canonical Markdown] --> B[Generated book]
P[Pinned verified official tools] --> C[Mermaid preprocessor]
B --> C
C --> D[HTML and bundled local scripts]
D --> E[Publication structure and asset gate]
E --> F[Browser SVG and layout check]
Require every diagram chapter to preserve its diagram count, contain no raw Mermaid code fence, and load the pinned local runtime before its initializer. The verifier checks actual generated HTML, source Markdown and script bytes. Existing source coverage, search, privacy and editorial checks remain required.
Consequences
The same diagram source serves GitHub and Pages without a runtime network request to a third-party CDN. Tool upgrades require explicit archive and bundled asset digest updates plus the full publication suite. The plugin adds JavaScript to the generated book; runtime errors and visual quality still require browser acceptance. Structural checks alone do not prove SVG rendering or layout.
Alternatives considered
- Plain mdBook leaves diagrams as raw code and does not meet publication needs.
- Runtime CDN scripts add a network dependency and mutable external delivery.
- A custom renderer or duplicated SVG snapshots add a second authoring path.
References
Browser acceptance
Actual local generated-site review rendered all four diagrams across three chapters with no raw Mermaid fence or renderer error. The knowledge flow now uses a vertical layout and wrapped labels while preserving all 17 directed relations. Its labels render at the normal 16-pixel scale in the reviewed book column, compared with approximately 3.8 pixels before. Wrapped sequence labels retain all five participants and eight messages. This is local browser evidence; hosted Pages deployment remains a separate gate. See the browser receipt.
Synapse: Business Rules
Documento normativo. Toda implementação que contradiga uma regra deste arquivo está errada e deve ser corrigida (ou a regra reaberta via PR). Cada regra carrega um identificador
BR-Npara citar em commits, testes e PRs.
Status: vigente desde 2026-05-24.
Escopo: v0.x. v1.0 endurece (ver SYN-104).
1. Workspace
BR-1.1: Um workspace é uma pasta com .synapse/ na raiz
Um diretório só é “workspace synapse” se contém .synapse/ como sub-pasta
direta. Workspace::find sobe ancestrais até encontrar; falha com
Error::NoWorkspace ao chegar à raiz do filesystem sem encontrar.
BR-1.2: A descoberta resolve symlinks
Workspace::find canonicaliza o cwd antes de subir, para tratar
corretamente /var ↔ /private/var em macOS e symlinks em geral. Dois
processos no mesmo workspace lógico nunca veem caminhos diferentes do
mesmo arquivo.
BR-1.3: Layout fixo na raiz do workspace
<workspace>/
├── .synapse/ # config + índice; gerenciado pelo CLI
│ ├── config.toml
│ └── index.db
├── projects/<slug>/_meta.md # projeto agrega sub-arquivos
├── people/<slug>.md
├── entities/<slug>.md
├── apis/<slug>.md
├── tasks/<id>.md # global; ou projects/<p>/tasks/<id>.md
└── notes/<slug>.md # global; ou projects/<p>/notes/<slug>.md
Outros diretórios podem coexistir; o CLI os ignora.
BR-1.4: Nada vive só no índice
.synapse/index.db é descartável. Apagar o arquivo + syn reindex
recria estado idêntico. Toda informação do usuário vive em .md
versionável.
BR-1.5: SYNAPSE.md é o cartão de identidade do workspace
Todo workspace possui um arquivo SYNAPSE.md na raiz (não dentro
de .synapse/) com frontmatter (name, description, topics,
tags, language, maintainer, visibility) e corpo livre. É o
manifesto do workspace: a única coisa que viaja com o diretório
quando ele é clonado, sincronizado ou compartilhado.
O workspaces.db global (BR-1.4 / ADR-010) apenas cacheia esses
campos para queries rápidas. Ao apagar o cache + syn ws refresh, o
estado é recuperado da fonte (o arquivo).
syn init bootstrapa o arquivo automaticamente; humanos editam via
syn ws edit; a IA pode propor edições via MCP que entram na fila de
confirmação humana (BR-12.3).
2. Slugs e identidade
BR-2.1: Identidade canônica = slug + tipo
Combinação (type, id) identifica unicamente uma entidade. O id é o
slug (kebab-case ASCII). Dois tipos podem compartilhar o mesmo slug
(projects/alfa e apis/alfa), garantido pela PK composta na tabela
entities.
BR-2.2: Slug é derivado, validado e estável
slugify(input) aplica, em ordem:
- Unicode NFD.
- Remove diacríticos (
̀-ͯ). - Lowercase ASCII.
- Substitui qualquer não-
[a-z0-9]+por-. - Colapsa hífens duplos.
- Remove hífens das pontas.
validate_slug(s) aceita apenas ^[a-z0-9][a-z0-9-]*[a-z0-9]$, com no
mínimo 1 e no máximo 64 caracteres. Slug com 1 caractere deve ser
[a-z0-9]. Inputs que viram slug vazio depois das regras retornam
Error::InvalidSlug.
BR-2.3: Renomear slug é operação explícita
Não há “atualização tácita” via edição manual do arquivo. Renomear é
syn <type> rename old new, que:
- Move o arquivo (e a pasta, no caso de
project). - Reescreve
idno frontmatter. - Atualiza toda referência (memberships, owner, projects[]) em uma
única
Workspace::tx.
BR-2.4: Slugs reservados
Os seguintes nunca podem ser slugs de entidade:
.synapse, _meta, _index, __pycache__, node_modules, target,
qualquer nome iniciado com _.
3. Frontmatter
BR-3.1: Fonte da verdade do frontmatter é YAML
Todo .md começa com bloco YAML entre --- na primeira linha e ---
após. Ausência é erro (Error::MissingFrontmatter).
BR-3.2: Campos comuns obrigatórios em toda entidade
type, id, title, created, updated. Datas em ISO-8601
(YYYY-MM-DD ou YYYY-MM-DDTHH:MM:SSZ).
BR-3.3: id no frontmatter deve casar com o nome do arquivo
Para projects/alfa/_meta.md, id no YAML deve ser alfa. Para
people/maria-silva.md, id deve ser maria-silva. Descasamento é
erro detectado por syn doctor.
BR-3.4: Round-trip preserva ordem dos campos
Reler um arquivo, não modificar, e reescrever deve produzir bytes
idênticos (excluindo eventual ajuste do campo updated, ver BR-3.6).
Diff ruidoso em git destrói confiança no CLI.
BR-3.5: Campos desconhecidos são preservados
Adicionar um campo livre no YAML não causa erro. O parser preserva o campo no round-trip. A validação só falha para campos conhecidos com tipo errado, não para campos desconhecidos.
BR-3.6: updated é gerenciado pelo CLI, created é imutável
Toda operação de escrita atualiza updated para o instante atual em
UTC. created é definido na criação e nunca mais alterado pelo CLI
(usuário pode editar à mão, mas não é incentivado).
BR-3.7: YAML routing único
Toda I/O YAML passa por crate::store::frontmatter. Importar
serde_yaml_ng em qualquer outro módulo viola a regra e quebra o lint.
Justificativa: trocar o crate YAML no futuro deve ser one-file change
(ver ADR-004).
4. Tipos de entidade
BR-4.1: Tipos suportados em v0.1
project, person, entity, api, task, note. Enum EntityType
em crate::model::entity_type é a fonte da verdade desse conjunto.
BR-4.2: Cada tipo tem schema fechado de campos conhecidos
Validação per-type lista campos conhecidos e seus tipos. Campos
desconhecidos passam (BR-3.5). Tipos errados em campos conhecidos
falham com Error::InvalidField { field, expected }.
BR-4.3: Enums fechados por campo
project.status:active | paused | concluded | archived.project.priority:low | medium | high.entity.kind:vendor | customer | internal | partner.api.auth:none | apikey | basic | oauth2 | jwt | mtls.api.status:production | staging | deprecated.task.status:open | in_progress | blocked | concluded.note.kind:decision | runbook | meeting | snippet | reference.
Valores em snake_case inglês no YAML (independente de locale). Tradução vive na camada de apresentação.
BR-4.4: Pessoas usam memberships, não roles
O campo de vínculo pessoa↔projeto chama-se memberships, cada item é
{project: <slug>, role: <free-form>}. role aqui é texto livre
descrevendo a função funcional. O campo roles (do schema original) foi
renomeado em SYN-18: ver ADR-005 e críticas seção 3.3.
title no nível raiz é sempre o nome legível (BR-3.2). O cargo
formal de uma pessoa vai em job_title (“Tech Lead”, “PM”) para evitar
colisão com title.
BR-4.5: Projetos são pastas, não arquivos
projects/<slug>/ é diretório; _meta.md é o manifesto. Permite
agregar tasks/, notes/ específicos do projeto. Os demais tipos são
arquivos diretos.
5. Referências e integridade
BR-5.1: Toda referência usa slug, nunca cópia
Pessoa que atua em três projetos existe uma vez em people/; os
projetos a referenciam por slug. Vendor compartilhado entre projetos
também.
BR-5.2: Foreign keys são reais no índice
Toda conexão SQLite executa PRAGMA foreign_keys = ON. Tabela links
tem FK em from_id e to_id com ON DELETE CASCADE. Tabela tasks
tem FK em project (também CASCADE). Quebra de referência é impedida
na escrita, não detectada depois.
BR-5.3: Deletar entidade reescreve referenciadores
syn person rm maria falha se Maria é dona (owner) de algum projeto
ou API. Não há cascade silencioso na camada de aplicação. O usuário
deve transferir ownership antes; se quiser cascade explícito, usar
--cascade (v0.2).
BR-5.4: syn doctor detecta o que o schema não impede
Inconsistências semânticas (tag órfã, memberships.project apontando
para projeto inexistente quando FK foi desativada manualmente, etc) são
relatadas mas nunca corrigidas automaticamente.
6. Atomicidade e durabilidade
BR-6.1: Toda escrita é all-or-nothing
Workspace::tx(closure) orquestra:
- Abre transação SQLite.
- Escreve arquivos em
*.tmpparalelo aos finais. - Em sucesso: rename atômico de cada
*.tmp→ final, depois commit SQLite. - Em falha: deleta
*.tmp, rollback SQLite.
Não há ponto intermediário onde disco e índice estão divergentes.
BR-6.2: Falha de SQLite após commit do filesystem é proibida
A ordem é: filesystem (rename) → SQLite (commit). Em hardware moderno,
ambos podem falhar; falha no segundo aciona compensação: arquivo é
revertido para versão pré-commit (a *.tmp antiga ou exclusão).
syn doctor detecta órfãos de uma compensação falha.
BR-6.3: Crash mid-tx deixa estado consistente
Se o processo morre entre o rename e o commit, ao reabrir o usuário
observa: arquivos atualizados no disco mas índice antigo. syn doctor
identifica e oferece syn reindex como remediação. Não corrompe o
index.db (SQLite garante).
BR-6.4: syn reindex é idempotente
Rodar duas vezes na mesma árvore produz mesmas contagens, mesmos
updated_at (do índice, não do frontmatter), mesmos hashes. Pode ser
chamado sem medo após qualquer operação suspeita.
7. Índice (SQLite)
BR-7.1: Schema versionado
Toda mudança de DDL passa por uma migration numerada
(migrations/NNNN__nome.sql). Tabela schema_migrations registra
aplicadas com version, applied_at, checksum. Checksum mismatch é
fatal e exige syn reindex --force-rebuild.
BR-7.2: Migrations são forward-only (reversal por descarte)
Migrations são forward-only. Migrations reversas (down) NÃO são
implementadas, por decisão (SYN-105): como o índice é descartável e o
Markdown é a fonte da verdade (BR-1.4 / BR-7.2.1), o mecanismo de reversão é
apagar index.db e rodar reindex no binário desejado: um down-migration
em SQLite seria complexidade sem valor, já que nenhum dado vive só no índice.
Para fazer downgrade: faça checkout do syn anterior, apague index.db,
reindex. Ver docs/MIGRATING.md.
BR-7.2.1: Migrations de índice são expand-only e de baixo risco
O índice SQLite é descartável: o Markdown é a fonte da verdade
(BR-1.4), então o pior caso de um bug de migração ou de um formato de
índice não reconhecido é recuperável com rm .synapse/index.db && syn reindex (ou syn reindex --force-rebuild): nenhum dado do usuário
vive só no índice. Por isso migrations de schema são expand-only:
colunas novas entram como nullable e são populadas pelo reindex,
nunca exigidas no momento da migração (ex.: 0007 adicionou
valid_from/valid_until/supersedes nullable; uma linha indexada
antes da 0007 tem valid_from NULL até o próximo reindex, e o filtro
--as-of trata NULL como “válido desde sempre”, nunca escondendo
linhas legadas). Migrations aplicadas são imutáveis (checksum-locked,
BR-7.1): corrigir uma significa adicionar a próxima, nunca editar a
anterior.
BR-7.3: Tabela entities espelha frontmatter
PK = (type, id). Colunas dedicadas para campos quentes (status,
owner, priority, due). Campos cold ficam no frontmatter
(JSON) com json_extract para filtros raros.
BR-7.4: entities_fts é FTS5 padrão (guarda o conteúdo; alimenta snippet())
Correção (SYN-294): entities_fts é uma tabela FTS5 padrão, não usa
content=. Além do índice invertido, ela guarda uma cópia de title/body/
tags. Essa cópia não é desperdício puro: é o que alimenta
snippet(entities_fts, ...), os trechos destacados retornados pela busca. O
comentário “External-content” no migration 0001__initial.sql está incorreto
e é imutável (checksum-locked, BR-7.1): esta regra é a fonte da verdade.
Medido (dbstat, índice de 2.000 notas curtas): os vetores dominam o índice
(~70%), entities ~9%, e a cópia de conteúdo do FTS (entities_fts_content)
~4%: material apenas para notas longas. O lever de storage real é int8 nos
vetores (SYN-237), não o FTS: converter o FTS para contentless/external
exigiria reimplementar snippet() em Rust + alinhar rowid com entities (a
coluna tags é sintetizada, não existe em entities): refactor com risco de
correção por um ganho dominado pelos vetores. Por isso não convertido.
BR-7.5: links materializa o grafo
Toda relação derivada do frontmatter (owner, memberships, projects,
api uses) tem uma linha em links com tupla (from_id, to_id, rel).
Reindexar reconstrói a tabela inteira.
8. Erros e mensagens
BR-8.1: Display é técnico, fluent_key é localizado
Error::Display é mensagem inglesa curta, estável, usada em logs e
panics. Error::fluent_key() retorna chave Fluent estável renderizada
no idioma do usuário pelo output::print_error. Mudar a string de
Display é breaking (logs de produção), mudar a chave Fluent também
(traduções existentes).
BR-8.2: Toda mensagem visível ao usuário tem chave Fluent
Nenhum println!/eprintln! com texto literal user-facing. Exceções
documentadas (versão --version, output JSON estruturado).
BR-8.3: Códigos de saída padronizados
0sucesso.1erro de uso (args inválidos, entidade não encontrada, validação).2bug interno (panic capturado, erro inesperado).64+reservados, semântica BSDsysexits.h.
9. CLI
BR-9.1: Toda mutação assume confirmação em TTY
rm, rename, link em TTY pedem confirmação. Não-TTY (script) ou
--force pula. Não-TTY sem --force em comando destrutivo é erro.
BR-9.2: --format global
Toda saída estruturada respeita --format text|json. Texto pode usar
cor (apenas em TTY). JSON é sempre canônico (mesmas chaves, mesma
ordem, mesma versão).
BR-9.3: Subcomando sem argumento mostra help, não erra
syn project (sem subcomando) imprime help específico de project,
exit 0. Inconsistência com arg_required_else_help no topo é
deliberada: syn sem nada imprime help também (já configurado).
10. i18n
BR-10.1: Locale por precedência
--lang > SYNAPSE_LANG > sys-locale > en. Resolução acontece
uma vez na inicialização; comando que muda localmente deve passar
explicitamente o novo loader.
BR-10.2: Inglês é fallback obrigatório
Toda chave deve existir em i18n/en/synapse.ftl. Chaves só em
pt-BR (ou outro locale) falham build via SYN-38.
BR-10.3: Argumentos de Fluent usam snake_case
Padrão Fluent. { $entity_id } no .ftl, entity_id = "..." no
código Rust. Não traduzir nomes técnicos.
11. Versionamento e estabilidade
BR-11.1: v0.x: sem garantia
Comandos podem mudar, schema pode mudar, MCP pode mudar. Cada breaking
em commit !.
BR-11.2: v1.0: schema vira semver
A partir de v1.0 (SYN-104), o surface público: schema de frontmatter, surface MCP e CLI: segue semver. Regra precisa do schema de frontmatter: adicionar uma chave opcional é minor; remover ou mudar o tipo de uma chave conhecida é major; chaves desconhecidas são preservadas (BR-3.5), nunca quebram. O índice SQLite NÃO faz parte do surface: é descartável (BR-1.4 / BR-7.2.1), logo mudanças de schema de índice não são breaking.
BR-11.2.1: Surface MCP congelado em v1.0 (SYN-106)
O conjunto de ferramentas MCP é parte do surface semver. Remover ou
renomear uma ferramenta é breaking (major + !); adicionar é minor. O
congelamento é testado: mcp::parity::tests::mcp_tool_surface_is_frozen
compara o router ao vivo contra FROZEN_V1_MCP_TOOLS e falha em qualquer
drift: o surface nunca muda em silêncio.
BR-11.3: Sempre prefira deprecate over remove em v1+
Mudança de nome de campo: aceitar ambos por uma minor; emitir warning
em --verbose; remover em próxima major.
12. Segurança e privacidade
BR-12.1: Synapse é local-only por default
Nenhum dado sai do disco do usuário sem ação explícita (export, publish, share). Telemetria em zero.
BR-12.2: Segredos não pertencem ao workspace
Se o usuário gravar segredos em .md, o arquivo está em git. Não
implementaremos “campo sensível” porque sugeriria proteção que git
não dá. Documentar no tutorial: use gerenciador de segredos externo.
BR-12.3: MCP não escreve sem confirmação humana
Ferramentas de mutação (capture_fact, link_entity, update_status, v0.2) gravam em fila pendente; só são aplicadas após syn pending accept. Princípio: IA propõe, humano sanciona.
Aprovação em lote (SYN-185, ADR-024): sancionar muitas propostas de uma vez (
syn pending accept --allou vários ids) é uma ação humana sobre a fila, não um bypass: a regra continua intacta. Cada proposta aplica atomicamente (SYN-216); o lote é best-effort (falha de uma não bloqueia as outras). Não há comandosyn agent: o loop autônomo é o agente externo via MCP.
Evolução implementada (ADR-022, Accepted 2026-06-07 · SYN-218): este gate é agora uma política em camadas por confiança (
[proposals] auto_apply). O default émanual: todo proposal enfileira, o gate absoluto descrito acima continua valendo por padrão. O opt-in"safe"auto-aplica apenas o tier de baixo risco e reversível (hoje:link_entityentre duas entidades já existentes e não-self; tudo o mais enfileira). O núcleo inegociável é proveniência + reversibilidade: todo auto-apply é carimbado e registrado na trilha comoauto-accepted(distinto de umacceptedhumano). Destrava SYN-185 (syn agent).
BR-12.4: Federation é opt-in por workspace (default OFF)
O registry global (workspaces.db) sabe da existência de todo
workspace que foi syn init-ado nesta máquina, mas só workspaces com
federated = 1 participam de queries cruzadas (--all no CLI,
federated_search no MCP). Default na criação é 0. Habilitação é
explícita (syn ws include <name>, com confirmação em TTY).
Por quê: um workspace pode ser cliente confidencial; default seguro é “não vaza para queries de outros contextos”. O usuário escolhe o que entra no pool federado.
Aplica-se a: todo código que itera múltiplos workspaces. Cada PR de federação deve referenciar SYN-79 explicitamente para o reviewer checar essa regra.
BR-12.5: Freshness check é advisory, nunca bloqueante (ADR-011)
Workspaces com git_remote capturado têm um sinal periódico (default
30min) que compara o tip local com o refs/remotes/origin/<branch>
rastreado. O status (up_to_date | behind | ahead | diverged | unreachable | unknown) é persistido no registry e exposto:
- CLI: linha única em stderr antes do output normal apenas
quando
status ∈ {behind, diverged}. Suprimível viaSYNAPSE_NO_FRESHNESS=1,--no-freshness,syn ws freshness off <name>ou[freshness] silent_cli = true. - MCP: campo
freshnessno payload de tool results quandostatus ∈ {behind, diverged, unreachable}. Omitido paraup_to_dateeunknown.
Phase 1 (SYN-93, v0.2): comparação filesystem-only dos SHAs
em .git/refs/heads/<branch> vs .git/refs/remotes/origin/<branch>:
sem rede, sem subprocess, sem credencial. Emite só
up_to_date, diverged (com behind_count = -1 por não caminhar
o grafo) e unknown. Os estados behind, ahead e unreachable
existem na enum mas só são emitidos pela Phase 2.
Phase 2 (SYN-93b, v0.5): ls-refs real via gix-protocol com
timeout de 3s, unreachable fallback, e walk de commits para
behind_count preciso. Layered sobre o mesmo schema e UI.
Nunca: bloqueia comando, dispara git fetch, armazena
credencial, ou fala se status é up_to_date.
Por quê: workspace compartilhado via git fica obsoleto em
silêncio, e dado obsoleto entregue com confiança é o pior modo de
falha de um second brain. O aviso é o mínimo informativo possível;
ação fica com o usuário (git pull explícito).
BR-13: Enriquecimento no insertion-time, não no query-time
Lands com v0.5 (SYN-160). Brief:
docs/researchs/2026-06-02-v0.5-semantic-layer-and-second-brain.md§1.2.
Toda escrita no workspace (syn project add, syn person add,
syn note add, syn entity add, syn api add, syn task add,
capture_fact via MCP, qualquer aceite de proposta na pending queue
que materialize uma entidade) dispara o pipeline de enriquecimento
síncrono antes do Workspace::tx comitar:
- Embedding:
title + body + tagsé vetorizado pelo modelo configurado em[enrichment] embedding_model(defaultBGE-M3multilingual; opt-downBGE-small-EN-v1.5-Q). O vetor entra na tabelaentities_vecda mesmaindex.db. - Extração de candidatos: varredura heurística (slugs existentes
por texto,
@mention, datas ISO, hostnames de URL, tokens de tag) + passo NER via[enrichment] ner_model(defaultGLiNER-multi-base-Q; opt-downGLiNER-base-en-Q). Tipos NER são hard-coded por Synapse:person,organization,product,feature_flag,api_endpoint,framework,tool,decision_topic. - Resolução: cada candidato bate contra o índice live. Match
conhecido vira
linktipado (rel = "mentions"ourel = "concept:<lemma>"); span NER sem entidade correspondente virapendingde kindcreate_entity_from_nerpara revisão humana (BR-12.3 continua valendo: MCP/CLI não cria entidade sem aceite). - Paralelismo controlado: embedding e NER rodam em threads
distintas dentro da mesma
Workspace::tx; wall-clock émax(embed, ner). Budget de latência: mediana ≤ 500 ms, p95 ≤ 800 ms num corpus de 10k entidades em CPU de referência (Apple M1 Pro). Excedeu o budget = bug de performance, abre tracking ticket. Async / background é v0.6+ (SYN-194 já registrada no brief). - Atomicidade preservada: qualquer falha no pipeline aborta a
escrita inteira. Usuário pode desligar com
[enrichment] mode = "off"para voltar à semântica v0.4 raw, oumode = "heuristic"para pular só o NER mantendo o embedding.
Por quê: princípio. A IA é o co-piloto da memória, não o reranker em tempo real. O custo é pago uma vez, no momento em que o usuário tem o contexto fresco; toda busca futura é barata, determinística e sobrevive offline.
Trade-off explícito. A escrita fica mais cara em latência e o binário cresce ~50 MB + ~2.5 GB de cache de modelos na primeira execução. Em troca: queries indiretas (“flag do Firebase do dia da reunião com a Carina”), filtros estruturais por participantes / tools / decisões, e graph traversal funcionam sem precisar de LLM no caminho da query.
Opt-out. [enrichment] mode = "off" no .synapse/config.toml do
workspace. Aceitável e suportado: usuário que quer FTS5-only e zero
modelos paga zero MB de cache e ~150 ms de embedding evitados por
escrita.
BR-14: Identidade do usuário: capture once, seed everywhere
Lands com v0.6 (SYN-170..173). ADR-020.
O Synapse tem um dono. A identidade desse dono é capturada uma única
vez, no syn setup global, e semeada em cada workspace como a
pessoa canônica self: true. A captura global é a fonte; a pessoa por
workspace é a projeção.
BR-14.1: Setup é compulsório, CI nunca bloqueia
syn init exige config global. Ausente + TTY interativo ⇒ roda o
setup guiado inline antes de prosseguir. Ausente + não-TTY
(CI, pipe, --no-input, SYNAPSE_AGENT) ⇒ cai no default silencioso
sem escrever nem bloquear. Esse fallback é invariante duro: nenhum
caminho de automação pode travar esperando input.
BR-14.2: Profile global é a fonte única
O profile vive em <config_dir>/profile.toml: separado do
config.toml (que descreve comportamento da ferramenta e cascateia
system → global → workspace). O profile descreve quem é o humano,
não cascateia: existe exatamente um, global. Override de path via
SYNAPSE_PROFILE (testes e usuários avançados). Campos:
name: obrigatório; nome vazio = “sem profile”, nada é semeado.birthdate: opcional, data ISO 8601YYYY-MM-DD(validada na captura).headline: opcional, profissão; alimentasyn cv export.employers[]: opcional, ordenado,{ name, role? }.
BR-14.3: Schema canônico da pessoa self
Uma, e só uma: fonte de verdade para a forma da pessoa self
(core, MCP context, cv export e federation dependem dela):
type: person
id: <slug-do-nome>
title: <Nome Completo>
self: true # exatamente um por workspace
birthdate: 1815-12-10 # opcional, ISO 8601
headline: Mathematician # opcional; lido por cv export
affiliations: # opcional, ordenado
- org: analytical-engine-co # slug de um entity(kind=org)
role: Lead Analyst # opcional
memberships: [] # BR-4.4: person→project, distinto de affiliations
created: …
updated: …
affiliations é deliberadamente distinto de memberships: BR-4.4
reserva memberships para vínculos person→project. Empregadores viram
entity(kind=org) materializados no seed; o vínculo person→org rico no
grafo é evolução futura.
BR-14.4: Seed é idempotente e garante exatamente-um
syn init materializa/atualiza a pessoa self a partir do profile.
Reusa o id da self existente (o slug não muda mesmo se o nome mudar),
preserva created/memberships/body ao refrescar, e garante exatamente
uma self por workspace (a invariante que cv/identity export já
exigem). O seed não dispara enrichment (BR-13): roda durante o init,
antes de os modelos existirem, e nunca pode forçar download nem falhar
quando ausentes: a self é embeddada na próxima edição ou syn reindex.
Como mudar uma regra: PR que edita este arquivo + cita ADR que justifica. Mudança silenciosa de comportamento sem mudar a regra é bug.
Synapse: Roadmap
Como ler este documento. Cada linha é um ticket
SYN-Nque vai virar issue no GitHub. Tickets são agrupados por fase de release (v0.1 → v1.0), com tamanho estimado, camada arquitetural afetada e dependências explícitas. Sem datas: o roadmap é por marcos, não por calendário.Fontes deste roadmap. Derivado de:
docs/design/01-data-schema.md: schema das entidades.docs/design/02-rust-architecture.md: layout original.docs/design/03-critique-and-decisions.md: críticas e decisões finais.README.md: features prometidas ao usuário.BUSINESS_RULES.md: regras de negócio normativas.- ADRs 001-011 (007 release pipeline, 010 workspace federation, 011 workspace freshness).
Convenções
| Coluna | Valores |
|---|---|
| ID | SYN-N, sequencial, imutável |
| Layer | cli, core, store, index, mcp, i18n, config, ci, release, docs |
| Size | XS (≤ 1h), S (≤ ½ dia), M (1 dia), L (≥ 2 dias) |
| Status | todo, wip, done, blocked, cut |
| DependsOn | IDs que precisam estar done antes |
Status (2026-05-26):
- v0.1 Foundation: 53/53
done. Tagv0.1.0em 2026-05-24. - v0.2 Daily polish: 24/24
done. Tagv0.2.0em 2026-05-25. - v0.3 Auditable AI Second Brain: 18/18
done. Tagv0.3.0em 2026-05-25. - v0.4 Refinement & extensibility: 18/18
done(12 core + 6 stretch); milestone GitHub#6fechado em 2026-05-26. Tagv0.4.0pendente: Cargo.toml ainda em0.3.0, release roda viacargo release. Brief emdocs/researchs/2026-05-25-v0.4-refinement-and-extensibility.md. Tema “finish what we started, open the doors”. - v0.5 Semantic layer & second-brain enrichment:
wip2026-06-02. Brief emdocs/researchs/2026-06-02-v0.5-semantic-layer-and-second-brain.md; BR-13 nova; 23 issues (11 originais SYN-80..89 + SYN-93b retituladas; 4 arch-debt SYN-151..154; 8 novas SYN-160..164 core + SYN-165..166 stretch + SYN-167..168 adoption). Janela: até 6 semanas. Tema: “make the graph think like the engineer”. - v1.0 Hardening: provable non-corruption: SHIPPED. Tag
v1.0.0em 2026-06-21 (SYN-262..270 + pre-release/improveplans 001–011 / SYN-272..282). Milestone #15 fechado. - v1.1.0: adoption & distribution + reproducible bench:
wip(milestone #16). Novas: SYN-287 (syn undo, ADR-039) done + SYN-288 (syn mcp install) done + SYN-289 (synapse-checkAction) done + SYN-290 (bench charts, ADR-040) done + SYN-291 (docs currency) done + SYN-292 (syn undoPhase 2). Carrega o backlog repositionado da v1.0 Stable: SYN-101..109 (installer/Homebrew/npm/semver-schema/reverse-migrations/freeze-MCP/MIGRATING/sign-releases/docs-site) + SYN-215 (mascote).
Numeração de IDs (gaps intencionais)
Cada fase tem uma faixa numérica reservada, com folga, para permitir inserir tickets novos sem renumerar nada:
| Fase | Faixa em uso | Slots livres |
|---|---|---|
| v0.1 Foundation | SYN-1..SYN-53 | SYN-54..SYN-59 |
| v0.2 Daily polish | SYN-60..SYN-79 + SYN-90..SYN-93 (manifest + freshness) + SYN-99 (uninstall) | - |
| v0.3 Auditable AI Second Brain | SYN-110..SYN-129 (ingestors SYN-119/120 vagos, deferidos) | SYN-131..SYN-149 |
| v0.4 Refinement & extensibility | SYN-130..SYN-149 + SYN-150 (refactor); cortes: SYN-134, SYN-144, SYN-145 | SYN-151..SYN-199 |
| v0.5 Semantic layer & second-brain | SYN-80..SYN-89 + SYN-93b + SYN-151..SYN-154 (arch-debt do v0.4) + SYN-160..SYN-168 (expansion) | - |
| v0.6 Personal onboarding & autonomy | SYN-170..SYN-173 (onboarding) + SYN-175..SYN-182, SYN-184, SYN-201 (agent/MCP) + SYN-186..SYN-193, SYN-195 (CLI UX & animação) + SYN-196..SYN-200 (brand & delight) + SYN-203..SYN-213 (pre-release audit) | - |
| v0.7 Governed-autonomy foundation | SYN-202 (atomic batch) + SYN-216 (crash-safe accept) + SYN-217 (incremental reindex) + SYN-218 (confidence tiers, ADR-022) + SYN-219 (sampling→Tasks) + SYN-183 (query) + SYN-214 (model sizes) | - |
| v0.8 Frictionless capture & governed autonomy | SYN-185 (syn agent, movida da v0.7) + SYN-220 (git ambient capture) + SYN-221 (syn capture frictionless) + SYN-222 (syn digest) + SYN-229 (reposition) | - |
| v0.9 Temporal governance & adoption | SHIPPED (v0.9.0, 2026-06-14): SYN-234 (open types) + SYN-239 (audit) + SYN-240 (diff/blame CLI + MCP recent_changes deltas) + SYN-241 (trust) + SYN-224 (decay) + SYN-243/244 (connections) + SYN-247 (receipts) + SYN-226 (search_everything) + SYN-227 (perf) + SYN-228 (import/export) + SYN-223 (valid-time + supersession + belief axis, ADR-031). Milestone #11: 0 open | - |
| v0.10 Governed memory-quality | Re-focused 2026-06-14 (single theme, not graph/scale grab-bag): SYN-242 (contradiction → gate) + SYN-250 (trust-in-ranking) + SYN-252 (proposal contract) + SYN-256 (both-doors parity: recent_changes CLI + diff/blame MCP) + SYN-257 (SYN-234 contract finish: collapse the fork) | - |
| v0.11 Scale & graph (evidence-gated) | SHIPPED (v0.11.0, 2026-06-17): SYN-245 (semantic edges) + SYN-248 (decay policy) + SYN-249C (sig posture) + SYN-251 (ingestion) + SYN-254 (identity dedup) + SYN-255 (anchor reranker) + SYN-258 (temporal invariant) + SYN-259 (retrieval) + SYN-260 (hygiene). Milestone #14: 9 closed; backlog-conditional SYN-246/225 deferred | - |
| v1.0 Hardening: provable non-corruption (milestone #15) | Headline syn verify: prove the AI memory was not silently corrupted: reproducible, auditable, testable, offline. Breakdown of SYN-253: SYN-262 (verify engine + canonical dump) → SYN-263 (syn verify + signed report, both doors) → SYN-264 (CLI≡MCP parity) → SYN-265 (invariant proptest) → SYN-266 (replay) → SYN-267 (supersession/temporal proptest) → SYN-268 (fuzz) → SYN-269 (--rebuild attestation) → SYN-270 (witness checkpoint + --since). Study: researchs/2026-06-16. Discipline: borrow the invariant, refuse the engine | SYN-261 (sig-verify) → Commercial |
| Commercial optionality (exploratory) | SYN-230 (E2EE sync) + SYN-231 (team) + SYN-232 (open-core) + SYN-233 (verifiable identity) + SYN-235 (HTTP/SSE gateway) | - |
| v2.0 Beyond stable | SYN-194 (async enrichment: revisitar) + SYN-174 (Intel macOS: demand-gated) + SYN-283..286 (contradiction invariant, enrichment backfill, multi-user seam, HNSW gate) | - |
| v1.1.0: adoption & distribution (milestone #16) | SHIPPED: SYN-287 (syn undo, ADR-039) + SYN-288 (syn mcp install) + SYN-289 (synapse-check Action) + SYN-290 (reproducible bench + SVG charts, ADR-040) + SYN-291 (docs currency). Backlog (repositioned from v1.0 Stable): SYN-101 installer + SYN-102 Homebrew + SYN-103 npm + SYN-104 semver-schema + SYN-105 reverse migrations + SYN-106 freeze MCP surface + SYN-107 MIGRATING.md (done) + SYN-108 sign releases + SYN-109 docs site + SYN-215 mascot. Follow-up: SYN-292 (syn undo Phase 2) | SYN-261 (sig-verify) → Commercial |
| Backlog | SYN-236 (higiene de numeração) + SYN-238 (pt→en issues) + SYN-250+ | - |
Demanda nova entra no próximo slot livre da fase correta, não vira
SYN-1.5 nem força reordenação retroativa.
Fase v0.1: Foundation
Meta: workspace utilizável solo via CLI, com cinco tipos de entidade, busca FTS5 e um único leitor MCP. Base que prova que o modelo funciona.
Critério de aceite global da fase:
syn init→syn project add→syn person link→syn searchencadeáveis em terminal real.- Reabrir o workspace dias depois e a busca encontra o que foi gravado.
- Claude Code consegue, via MCP, responder “quem é ponto-focal do projeto X” com fatos do workspace.
Núcleo & dispatch
| ID | Layer | Size | Title | DependsOn | Source |
|---|---|---|---|---|---|
| SYN-1 | core/cli | M | AppDeps + Dispatch trait, eliminando god-match em commands/mod.rs | - | 03-critique §4.1 §4.2 |
| SYN-2 | store | M | Workspace::find (sobe diretórios procurando .synapse/) | SYN-1 | 02-arch §commands |
| SYN-3 | store | L | Workspace::tx atômico: temp + rename + transação SQLite | SYN-2 | 03-critique §4.5 |
| SYN-4 | core | S | core::CreateProject use case (sem I/O direto) | SYN-3 | 03-critique §4.4 |
| SYN-5 | core | S | core::Slugify + validação de slug | - | 01-schema §convenções |
Schema, índice e migrations
| ID | Layer | Size | Title | DependsOn | Source |
|---|---|---|---|---|---|
| SYN-6 | index | M | DDL inicial: entities, tags, links, FTS5: com PK composta (type,id) | SYN-3 | 03-critique §3.5 |
| SYN-7 | index | XS | Toda conexão executa PRAGMA foreign_keys = ON | SYN-6 | 03-critique §3.1 |
| SYN-8 | index | S | FK explícitas em links (CASCADE) e tasks.project | SYN-7 | 03-critique §3.1 |
| SYN-9 | index | M | Migration runner (versionado, idempotente) | SYN-6 | 02-arch §index |
| SYN-10 | index | S | Colunas explícitas para campos quentes: status, owner, priority, due | SYN-6 | 03-critique §3.6 |
| SYN-11 | index | M | syn reindex: varredura idempotente | SYN-9 SYN-10 | 01-schema §reindex |
Frontmatter & parsing
| ID | Layer | Size | Title | DependsOn | Source |
|---|---|---|---|---|---|
| SYN-12 | store | M | Parser/serialize de frontmatter (gray_matter + serde_yaml_ng) | - | 03-critique §5 / ADR-004 |
| SYN-13 | store | S | Round-trip preserva ordem dos campos no YAML (UX de diff) | SYN-12 | 02-arch §store |
| SYN-14 | store | S | Validação de schema por type (campos obrigatórios) | SYN-12 | 01-schema |
Tipos de entidade: CLI
| ID | Layer | Size | Title | DependsOn | Source |
|---|---|---|---|---|---|
| SYN-15 | cli/core | M | syn project add|list|show|rm|rename | SYN-4 SYN-11 | README |
| SYN-16 | cli/core | M | syn person add|list|show|rm|rename | SYN-15 | README |
| SYN-17 | cli/core | M | syn person link (cria membership N:N project ↔ person) | SYN-16 | 01-schema §person |
| SYN-18 | cli/core | M | memberships em vez de roles (renomeação semântica) | SYN-17 | 03-critique §3.3 |
| SYN-19 | cli/core | M | syn entity add|list|show|rm | SYN-15 | 01-schema §entity |
| SYN-20 | cli/core | M | syn api add|list|show|rm | SYN-15 | 01-schema §api |
| SYN-21 | cli/core | M | syn task add|list|show|rm|done (id sequencial) | SYN-15 | 01-schema §task |
| SYN-22 | cli/core | M | Novo tipo note (decision | runbook | meeting | snippet | reference) | SYN-15 | 03-critique §3.2 |
| SYN-23 | cli/core | S | syn tag list|rename|merge (operações globais sobre tags) | SYN-11 | 02-arch §commands |
Busca
| ID | Layer | Size | Title | DependsOn | Source |
|---|---|---|---|---|---|
| SYN-24 | core/index | M | core::Search: FTS5 + filtros --type --tag --owner | SYN-11 | 01-schema §FTS5 |
| SYN-25 | cli | S | syn search <query> com formatação text/json | SYN-24 | README |
| SYN-26 | core/index | M | core::Graph::neighbors: base p/ “quem depende de X” | SYN-11 | 01-schema §links |
| SYN-27 | cli | S | syn graph project <slug> (quem aponta, quem é apontado) | SYN-26 | 02-arch §commands |
Saúde & meta
| ID | Layer | Size | Title | DependsOn | Source |
|---|---|---|---|---|---|
| SYN-28 | cli/index | M | syn doctor: referências quebradas, slugs órfãos, frontmatter inválido | SYN-11 | 01-schema §reindex |
| SYN-29 | cli | XS | syn info: resumo do workspace, contagem por type | SYN-11 | 02-arch §commands |
| SYN-30 | config | M | Cascade config (system → global → workspace → runtime) com etcetera | - | 02-arch §config |
| SYN-31 | cli | S | syn setup (interativo: locale, editor padrão) | SYN-30 | 02-arch §setup |
| SYN-32 | cli | S | syn init [PATH] cria .synapse/ e estrutura base | SYN-30 | 02-arch §init |
Erros & saída
| ID | Layer | Size | Title | DependsOn | Source |
|---|---|---|---|---|---|
| SYN-33 | core | S | Error::fluent_key() separado de Display (i18n sem perder log) | - | 03-critique §4.3 |
| SYN-34 | cli | S | output::render text/json centralizado | - | 02-arch §output |
| SYN-35 | cli | XS | Códigos de saída padronizados (1 user error, 2 bug, 64+ reservados) | SYN-34 | 02-arch §main |
Internacionalização
| ID | Layer | Size | Title | DependsOn | Source |
|---|---|---|---|---|---|
| SYN-36 | i18n | M | Loader i18n-embed + Fluent, com detecção via sys-locale | - | 02-arch §i18n |
| SYN-37 | i18n | S | --lang (CLI) e SYNAPSE_LANG (env) | SYN-36 | 02-arch §i18n |
| SYN-38 | i18n | M | Cobertura completa pt-BR e en para todas as mensagens v0.1 | SYN-36 | 02-arch §i18n |
MCP (leitura)
| ID | Layer | Size | Title | DependsOn | Source |
|---|---|---|---|---|---|
| SYN-39 | mcp | M | Servidor rmcp 1.7 sobre stdio, feature-gated | SYN-24 | ADR-002 |
| SYN-40 | mcp | S | Tool search_knowledge (mesma função que syn search) | SYN-39 | 03-critique §4.4 |
| SYN-41 | mcp | S | Tool get_entity (project / person / api / task / note) | SYN-39 | 02-arch §mcp |
| SYN-42 | mcp | S | Tool list_by_type com filtros | SYN-39 | 02-arch §mcp |
| SYN-43 | mcp | S | Tool project_graph (vizinhança) | SYN-26 | 02-arch §mcp |
| SYN-44 | mcp | S | Lazy init de tokio só quando syn mcp é chamado | SYN-39 | 03-critique §4.6 |
CI / qualidade / build
| ID | Layer | Size | Title | DependsOn | Source |
|---|---|---|---|---|---|
| SYN-45 | ci | S | Workflow ci.yml (fmt, clippy, nextest matrix, deny, shear, MSRV) | - | feito em SYN-0: validar |
| SYN-46 | ci | S | Workflow commit-lint.yml valida PRs | - | feito em SYN-0: validar |
| SYN-47 | ci | XS | Job cargo doc -D warnings no CI | SYN-45 | 03-critique §7 |
| SYN-48 | build | XS | build.rs com vergen-gix (versão + git hash + rustc) | - | feito em SYN-0: validar |
| SYN-49 | core | XS | #![warn(missing_docs)] + intra-doc links no lib.rs | - | feito em SYN-0: validar |
Testes
| ID | Layer | Size | Title | DependsOn | Source |
|---|---|---|---|---|---|
| SYN-50 | tests | S | Fixtures de workspace mínimo + assert_cmd round-trip | SYN-32 | 02-arch §testes |
| SYN-51 | tests | S | Snapshots insta para saída text/json | SYN-25 | 02-arch §testes |
| SYN-52 | tests | M | Property tests proptest para slug | SYN-5 | 02-arch §testes |
| SYN-53 | tests | M | Teste de transação Workspace::tx (falha em índice → rollback do FS) | SYN-3 | 03-critique §4.5 |
Fase v0.2: Daily polish
Meta: experiência sem fricção no dia-a-dia. Completions, watcher, export, MCP que escreve.
| ID | Layer | Size | Title | DependsOn | Source |
|---|---|---|---|---|---|
| SYN-60 | mcp | M | Tool capture_fact: IA propõe gravação; CLI confirma | SYN-39 | 03-critique §2.2 |
| SYN-61 | mcp | M | Tool link_entity (cria vínculo project↔person, etc.) com confirmação | SYN-60 | 03-critique §2.2 |
| SYN-62 | mcp | S | Tool update_status (task done, project pausado) | SYN-60 | README v0.2 |
| SYN-63 | cli | S | Completions bash/zsh/fish/elvish/powershell via clap_complete | SYN-45 | README v0.2 |
| SYN-64 | cli | S | Man page man syn via clap_mangen | SYN-63 | README v0.2 |
| SYN-65 | cli | M | Watcher mode (syn watch) com notify 8: reindex em mudança | SYN-11 | README v0.2 |
| SYN-66 | cli | M | syn export --format=html (browser-friendly do workspace) | SYN-24 | README v0.2 |
| SYN-67 | cli | S | syn export --format=json (single-file) | SYN-24 | README v0.2 |
| SYN-68 | core/index | M | Indexar last_modified_commit (sem implementar --at ainda) | SYN-11 | 03-critique §3.4 |
| SYN-69 | cli | S | syn config get|set|unset no nível workspace | SYN-30 | 02-arch §commands |
| SYN-70 | cli | S | syn open <slug> abre o arquivo no $EDITOR | SYN-32 | UX comum |
| SYN-71 | cli | S | syn rm confirma com prompt; --force para script | SYN-15 | 02-arch §commands |
| SYN-72 | docs | M | Tutorial passo-a-passo (docs/tutorial.md) | v0.2 fim | UX adoção |
| SYN-73 | docs | M | Receitas (docs/recipes.md): casos de uso comuns | v0.2 fim | UX adoção |
Workspace federation (ADR-010)
Solução para o “e se o usuário toca múltiplos projetos com equipes separadas”. Registry leve em SQLite global (metadata only, jamais conteúdo). Privacy gate opt-in. Detalhes completos em ADR-010.
| ID | Layer | Size | Title | DependsOn | Source |
|---|---|---|---|---|---|
| SYN-74 | cli | S | syn uninstall (binary + --purge config + --everything registry; nunca toca workspaces) | - | UX gap apontado pelo usuário |
| SYN-75 | store/index | M | Workspace registry: workspaces.db global + auto-register no syn init + git remote auto-detect + refresh last_seen | - | ADR-010 §F1 |
| SYN-76 | cli | S | syn ws list|show|forget|gc|include|exclude | SYN-75 | ADR-010 §F1 |
| SYN-77 | core/cli | M | --all em search/info/graph: federated read sobre workspaces opt-in | SYN-75 SYN-76 SYN-79 | ADR-010 §F2 |
| SYN-78 | mcp | M | MCP tools: list_workspaces, federated_search, get_entity_from(ws, type, id) | SYN-77 | ADR-010 §F3 |
| SYN-79 | store/cli | S | Privacy gate: federated=0 default, opt-in explícito, BR-12.4 | SYN-75 | ADR-010 (privacy) |
| SYN-90 | store/core | M | SYNAPSE.md identity card spec + I/O + bootstrap no syn init (BR-1.5) | SYN-75 | ADR-010 §F7 |
| SYN-91 | cli | S | syn ws edit|refresh|topic add|tag add (curadoria do manifest) | SYN-90 | ADR-010 §F7 |
| SYN-92 | mcp | M | MCP tools get_workspace_manifest, add_workspace_topic, add_workspace_tag (fila de aprovação) | SYN-90 SYN-60 | ADR-010 §F7 |
| SYN-93 | store/cli/mcp | L | Workspace freshness: git ls-remote async + advisory em CLI/MCP (BR-12.5) | SYN-75 | ADR-011 |
Fase v0.3: Auditable AI Second Brain
Meta: virar a aplicação em “AI-Act-grade local-first second brain”
antes da janela de enforcement da EU AI Act Article 50 (2026-08-02).
Provenance + audit + reprodução + MCP-nativo expandido + protótipo de
identidade portátil. Brief completo em
docs/researchs/2026-05-25-v0.3-multi-context-developer.md.
| ID | Layer | Size | Title | DependsOn | Source |
|---|---|---|---|---|---|
| SYN-110 | core/index | M | audit.ndjson schema + atomic appender + rotação (90d default) | - | research §5 #1 |
| SYN-111 | core | S | Frontmatter stamps x-ai-* em propostas aceitas | SYN-110 | bet 2 |
| SYN-112 | cli | S | syn audit show|list|prune | SYN-110 | research §5 #1 |
| SYN-113 | core/cli | M | syn audit replay <id> contra context snapshot | SYN-114 | bet 5 |
| SYN-114 | core/index | S | Coluna pending.context_snapshot + payload | SYN-110 | bet 5 |
| SYN-115 | mcp | M | MCP Resources (workspace/manifest, entity/{id}, audit/recent) | - | research §2.2 |
| SYN-116 | mcp | M | MCP Prompts (capture-decision, daily-review, weekly-brief) | - | research §2.2 |
| SYN-117 | mcp | S | .synapse/server-card.json + capability disclosure schema | - | bet 4 |
| SYN-118 | mcp | M | MCP Tasks extension em reindex (feature-gated) | - | research §2.2 |
| SYN-121 | core/cli | S | syn timeline <slug> (git log filtrado por path da entity) | SYN-68 | research §5 #3 |
| SYN-122 | core/cli | M | syn at <ref> -- <subquery> (subset: search + show) | SYN-68 SYN-121 | research §5 #3 |
| SYN-123 | core/cli | M | syn audit stale --over <window> (ranking cross-workspace) | SYN-77 | bet 3 |
| SYN-124 | cli | S | syn capture (stdin → pending capture_fact) | SYN-60 | research §5 #6 |
| SYN-125 | core/cli | L | me.synapse bundle: syn identity export|import|inspect (prototype, ed25519 opcional) | SYN-90 | bet 1 |
| SYN-126 | docs | M | ADR-012: provenance frontmatter convention | SYN-111 | governança |
| SYN-127 | docs | M | ADR-013: audit.ndjson format + retention policy | SYN-110 | governança |
| SYN-128 | docs | M | ADR-014: portable identity bundle (me.synapse) | SYN-125 | governança |
| SYN-129 | docs | M | docs/AUDITABILITY.md + docs/PROVENANCE.md (user-facing) | SYN-111 SYN-112 | UX adoção |
Cortes assumidos (registrados no brief, §9):
- Ingestors GitHub / Linear (eram SYN-119 e SYN-120): adiados para pós-v0.5 por conflito com a invariante local-first.
syn agent(autonomous loop com batch approval): movido para v0.6 (owner decision 2026-05-25; registrado no §Cortes da fase v0.4).syn-lsp(completions LSP em editores): entregue na v0.4 (SYN-130, ADR-018).- TUI / fuzzy pick / share entity granular: backlog.
Fase v0.4: Refinement & extensibility
Meta: fechar dívidas explícitas da v0.3 (audit replay, identity
crypto, MCP Tasks placeholder, time-travel parcial, redaction
parcial) e abrir extensibility (LSP companion, mcp/server.rs split
per ADR-015). Tema “finish what we started, open the doors”. Brief
completo em
docs/researchs/2026-05-25-v0.4-refinement-and-extensibility.md.
Core (must land)
| ID | Layer | Size | Title | DependsOn | Source |
|---|---|---|---|---|---|
| SYN-150 | mcp | L | Refatorar mcp/server.rs em submódulos focados (tools/{read,federation,write}, resources, prompts, freshness, helpers); surface MCP byte-stable. LANDS FIRST. | - | ADR-015 |
| SYN-130 | cli/lsp | L | syn-lsp companion: completions de slug/type/tag em qualquer editor LSP (Cursor/VSCode/Helix/Neovim/Zed) | SYN-115 | brief bet: adoption |
| SYN-131 | core/cli | L | syn identity keygen + ed25519 obrigatório + revocation list | SYN-125 | ADR-014 deferred |
| SYN-132 | core/cli | M | syn experience add + skills auto-derive from tag frequency | SYN-125 | ADR-014 §Future direction |
| SYN-133 | core/cli | M | syn cv export Markdown deterministic from bundle + workspace | SYN-132 | brief disruptive: curriculum surface |
| SYN-135 | core/cli | M | syn audit verify: re-hash pending payloads vs audit.payload_sha256 | SYN-110 | ADR-013 implicit gap |
| SYN-136 | core/cli/mcp | L | syn audit replay --execute: real model rerun via MCP client | SYN-113 | brief bet 5 closure |
| SYN-138 | mcp | M | MCP Tasks real (reindex + federated_search progress) | SYN-118 | SYN-118 placeholder closure |
| SYN-141 | core/cli | M | syn at <ref> -- graph|list|info subcommands | SYN-122 | SYN-122 surface completion |
| SYN-147 | docs | M | ADR-016: portable identity v2 (crypto + revocation) | SYN-131 | governance |
| SYN-148 | docs | M | ADR-017: curriculum schema | SYN-132 SYN-133 | governance |
| SYN-149 | docs | M | docs/IDENTITY.md user-facing (bundle + curriculum) | SYN-131 SYN-133 | UX adoption |
Stretch (lands if window allows; otherwise rolls to v0.5)
| ID | Layer | Size | Title | DependsOn | Source |
|---|---|---|---|---|---|
| SYN-137 | core | M | x-ai-history chain on entity re-mutations | SYN-111 | ADR-012 §Open question |
| SYN-139 | mcp | M | MCP resources pagination via cursor | SYN-115 | SYN-115 documented limit |
| SYN-140 | mcp/core | M | Tag-level federation redaction (SearchHit carries tags) | SYN-117 | brief bet 4 closure |
| SYN-142 | core/cli | L | Full as-of FTS index reconstruction (sandbox) | SYN-122 | SYN-122 documented limit |
| SYN-143 | core/cli | M | syn ws clone <git-url> lifecycle starter | - | brief §2.5 partial |
| SYN-146 | core/cli | M | syn doctor --lint-provenance | SYN-111 | brief §2.1 audit |
Cortes assumidos (registrados no brief, §9)
syn agentautonomous loop: movido para v0.6 (owner decision 2026-05-25).syn cv export --as pdf: adiado para v0.5 (Markdown sai em SYN-133).syn ws fork,syn ws archive: adiados; lifecycle v0.4 só clone.- Sigstore transparency log, cross-bundle merge, HTTP discovery: v0.5+.
Fase v0.5: Semantic layer & second-brain enrichment
Meta: Synapse vira active knowledge graph que processa dados na
hora da inserção (BR-13). Busca semântica via embeddings + entity
linking via NER pequeno + ranking híbrido RRF. Originalmente atrás de
--features vector (off-by-default); promovido a default na própria
v0.5 via SYN-168 (opt-out: default = ["mcp", "v05_backends"]), com
os ~920 MB de modelos baixados só por consentimento. Brief completo em
docs/researchs/2026-06-02-v0.5-semantic-layer-and-second-brain.md.
Decisões fechadas em 2026-06-02: BGE-M3 multilingual default, GLiNER core,
budget de escrita 500 ms mediana, language per-workspace.
Arquitetura: débito do v0.4 (lands primeiro)
| ID | Layer | Size | Title | DependsOn | Source |
|---|---|---|---|---|---|
| SYN-151 | mcp | M | refactor mcp/server.rs para dispatcher pattern (thin #[tool] shims + lógica em core::mcp::*); arquivo volta abaixo de 500 linhas e fica lá | - | v0.4 closure §2.1 |
| SYN-152 | cli | S | split cli/at.rs (620 linhas, yellow tier) em cli/at/{mod,search,show,list,info,graph}.rs | - | ADR-015 |
| SYN-153 | cli | S | split cli/audit.rs (611 linhas, yellow tier) em cli/audit/{listing,verify,replay,stale,prune}.rs | - | ADR-015 |
| SYN-154 | ci | XS | CI lint rejeitando src/** acima de 800 linhas (red), warning em 600 (yellow): ADR-015 enforcement | - | ADR-015 §Playbook |
Core: camada semântica
| ID | Layer | Size | Title | DependsOn | Source |
|---|---|---|---|---|---|
| SYN-89 | docs | M | ADR-008: embedding backend escolhido após benchmark spike (default fastembed-rs + BGE-M3 multilingual quantised; opt-down BGE-small-EN-v1.5-Q por workspace) | - | governança |
| SYN-80 | index/build | M | Feature vector em Cargo.toml + migration separada migrations_vec/*.sql | - | 03-critique §3.7 |
| SYN-81 | index | L | Integração sqlite-vec (bundled+vtab+load_extension + tabela entities_vec + gotchas documentadas em ADR-008) | SYN-80 | brief §2.2 |
| SYN-82 | core | M | trait EmbeddingBackend + trait NerBackend (contracts para o pipeline paralelo) | SYN-80 | 03-critique §3.7 |
| SYN-84 | core/tests | S | Stubs determinísticos para EmbeddingBackend (SipHash fake vector) e NerBackend (regex fake NER) | SYN-82 | testes |
| SYN-83 | core | M | EmbeddingBackend impls: fastembed-rs (BGE-M3 default, BGE-small-EN-v1.5-Q opt-down per workspace); Ollama configurável | SYN-82 SYN-89 | brief §2.1 |
| SYN-164 | core | M | NerBackend impl: GLiNER-multi-base-Q default + GLiNER-base-en-Q opt-down per [enrichment] ner_model; tipos NER hard-coded | SYN-82 SYN-89 | brief §2.4 |
| SYN-85 | cli | M | syn embed --all / syn embed <slug> / syn embed --check / syn embed --reset-cache | SYN-81 SYN-83 | brief §9.2 |
| SYN-160 | core | L | Pipeline de enriquecimento core::enrichment::run_on_write invocado por cada write helper; embedding + NER em threads paralelas dentro da Workspace::tx; honra [enrichment] mode; BR-13 codificada | SYN-83 SYN-164 | brief §1.2 + §9.2 |
| SYN-161 | core | M | Heuristic entity-linker + NER-span resolver (slug/data/@mention/URL/tag + spans GLiNER → links tipados ou pending proposals link_entity / create_entity_from_ner) | SYN-160 | brief §2.4 + §9.2 |
| SYN-86 | core/index | L | Ranking híbrido RRF (k=60) sobre FTS + vector + graph-neighbourhood | SYN-81 SYN-85 SYN-161 | brief §2.3 |
| SYN-87 | cli | S | syn search default = híbrido; --semantic = vector-only; --no-vector = FTS+graph only | SYN-86 | brief §2.3 |
| SYN-162 | core/tests | L | Bench corpus generator (1k/5k/10k entities) + query bank 50q × 3 tiers, variantes pt-BR + en-US | SYN-83 SYN-164 | brief §6.1 |
| SYN-163 | core/tests/docs | M | Bench runner + results report; CI artefact; thresholds gate (T2 P@5 ≥ 0.85, MRR ≥ 0.55, write median ≤ 500 ms, search median ≤ 150 ms) | SYN-162 | brief §6.3 |
Adoption inside v0.5
| ID | Layer | Size | Title | DependsOn | Source |
|---|---|---|---|---|---|
| SYN-167 | docs/cli | S | README §Editor integration: seção promovendo syn lsp próximo ao topo + link para docs/EDITOR-INTEGRATION.md. Homebrew tap fora do v0.5 (mantém SYN-102 em v1.0); asciinema de syn lsp mapeado para v0.5.1 | - | v0.4 closure §3 + owner decision 2026-06-02 |
| SYN-168 | core/cli/docs | M | Semantic layer opt-out: default = ["mcp", "v05_backends"] (fastembed + GLiNER no binário padrão; network_backends/test_backend seguem opt-in). Modelos (~920 MB) baixados só com consentimento: sentinela de readiness por modelo (core::embeddings::readiness), skip gracioso em run_on_write/hybrid/embed --check (zero download implícito), onboarding interativo no syn init. release.yml: --all-features → --features network_backends (não vaza o stub test_backend para produção). i18n en+pt-BR; docs varridas opt-in→opt-out | SYN-160 SYN-164 SYN-85 | owner decision 2026-06-04 |
Stretch (lands if window allows; otherwise v0.6)
| ID | Layer | Size | Title | DependsOn | Source |
|---|---|---|---|---|---|
| SYN-165 | core | M | EmbeddingBackend impls para OpenAI + Voyage (network-based, opt-in explícito) | SYN-83 | brief §9.3 |
| SYN-166 | cli/core | S | syn search --grade para capturar pares query → relevant; foundation para convex combination tuning v0.6 | SYN-86 | brief §9.3 |
| SYN-88 | mcp | S | MCP tool semantic_search: somente se algum cliente precisar do modo vector-only direto | SYN-86 | brief §9.3 |
Track paralelo (independente da semantic stack)
| ID | Layer | Size | Title | DependsOn | Source |
|---|---|---|---|---|---|
| SYN-93b | store/mcp | L | Workspace freshness Phase 2: ls-refs real via gix-protocol (3s timeout, unreachable fallback, walk de commits para behind_count preciso) | SYN-93 | ADR-011 §Phasing |
Cortes assumidos v0.5
- Homebrew tap: fica em v1.0 (SYN-102). Owner decision 2026-06-02.
- Asciinema demo do
syn lsp: mapeado para v0.5.1, não cabe nessa janela. - GraphRAG completo com community summaries: 3-5× custo de ingestão; substituído pelo combo heurística + GLiNER no v0.5; reavaliação em v0.6+.
- Async / background enrichment: registrado como SYN-194 em v0.6 follow-up.
syn agent: segue cortado para v0.6 (decisão herdada do v0.4).
Fase v0.6: Personal onboarding & autonomy
Meta: o Synapse passa a saber quem é o usuário. Um onboarding
guiado e padronizado captura um perfil-de-identidade canônico (nome,
nascimento, profissão, o que faz, empresas) usado para enriquecer toda
interação: roteamento de projetos, contexto do MCP, federação,
personalização do assistente. Acompanha a autonomia adiada (syn agent)
e o enriquecimento assíncrono (SYN-194).
Decisão do owner 2026-06-05. Resolve a lacuna achada na v0.5: a flag
selfnoperson(a “pessoa-você” canônica,src/core/identity) existe e é lida porcv export/identity export, mas não há como criá-la, nem CLI, nem onboarding. E osyn setupé opcional, então o idioma/identidade nunca são definidos de forma garantida.
Onboarding obrigatório & identidade própria
| ID | Layer | Size | Title | DependsOn | Source |
|---|---|---|---|---|---|
| SYN-170 | cli/core | M | Setup compulsório. syn init (e demais comandos de workspace) exigem config global; ausente + TTY ⇒ roda o setup guiado inline antes de prosseguir; non-TTY mantém o fallback silencioso de defaults (CI/pipe nunca bloqueia: invariante dura). Supersede o “setup opcional” da v0.5 (SYN-31) | SYN-31 SYN-32 | owner 2026-06-05 |
| SYN-171 | cli/core | L | Onboarding de identidade guiado. Durante o setup, captura o perfil-self padronizado: nome (obrigatório), data de nascimento (YYYY-MM-DD, opcional/pulável), profissão/headline + um resumo livre “o que você faz”, e (opcional, multi) empresas/empregadores. Persistido como perfil global (no dir de config global), fonte única para semear cada workspace | SYN-170 SYN-131 | owner 2026-06-05 |
| SYN-172 | core | M | Semear o self person por workspace. No syn init, materializa/atualiza a person canônica (self: true) a partir do perfil global. Empresas → entity (org) opcionais + memberships, para organizar projetos por empregador mais à frente. Garante exatamente-um-self por workspace (invariante que o export já exige) | SYN-171 SYN-32 | owner 2026-06-05 |
| SYN-173 | core/docs | M | Schema canônico do self-profile + BR-14 + ADR. Define a forma normativa do frontmatter da pessoa-self (campos, tipos, datas ISO, estrutura de affiliations) para que core, MCP-context, cv export e regras de negócio dependam de uma forma estável e abstraível. ADR para o design captura-global → seed-por-workspace; documenta como o perfil enriquece as interações (assistente sabe nome/papel/empresas ⇒ melhor roteamento e federação) | SYN-171 SYN-172 | owner 2026-06-05 |
Schema-alvo (rascunho, fechar no SYN-173 / BR-14): frontmatter da person com self: true:
self: true
title: "Rômulo Febasi" # nome de exibição (obrigatório)
birthdate: "1990-04-12" # YYYY-MM-DD (opcional)
profession: "Senior Software Engineer" # headline curto (opcional)
affiliations: # empresas/empregadores (opcional, multi)
- company: "Febasi" # texto ou slug de uma entity (org)
role: "Founder / Eng"
since: "2024-01" # YYYY-MM
until: null # null = atual
# corpo Markdown = "o que você faz" (resumo livre)
Princípio: um único shape, fácil de interpretar e abstrair, nada de
campos ad-hoc por feature. O que não couber aqui vira entidade/relação no
grafo (ex.: empresa → entity org + membership), não um campo solto.
Carry-overs já registrados para v0.6
- SYN-185:
syn agent: loop autônomo com batch approval (cortado do v0.4/v0.5; numerado 2026-06-05). - SYN-194: async / background enrichment (follow-up do v0.5; cold-start do BGE-M3 fora do budget de escrita síncrono).
Plataforma
| ID | Layer | Size | Title | DependsOn | Source |
|---|---|---|---|---|---|
| SYN-174 | build/ci | M | Restaurar Intel macOS (x86_64-apple-darwin). ort 2.0.0-rc.12 não publica prebuilt do ONNX Runtime para esse target; a v0.5 removeu o alvo do release. Investigar caminho: backend puro-Rust (ort-tract) ou ONNX Runtime from-source no CI. Reavaliar demanda real antes de pagar o custo | SYN-83 SYN-164 | v0.5 release finding 2026-06-05 |
Integração com agentes (skill / llms.txt)
| ID | Layer | Size | Title | DependsOn | Source |
|---|---|---|---|---|---|
| SYN-175 | cli | M | syn skill install / syn agents init: emitir/instalar o Agent Skill (SKILL.md, formato agentskills.io) + um llms.txt/LLM.md para o agente do usuário (~/.claude/skills/synapse/ pessoal ou .claude/skills/ no projeto; e/ou AGENTS.md/llms.txt na raiz do workspace). Conteúdo gerado a partir do código (descrições reais das 15 tools MCP + shapes de retorno verificados) para não driftar dos docs. Os artefatos v0.5 já vivem no synapse-releases (skills/synapse/SKILL.md, LLM.md); este ticket os torna instaláveis pelo CLI e os mantém em sincronia com a superfície MCP. Ensinar uso token-eficiente (snippet→get_entity, federada=keyword, propose/accept) | SYN-150 (mcp split) | owner decision 2026-06-05 |
Ergonomia de agente / MCP: token-efficiency
Brief: docs/researchs/2026-06-05-mcp-agent-ergonomics-token-efficiency.md. Auditoria do src/mcp/ + src/core/mcp/ contra os princípios da Anthropic (“writing effective tools for agents”, “code execution with MCP”) e o MCP 2026 (structuredContent/outputSchema, ResourceLink). Sketches, promover a issue quando a fase for greenlit.
| ID | Layer | Size | Title | DependsOn | Source |
|---|---|---|---|---|---|
| SYN-176 | mcp | S | Bound list_by_type: limit + cursor. É a única read ilimitada (search/federated já capam); viola o princípio de response-constraint da Anthropic | SYN-150 | brief §2.1 |
| SYN-177 | mcp | M | structuredContent + outputSchema + JSON compacto em todas as reads. Hoje json_content devolve JSON pretty-printed como texto (whitespace = tokens desperdiçados; cliente re-parseia texto) | SYN-150 | brief §2.2 |
| SYN-178 | mcp | S | ResourceLink para synapse://entity/{type}/{id} nos hits de search_knowledge e no get_entity: entidades já são resources (SYN-115); leitura lazy/cacheável | SYN-115 | brief §2.2 |
| SYN-179 | mcp/cli | M | workspace_overview: tool/resource de digest (contagens por tipo + entidades-chave/recentes) numa chamada; mata o custo de exploração do agente | SYN-150 | brief §5 |
| SYN-180 | mcp/core | M | recent_changes {since}: delta sobre audit/timeline; agente recorrente puxa só o que mudou desde a última execução (cursor server-issued) | SYN-110 | brief §6 |
| SYN-181 | mcp | M | get_entities batch + projeção fields: buscar N entidades / só os campos necessários numa chamada | SYN-150 | brief §3 |
| SYN-182 | mcp/core | M | propose_batch: múltiplas propostas → um bundle pending → um accept. Uma linha pending por write lógico; agrupa só a UX do accept | SYN-151 | brief §5 |
| SYN-183 | mcp/core | L | query read-only (compute-out-of-context): projeção/agregação segura sobre o JSON do workspace (subset jq / ops whitelisted, caps de linha/tempo). O padrão “98%” da Anthropic; spike de segurança antes de commitar | SYN-86 | brief §2.3 + §6 |
| SYN-184 | cli/dist | M | Synapse como plugin Claude: .claude-plugin/plugin.json empacotando skill + config MCP + hooks (ex.: SessionStart → syn pending list). Um install fia o loop inteiro | SYN-175 | brief §6 |
CLI UX & agent-friendliness
Brief: docs/researchs/2026-06-05-cli-ux-and-agent-ergonomics.md. Auditoria do output do CLI (src/output.rs, src/cli.rs, src/exit.rs) vs CLIs modernos (Vercel/gcloud/gh/cargo) + clig.dev + literatura CLI-para-agentes. Achado: contrato (json, exit codes sysexits, output module limpo) à frente; apresentação (cor, glyphs, tabelas, progress, NO_COLOR) e não-interatividade muito atrás (cor só no label de erro; sem --no-color/--no-input).
| ID | Layer | Size | Title | DependsOn | Source |
|---|---|---|---|---|---|
| SYN-186 | cli/output | M | Camada de styling TTY-gated (cor, ✓/✗/▲, bold/dim) + compliance NO_COLOR/--no-color/TERM=dumb/SYNAPSE_NO_COLOR (clig.dev). Crate de cor (anstyle/anstream) atrás de ADR; nunca colorir JSON; snapshot-test do output plano | SYN-34 | brief §2.1 |
| SYN-187 | cli | S | Modo não-interativo: --no-input/--yes + --quiet; todo prompt (onboarding do init, setup) respeita. Non-TTY implica no-input. Agente/CI nunca trava | SYN-170 | brief §2.3 |
| SYN-188 | cli/output | M | --format json 100% + erros JSON machine-readable (stderr) + contrato de output congelado (aditivo-only p/ agentes). Hoje json cobre ~metade e print_error é sempre texto | SYN-34 | brief §2.3 |
| SYN-189 | core/cli | S | Exit codes mais granulares p/ agentes (not-found / already-exists / conflict): hoje sysexits category-level (ambos UserError=1) | - | brief §1 |
| SYN-190 | cli/output | M | Tabelas + layout estruturado p/ list/search/info/doctor no TTY; colunas planas no pipe | SYN-186 | brief §2.2 |
| SYN-191 | cli | S | Spinner/progress no TTY p/ embed –all / reindex / watch; stream plano off-TTY (clig.dev: sem animação off-TTY) | SYN-186 | brief §2.1 |
| SYN-192 | cli/output | M | Loading & animação premium (AAA): spinners branded (Lil Syn), barras multi-fase com ETA/rate, conclusão polida; TTY-gated. Posicionar em tier de CLI moderno premium | SYN-186 SYN-191 | brief §2.4 |
| SYN-193 | cli/output | M | Rich rendering: graph como árvore unicode (├─└─) + labels coloridos; show como painel/card (não YAML cru); snippets de search limpos (markdown cru hoje parece quebrado); info dashboard; doctor com glyphs | SYN-186 SYN-190 | brief §2.4 |
| SYN-195 | cli/output | S | Contrato de modos de renderização: AAA/rich é o default (opt-out); --plain/--raw + SYNAPSE_PLAIN => output cru/mínimo p/ economia de tokens; gate único is_rich() (TTY && !plain && !NO_COLOR && text). Governa SYN-186/190/191/192/193 | SYN-186 | brief §3 |
Brand & delight (mascote + animação; footprint Moderado, owner 2026-06-05)
Mascote canônico escolhido: constelação de grafo (nós + arestas = o grafo de
entidades = o segundo cérebro). Tudo rich-only (is_rich()), NO_COLOR-aware,
nunca em pipe/CI/agente/--format json; animação só em momentos raros/significativos,
nunca em add/done/uso repetido.
| ID | Layer | Size | Title | DependsOn |
|---|---|---|---|---|
| SYN-196 | output | M | output/brand.rs: mascote (constelação) full/compact/glyph + wordmark; fundação do cluster | SYN-186 |
| SYN-197 | cli/output | M | Revelação animada no onboarding (setup/init, 1x, pulável, no-op off-TTY): o “cérebro online” | SYN-196 SYN-170 |
| SYN-198 | cli/output | S | Banner branded em syn (help) e --version: estático, rich-only | SYN-196 |
| SYN-199 | core/output | S | Estados vazios/saudáveis charmosos: mascote em list/search vazios e doctor limpo (estático) | SYN-196 |
| SYN-200 | cli/output | M | Experiência de download branded (~920 MB): constelação “carregando” sobre a barra; reusa SYN-191 | SYN-196 SYN-191 |
Fase v0.7: Governed-autonomy foundation
Meta: construir o núcleo de governança + performance que torna a
autonomia segura: antes de shipar a autonomia. A auditoria estratégica
(2026-06-07, 5 lentes) concluiu que autonomia não deve preceder a fundação:
o syn agent (SYN-185) foi movido para a v0.8, atrás de SYN-202 e
SYN-218. Reframe do nome da fase de “Autonomy & performance” →
“Governed-autonomy foundation”. Milestone GitHub v0.7 Autonomy & performance (#8).
| ID | Layer | Size | Title | DependsOn |
|---|---|---|---|---|
| SYN-202 | core | L | propose_batch atômico: uma tx compartilhada nas apply paths (hoje best-effort sequencial; falha parcial trava o bundle). Follow-up da SYN-182 | SYN-182 |
| SYN-216 | core | M | Accept de proposal único crash-safe / idempotente: apply+audit+mark numa tx (hoje 3 separadas; crash entre apply e mark re-aplica) | SYN-202 |
| SYN-217 | index/core | L | Reindex incremental por dirty-tracking (mtime+hash): substitui o DELETE *; rescan full; pré-requisito de sync e do swap HNSW | - |
| SYN-218 | core | L | Proposals por tier de confiança + auto-apply de baixo risco reversível (ADR-022). Mata o rubber-stamping e destrava autonomia segura | SYN-202, ADR-022 |
| SYN-219 | mcp | M | Migrar auditoria de MCP Sampling → Tasks extension (SEP-2577 depreca Sampling/Roots/Logging; janela ~12 meses) | - |
| SYN-183 | mcp/core | L | query read-only compute-out-of-context. Spike de segurança ANTES (SQL-injection/exfiltração): bloqueante | - |
| SYN-214 | docs/core | S | Reconciliar números de tamanho dos modelos (comentários config/mod.rs ~720 MB vs docs ~920 MB): medir o real, alinhar tudo | - |
Fase v0.8: Frictionless capture & governed autonomy
Meta: inverter o value-timing da captura (a lente comportamental
apontou a fricção de captura como o maior risco de adoção: captura
estruturada manual + accept-loop = taxa cobrada duas vezes) e shipar a
autonomia agora que a fundação da v0.7 existe. Milestone GitHub v0.8 Frictionless capture & governed autonomy (#10).
| ID | Layer | Size | Title | DependsOn |
|---|---|---|---|---|
| SYN-185 | cli/core | L | syn agent: loop autônomo. Movido da v0.7: atrás de SYN-202 + SYN-218. Sob ADR-022, sanciona por política (não mais gate absoluto) | SYN-202, SYN-218 |
| SYN-220 | cli/core | L | Captura ambiente do git: autores→pessoas, repos→projetos, merges→decisões candidatas, como proposals. A maior alavanca de adoção | SYN-218 |
| SYN-221 | cli/core | M | Promover syn capture "<texto>" como caminho padrão: frictionless + estruturação diferida pelo agente | - |
| SYN-222 | cli/core | S | syn digest: “o que aprendi esta semana” (cria o prompt que falta; força retrieval) | - |
| SYN-229 | docs/brand | M | Reposicionar README/docs/marca: governança auditável (“a memória que agentes não corrompem”) como pitch central (owner 2026-06-07) | - |
Fase v0.9: Temporal governance & adoption
Meta (re-ancorada 2026-06-12): a v0.9 deixa de “perseguir a fronteira do grafo” e passa a aprofundar o fosso de governança: a única posição defensável (auditoria estratégica 2026-06-07; confirmada pela pesquisa de mercado 2026-06-12). O enquadramento antigo (“tirar o grafo do meio espremido / alcançar Zep-Graphiti-mem0”) era perseguir commodity: paridade que o concorrente já tem, contra a própria tese da v0.8 (governança como diferencial). A pesquisa valida a virada:
- Letta #3320 (líder de categoria) pede explicitamente “policy enforcement + tamper-evident audit trails” para memória de agente.
- mem0 acumula até 97,8% de lixo em 32 dias (#4573), promete resolução de contradição que não existe no código (#4896), e não tem decay nenhum (#5245).
- Só Graphiti/Zep têm bitemporal de verdade, e mesmo assim sem proveniência por fato, sem accept-gate, com bugs de supersessão que corrompem o grafo silenciosamente (#1489).
- Ninguém combina temporal + proveniência assinada + gate humano + auditoria à prova de adulteração. Um produto local-first é o único que pode: a verdade é Markdown no disco do usuário, sem vendor na cadeia de confiança. Frase-moat: “a memória que seus agentes não conseguem corromper silenciosamente, e que você consegue provar que não corromperam.”
Milestone GitHub v0.9 Temporal governance & adoption (#11).
Re-escopo (2026-06-14, auditoria crítica multi-agente). A v0.9 ficou um grab-bag de duas fases sob um nome: uma cadeia de governança (o moat real) e uma cadeia de adoção/escala (grafo-UI, HNSW, interop) que é outra fase usando o mesmo crachá. Decisão do dono: liderar pelo provável/git-native, não pelo portão, e cortar o gold-plating que um tool single-user nunca exercita. Aplicado:
CUT → v0.10/backlog (sem perda para o showcase solo):
- SYN-246 (servidor de UI de grafo): manter o contrato JGF do SYN-243; cortar o servidor HTTP + 2 ADRs de segurança (maior sumidouro de ROI).
- SYN-225 (HNSW/vectorlite): flat-scan basta a ~100k; o corpus de um usuário não cruza o teto. Nova dep + 2ª exceção
unsafeinjustificadas.- SYN-242 (contradição): redundante com o humano que já olha o diff no gate, para um curador único.
- SYN-228 import de formato externo: substituir por import de vault Markdown (round-trip do nosso próprio
export --as obsidian, testável; os schemasmemory.json/basic-memorynão estão pinados em lugar nenhum).DESCOPE (entregar a metade útil, adiar a metade moat-demo):
- SYN-223: entregar valid-time write + display; adiar o replay temporal / integração com
at(a “parte difícil” do ADR-027, ~zero uso diário solo).- SYN-249: entregar tamper-evidence single-author; adiar segmentos por-autor / binding multi-usuário (declarado fora de escopo: single-user).
- SYN-245: está atrás de um self-kNN do sqlite-vec não verificado; verificar antes de construir, ou cortar.
KEEP (o moat real): SYN-239 (tier single-author), SYN-240, SYN-241, SYN-247, SYN-224, SYN-234 (open types, entregue), SYN-243/244, SYN-226 (entregue), SYN-227.
v1.0 mínimo (“done enough” p/ um tool solo, local-first, governado): (1) loop de governança completo e provável: propose→accept→
x-ai-*→audit verify→blame/diff→receitas assinadas (já entregue); (2) trust + decay visíveis (já entregue); (3) open types end-to-end e documentados (entregue; faltam gaps de contract de 2ª classe: ver SESSION-HANDOFF §3); (4) todo comando em COMMANDS.md + um caminho TUTORIAL/RECIPES (o gate real entre “código existe” e v1.0: gates de pre-commit adicionados); (5) um import honesto (vault Markdown); (6) perf budget (SYN-227) para poder citar um número. Bitemporal-replay, contradição, HNSW, UI-server e audit multi-usuário são v1.x / bônus de showcase, não v1.0.
Ordem de implementação (recomendada). Há duas cadeias independentes, e a chave é que a de governança não depende do schema, então é construída primeiro (maior valor, menor risco, é o showcase):
- (A) Cadeia de auditoria/governança: PRIMEIRO: SYN-239 (audit two-tier) → SYN-249 (commits assinados / git-nativo) → SYN-240 (diff/blame). Não toca o schema; é o fosso; ADRs 026/030 aceitos.
- (B) Cadeia de schema/temporal: SYN-234 (tipos abertos: antes de bitemporal e do freeze de schema v1.0/SYN-104) → SYN-223 (bitemporal, split) → SYN-224 (decay) → SYN-241 (trust) → SYN-242 (contradição) → SYN-247/248.
- (C) Grafo vivo: SYN-243 → 244 → 245 → 246 (o “this day” reusa o eixo temporal de SYN-223: sinergia deliberada).
- (D) Performance, medida antes de otimizar: SYN-227 (budgets) → SYN-225 (HNSW, condicional). Por fim SYN-226 (ergonomia).
Dependências respeitadas (234 antes de 223; 227 antes de 225); só a ordem de início prioriza valor/risco.
1. Fundação de schema (antes de bitemporal e do freeze v1.0)
| ID | Layer | Size | Title | DependsOn |
|---|---|---|---|---|
| SYN-234 | store/index | M | Tipos abertos typed-but-extensible (frontmatter + registry .synapse/types.toml); pagar o refactor cross-cutting uma vez, antes de bitemporal e do freeze de schema (SYN-104). Cuidado: não reabrir a superfície de injeção fechada no ADR-023 (colunas hard-coded). ADR-028 | - |
2. Núcleo temporal + governança (o fosso)
| ID | Layer | Size | Title | DependsOn |
|---|---|---|---|---|
| SYN-223 | store/index | L | Fatos bitemporais: valid_from/valid_until (valid-time ≠ transaction-time) + arestas de supersessão. Estende ADR-012. Split em (a) schema+migração, (b) escrita/supersessão, (c) replay/audit cientes de tempo (o moat: audit replay e at <ref> têm que entender valid-time). ADR-027 | SYN-234 |
| SYN-224 | core/index | M | Decay / staleness de memória + syn prune --stale (advisory, reversível). Antídoto direto ao “stale served as confident” (a dor #1 do mem0, que não tem decay) | SYN-223 |
| SYN-239 | core/store | M | Auditoria à prova de adulteração (dois tiers): versionado (comum): log semântico em segmentos por autor .synapse/audit/<id>.ndjson (merge-safe), integridade/ordem delegadas ao Merkle DAG do git; não-versionado (fallback): cadeia auto-encadeada + checkpoint audit.head.json assinado em ed25519. syn audit verify fica git-aware. Tamper-evident, não tamper-proof. Zero dep nova. Estende ADR-013. ADR-026 | - |
| SYN-249 | core/cli/mcp | M | Auditoria git-nativa + commits assinados + binding de identidade, a identidade ed25519 (ADR-016) vira chave de assinatura SSH do git; verify/doctor validam assinaturas contra identidades conhecidas e não-revogadas; atribuição multi-usuário não-repudiável (campo author do git é forjável). Recommend-and-verify por padrão; drive-and-sign opt-in (gix). Remote = witness (compõe ADR-011). ADR-030 | SYN-239 |
| SYN-240 | core/cli/mcp | M | syn diff / syn blame: “o que a IA mudou”: before/after por campo sobre a cadeia x-ai-history + git, com autoria (IA tool/model vs humano) e status accept/reject. MCP recent_changes ganha deltas por campo. A view universalmente ausente nos concorrentes (Letta sobrescreve destrutivo, basic-memory nem atribui autoria) | - |
| SYN-241 | core/cli/mcp | M | Trust/freshness score por fato = f(confidence_tier ADR-022, idade, profundidade de supersessão, proveniência assinada?). Exibido em show/search (ordenar/filtrar por frescor) + structuredContent no MCP. Janela de staleness configurável; marca fatos vencidos como “stale: re-verify”. Ninguém no espaço personal-KG tem isso. ADR-029 (fórmula determinística WPM) | SYN-224 |
| SYN-242 | core | M | Detecção de contradição no propose-time: fato novo que contradiz um aceito (mesmo sujeito+predicado, objeto diferente) é roteado ao gate humano mesmo sob auto_apply = safe; a resolução vira link de supersessão em x-ai-history. Ataca o bug #1 não resolvido do mem0 (#4896). Stretch se a janela apertar | SYN-223 |
| SYN-247 | core/cli/mcp | M | Alinhamento W3C PROV + recibos de proveniência assinados e portáteis: mapear x-ai-* para PROV-DM + emitir atestação por fato no formato in-toto/DSSE, assinada na identidade ed25519 (ADR-016), verificável fora do Synapse (“verify me, não trust me”). Compõe com ADR-026/ADR-027. Stretch | SYN-239 |
| SYN-248 | core | M | Política de governança por tipo: definição de tipo em types.toml carrega write_policy/pii/link_policy; o core::policy consulta a política do tipo-alvo antes de qualquer auto-apply (ADR-022). Só endurece o gate, nunca burla a BR-12.3. Stretch | SYN-234 |
3. Grafo vivo (adoção / showcase): staged
Visão do dono:
synexpõe o grafo de forma viva, buscável e filtrável, > melhor que o graph view do Obsidian (criticado como “bonito e inútil” por ser global-hairball sem filtro). Escolher um dia do ano e ver o que conecta a ele; buscar uma pessoa e ver como ela liga a tópicos/ideias/projetos/dias; arestas inferidas também de termos vetoriais de alto valor e recorrência. Staged: primeiro um contrato de dados JSON (CLI+MCP), depois UI servida localmente.
| ID | Layer | Size | Title | DependsOn |
|---|---|---|---|---|
| SYN-243 | core/index | M | Motor core::graph + contrato JSON (JGF): ego-subgrafo a N saltos sobre link+memberships; perfil JSON Graph Format (node id = type/slug; edge metadata.kind∈{link,membership,temporal,semantic}/weight/provenance; contract_version). Filtro since/until + nó-âncora sintético day/<date> (“o que conecta a este dia”). Só serde_json, zero dep nova | - |
| SYN-244 | cli/mcp | M | syn connections <query> + syn graph export (ego-graph default, nunca global-by-default; --hops/--type/--since/--until/--limit; --format json emite JGF; resumo TTY rico por padrão) + MCP get_connections/export_graph chamando o mesmo core::graph (sem drift de portas), com resource_links. i18n en+pt-BR no mesmo commit | SYN-243 |
| SYN-245 | index/core | M | Arestas semânticas via sqlite-vec mutual-kNN: --semantic: kNN por nó + filtro mutual-kNN (anti-hairball) + rescale local max(0,(cos−μ_local)/(1−μ_local)) (sem limiar absoluto de cosseno). Toda aresta carrega proveniência (cosine,k,via_notes,shared_terms): explicável ou não desenha. Ranking explicit>temporal>semantic; sob limite, dropa semantic primeiro. NÃO usar MST/NN backfill (desconectado é feature na viz). ADR-029 (peso de aresta) | SYN-243, SYN-217 |
| SYN-246 | cli/mcp/release | L | UI de grafo servida em localhost (Stage 2): syn graph serve read-only em 127.0.0.1, GET /graph devolve o mesmo JGF (contrato não muda entre stages); UI estática (Cytoscape.js) com busca/filtro/slider temporal. Segurança: bind loopback, allowlist de Origin/Host (defesa DNS-rebinding), sem CORS wildcard, GET-only, token por sessão. 2 ADRs (dep HTTP + segurança loopback). Stretch / pode escorregar p/ v1.0 | SYN-244 |
4. Performance: medir antes de otimizar
| ID | Layer | Size | Title | DependsOn |
|---|---|---|---|---|
| SYN-227 | ci/index | S | Budgets de performance em CI (1k/10k/50k entidades sintéticas): teto de 150ms vira invariante. Vem antes do HNSW: é o gate que prova (ou não) que a otimização é necessária | SYN-217 |
| SYN-225 | index | M | Swap sqlite-vec flat-scan → HNSW (vectorlite), plano pré-estagiado no ADR-008 (17–100× a 100k). Condicional: só executar se os budgets do SYN-227 mostrarem um corpus real cruzando o teto. Custo: dep nova + 2ª exceção unsafe-FFI → ADR. Forte candidato a escorregar p/ v1.0/backlog se o gate não disparar | SYN-227 |
5. Ergonomia
| ID | Layer | Size | Title | DependsOn |
|---|---|---|---|---|
| SYN-226 | mcp | S | Tool único search_everything (RRF sobre todos os tipos + resource_links); manter as tools tipadas | - |
Nota de escopo (crítica, profissional). Com os tickets novos a v0.9 ficou grande (~15 itens). É deliberado manter tudo na fase a pedido do dono, mas a implementação deve respeitar os tiers: núcleo = seções 1–2 + grafo Stage 1 (SYN-243..245); stretch/condicional = SYN-242 (contradição), SYN-246 (UI server), SYN-225 (HNSW). Se a janela apertar, esses três são o spillover natural para v0.10/v1.0: sem reordenar nada, só descem de tier.
Corte de release v0.9.0 (decidido 2026-06-14)
A fase entregou quase todo o moat de governança. O GitHub foi sincronizado com a realidade: 10 issues fechadas (SYN-234/239/240-CLI/241/243/244/247 parcial/226/227/228/224-advisory), e o restante reescopado/movido. Para taguear v0.9.0 faltam só duas features (honram o “Temporal” do nome):
- SYN-223 (#215): valid-time bitemporal (descopado):
x-valid-from/x-valid-until+x-supersedes, filtro--as-of <date>que compõe comat <ref>; eixo de crença explícito (x-recorded-at/x-invalidated-at) em vez de derivar tudo do git; replay temporal → v0.10. - SYN-240 (#228): MCP
recent_changesper-field deltas (reusacore::diff::diff_docs).
Movidos para v0.10/backlog: SYN-242, SYN-245, SYN-248, SYN-249C, SYN-246 (backlog), SYN-225 (backlog).
Fase v0.10: Governed memory-quality (milestone #13)
Meta (re-focada 2026-06-14): um único tema: aprofundar o moat de qualidade de memória governada (proveniência + reversibilidade + não-corrupção), que é o diferencial real vs. mem0/Letta/Graphiti. A v0.10 anterior misturava três temas (graph + scale + memory-quality): o mesmo sintoma de dispersão que a v0.9 teve antes da re-priorização. Scale e graph foram para a v0.11, puxáveis só sob evidência de necessidade (a régua do flat-vector/adiar-HNSW aplicada a tudo). Both-doors entra agora (era dívida acumulada, não design).
| ID | Layer | Size | Title | DependsOn |
|---|---|---|---|---|
| SYN-256 | cli/mcp | S | Both-doors parity: CLI syn changes --since para o recent_changes (hoje MCP-only) + tools MCP blame/diff (hoje CLI-only). Fecha a última assimetria de portas antes do invariante de paridade da v1.0 (SYN-253) | SYN-240 |
| SYN-257 | core/mcp | M | SYN-234 contract finish: colapsar o fork EntityType/string: set_field kind-aware → update_status custom + close de supersessão de F1 custom (gap do ADR-031); search --type <custom>; pattern regex (pode fatiar p/ v0.11, precisa dep+ADR) | SYN-234, SYN-223 |
| SYN-242 | core | M | Contradição → gate: determinístico (mesmo sujeito+predicado, objeto diferente) + dedup exato → recuperação de candidatos semânticos → escalar ao gate (não auto-resolver com LLM; o campo erra <26% no auto-update). Resolução = supersessão (aritmética de datas) no confirm | SYN-223 |
| SYN-250 | core/search | S | Trust no ranking: injeta o score do ADR-029 na fusão (hoje só no blame); stale e ranking compartilham a curva de decay. Determinístico/explicável | SYN-241 |
| SYN-252 | docs/mcp | S | Contrato de qualidade de proposta p/ agentes MCP: encapsula a “arte de extração” (atomicidade, data-de-observação, no-echo, no-meta-extraction, atribuição); ataca o estágio que o gate protege (HaluMem auto-extract <62%) | - |
Fase v0.11: Scale & graph (evidence-gated) (milestone #14)
Meta: o que saiu do re-foco da v0.10 + os gaps técnicos mapeados no
post-mortem da v0.9. Régua: puxar cada item só contra necessidade real do
caso single-user. Re-escopo dirigido por estudo (2026-06-15, 4 subagents, researchs/2026-06-15):
todas as 9 issues permanecem, mas reformuladas; ordem de execução abaixo. Achados:
SYN-259(2) e o “blocker” do SYN-245 já estavam entregues (riscados); vários
itens têm valor real só multi-user → entregues em forma single-user-mínima/opt-in;
o moat agora é provar a não-corrupção (syn verify/SYN-253 → headline da v1.0,
semeado pelo SYN-258 reformulado).
| # | ID | Size | Title (reformulado) | DependsOn |
|---|---|---|---|---|
| 1 | SYN-260 | S | Hygiene + 2 bugs reais: corrigir PATH do pre-commit fmt (gate inerte); clippy::unwrap_used deny (escalonado); split de queries.rs (ADR-015) | - |
| 2 | SYN-259 | S | Reformulado: empurrar valid_at_predicate para as queries de candidatos FTS+vector (bug latente de recall) + search --type <custom>. Parte (2) delta-por-campo riscada (já entregue no SYN-256); pattern regex → dep+ADR próprio | SYN-223, SYN-257 |
| 3 | SYN-251 | M | Reformulado: batch-embed só de campos embeddable alterados + detecção de move por content-hash (preserva id/links). Watermark adiado (lição multi-writer) | SYN-227 |
| 4 | SYN-254 | S | Reformulado: fingerprint de identidade determinístico p/ dedup-compare + colisão→gate (detect-don’t-merge). Nunca deriva o id/slug do hash (pilar). Opt-in por tipo; fuzzy/MinHash adiado. ADR | SYN-234 |
| 5 | SYN-245 | M | Reformulado: arestas semânticas mutual-kNN + rescale local + proveniência por aresta (“explicável ou não desenha”); consumidor = contrato JGF. Blocker self-kNN resolvido (removido) | SYN-243 |
| 6 | SYN-255 | S | Anchor-distance reranker reusando graph_rank: bench de recall como pré-condição; se <2-3% MRR, vira no-op de baixa prioridade | SYN-243, SYN-250 |
| 7 | SYN-248 | S | Reformulado: só o eixo profile-vs-log (mutabilidade/decay) por tipo; só endurece o gate; sem matriz write/pii/link. ADR | SYN-234 |
| 8 | SYN-249C | M | Verificação de assinatura de commit (ssh-key SSHSIG vs identidades não-revogadas): opt-in, último (valor multi-user; ADR-030 pré-autoriza). dep ssh-key + ADR | SYN-239 |
| 9 | SYN-258 | M | Reformulado: NÃO é engine de replay. Teste de invariante de correção temporal (at <ref> -- … --as-of <date> reconstrói o estado que valia): semente do syn verify/SYN-253 | SYN-223 |
Backlog-condicional (puxar só sob demanda real): SYN-246 (UI server HTTP, manter só o contrato JGF), SYN-225 (HNSW: flat basta < ~100k).
Para v1.0 Stable: SYN-253: harness de invariantes (proveniência, reversibilidade, no-silent-corruption, correção temporal, paridade de portas, round-trip). NÃO adotar LoCoMo/LongMemEval/HaluMem (assumem um pipeline de auto-extração que não temos).
Fase Commercial optionality (exploratory)
Meta: optionalidade comercial sem trair local-first: o vault
permanece a fonte de verdade local; vende-se conveniência/governança em
cima. Pós-PMF. Milestone GitHub Commercial optionality (exploratory)
(#12). A análise econômica: não há fosso hoje; o único monetizador provado
da categoria é sync (playbook Obsidian/Logseq), e as features únicas
(audit+proveniência+identidade) só mapeiam para willingness-to-pay em
times/agent-governance.
| ID | Layer | Size | Title | DependsOn |
|---|---|---|---|---|
| SYN-230 | core/release | L | syn sync: vault sync E2E-criptografado opcional (hospedado + self-hostable). Supersedes #91/#105 | SYN-217 |
| SYN-231 | core | L | Time / workspace compartilhado com edições de IA governadas (por assento): o wedge B2B | SYN-230 |
| SYN-232 | docs/release | S | Declarar fronteira open-core (governança/compliance edition) cedo e transparente | - |
| SYN-233 | core | M | Camada de identidade/CV verificável (atestações assinadas checáveis) | - |
| SYN-235 | mcp | M | MCP sobre HTTP/SSE: tijolo do hosted gateway (acesso remoto seguro ao vault local) | - |
Fase v2.0: Beyond stable
Meta: pós-v1.0: itens demand-gated ou que precisam ser revisitados
antes de valerem o custo. Milestone GitHub v2.0 Beyond stable (#9). Owner
decision 2026-06-07.
| ID | Layer | Size | Title | Por que aqui |
|---|---|---|---|---|
| SYN-194 | core | M | Async / background enrichment (cold-start do BGE-M3 fora do budget síncrono) | Trade-off grande: a janela de consistência eventual contraria o espírito da BR-13 (“enriquecer antes de commitar”). Despriorizado até o trade-off ser revisitado |
| SYN-174 | build/ci | M | Restaurar Intel macOS via outro backend ONNX (tract / from-source) | Demand-gated, sem prioridade: só se houver demanda real de usuários Intel-Mac |
Fase v1.0: Stable
Meta: distribuição pronta para usuário externo, schema com semver.
| ID | Layer | Size | Title | DependsOn | Source |
|---|---|---|---|---|---|
| SYN-100 | release | M | dist init + workflow gerado; primeiro release real v0.1.0 | SYN-45 | ADR-007 |
| SYN-101 | release | S | Installer shell (curl … | sh) hospedado em Pages | SYN-100 | ADR-007 |
| SYN-102 | release | M | Homebrew tap romulofebasi/synapse | SYN-100 | README v1.0 |
| SYN-103 | release | S | (opcional) Pacote npm para npx synapse | SYN-100 | ADR-007 |
| SYN-104 | docs | M | Política de semver para o schema de frontmatter | SYN-15..22 | governança |
| SYN-105 | core | M | Migration de schema entre versões (forward + reversa básica) | SYN-104 | 01-schema |
| SYN-106 | mcp | M | Congelar a tool surface MCP (toda mudança via ! em commit) | SYN-39..43 | governança |
| SYN-107 | docs | M | MIGRATING.md 0.x → 1.0 | SYN-104 | onboarding |
| SYN-108 | ci | S | Release assinada (cosign ou sigstore) | SYN-100 | segurança |
| SYN-109 | docs | M | Site simples em GitHub Pages (estilo Fumadocs / mdBook) | SYN-100 | adoção |
| SYN-215 | brand/docs | S | Resolver o mascote duplo (Lil Syn × constelação de grafo) antes do freeze de marca; alinhar README/BRAND.md e output/brand.rs | SYN-196 | audit v0.6 |
Backlog: pode subir para qualquer fase
Itens explicitamente fora das fases acima até alguém pedir. Listados para deixar rastro do que se discutiu e não está esquecido.
Numbering audit (2026-10-12, SYN-236): seven legacy IDs that collided with completed tasks are now SYN-331 through SYN-337. GitHub issue numbers remain stable. Earlier completed task IDs and commits retain their historical meaning. The allocation begins after SYN-330; SYN-237+ had already been consumed.
| ID (atual) | Title | Por que não está numa fase |
|---|---|---|
| SYN-331 (#90) | Encryption-at-rest do .synapse/index.db | YAGNI no MVP; usuário pode usar disco encriptado |
| SYN-332 (#93) | TUI (ratatui) interativa | terceira porta, depois da base estável |
| SYN-333 (#94) | Desktop GUI (tauri) | idem |
| SYN-334 (#95) | Suporte a TOML frontmatter (alternativa a YAML) | YAGNI |
| SYN-335 (#96) | Integrações nativas (Slack, Linear, Notion import) | escopo de produto; v2 |
| SYN-336 (#98) | Suporte multi-workspace simultâneo no MCP | aguardar caso de uso |
| SYN-337 (#106) | Federation remota (syn remote add <git-url>) | aguardar demanda |
| SYN-236 (#225) | Legacy numbering hygiene | Completed allocation SYN-331..337; historical commits retained |
| SYN-238 | Higiene: traduzir descrições/comentários de issues de pt-BR → inglês (a convenção do repo é inglês; um lapso em 2026-06 deixou as issues que criei na sessão: faixa ~#205–#226: com bodies em pt). Bloqueado por indisponibilidade da API do GitHub em 2026-06-10 (DNS resolvendo api.github.com para IP errado). Aplicar via gh issue edit --body quando a API voltar; o hook .claude/hooks/no-portuguese-vcs.py previne reincidência | meta-tarefa de governança |
Não-objetivos explícitos
Coisas que não entram, em nenhuma fase, sem reabertura de design:
- Web UI hospedada. Synapse é local; nuvem fere a premissa de “conhecimento que é seu”. Pode haver site para docs, não para dados.
- Login / contas. Sem usuários, sem RBAC. Quem manda no diretório é o dono do disco.
- Banco de dados não-SQLite. O índice é descartável; trocar engine é esforço para zero benefício.
- Formato proprietário. Tudo permanece Markdown + YAML legível.
Como contribuir com este roadmap
- Cada ticket aqui vira issue no GitHub com label
phase:v0.1(etc.) elayer:<camada>. Issues novas devem nascer com formatoSYN-<ID>no título. - Mudar prioridade ou cortar item: editar este arquivo numa PR; sem discussão paralela em Slack/Notion.
- Tickets de mais de 1 dia (
L) devem ganhar uma sub-issue com plano de implementação antes dowip.
Atualizado em 2026-05-24.
Synapse: Schema de Dados e Índice
Documento de referência para a camada de dados: o frontmatter de cada tipo de entidade e como o índice SQLite mapeia esses arquivos.
Princípios
- O markdown é a fonte da verdade. O SQLite é um índice descartável,
reconstruível com
syn reindex. Nada vive só no banco. - Frontmatter para o estruturado, corpo para o livre. Campos consultáveis (status, tags, vínculos) vão no YAML; contexto, notas e histórico vão no corpo markdown.
- IDs estáveis e legíveis. O nome do arquivo (slug) é o ID.
maria-silva.mdé referenciado comomaria-silva. Renomear é uma operação explícita (syn person rename) que atualiza os vínculos. - Vínculos por referência, nunca por cópia. Uma pessoa que atua em três projetos existe uma vez; os projetos a referenciam pelo slug.
Convenções de campo comuns
Todo arquivo carrega um núcleo comum no frontmatter:
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
type | enum | sim | project, person, entity, api, task |
id | string (slug) | sim | Igual ao nome do arquivo sem extensão |
title | string | sim | Nome legível (“Maria Silva”, “API Pagamentos”) |
tags | lista | não | Tags livres, normalizadas em minúsculas |
created | data ISO 8601 | sim | Preenchido na criação |
updated | data ISO 8601 | sim | Atualizado a cada syn ... edit |
Datas sempre em ISO 8601 (2026-05-23). Slugs sempre minúsculos, sem acento,
hífen como separador.
Tipo: project
Arquivo: projects/<slug>/_meta.md
Um projeto é uma pasta, não um arquivo solto, porque agrega sub-documentos
(stakeholders, apis, demandas específicas daquele projeto). O _meta.md é o
manifesto.
---
type: project
id: projeto-alfa
title: Projeto Alfa
status: ativo # ativo | pausado | concluido | arquivado
description: Plataforma de pagamentos B2B
owner: maria-silva # referência a um person.id
priority: alta # baixa | media | alta
tags: [pagamentos, b2b, prioridade]
links:
repo: https://github.com/org/alfa
docs: https://wiki.empresa.com/alfa
created: 2026-01-10
updated: 2026-05-20
---
## Contexto
Texto livre sobre o projeto, decisões, histórico.
## Pontos de atenção
- Migração do webhook v1 → v2 pendente
Campos específicos: status, owner, priority, links.
Tipo: person
Arquivo: people/<slug>.md
Pessoas são globais (não vivem dentro de um projeto) justamente para permitir
o vínculo N:N. O campo roles lista em quais projetos a pessoa atua e com que
papel: esse é o coração do modelo de grafo.
---
type: person
id: maria-silva
title: Maria Silva
email: maria@empresa.com
org: acme-corp # referência opcional a um entity.id
roles: # vínculos N:N com projetos
- project: projeto-alfa
role: ponto-focal
- project: projeto-beta
role: aprovadora
tags: [pagamentos, lideranca]
created: 2026-01-12
updated: 2026-05-18
---
## Notas
- Prefere contato por e-mail, responde rápido
- Fuso: GMT-3
Campos específicos: email, org, roles (lista de {project, role}).
O comando syn person link "Maria Silva" --project gama --role revisora
adiciona uma entrada em roles sem tocar no resto.
Tipo: entity
Arquivo: entities/<slug>.md
Organizações, times, fornecedores, clientes: qualquer ator que não é uma pessoa individual.
---
type: entity
id: acme-corp
title: Acme Corp
kind: fornecedor # fornecedor | cliente | time-interno | parceiro
contact: contato@acme.com
projects: [projeto-alfa] # projetos onde aparece
tags: [pagamentos]
created: 2026-02-01
updated: 2026-04-30
---
## Notas
- SLA de resposta: 48h
Campos específicos: kind, contact, projects.
Tipo: api
Arquivo: apis/<slug>.md
APIs podem ser compartilhadas entre projetos, por isso ficam no diretório global e referenciam projetos por slug.
---
type: api
id: api-pagamentos
title: API Pagamentos
base_url: https://api.pagamentos.com/v2
auth: oauth2 # none | apikey | basic | oauth2 | jwt | mtls
status: producao # producao | homologacao | descontinuada
owner: maria-silva
projects: [projeto-alfa, projeto-beta]
docs_url: https://docs.pagamentos.com
tags: [pagamentos, externa]
created: 2026-01-15
updated: 2026-05-10
---
## Endpoints relevantes
- `POST /charges`: cria cobrança
- `POST /webhooks`: registra webhook
## Observações
- Rate limit: 100 req/s
- Migração v1 → v2 em andamento
Campos específicos: base_url, auth, status, owner, projects,
docs_url.
Tipo: task (demanda)
Arquivo: projects/<projeto>/tasks/<id>.md ou tasks/<id>.md (global)
Demandas são curtas e numerosas, então o id é numérico/sequencial gerado
pelo CLI, e o title carrega a descrição.
---
type: task
id: alfa-042
title: Migrar webhook para v2
project: projeto-alfa
status: aberta # aberta | em-andamento | bloqueada | concluida
owner: maria-silva
api: api-pagamentos # referência opcional
due: 2026-06-15
tags: [migracao, prioridade]
created: 2026-05-20
updated: 2026-05-22
---
## Detalhes
Contexto da demanda, links, critérios de aceite.
Campos específicos: project, status, owner, api, due.
O índice SQLite
O índice existe para uma coisa: busca rápida e filtragem. Ele nunca é a fonte da verdade. Estrutura mínima (fase 1, sem vetores):
Tabela entities (espelho do frontmatter)
CREATE TABLE entities (
id TEXT PRIMARY KEY, -- slug
type TEXT NOT NULL, -- project|person|entity|api|task
title TEXT NOT NULL,
status TEXT,
owner TEXT, -- person.id
path TEXT NOT NULL, -- caminho do .md no disco
frontmatter TEXT NOT NULL, -- JSON do YAML completo (fonte p/ filtros)
body TEXT NOT NULL, -- corpo markdown
created TEXT,
updated TEXT
);
CREATE INDEX idx_entities_type ON entities(type);
CREATE INDEX idx_entities_status ON entities(status);
CREATE INDEX idx_entities_owner ON entities(owner);
O frontmatter guardado como JSON permite filtros arbitrários sem precisar de
uma coluna por campo: você consulta com json_extract().
Tabela tags (relação N:N)
CREATE TABLE tags (
entity_id TEXT NOT NULL REFERENCES entities(id),
tag TEXT NOT NULL,
PRIMARY KEY (entity_id, tag)
);
CREATE INDEX idx_tags_tag ON tags(tag);
Tabela links (o grafo)
Materializa todos os vínculos entre entidades, vindos de roles, projects,
owner, api, etc. É isto que torna possível perguntar “quem são os pontos
focais do projeto alfa” ou “quais tasks dependem da API de pagamentos”.
CREATE TABLE links (
from_id TEXT NOT NULL, -- ex: maria-silva
to_id TEXT NOT NULL, -- ex: projeto-alfa
rel TEXT NOT NULL, -- ex: ponto-focal, owner, uses-api
PRIMARY KEY (from_id, to_id, rel)
);
CREATE INDEX idx_links_to ON links(to_id, rel);
CREATE INDEX idx_links_from ON links(from_id, rel);
Tabela virtual FTS5 (busca textual)
CREATE VIRTUAL TABLE entities_fts USING fts5(
id UNINDEXED,
title,
body,
tags,
content='' -- índice externo, não duplica conteúdo
);
Consulta de busca típica que o syn search executa:
SELECT e.id, e.type, e.title
FROM entities_fts f
JOIN entities e ON e.id = f.id
WHERE entities_fts MATCH 'oauth'
AND e.type = 'api' -- filtro --type
AND e.id IN (SELECT entity_id FROM tags WHERE tag = 'prioridade')
ORDER BY rank;
Fase 2: busca semântica (sqlite-vec)
Quando o FTS não bastar, adiciona-se uma tabela virtual de vetores no mesmo banco:
CREATE VIRTUAL TABLE entities_vec USING vec0(
id TEXT PRIMARY KEY,
embedding FLOAT[384] -- dimensão do modelo de embedding escolhido
);
A busca híbrida combina o rank do FTS5 com a distância vetorial, fundindo os
dois sinais (reciprocal rank fusion é o método simples e robusto). Mas isso só
entra quando a busca textual demonstrar ser insuficiente, não antes.
Fluxo de reindexação
syn reindex faz, de forma idempotente:
- Apaga e recria as tabelas (ou faz upsert por
updated). - Varre
projects/,people/,entities/,apis/,tasks/. - Para cada
.md: faz parse do frontmatter (YAML → JSON), extrai corpo. - Popula
entities,tags,links,entities_fts. - Reporta contagens e inconsistências (ex:
ownerapontando para pessoa inexistente): esse relatório é o que osyn doctorconsome.
A reindexação completa de alguns milhares de arquivos leva milissegundos, então
ela pode rodar automaticamente após qualquer comando de escrita (add, rm,
edit) ou ser disparada por um watcher de filesystem opcional.
Synapse: Arquitetura do Projeto Rust
Documento de referência para a organização do código: estrutura de src/,
como o clap derive mapeia os comandos, e como as camadas se conversam.
Princípio de organização
O código segue a separação borda → núcleo → dados:
- Borda (
cli,i18n, output): traduz intenção do usuário em chamadas. Não contém lógica de negócio. - Núcleo (
commands,model): a lógica. Não sabe que existe um terminal. - Dados (
store,index,config): persistência e leitura. Não sabe o que é um comando.
A regra de ouro: dependências apontam para dentro. commands chama store,
nunca o contrário. Isso mantém o núcleo testável sem terminal e sem disco real.
Layout de diretórios
synapse/
├── Cargo.toml
├── build.rs # injeta git hash na versão
├── i18n.toml # config do cargo-i18n
├── i18n/
│ ├── en/synapse.ftl
│ └── pt-BR/synapse.ftl
├── src/
│ ├── main.rs # entrypoint: parse args, dispatch, trata erro
│ ├── cli.rs # definição clap (structs + enums de subcomando)
│ ├── error.rs # tipos de erro (thiserror)
│ ├── i18n.rs # loader Fluent + macro de tradução
│ ├── output.rs # formatação de saída (text/json), cores
│ │
│ ├── config/
│ │ ├── mod.rs # API pública do módulo
│ │ ├── cascade.rs # resolução sistema→global→local→runtime
│ │ └── paths.rs # diretórios XDG/nativos (crate `directories`)
│ │
│ ├── model/
│ │ ├── mod.rs
│ │ ├── entity.rs # enum Entity + traits comuns
│ │ ├── project.rs # struct Project + frontmatter
│ │ ├── person.rs
│ │ ├── api.rs
│ │ ├── task.rs
│ │ └── link.rs # vínculos do grafo
│ │
│ ├── store/ # leitura/escrita dos .md (fonte da verdade)
│ │ ├── mod.rs
│ │ ├── frontmatter.rs # parse/serialize YAML <-> struct
│ │ ├── slug.rs # geração e validação de slugs
│ │ └── workspace.rs # localizar raiz do workspace (.synapse/)
│ │
│ ├── index/ # SQLite: índice descartável
│ │ ├── mod.rs
│ │ ├── schema.rs # DDL das tabelas + migrations
│ │ ├── reindex.rs # varredura e repopulação
│ │ └── query.rs # busca FTS5, filtros, grafo
│ │
│ ├── commands/ # um arquivo por grupo de comando
│ │ ├── mod.rs # trait Command { fn run(&self, ctx) }
│ │ ├── setup.rs
│ │ ├── init.rs
│ │ ├── config.rs
│ │ ├── project.rs
│ │ ├── person.rs
│ │ ├── entity.rs
│ │ ├── api.rs
│ │ ├── task.rs
│ │ ├── tag.rs
│ │ ├── search.rs
│ │ ├── mcp.rs
│ │ └── info.rs
│ │
│ └── mcp/ # servidor MCP sobre stdio
│ ├── mod.rs
│ ├── server.rs # loop de protocolo
│ └── tools.rs # ferramentas expostas (search, get, list...)
└── tests/
├── cli.rs # testes de integração end-to-end
└── fixtures/ # workspaces de exemplo
A definição clap (cli.rs)
A estrutura segue o padrão de produção: um Cli no topo com flags globais, e
um enum Commands com os subcomandos. Subcomandos com sub-níveis (como
project add) usam enums aninhados.
#![allow(unused)]
fn main() {
use clap::{Parser, Subcommand};
use std::path::PathBuf;
#[derive(Parser)]
#[command(name = "syn", author, version, about)]
#[command(propagate_version = true)]
pub struct Cli {
/// Saída detalhada
#[arg(short, long, global = true)]
pub verbose: bool,
/// Formato de saída
#[arg(long, global = true, default_value = "text")]
pub format: OutputFormat,
/// Sobrescreve o idioma (ex: en, pt-BR)
#[arg(long, global = true, env = "SYNAPSE_LANG")]
pub lang: Option<String>,
#[command(subcommand)]
pub command: Commands,
}
#[derive(Subcommand)]
pub enum Commands {
/// Configuração global inicial (interativo)
Setup,
/// Inicializa um workspace no diretório atual
Init { path: Option<PathBuf> },
/// Gerencia configuração
Config(ConfigArgs),
/// Gerencia projetos
Project(ProjectArgs),
/// Gerencia pessoas
Person(PersonArgs),
/// Gerencia APIs
Api(ApiArgs),
/// Gerencia demandas
Task(TaskArgs),
/// Busca na base
Search(SearchArgs),
/// Controla o servidor MCP
Mcp(McpArgs),
/// Mostra estado do workspace
Info,
/// Reconstrói o índice
Reindex,
/// Diagnóstico de saúde
Doctor,
}
// Exemplo de subcomando com níveis aninhados
#[derive(clap::Args)]
pub struct ProjectArgs {
#[command(subcommand)]
pub action: ProjectAction,
}
#[derive(Subcommand)]
pub enum ProjectAction {
Add {
name: String,
#[arg(long)]
desc: Option<String>,
#[arg(long, default_value = "ativo")]
status: String,
},
List {
#[arg(long)]
status: Option<String>,
#[arg(long)]
tag: Option<String>,
},
Show { name: String },
Rm {
name: String,
#[arg(long)]
force: bool,
},
Rename { old: String, new: String },
}
#[derive(Clone, clap::ValueEnum)]
pub enum OutputFormat { Text, Json }
}
O dispatch (main.rs)
O main é fino: monta o contexto, despacha, e formata o erro de saída. Toda a
lógica vive em commands.
use anyhow::Result;
use clap::Parser;
fn main() {
if let Err(e) = run() {
// erro traduzido e colorido, código de saída != 0
output::print_error(&e);
std::process::exit(1);
}
}
fn run() -> Result<()> {
let cli = cli::Cli::parse();
// 1. resolve config em cascata + idioma
let config = config::load(cli.lang.as_deref())?;
let loader = i18n::init(&config.lang);
// 2. monta contexto compartilhado passado a todo comando
let ctx = Context {
config,
loader,
format: cli.format,
verbose: cli.verbose,
workspace: store::workspace::find(), // Option, nem todo cmd exige
};
// 3. despacha
commands::dispatch(cli.command, &ctx)
}
A trait Command (commands/mod.rs)
Cada comando implementa uma interface comum. Isso uniformiza tratamento de erro, acesso ao contexto e testabilidade.
#![allow(unused)]
fn main() {
pub struct Context {
pub config: Config,
pub loader: FluentLanguageLoader,
pub format: OutputFormat,
pub verbose: bool,
pub workspace: Option<Workspace>,
}
pub fn dispatch(cmd: Commands, ctx: &Context) -> anyhow::Result<()> {
match cmd {
Commands::Setup => setup::run(ctx),
Commands::Init { path } => init::run(path, ctx),
Commands::Project(a) => project::run(a, ctx),
Commands::Search(a) => search::run(a, ctx),
Commands::Mcp(a) => mcp::run(a, ctx),
// ...
}
}
}
Comandos que exigem um workspace (quase todos exceto setup e init) começam
com uma checagem que falha cedo e com mensagem clara:
#![allow(unused)]
fn main() {
let ws = ctx.workspace.as_ref()
.ok_or_else(|| Error::NoWorkspace)?; // "não está em um workspace synapse"
}
Tratamento de erros (error.rs)
Padrão consolidado em Rust: thiserror para erros tipados da biblioteca,
anyhow para propagação ergonômica na aplicação. Os erros carregam chaves de
tradução, não strings fixas: assim a mensagem sai no idioma do usuário.
#![allow(unused)]
fn main() {
use thiserror::Error;
#[derive(Error, Debug)]
pub enum Error {
#[error("error-no-workspace")] // chave Fluent, não texto final
NoWorkspace,
#[error("error-entity-exists")]
EntityExists { id: String },
#[error("error-entity-not-found")]
NotFound { id: String },
#[error("error-invalid-slug")]
InvalidSlug { input: String },
#[error(transparent)]
Io(#[from] std::io::Error),
#[error(transparent)]
Sqlite(#[from] rusqlite::Error),
}
}
A camada output::print_error recebe o Error, pega a chave, e resolve via
Fluent com os parâmetros: separando a identidade do erro da sua redação.
Camada de i18n (i18n.rs)
Usa i18n-embed com Fluent. Os .ftl são embutidos no binário em tempo de
compilação via rust-embed, então o binário final não tem dependência externa
de arquivos de tradução.
#![allow(unused)]
fn main() {
use i18n_embed::fluent::{fluent_language_loader, FluentLanguageLoader};
use i18n_embed::{DesktopLanguageRequester, LanguageRequester};
use rust_embed::RustEmbed;
#[derive(RustEmbed)]
#[folder = "i18n/"]
struct Localizations;
pub fn init(lang: &str) -> FluentLanguageLoader {
let loader = fluent_language_loader!();
// carrega fallback (en) + idioma pedido
loader.load_languages(&Localizations, &[lang.parse().unwrap()]).ok();
loader
}
}
No código de comando, traduzir é uma macro com checagem em tempo de compilação:
#![allow(unused)]
fn main() {
use i18n_embed_fl::fl;
println!("{}", fl!(ctx.loader, "project-created", name = name));
}
Detecção do idioma da máquina no setup usa sys-locale:
#![allow(unused)]
fn main() {
let detected = sys_locale::get_locale() // ex: "pt-BR"
.unwrap_or_else(|| "en".to_string());
}
Camada de saída (output.rs)
Centraliza como as coisas aparecem, respeitando --format. Comandos
devolvem dados estruturados; o output decide entre tabela colorida (humano)
ou JSON (scripting/máquina). Isso é o que permite o --format json funcionar
uniformemente em todo comando sem código duplicado.
#![allow(unused)]
fn main() {
pub fn render<T: Serialize + TableDisplay>(data: T, fmt: OutputFormat) {
match fmt {
OutputFormat::Json => println!("{}", serde_json::to_string_pretty(&data).unwrap()),
OutputFormat::Text => print_table(data),
}
}
}
Camada MCP (mcp/)
O servidor MCP roda sobre stdio (é assim que o Claude Code consome). Ele
reusa exatamente as mesmas funções de index::query que o syn search usa, não há lógica duplicada entre “buscar pelo CLI” e “buscar pelo MCP”. As
ferramentas expostas mapeiam 1:1 para operações de leitura da base:
#![allow(unused)]
fn main() {
// tools.rs: cada ferramenta MCP é uma fina casca sobre index::query
pub fn tools() -> Vec<Tool> {
vec![
Tool::new("search_knowledge", "Busca textual na base"),
Tool::new("get_project", "Detalhes de um projeto"),
Tool::new("find_person", "Encontra pessoa e seus vínculos"),
Tool::new("list_apis", "Lista APIs, com filtros"),
Tool::new("project_graph", "Relações de um projeto"),
]
}
}
A implementação do protocolo pode usar o SDK oficial de MCP para Rust quando
disponível, ou ser construída sobre tokio + serde_json lendo/escrevendo
mensagens JSON-RPC no stdio. O ponto arquitetural importante: o MCP é só mais
uma borda, como o CLI. Ambos chamam o mesmo núcleo.
Estratégia de testes
| Camada | Como testar |
|---|---|
store::frontmatter | unit: round-trip YAML ↔ struct |
store::slug | unit: casos de normalização e validação |
index::query | integração: banco em memória (:memory:) com fixtures |
commands | integração: workspace temporário em tempfile::tempdir() |
| CLI completo | tests/cli.rs com assert_cmd rodando o binário real |
O padrão recomendado para testar a init: criar um diretório temporário,
rodar syn init, e verificar que a estrutura de pastas e o index.db foram
criados, e que rodar de novo no mesmo lugar falha com erro claro (idempotência
e proteção contra sobrescrita).
build.rs: versão com git hash
Convenção comum em CLIs: a versão exibida combina a versão do crate com o hash do commit, útil para suporte (“qual build você está rodando?”).
// build.rs
fn main() {
let hash = std::process::Command::new("git")
.args(["rev-parse", "--short", "HEAD"])
.output().ok()
.and_then(|o| String::from_utf8(o.stdout).ok())
.unwrap_or_default();
println!("cargo:rustc-env=SYNAPSE_GIT_HASH={}", hash.trim());
}
Ordem de implementação dos módulos
Para não construir tudo de uma vez, a sequência que mantém o projeto sempre rodável:
config/+store/workspace.rs+cli.rsmínimo →syn setup,syn initmodel/+store/frontmatter.rs→ entidades existem como arquivosindex/(schema + reindex + query FTS) →syn searchfuncionacommands/{project,person,api}add/list/show/rm → base utilizáveli18n.rs+.ftl→ tudo traduzido (estruture as chaves desde o passo 1)mcp/→ Claude Code consulta a base- Resto:
task,tag,doctor,export, completions
Synapse: Crítica Profissional e Decisões de Arquitetura
Documento de revisão dos arquivos
01-data-schema.mde02-rust-architecture.md. Faz o papel de “ADR-zero”: consolida o que foi questionado, o que muda, o que fica.
Data: 2026-05-24 Status: Aprovado para iniciar implementação Revisor: consultoria Claude (Opus 4.7)
1. Veredito em uma página
| Camada | Veredito | Ação |
|---|---|---|
| Ideia | Sólida, foco correto, gargalo real bem nomeado. Risco principal: virar “Obsidian-com-CLI-extra” se a porta MCP não for o eixo. | Manter MCP como ferramenta de primeira classe, não como exportador. Cada feature de CLI tem de ter contraparte MCP. |
| Schema de dados | Bem desenhado conceitualmente; vazamentos: integridade referencial difusa, ausência de versionamento e de tipo “decision/note” genérico, roles mistura papel social e organizacional. | Endurecer (ver §3): chaves estrangeiras explícitas, tipo note genérico, separar role (RBAC do projeto) de relationship (papel social). |
| Arquitetura Rust | Borda → núcleo → dados está correto; mas o Context global é um anti-padrão, commands/mod.rs vira god-file, e i18n no error.rs mistura camadas. | Refatorar para Application + traits explícitas (ver §4). Manter clap v4: alternativas não justificam o custo. |
| Stack | Versões não especificadas no doc original. | Adotar tabela do §5 (Rust 1.85+ edição 2024, rmcp 1.7, rusqlite 0.39 bundled, serde_yaml_ng: nunca serde_yaml nem serde_yml). |
| Documentação | Não existe convenção estabelecida. | Adotar Rust API Guidelines: //! em módulos, /// em itens, # Examples/# Errors/# Panics, #![warn(missing_docs)] no lib.rs, cargo doc -D warnings na CI. |
| Git/commits | Não definido. | Conventional Commits estilo Soma/Eved/Auth (type(SCOPE): subject), core.hooksPath = .githooks/ com scripts shell: sem husky/pre-commit (Node/Python). |
2. Crítica da ideia
2.1 O que está certo
A dor está bem nomeada: o gargalo do contexto que evapora é real e tende a piorar à medida que a IA derruba o gargalo da execução. A escolha por texto como fonte da verdade com SQLite como índice descartável é tecnicamente conservadora no melhor sentido: você não acopla a vida do conhecimento à vida da ferramenta. E iniciar por CLI + MCP, deixando UI para depois, é a decisão correta: UI sem base de conhecimento é uma tela bonita vazia.
2.2 O que precisa de atenção
O posicionamento “segundo cérebro” é largo demais. Notion, Obsidian, Logseq, mem.ai, Capacities, Reflect, Tana, Anytype: todos se vendem como “segundo cérebro”. A diferenciação real e defensável do Synapse não é “outro PKM”: é PKM operacional para quem orquestra IA. A versão atual do README enfatiza isso só ao final (“a IA finalmente consegue consultar também”). Esse é o lead, não o desfecho. Sem essa nitidez, o produto vira “Obsidian sem GUI”.
O MCP precisa ser ortogonal, não apêndice. O atual commands/mcp.rs em §02 sugere que o MCP é “mais um comando”. Não é: é a segunda interface igualmente importante. Toda função de leitura em commands/ precisa ter contrapartida em mcp/tools.rs, e a arquitetura tem de tornar essa duplicação inviável (compartilhar a função abaixo, nunca reimplementar). Senão você acumula débito: feature nova no CLI, MCP não vê: exatamente a dor original do produto.
A IA escreve, não só lê. O escopo MVP fala só de leitura (search_knowledge, get_project, find_person). Mas o ganho real chega quando a IA também registra: “vi que você mencionou X em chat, criei tasks/alfa-053.md”. Sem ferramentas de escrita no MCP (com confirmação humana), o Synapse trata a IA como leitor: o que limita seu valor a metade.
Risco de vendor-lock no MCP. MCP é Anthropic. Apostar nele 100% é uma aposta no Claude Code dominar. Mitigar: o mcp/ é uma borda: manter o núcleo index::query portável o suficiente para expor por HTTP/JSON-RPC simples caso surja um padrão concorrente (Gemini Computer Use, OpenAI Apps SDK). Custo: nenhum: basta não importar rmcp no núcleo.
3. Crítica do schema (01-data-schema.md)
3.1 Integridade referencial é decorativa
O schema diz “vínculos por referência nunca por cópia” e mostra roles: [{project: projeto-alfa, role: ponto-focal}] em pessoa. Mas nada no banco impede que projeto-alfa não exista (syn doctor detecta depois: tarde). Em SQLite com FOREIGN KEY ativo, isso é uma checagem barata e síncrona.
Decisão:
- Habilitar
PRAGMA foreign_keys = ONem toda conexão (rusqlite não habilita por default). - Em
links, adicionarFOREIGN KEY (from_id) REFERENCES entities(id) ON DELETE CASCADEe o mesmo parato_id. - Para o tipo
task, adicionarFOREIGN KEY (project) REFERENCES entities(id): mesmo ofrontmattersendo a fonte da verdade, o índice deve refletir erros imediatamente, não após reindex.
3.2 Ausência do tipo note genérico
O schema tem project, person, entity, api, task: todas categorias de “atores ou demandas”. Não há onde guardar: uma decisão (“decidimos não usar Stripe por X”), uma runbook (“como rotacionar o secret do webhook”), uma transcrição de meeting, um snippet de comando útil. Tudo isso é exatamente o tipo de informação que escapa hoje.
Decisão: adicionar note como tipo genérico.
---
type: note
id: decisao-pagamentos-stripe
title: Por que não escolhemos Stripe
kind: decision # decision | runbook | meeting | snippet | reference
project: projeto-alfa # opcional
tags: [pagamentos, decisao-arquitetural]
created: 2026-05-24
updated: 2026-05-24
---
Sem isso, o usuário cria entities/decisao-stripe.md (sobrecarrega entity) ou um tasks/ falso. Ambos são erros de design que pagamos depois.
3.3 roles mistura RBAC com papel social
roles: [{project, role}] em person carrega coisas heterogêneas: ponto-focal, aprovadora, revisora, dev, pm. Algumas são organizacionais (cargo na empresa), outras são funcionais (papel naquele projeto).
Decisão: renomear para memberships (papel funcional naquele projeto, livre) e adicionar title no nível de person (cargo formal: “Tech Lead”, “PM”). Pequena mudança, semântica ganha. role é palavra com peso em sistemas auth (Febasi Auth API usa exatamente esse termo): confusão evitável.
3.4 Falta versionamento e auditoria leve
Todo arquivo tem created/updated. Mas e quando você quer saber: quem era ponto-focal do Alfa em fevereiro? O git resolve, sim, mas a busca por “estado em data X” via git log é cara e quebra o modelo “consulte via SQL”.
Decisão para v1: confiar no git (não inventar coluna de versão). Mas indexar a data do último commit que tocou o arquivo na tabela entities (coluna last_modified_commit) e expor --at <date> no syn search futuramente. O custo agora é zero (não implementar); o custo de não desenhar agora é alto (mudar schema depois). Documentar como futuro, não como pendente.
3.5 IDs duplicados entre tipos
Um project chamado pagamentos, uma api chamada pagamentos, uma entity chamada pagamentos: o esquema permite. A coluna PRIMARY KEY é id, sem type. Vai pegar fogo.
Decisão: PRIMARY KEY composta (type, id) em entities, ou prefixar id internamente (person:maria-silva, api:pagamentos) no índice: slug puro vira stable_ref (nullable, único por type). A primeira opção é mais simples; a segunda mais ergonômica em queries. Vou pela primeira por consistência com como o usuário pensa o arquivo (apis/pagamentos.md ≠ entities/pagamentos.md).
3.6 Frontmatter armazenado como JSON é OK, mas custa portabilidade de filtro
frontmatter TEXT NOT NULL em JSON com json_extract() funciona, mas filtros tornam-se invisíveis a quem só lê o schema. Promover colunas explícitas para os campos consultáveis quentes (status, owner, priority, due), não todos. JSON fica como fallback para campos raros. Custo: zero migration agora; ganho: índices reais e queries legíveis.
3.7 sqlite-vec é alpha (0.1.10-alpha.4)
O documento promete “fase 2: busca semântica (sqlite-vec)” sem flag. Risco: breaking changes garantidos. Decisão:
- Manter o plano, mas isolar atrás de feature flag (
--features vector) e em migration separada (migrations/vector/*.sql), nunca na migration principal. - Não emitir embeddings na fase 1. Sequer instalar o crate. Documentar como roadmap.
4. Crítica da arquitetura Rust (02-rust-architecture.md)
4.1 Context é um Service Locator disfarçado
#![allow(unused)]
fn main() {
pub struct Context {
pub config: Config,
pub loader: FluentLanguageLoader,
pub format: OutputFormat,
pub verbose: bool,
pub workspace: Option<Workspace>,
}
}
Passar &Context para todo comando é cômodo, mas é o padrão Service Locator: você só descobre as dependências reais de um comando lendo o corpo dele. Em Rust, isso compila ótimo e testa mal: para mockar o workspace em um teste, você monta o Context inteiro.
Decisão: quebrar em AppDeps (composição) com submódulos injetáveis: Db, Workspace, I18n, Clock. Cada comando aceita só o que usa:
#![allow(unused)]
fn main() {
pub trait CommandRun {
fn run(self, deps: &AppDeps) -> Result<Output>;
}
}
E para teste:
#![allow(unused)]
fn main() {
let deps = AppDeps::builder()
.db(in_memory_db())
.clock(FakeClock::fixed("2026-05-24"))
.build();
}
O Context original vira o AppDeps; o ganho é que cada teste só popula o que importa.
4.2 commands/mod.rs vira god-file de match
#![allow(unused)]
fn main() {
match cmd {
Commands::Setup => setup::run(ctx),
Commands::Project(a) => project::run(a, ctx),
// ... 12 variants ...
}
}
Em 6 meses esse arquivo tem 200 linhas e cada PR que adiciona comando edita ele. Decisão: o enum Commands do cli.rs implementa diretamente um trait Dispatch:
#![allow(unused)]
fn main() {
impl Dispatch for Commands {
fn dispatch(self, deps: &AppDeps) -> Result<Output> {
match self {
Self::Setup(a) => a.run(deps),
Self::Project(a) => a.run(deps),
// delegação direta, não rebound de função
}
}
}
}
Cada *Args implementa CommandRun. O main.rs só chama cli.command.dispatch(&deps). Adicionar comando = adicionar variant + impl, zero edição em mod.rs.
4.3 Erros com chave i18n no thiserror é ruim de ler
#![allow(unused)]
fn main() {
#[error("error-no-workspace")]
NoWorkspace,
}
O #[error] perde sua função (mensagem default em Debug/Display). Quem lê stack trace vê error-no-workspace em vez do que é. E o format!/painic em release vai vazar a chave.
Decisão: manter #[error("...")] com mensagem em inglês curto e técnico (default sem i18n) e adicionar método separado fn fluent_key(&self) -> &'static str no impl Error:
#![allow(unused)]
fn main() {
#[derive(Error, Debug)]
pub enum Error {
#[error("workspace not found")]
NoWorkspace,
#[error("entity {id} already exists")]
EntityExists { id: String },
}
impl Error {
pub fn fluent_key(&self) -> &'static str {
match self {
Self::NoWorkspace => "error-no-workspace",
Self::EntityExists { .. } => "error-entity-exists",
// ...
}
}
pub fn fluent_args(&self) -> Vec<(&'static str, FluentValue<'_>)> { ... }
}
}
Debug/logs ficam legíveis em inglês; UI fica traduzida. Separa identidade (variant) de redação (chave) de fallback técnico (string #[error]).
4.4 Camadas faltando: núcleo está acoplado a borda
O documento original diz “borda → núcleo → dados” mas o commands/project.rs faz tudo: parse args, valida, escreve no disco, atualiza índice, formata saída. Sem use cases puros, testar a regra “criar projeto não permite duplicar slug” exige montar workspace em disco.
Decisão: introduzir core/ (ou app/) entre commands e store:
src/
├── cli.rs # clap (borda)
├── commands/ # ↓ chama core, traduz erros
├── core/ # USE CASES PUROS (sem io::, sem fs::, sem clap)
│ ├── create_project.rs
│ ├── link_person.rs
│ └── search.rs
├── store/ # filesystem
├── index/ # sqlite
└── mcp/ # ↓ chama core também
Tanto commands/ quanto mcp/ chamam a mesma função em core/. Essa é a única forma de garantir “não há duas verdades” entre CLI e MCP: o doc original promete isso mas o layout permite divergência.
4.5 Falta Workspace ter responsabilidade de transação
syn project add faz: cria pasta → escreve _meta.md → atualiza índice. Se o índice falha, o arquivo já está lá. Quando o syn reindex rodar, fica órbita inconsistente.
Decisão: Workspace expõe tx<R>(f: impl FnOnce(&mut Tx) -> Result<R>) que escreve em pasta temporária + rename atômico no commit, e abre transação SQLite simultânea. Se qualquer um falhar, rollback dos dois. Não é “implementar mais tarde”: é uma decisão de design que afeta a assinatura do Store.
4.6 MCP sobre stdio com tokio no binário CLI custa a vida cara
Se o tokio for puxado por causa do MCP, ele entra na rota do CLI mesmo quando você só roda syn search foo. Isso atrasa cold-start (centenas de ms para inicializar o runtime).
Decisão: feature flag mcp (default-on para o release, mas opcional). O comando syn mcp puxa tokio via #[cfg(feature = "mcp")]. O resto do binário roda síncrono (rusqlite é síncrono mesmo). Para o release principal, dois binários: syn (sem tokio) e syn-mcp (com tokio), ou um só com lazy runtime init dentro do comando mcp. Lazy init é o caminho mais simples e suficiente.
4.7 clap vs alternativas: manter clap
Comparativo (maio 2026):
| Crate | Binário | Build | UX | Veredito |
|---|---|---|---|---|
| clap v4 | ~574 KiB | ~3 s | env vars, completions (5 shells), mangen, derive maduro | usar |
| bpaf 0.9 | ~256 KiB | ~830 ms | combinator + derive, completion dinâmica, sem mangen, ecossistema menor | considerar em v2 se compile time doer |
| argh | ~38 KiB | ~4 s | last release 2023, 63 issues abertas | evitar |
| lexopt | ~37 KiB | ~330 ms | hand-written, zero ergonomia pronta | evitar |
| pico-args | ~24 KiB | ~300 ms | help fraco, frágil | evitar |
O delta de 500 KiB do clap é irrelevante perto do tokio + rmcp + i18n-embed + fluent (~3-6 MiB). O ganho de UX (completions, env, mangen, suggestions on typo) é grande. Decisão: clap v4 com features enxutas (ver Cargo.toml em §5).
4.8 build.rs com git rev-parse quebra build sem .git
O build.rs do doc roda git rev-parse. Em release que vier de cargo install synapse (sem repo), isso é silencioso (.ok()), mas perde a info. Em build dentro de Docker layered sem .git, idem.
Decisão: usar vergen-gix (não vergen-git clássico: depende do git CLI) que extrai do .git/ direto via libgit2 e emite as envs. Cai bem em CI e local sem dep externa. Fallback gracioso quando não há .git.
5. Stack definitiva (maio 2026)
Cargo.toml de partida (todas as versões verificadas em lib.rs/crates.io em 2026-05-24):
[package]
name = "synapse"
version = "0.1.0"
edition = "2024"
rust-version = "1.85" # mínimo p/ edição 2024; igual ao ripgrep
license = "MIT"
authors = ["Rômulo Febasi <romulo@febasi.com.br>"]
description = "Second-brain CLI + MCP server for AI-augmented developers"
repository = "https://github.com/romulofebasi/synapse"
keywords = ["cli", "mcp", "knowledge-base", "second-brain", "claude"]
categories = ["command-line-utilities", "database"]
[dependencies]
# --- CLI ---
clap = { version = "4.6", features = ["derive", "wrap_help", "env", "string"] }
clap_complete = "4.6"
# --- Errors ---
anyhow = "1.0"
thiserror = "2.0"
# --- Serde stack ---
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
serde_yaml_ng = "0.10" # NÃO use serde_yaml (archived) nem serde_yml (RUSTSEC-2025-0068)
toml = "1.1"
# --- Storage ---
rusqlite = { version = "0.39", features = [
"bundled", # libsqlite embutida: elimina classe de bugs "qual sqlite o user tem"
"modern_sqlite",
"blob",
"backup",
"functions",
"load_extension", # necessário p/ sqlite-vec (fase 2)
] }
# --- Markdown + frontmatter ---
pulldown-cmark = { version = "0.13", default-features = false, features = ["html"] }
gray_matter = "0.3"
# --- Paths / fs ---
etcetera = "0.11" # XDG-aware, edição 2024; melhor que `directories` para CLI
notify = "8" # 9.0 está em RC; manter linha estável
# --- i18n (Fluent) ---
i18n-embed = { version = "0.16", features = ["fluent-system", "desktop-requester"] }
i18n-embed-fl = "0.10"
fluent = "0.17"
sys-locale = "0.3"
rust-embed = "8"
# --- Logging ---
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] }
# --- Slug / unicode ---
slug = "0.1"
unicode-segmentation = "1.13"
# --- MCP (gated) ---
rmcp = { version = "1.7", features = ["server", "transport-io", "macros"], optional = true }
tokio = { version = "1.52", features = ["rt-multi-thread", "macros", "fs", "signal", "io-util"], optional = true }
[features]
default = ["mcp"]
mcp = ["dep:rmcp", "dep:tokio"]
vector = [] # fase 2: sqlite-vec entra aqui
[build-dependencies]
clap = { version = "4.6", features = ["derive"] }
clap_complete = "4.6"
clap_mangen = "0.3"
vergen-gix = { version = "1.0", features = ["build", "cargo", "rustc", "si"] }
[dev-dependencies]
assert_cmd = "2.2"
predicates = "3.1"
tempfile = "3.27"
insta = { version = "1.47", features = ["yaml", "json"] }
rstest = "0.26"
[profile.release]
lto = "thin"
codegen-units = 1
strip = "symbols"
panic = "abort" # CLI: sem unwinding, menor binário
Tooling de projeto (não-deps):
| Ferramenta | Versão | Função |
|---|---|---|
cargo-nextest | 0.9 | test runner em CI (paralelismo melhor que cargo test) |
cargo-deny | 0.19 | licenses + advisories + bans + sources |
cargo-shear | 1.12 | detector de deps órfãs (parser-based) |
cargo-dist | 0.31 | release multi-plataforma |
committed | latest | linter Conventional Commits (Rust, sem Node) |
typos | latest | catador de typos em pre-commit |
6. Convenção de commits e hooks
Formato: Conventional Commits estilo Febasi.
type(SCOPE): subject
[optional body]
[optional footer]
type ∈ {feat, fix, docs, refactor, perf, test, chore, build, ci, revert}SCOPE ∈ {SYN-N, cli, mcp, index, store, model, i18n, config, deps, ci, hooks}(preferir ticket; senão área)subject: imperativo, minúsculo no início, sem ponto final, ≤72 char incluindo prefixo
Exemplos:
feat(SYN-1): bootstrap workspace skeleton
fix(index): respeitar PRAGMA foreign_keys em conexões reabertas
docs(adr): record decision to use rmcp 1.7 official sdk
chore(deps): bump rusqlite 0.38 → 0.39 (modern_sqlite default)
Hooks via core.hooksPath = .githooks/:
| Hook | Faz |
|---|---|
commit-msg | Valida formato Conventional via regex shell ou committed --commit-file $1 |
pre-commit | cargo fmt --check (rápido) + typos (catador de typos) |
pre-push | cargo clippy --all-targets -- -D warnings + cargo nextest run |
Por que não husky/lefthook/pre-commit? São deps de outro ecossistema (Node/Go/Python) em projeto Rust puro. core.hooksPath é nativo do git, zero install, funciona em CI igual local. Os hooks ficam versionados em .githooks/, ativados com git config core.hooksPath .githooks (rodado por scripts/bootstrap.sh).
7. Convenção de documentação (rustdoc)
Adotar Rust API Guidelines rigorosamente. Análogo ao JavaDoc/DartDoc, mas com a vantagem de que exemplos são compilados e testados:
Regras:
lib.rse cada arquivo de módulo começam com//!(doc de módulo) explicando: o que esse módulo faz, quando usar, um exemplo mínimo.- Cada item público (
fn,struct,enum,trait, campos públicos) tem///com:- Linha de resumo (uma frase, ponto final, infinitivo: “Computes …”, “Returns …”).
- Parágrafos de descrição se necessário.
- Seções padrão quando aplicáveis:
# Examples,# Errors,# Panics,# Safety.
- Intra-doc links sempre:
[`Workspace`],[`Db::open`]em vez de prosa. - Exemplos usam
?, nuncaunwrap(). - CI:
cargo doc --no-deps --all-features -D warnings. lib.rsdeclara:#![allow(unused)] #![warn(missing_docs)] #![warn(rustdoc::broken_intra_doc_links)] #![warn(rustdoc::private_intra_doc_links)] fn main() { }
Exemplo de doc de produção:
#![allow(unused)]
fn main() {
/// Cria um projeto novo no workspace e indexa o seu `_meta.md`.
///
/// O slug é derivado do `name` via [`crate::slug::slugify`] e deve ser único
/// dentro do workspace. Se já existir um projeto com o mesmo slug, retorna
/// [`Error::EntityExists`].
///
/// # Examples
///
/// ```
/// # use synapse::core::CreateProject;
/// # use synapse::test_deps;
/// let deps = test_deps();
/// CreateProject::new("Projeto Alfa").run(&deps)?;
/// # Ok::<_, synapse::Error>(())
/// ```
///
/// # Errors
///
/// - [`Error::EntityExists`] se o slug derivado já existe.
/// - [`Error::Io`] em falha de escrita do `_meta.md`.
/// - [`Error::Sqlite`] em falha na transação de índice.
pub fn run(self, deps: &AppDeps) -> Result<ProjectId, Error> { … }
}
8. Itens que ficam para depois (não-decisões)
| Item | Por que não agora |
|---|---|
sqlite-vec / embeddings | Crate alpha (0.1.x), breaking changes garantidas. Volta na fase 2 atrás de feature flag. |
| UI (TUI ou GUI) | Documento principal já diz: terceira porta, depois da base estar sólida. |
| Sync entre máquinas | Git já resolve. Documento de sync entra quando alguém pedir colaboração. |
| Plugins | YAGNI. Quando alguém precisar, abrir trait EntityType. |
Watcher (notify) | Reindex é milissegundos; watcher é otimização. Implementar quando alguém reclamar de re-rodar syn reindex. |
| LLM local p/ embeddings | Atrelado ao item de embeddings. |
Próximos artefatos: Cargo.toml, .githooks/, src/main.rs, src/lib.rs, .gitignore, rustfmt.toml, clippy.toml, deny.toml, README.md, AGENTS.md, CLAUDE.md, docs/adr/001-projeto-rust-edicao-2024.md, docs/adr/002-mcp-via-rmcp.md, docs/adr/003-sqlite-bundled-rusqlite.md, docs/adr/004-yaml-via-serde_yaml_ng.md.
Composable knowledge framework and trust architecture
Date: 2026-10-12 · Status: accepted architectural direction; runtime not implemented here
Baseline: 3ceddd0 · Owner: Rômulo Febasi
Product
Operational memory for agents, with governed changes, preserved history, and explicit integrity checks.
Synapse is an independently usable knowledge dependency for agents, Soma and other clients. It complements Notion, Jira, Confluence, Obsidian, documentation repositories, Git and MCP. Bridges may use QMD, Mem0 or Zep for retrieval/memory with transparent trust mapping. No claim to replace those tools or prove the truth of all prose.
Keep the existing edge -> core -> data rule. Provide optimized, budgeted source-linked queries and governed proposals. A dashboard can use a bundled/installed binary via direct argv/JSON; HTTP/FFI is not necessary initially. This design does not create a running API or install a model.
Existing state and gaps
| Area | Shipped baseline | Next contract |
|---|---|---|
| Content | Typed Markdown, disposable SQLite | Same source principle, revisioned external references |
| Reads | CLI JSON/MCP, hybrid/federated search | Budget/purpose/ACL/version-aware evidence envelope |
| Writes | Proposals, policies, audit/supersession | Durable sealed transition coverage and receipts |
| Logical checks | Structured gate; INV-13 intervals/cycles | Separate probabilistic prose diagnostics |
| INV-5 | Declared predecessor closure | Detect unjournaled current-source drift from a sealed baseline |
| Receipts | Export-on-demand; INV-8 Skip | Persisted signed receipts with profile-aware verification |
| Signing | Report signature may be absent; commit posture | Genuine signature/trust/authority checks |
| Collaboration | Per-user catalog/federation; future team work | Optional authority/roster/witness adapters |
| Sources | Agent-composed context; import/export | Optional revision-monitoring adapters and impact proposals |
Existing audit/version schemas remain readable. A new design does not retroactively attest old events or automatically promote all workspaces to strict assurance.
Components
flowchart TB Sources["Documentation<br/>and API sources"] --> Acquisition["Optional source<br/>adapters"] Acquisition --> Revision["Revision and<br/>access filter"] Revision --> Candidates["Candidate facts<br/>and changes"] Candidates --> Retrieval["Scoped retrieval"] Retrieval --> Decision["Optional typed<br/>decision provider"] Decision --> Proposals["Reviewable proposals"] Candidates --> Exact["Deterministic checks"] Exact --> Proposals Proposals --> Gate["Authority and<br/>acceptance gate"] Gate --> Transition["Durable transition<br/>and receipt"] Transition --> Markdown["Markdown content"] Markdown --> Index["Disposable index"] Transition --> Verify["Profile-aware<br/>verifier"] Verify --> Witness["Optional independent<br/>witness"] Client["Soma or<br/>another agent"] --> Query["CLI JSON or MCP"] Query --> Retrieval Query --> Proposals classDef edge fill:#ede9fe,stroke:#7c3aed,color:#2e1065,stroke-width:1.5px classDef core fill:#e0f2fe,stroke:#0284c7,color:#0c4a6e,stroke-width:1.5px classDef governance fill:#fef3c7,stroke:#d97706,color:#78350f,stroke-width:1.5px classDef storage fill:#f1f5f9,stroke:#64748b,color:#0f172a,stroke-width:1.5px class Sources,Acquisition,Decision,Client,Query,Witness edge class Revision,Candidates,Retrieval,Exact,Verify core class Proposals,Gate,Transition governance class Markdown,Index storage
SourceAdapter performs acquisition at an edge; DecisionProvider performs optional judgment at an edge; core consumes typed data and deterministic policies. Never put network I/O/model runtime/identity login into pure core. RetrievalProvider/KnowledgeClient adapters declare their evidence/trust capabilities; foreign memory output is not automatically a Synapse-authenticated fact.
Query and proposal contract
A KnowledgeQuery specifies workspace/project/source scope, authorized principal/policy evidence when shared, purpose, query, API/version/environment/time filters, token/latency budget and requested assurance. Local owner access and shared access are distinct modes.
A KnowledgeResult returns bounded passages/facts, source/ref/revision/content digest, observation/valid times, freshness, confidence class, retrieval score, applicable verified coverage, omitted/unavailable reasons and optional grounded summary citations. A score does not establish truth or policy authority. No invented citations; insufficient evidence produces abstention.
ACL must constrain candidate retrieval before ranking/model calls and output; do not retrieve everything then merely hide the final text. Cache includes source/ACL/policy/model revisions and is invalidated on changes. For remote-source data, offline use requires an explicit bounded lease; otherwise unavailable. Source deletion/access loss triggers policy-governed restriction/redaction of cached passages. Receipts preserve evidence commitments under retention but do not override current document access.
A KnowledgeProposal includes a stable idempotency key, candidate changes, source digests/expected revisions, run/context reference and decision diagnostics. Only approved changes pass current authority checks. Client APIs expose proposal/review/receipt states; raw transcript persistence is not the default. Reference documents remain canonical in their own tools.
Source revision and shared knowledge lifecycle
observed -> analyzed -> proposed -> reviewed -> accepted -> superseded is separate from the acquisition state (available, deleted, access_lost, fetch_failed). Content observed upstream is evidence until accepted as operational knowledge.
SourceRef: connector/source IDs, document/revision ID, URI, digest, observed time, access policy reference and optional API/version/environment identity. A candidate binds concrete source versions; a patch uses expected-upstream-revision to prevent stale write-back. Store secret handles only at the edge.
Deduplicate revision events and reconcile gaps after webhook/poll loss. Start with Git/Markdown and one remote source, then add Notion/Jira/Confluence/Obsidian adapters. File import/export is not continuous source synchronization. ADR-043 agent composition remains available; optional source workers amend its blanket exclusion of native acquisition.
Example: an authentication contract changes on Payments API v2. consumes/depends_on edges identify impacted project/squad records. Generate source-linked updates and documentation patches for authorized consumers; suppress unauthorized existence/impact details. Preserve old valid versions instead of declaring v1/prod/staging notes mutually contradictory. Maintainer acceptance, upstream publication and consumer acknowledgment are different recorded actions.
Probabilistic decisions and small models
Pipeline: ACL/scope filter -> bounded hybrid candidates -> exact structural/date/version rules -> pairwise NLI/typed decisions -> abstention/review -> authorized acceptance. Candidate recall bounds the entire system: an unseen conflicting note cannot be classified.
Use typed relations (contradiction, entails, independent, temporal_change, insufficient_context), source evidence spans, provider/model version, rubric/schema digest and uncertainty. A native NLI model outputs contradiction/entailment/neutral; richer business labels require explicit context/rule composition. Dates/counts/permissions stay in code. ML diagnostics stay outside deterministic invariants.
Jev is a useful optional hosted decision provider with Choice/Score/Noul. Do not assume small parameter count, local weights, perfect correctness or offline execution. Benchmark it against a permissively licensed local NLI candidate and rules+retrieval. Model selection/default changes require EN/PT evidence, resource/license/runtime checks, egress policy and fallback. Details: research protocol.
Authenticated transitions
Current Markdown is content; a sealed journal proves a governed mutation suffix. Journal records bind complete managed-file before/after inventories, content-image references, proposal/context/rule digests, actor/device, sequence/parents and schema/canonicalization. Cover deletions/renames and composite type+entity identities. Explicitly list unmanaged/excluded paths.
Hashes without retained payloads do not reconstruct history. Maintain retrievable pre/post images under encryption/retention policy. Canonical encodings use domain separation and exact types with test vectors. Existing signatures are not silently reinterpreted under new canonicalization.
%%{init: {'sequence': {'actorMargin': 20, 'width': 110}}}%%
sequenceDiagram
participant Client
participant Gate as Policy and<br/>identity gate
participant Tx as Durable<br/>coordinator
participant Store as Markdown<br/>and index
participant Evidence as Journal<br/>and receipt
Client->>Gate: Proposal with<br/>expected state
Gate->>Gate: Check current scope<br/>and authority
Gate->>Tx: Authorized transition
Tx->>Tx: Lock and<br/>durable prepare
Tx->>Store: Apply checked postimage<br/>and projection
Tx->>Evidence: Persist signed<br/>transition receipt
Tx->>Tx: Durable commit marker
Tx-->>Client: Committed transition id
The coordinator recovers a prepared mutation after crash or quarantines mismatched state; acknowledge only after durable commit. Index rebuild remains independent. Direct Markdown edits are allowed as user content changes but become Unattested drift until reconciled; reindex cannot erase that fact. A missing/new managed file also changes the manifest.
Migrate through an owner-acknowledged baseline and explicitly limited coverage start. Never manufacture old authorship. Concurrent writers reference accepted parents and reject stale preconditions; divergent changes require review, not last-write-wins. Run crash tests around all filesystem/index/journal/receipt boundaries before claiming atomic acceptance.
Receipts, reports and assurance
| Profile | Required evidence | Missing prerequisite |
|---|---|---|
| Basic/compatible | Available storage/history checks with explicit exclusions/range | Report partial/Skip; preserve v1 behavior |
| Strict-local | Sealed suffix, payload coverage, persisted receipts, pinned local signer trust | Fail with a stable reason |
| Strict-team | Strict-local plus subject/key binding, approval grant/policy, revocation freshness and selected independent witness | Fail with a stable reason |
Semantic diagnostics appear separately as assessed/unknown; no model confidence creates a deterministic Pass. Missing optional ML does not invalidate storage integrity, but a required semantic-review procedure can stop acceptance until reviewed. Report current data integrity, historical transitions, signer identity, authority evidence, witness consistency and exclusions separately.
Define a versioned v2 report/extension with baseline/head, covered range, required/available/failed checks, pinned policy and evidence references. Readers retain the v1 contract. A basic zero exit code cannot appear in Soma/CI as all guarantees verified. Stored receipts replace INV-8 Skip only when implemented.
Receipt verification uses trusted roots/bindings, not merely the embedded public key. Pin historical trust/witness snapshots; distinguish revocation now, compromise-at-time, and authority for a new approval. Standard in-toto/DSSE packaging is useful after the native evidence is real; C2PA is not a required MVP dependency.
Identity and independent witnessing
OIDC authenticates (issuer,sub) online; device proof-of-possession binds that subject to a signing key. The external authority grants workspace/project actions and exports signed roster/binding evidence. Private keys and bearer/refresh/ID tokens never enter the knowledge store or public witness. Synapse core verifies exported typed evidence offline without implementing an IdP.
Offline proposals can be prepared using a cached snapshot; shared acceptance requires fresh server revalidation. An offline verifier states revocation freshness and cannot know unseen updates. Verify genuine SSHSIG signatures through existing SYN-261; detecting gpgsig remains posture only.
| Mechanism | What it can establish | What it does not establish |
|---|---|---|
| Digest/hash chain | Bytes/links relative to retained expected head | Author identity, correctness, unseen truncation without witness |
| Signature | Possession of an explicitly trusted key | Organizational membership by itself |
| OIDC binding + scoped roster | Subject/key relationship and evidenced approval authority | Truth of a claim or permanent future permission |
| Independent checkpoint/Merkle proof | Inclusion/extension relative to trusted retained heads | Every source being correct or a dishonest log never equivocating |
| Optional blockchain anchor | An externally witnessed checkpoint commitment under chain assumptions | Identity mapping, current membership, retained content availability |
Start with existing git/ed25519 plus an independently retained signed witness. Hash/Merkle/append-only concepts from blockchains are useful without operating a blockchain. Public anchoring must disclose metadata/low-entropy digest inference risk; commit only a deliberately privacy-preserving checkpoint envelope. No document, prompt, key or token is placed on-chain. Public Sigstore is optional; private knowledge may prefer a private witness. Witnesses must compare/retain checkpoints to expose equivocation.
Delivery and verification gates
- Clarify product/current guarantee boundaries and versioned embedding contract (existing SYN-321).
- Implement sealed transitions and crash-safe migration; then persisted receipts/strict profiles.
- In parallel as an independent spike, evaluate typed decisions; ship only measured local/hosted options.
- Add device/subject binding and real signature/authority enforcement before strict-team acceptance.
- Add source-revision/impact semantics and one adapter; integrate the Soma pilot.
Scope-preserving tests: legacy/v2 readers, canonical vectors, create/update/delete/rename, unauthorized/direct edits, journal truncation/fork, crash recovery, wrong signer, absent receipt, stale/revoked authority, source ACL loss, stale patches, EN/PT diagnostics and model failure.
No runtime tests are executed for this documentation change. Future implementation uses the repository gates (cargo fmt --check, cargo clippy --all-targets --all-features -- -D warnings, cargo nextest run --all-features, doctests/docs and cargo deny check on dependency changes). A new model/runtime or transport requires a corresponding ADR and measured budget.
ADRs and deduplicated work
- ADR-049: consumption/source evolution.
- ADR-050: probabilistic diagnostic boundary.
- ADR-051: transition coverage and witness.
- ADR-052: strict assurance.
- ADR-053: identity/authority.
- architecture: https://github.com/romulofebasi/synapse/issues/307
- decisions: https://github.com/romulofebasi/synapse/issues/308
- transitions: https://github.com/romulofebasi/synapse/issues/309
- assurance: https://github.com/romulofebasi/synapse/issues/310
- identity: https://github.com/romulofebasi/synapse/issues/311
- sources: https://github.com/romulofebasi/synapse/issues/312
Reuse: SYN-321 #306, SYN-320 #305, SYN-319 #304, SYN-261 #249, SYN-231 #222, SYN-206 #96, SYN-318 #303. No duplicate team/connector/SSHSIG/envelope issue is created.
Primary references
- OIDC Core
- Merkle inclusion/consistency
- Canonical JSON
- Sigstore bundle
- TypeSafe decisions
- Companion Soma design
SYN-234 spike: EAV vs per-type generated columns for indexed fields
Date: 2026-06-13
Resolves: the open item in ADR-028
§Consequences (“EAV vs per-type generated columns for indexed fields … prototype
EAV first, spike against real query patterns”).
Outcome: EAV: a single entity_fields(type, id, field_name, value) side
table, indexed on (field_name, value).
The question
A user-defined type (e.g. book) declares a whitelist of indexed fields in
.synapse/types.toml:
[types.book]
store_dir = "books"
indexed = ["author", "year"] # ≤ 16, ADR-028
Those fields must become filterable / group-by-able through the existing
read surfaces: query (ADR-023), list, search: while:
- preserving ADR-023’s invariant: a request string never reaches a SQL
identifier position;
field_nameis bound as a value, never interpolated; - keeping the index disposable and rebuildable (
syn reindexdrops and recreates it from the Markdown source of truth: seeAGENTS.md); - adding a new type or field staying purely additive (
types.tomledit only, no schema migration, noALTER TABLE): the expand-contract discipline ADR-028 commits to.
Two candidate storage shapes were on the table.
Option A: EAV side table
CREATE TABLE entity_fields (
type TEXT NOT NULL,
id TEXT NOT NULL,
field_name TEXT NOT NULL,
value TEXT,
PRIMARY KEY (type, id, field_name),
FOREIGN KEY (type, id) REFERENCES entities(type, id) ON DELETE CASCADE
) STRICT;
CREATE INDEX idx_ef_field_value ON entity_fields (field_name, value);
One row per (entity, indexed field). Filter / group-by join entities and bind
both field_name and value:
SELECT e.type, e.id, e.title
FROM entities e
JOIN entity_fields ef ON ef.type = e.type AND ef.id = e.id
WHERE ef.field_name = ?1 AND ef.value = ?2; -- ?1, ?2 both bound
Option B: per-type generated / real columns
Either ALTER TABLE entities ADD COLUMN gc_book_author TEXT GENERATED ALWAYS AS (json_extract(frontmatter, '$.author')) per indexed field, or a per-type side
table with one real column per field.
Empirical spike
Prototyped Option A against the real schema subset at 10 000 synthetic
entities, each with two indexed fields (20 000 EAV rows), on SQLite 3.51
(the bundled floor is older but the planner behaviour is the same). Full
script and output reproduced below.
Point filter on a custom indexed field: index-backed SEARCH, not a scan:
QUERY PLAN
|--SEARCH ef USING INDEX idx_ef_field_value (field_name=? AND value=?)
`--SEARCH e USING INDEX sqlite_autoindex_entities_1 (type=? AND id=?)
Group-by on a custom indexed field (count per year): index-backed,
covering index on the entities PK:
QUERY PLAN
|--SEARCH ef USING INDEX idx_ef_field_value (field_name=?)
|--SEARCH e USING COVERING INDEX sqlite_autoindex_entities_1 (type=? AND id=?)
`--USE TEMP B-TREE FOR ORDER BY
Injection invariant holds by construction: binding a SQL payload as
field_name matches nothing and leaves the schema intact:
SELECT COUNT(*) FROM entity_fields
WHERE field_name = 'author''; DROP TABLE entities; --' AND value = 'author_7';
-- hits = 0 ; SELECT COUNT(*) FROM entities still returns 10000
This is the same guarantee ADR-023 gives the hot columns, but stronger: with
columns the field identity lives in the SQL text; with EAV it lives in a bound
value, so the whitelist check (field_name ∈ registry.indexed[kind]) is a
defence-in-depth in-memory check, not the only thing standing between a
request and the SQL identifier position.
Decision: EAV
| Criterion | EAV (A) | Generated columns (B) |
|---|---|---|
| New type/field = additive, no DDL | ✅ types.toml edit only | ❌ ALTER TABLE per field |
| Fits disposable/rebuildable index | ✅ rebuilt by reindex, no schema drift | ⚠️ DDL must be replayed on every rebuild, ordered by registry |
| ADR-023 invariant | ✅ field_name bound as value | ⚠️ column name derived from (type, field) → identifier is dynamic |
| Query cost at local-KG scale (1k–50k, ≤16 fields/type) | ✅ index SEARCH, negligible | ✅ marginally faster, irrelevant at this scale |
| Sparse-column / wide-table risk | ✅ none (tall, narrow) | ❌ one shared entities table accreting a column per type×field |
| Multi-field analytical joins at >1M rows | ⚠️ join per field | ✅ wins, but not this workload |
EAV wins on every axis that matters for a single-user, local, Markdown-as-truth
KG with a disposable index. Generated columns only pull ahead at a scale and
query complexity Synapse does not target, and they would break the property that
keeps types.toml purely additive (adding a type must never mutate the index
schema). The join cost EAV pays is, per the query plans above, an indexed
SEARCH: invisible at 50k entities. If SYN-227’s perf budgets ever surface a
real regression, a STORED generated column is a localised, backward-compatible
optimisation we can add then without reopening this decision.
The closed core stays in entities. Only the per-type indexed whitelist
projects into entity_fields. The built-in hot columns (status, owner,
priority, due) keep their dedicated columns: EAV is strictly additive
surface for the open skin, never a replacement for the frozen spine (ADR-028:
freeze the spine, not the skin).
Implementation consequences (for the slices that follow)
- Migration
0006addsentity_fields+ its index. Additive; no change to existing rows. reindex/upsert_entitypopulateentity_fieldsfor a kind’s registeredindexedfields (resolved fromRegistry), boundingfield_nameto the whitelist before insert.query/list/searchgain a registry-checked dynamic-field filter that resolvesfield_nameagainstregistry.indexed[kind](a closed in-memory set) and binds it: thegroup_columnallow-list incore::mcp::queryextends to “or a registered indexed field for the filtered kind”, never to an arbitrary string.- Built-in hot columns are untouched: no data migration, no behaviour change for the six built-ins.
Reproduction
CREATE TABLE entities (
type TEXT NOT NULL, id TEXT NOT NULL, title TEXT NOT NULL,
status TEXT, owner TEXT, priority TEXT, frontmatter TEXT NOT NULL,
PRIMARY KEY (type, id)
) STRICT;
CREATE TABLE entity_fields (
type TEXT NOT NULL, id TEXT NOT NULL, field_name TEXT NOT NULL, value TEXT,
PRIMARY KEY (type, id, field_name),
FOREIGN KEY (type, id) REFERENCES entities(type, id) ON DELETE CASCADE
) STRICT;
CREATE INDEX idx_ef_field_value ON entity_fields (field_name, value);
WITH RECURSIVE n(i) AS (SELECT 1 UNION ALL SELECT i+1 FROM n WHERE i < 10000)
INSERT INTO entities(type,id,title,frontmatter)
SELECT 'book','b'||i,'Book '||i,'{}' FROM n;
WITH RECURSIVE n(i) AS (SELECT 1 UNION ALL SELECT i+1 FROM n WHERE i < 10000)
INSERT INTO entity_fields(type,id,field_name,value)
SELECT 'book','b'||i,'author','author_'||(i % 50) FROM n
UNION ALL
SELECT 'book','b'||i,'year', CAST(2000 + (i % 26) AS TEXT) FROM n;
ANALYZE;
EXPLAIN QUERY PLAN
SELECT e.type, e.id, e.title FROM entities e
JOIN entity_fields ef ON ef.type=e.type AND ef.id=e.id
WHERE ef.field_name='author' AND ef.value='author_7';
Prioritized execution register
Updated: 2026-10-12
This register accounts for every open issue at the start of this implementation campaign. Priority follows security and correctness dependencies, not issue age. P0 restores a trustworthy development baseline; P1 delivers the operational loop; P2 measures and extends it; P3 carries explicit demand/architecture gates. A gated issue is not an implementation authorization to bypass its acceptance criteria. Priority is not a completion claim.
Current sequence
- SYN-330, hook/test fixes and numbering hygiene.
- SOMA-34 Rust account/authority foundation, then measured usage and adapter guarantees.
- Synapse transition integrity/identity and Soma capability/context/event contracts.
- Versioned knowledge integration, deterministic diagnostics and two-operator pilot.
- Retrieval/semantic/source/visual extensions and demand-gated infrastructure.
Completion rules
Use small reviewable changes, linked ADRs and focused regression evidence. Every PR records actual checks and remaining acceptance criteria. Close delivered issues through the finishing PR; consolidate duplicates with links and preserve their unique requirements. Do not describe a stub, design document, unavailable live credential or skipped measurement as implemented. Account service credentials belong only on the backend. Do not rewrite existing user changes or old commits.
Research notes
Long-form briefs that informed roadmap decisions. Each file captures a moment in time: the question, the market scan, the cross-check against the product’s positioning, and the concrete recommendation that landed in the roadmap (or did not).
These are not ADRs. ADRs record a decision that was taken and
its consequences (docs/adr/). Research briefs record the
landscape that informed the decision: they cite sources, dissent
freely, and become outdated. Once a brief stops shaping the product,
it stays here as a historical receipt; it is not edited to track new
reality.
Naming convention
YYYY-MM-DD-<slug>.md
YYYY-MM-DDis the date the brief was finalised, not when the research started.<slug>is kebab-case, scope-first:v0.3-multi-context-developer,mcp-spec-2026-q3,embeddings-backend-benchmark.- One file per brief; do not bundle topics.
Document structure (skeleton)
Each brief follows the same outline so they remain comparable. New
briefs copy _template.md:
- Metadata header (date, author, scope, status, inputs).
- Executive summary: 3-4 sentences, no longer.
- Context and premisses: what triggered the brief; what stays off-limits (e.g. “v0.5 semantic layer is out of scope here”).
- Market scan: sectioned by question, each section ending in a one-line “what this means for Synapse”.
- Cross-check: what the market scan validates / challenges / adds versus the team’s prior hypotheses.
- Positioning: where this product fits versus named competitors; the moat to defend.
- Recommendation: the theme proposed, the priorities, what is explicitly not in scope.
- Disruptive bets: non-obvious ideas worth tracking even if they fall out of the current cycle.
- Ticket sketch (provisional IDs, blocks, sizes. Sketch) not issues until milestone is opened.
- Risk register: severities + mitigations.
- Open decisions: questions still requiring human direction.
- References: every external link cited above.
Index
| Date | Brief | Status |
|---|---|---|
| 2026-05-25 | v0.3: Auditable AI second brain (multi-context devs) | shipped v0.3.0; milestone #5 closed 18/18 |
| 2026-05-25 | v0.4: Refinement & extensibility | shipped v0.4.0 2026-05-27; milestone #6 closed 18/18 (12 core + 6 stretch) |
| 2026-06-02 | v0.4 closure & roadmap review | accepted; input to README §Status refresh (b37a418) and to the v0.5 brief |
| 2026-06-02 | v0.5: Semantic layer & second-brain enrichment | accepted 2026-06-02; 23 issues filed in milestone #7; SYN-89 spike + ADR-008 shipped 640116e |
| 2026-06-02 | Vector storage options vs sqlite-vec | accepted; confirms sqlite-vec for v0.5; vectorlite registered as v0.7+ upgrade path; LanceDB/SurrealDB rejected as v0.5 migrations |
| 2026-06-14 | Agent-memory field comparison | draft; validates supersession/provenance/trust/flat-vector bets; recommends trust-in-ranking, contradiction→gate, batched embedding, proposal contract, invariant eval harness |
| 2026-07-14 | Market positioning and Febasi substrate | historical draft; exclusivity/demand hypotheses qualified by 2026-10-12 design; issues #303–306 reused |
| 2026-10-12 | Typed decisions and trust | accepted evaluation protocol; local NLI/Jev not benchmarked; ADR-049–053 and implementation issues |
When a brief informs a milestone, link the milestone here in the Status column once it is opened.
v0.3: Auditable AI second brain for multi-context developers
| Field | Value |
|---|---|
| Date finalised | 2026-05-25 |
| Author | Rômulo Febasi <romulo@febasi.com.br> |
| Scope | Roadmap brainstorm for Synapse v0.3.0: the bridge between v0.2 (daily polish, shipped 2026-05-25) and v0.5 (semantic layer, reserved). |
| Status | accepted; milestone opened 2026-05-25 |
| Decisions log | Owner answered the §9 open questions on 2026-05-25: (1) target the 2026-08-02 EU AI Act window for the core auditable, (2) ship the portable identity prototype in v0.3, (3) cut the GitHub/Linear ingestors from v0.3: defer to post-v0.5 because the dep on external APIs is heavy and not aligned with the local-first principle right now, (4) keep numbering as v0.3.0, (5) open the milestone and issues immediately. Plus: syn-lsp moves explicitly to v0.4. |
| Inputs | Web market research (multi-context developer pain points 2025-2026; MCP spec 2026-07-28 RC; EU AI Act Article 50; knowledge-graph and integration landscape; sleeper trends). Internal brainstorm cross-checking the Synapse positioning matrix. |
| Out of scope | Semantic search and embeddings (reserved for v0.5). LSP companion, TUI, schema-declarable per-workspace, HTTP MCP transport (backlog or v0.4). |
Executive summary
The EU AI Act Article 50 enforcement window opens on 2026-08-02 with penalties up to €15M or 3% of global turnover for missing provenance on AI-authored content. Synapse already has the propose/sanction queue (BR-12.3) that aligns conceptually; what is missing is provenance frontmatter on accepted proposals, an immutable audit log, and syn pending replay for reproducibility, the three primitives that turn the existing v0.2 surface into an AI-Act-grade local-first second brain, a positioning no SaaS competitor can credibly copy.
v0.3 should ship that core auditable plus the MCP-native expansion (resources + prompts + server-card.json + Tasks) that the MCP 2026-07-28 RC moved from optional to expected, and two ingestors (GitHub, Linear) that resolve the adoption bottleneck. Theme: “Auditable AI Second Brain: shipped before the deadline.”
1. Context and premisses
What state is assumed
- v0.2.0 shipped on 2026-05-25 with 13 MCP tools (4 local read + 4 federation read + 5 write proposals via pending queue), 24 CLI subcommands, workspace federation with a structural privacy gate (BR-12.4 / SYN-79), filesystem-only freshness advisory (SYN-93 Phase 1 / ADR-011),
last_modified_commitindexed per entity (SYN-68). - 94 tests green, clippy clean under
-D warnings. - Cargo.toml at
0.2.0. ADR index covers 001-007, 009-011 (008 is a documented gap). - The propose/sanction queue (BR-12.3) is the load-bearing invariant: MCP cannot write to the workspace without
syn pending accept.
What is explicitly off-limits in this brief
- v0.5 semantic layer. Embeddings, sqlite-vec, hybrid ranking, the embedding backend ADR (SYN-89): all reserved.
- HTTP / SSE MCP transport. Backlog (SYN-207).
- Schema declarable per-workspace. Architecturally heavy; v0.4 or v1.0.
syn-lspLSP companion. Worth doing, wrong cycle.- Cursor / Copilot territory. Synapse is the business context layer, not the coding assistant layer. v0.3 must not blur that.
Triggers for this brief
- v0.2 closed 24/24 tickets; the team needs a defensible next theme.
- The MCP spec 2026-07-28 release candidate moved several capabilities from optional to expected, opening a window to differentiate before SaaS catches up.
- EU AI Act Article 50 sets a concrete deadline that aligns with Synapse’s existing safety surface.
2. Market scan
2.1 Multi-context pain points devs actually complain about (2025-2026)
The chronic gripes from devs running 5+ contexts are structural, not “yet another note-taker”:
- Cross-context bleed in AI assistants. A consultant in Cursor or Claude Code on Client A’s repo gets autocompletes contaminated by Client B’s vocabulary because the assistant has a single global memory. Notion AI and Mem.ai both struggle here: “personal graph” features (Glean rolled one out in late 2025) actively fight isolation by design.
- Workspace silos that can’t talk when you want them to. Obsidian vaults are isolated by file path but offer no first-class “ask across vaults”; Logseq has the same gap. Multi-vault graph queries are still an open issue on Obsidian’s forum in early 2026.
- Re-onboarding tax. Senior ICs across multiple contexts lose 15-30 min reorienting per context switch (Datadog State of AI Engineering 2026). Backstage / Cortex solve this for the company, not for the human spanning multiple companies.
- Mem.ai / Reflect “auto-organize” black box. Power users distrust LLM-managed graphs because they can’t see why something was linked or tagged. Loud on r/PKMS in early 2026.
- Tana / Anytype paradigm tax. Supertags and Object-types take weeks to internalize; consultants juggling 5 clients can’t afford that ramp per workspace.
- No tool gives “what’s stale per context” across multiple workspaces. Linear / Jira tell you what is open in this instance; nothing tells you “Project X has had zero activity for 21 days and three open commitments” across all your contexts.
What this means for Synapse: lean into per-workspace identity (different “voice” per SYNAPSE.md), make federation queries explainable (show which workspace each hit came from with a relevance reason), and make the freshness advisory cross-workspace by default for the new syn audit stale command.
2.2 MCP ecosystem maturity (2026)
State of the art as of MCP spec 2026-07-28 RC:
- Stateless servers +
.well-knowndiscovery (SEP-1649 →/.well-known/mcp/server-card.json) is the new default. Stateful is niche. - Tasks extension for long-running work (indexing, reindexing, embedding generation): devs expect MCP servers to expose progress, not block.
- MCP Apps (server-rendered UI snippets): consumed by Cursor, Zed, Claude Desktop.
- Resources are now expected, not optional. The de-facto pattern: a server exposes
resource://workspace/manifestandresource://entity/{id}as canonical URIs. Tools mutate; resources are addressable read state. - OAuth / OIDC for auth is now mandatory for remote MCP. Local servers (Synapse’s case) skip this but should still expose a “consent scope” descriptor.
- De-facto default servers devs install: GitHub, Linear, Slack (all official Anthropic), Filesystem, Playwright, Postgres, Sentry. ~2,000 servers in the public registry.
- Audit-resource pattern is emerging: servers expose
resource://audit/recentlisting the last N tool invocations with timestamp and caller: pushed by Glean / Notion enterprise demands.
What this means for Synapse: ship .synapse/server-card.json; expose entities and manifest as MCP resources (not just tools); implement the Tasks extension for reindex / federated_search; expose resource://audit/recent derived from the pending queue plus the new audit log.
2.3 AI auditability + safety: where the bar sits
EU AI Act Article 50 enforcement starts 2026-08-02. Penalty ceiling: €15M or 3% of global turnover. What is now table stakes:
- Machine-readable provenance on AI-authored content. C2PA-style metadata + an invisible marker. Notion’s audit log already records
MCP server connectedandUser information readevents; Glean syncs ACLs in real time and logs every retrieval. - Decision traces. Gartner’s context graph framing (Mar 2026): record what triggered the AI action, what context informed it, what rule governed it, what was the outcome. Not just “AI did X.”
- Pre-action approval, not post-hoc review. Pre-action approval has become the canonical “calibrated autonomy” pattern. Batch approval of 10-50 items is the productivity sweet spot.
- Reproducibility. Every AI write should be replayable from inputs.
What this means for Synapse: the propose/sanction queue is already aligned. Gap to close: (a) stamp every accepted proposal with provenance frontmatter (x-ai-source, x-prompt-hash, x-model, x-decision-trace-id); (b) maintain an immutable audit log of every accepted / rejected proposal in .synapse/audit.ndjson; (c) snapshot the context that informed each proposal so syn pending replay <id> re-runs against the same inputs.
2.4 Engineering knowledge graphs: where they fail the senior IC
Glean ($7.2B valuation, $200M ARR), Sourcegraph (code graph), Cortex / Backstage / Port (service catalogs). Where they break:
- Org-bounded. A senior IC at a big company with side consulting can’t carry their graph across employers. The graph dies with the badge.
- Service-catalog tools (Backstage / Cortex) are about services owned, not decisions made. ADRs, RFCs, post-mortems, and “why we picked X over Y” live elsewhere.
- Glean’s personal graph is still SaaS. Any consultant under NDA can’t legitimately point it at client data.
- Code graph ≠ decision graph. Sourcegraph knows the call graph but not “why this module exists.” The 2026 context graph trend (Foundation Capital, Gartner) is exactly this fusion.
- No first-class “person ↔ project ↔ org ↔ api” model that survives job changes. LinkedIn is too coarse; Obsidian does not enforce types.
What this means for Synapse: the 6-entity model (project, person, entity, api, task, note) is already the right shape. Differentiator to lean into: the graph belongs to the human, follows them across employers, and federates into an employer workspace (read-only) rather than living in it. This is the me.synapse portable identity bet in §6.
2.5 Integrations that actually have ROI
Real adoption signal, ranked by dev value:
- GitHub PRs + issues. Highest ROI by miles. PR titles, descriptions, review threads, merged commits = ground truth of “what shipped.”
- Linear issues > Jira. Devs want Linear ingestion; they have to live with Jira.
- Calendar (Google / Outlook). Mostly for “who did I meet about Project X this week”: surprisingly high ROI for consultants billing hours.
- Slack threads (selective). Whole-Slack ingestion is noise; thread-by-URL capture is the pattern that won (CodeRabbit, Granola).
- Browser history / page capture. Arc is dying; Dia / Comet / Atlas rising. Lightweight “save this tab to Synapse with a note” beats full history sync.
- Meeting transcripts. Granola, Fathom, Otter: devs want decisions and action items, not the transcript. Structured extraction wins.
What this means for Synapse: prioritize GitHub + Linear as syn ingest adapters that emit proposals into the pending queue (every imported entity is reviewable). Calendar and Slack-thread-by-URL come next. Skip full Slack / browser-history ingestion.
2.6 Sleeper trends the obvious list misses
- Decision-graph + code-graph fusion (Foundation Capital, Gartner): pairing what the code does with why we decided it. No local-first tool does this.
- Meeting-as-input, decisions-as-output. Granola-style “extract decisions and owners” is replacing “save the transcript.”
- Personal-graph-as-portable-CV. The Glean personal-graph idea leaking into consumer territory: people want a knowledge graph they own.
- MCP servers as the “agent’s keyboard.” Devs are abandoning ChatGPT plugins and assistant-specific integrations because MCP is becoming OS-level (Apple Intelligence MCP support shipped in iOS 19.2 beta).
- Provenance-first writing. Every AI-authored paragraph stamped with model + prompt hash + retrieval set. The C2PA metadata norm from images is propagating to text.
- Cross-workspace queries with capability disclosure: federated read where each workspace publishes what it allows others to see. Almost no one ships this.
What this means for Synapse: the moat is the intersection: local-first + MCP-native + typed entities across workspaces + human-owned graph. v0.3 should make that intersection legible.
3. Cross-check vs prior hypotheses
The internal brainstorm proposed 8 blocks (A-H). The market scan filtered them:
| Hypothesis | Status | Reason |
|---|---|---|
Block A: Time & audit (syn timeline, syn blame, syn at <ref>, syn pending log) | Validated | EU AI Act + Notion/Glean audit logs + reproducibility requirement (§2.3). |
| Block B: MCP resources + prompts | Validated and expanded | MCP 2026-07-28 RC made resources expected (§2.2). Add server-card.json + Tasks. |
| Block C: Ingestors | Validated, sequenced | GitHub > Linear > Calendar > Slack URL > meeting transcripts (§2.5). v0.3 ships GitHub + Linear; rest defers. |
| Block D: Briefings | Reframed | “Cross-workspace stale & uncommitted” advisory has higher pull than per-project briefings (§2.1). |
| Block E: TUI / fuzzy pick | Cut | Does not emerge from named pains. Moves to backlog. |
| Block F: Share entity (granular HTML) | Cut | Not a stated need. syn export is enough. |
| Block G: Workspace lifecycle (fork/clone/archive) | Partial | syn workspace clone <git-url> aligns with portable identity (§2.4); the rest defers. |
D1: syn capture (stdin → pending) | Kept | Low-cost, disruptive UX. No market signal against it. |
D2: syn agent (autonomous + batch approval) | Deferred to v0.4 | Validated by batch-approval trend (§2.3) but architectural risk. |
| D3: Cross-workspace entity references | Kept (read-only) | Aligns with capability-disclosure trend (§2.2). Write-side stays in v0.4. |
D4: syn-lsp | Cut | Worth doing, wrong cycle. v0.4 or v1.0. |
| D5: Schema declarable per-workspace | Cut | Heavy architecture. v0.4 or v1.0. |
Net adds the market scan exposed that were not in the internal brainstorm:
- Decision-trace frontmatter stamps (
x-ai-source,x-prompt-hash,x-model,x-decision-trace-id): became a hard requirement because of EU AI Act. syn pending replay <id>: re-runs the prompt against the original context snapshot. SaaS cannot copy this (no client-side snapshot).- Immutable audit log at
.synapse/audit.ndjson, separate from the pending queue. .well-known/mcp/server-card.jsonwith capability disclosure for federation.- MCP Tasks extension on
reindexandfederated_searchso long-running work reports progress.
4. Positioning
| Axis | Synapse v0.3 target | Notion | Obsidian | Linear | Glean | Cursor |
|---|---|---|---|---|---|---|
| Local-first | ✓ | ✗ | ✓ | ✗ | ✗ | partial |
| AI-native (MCP) | ✓ | proprietary | plugin | ✗ | ✓ | ✓ |
| Typed entities | ✓ | DB blocks | tags | ✓ | ✓ | ✗ |
| Multi-workspace with privacy gate | ✓ | ✗ | vaults | SaaS workspaces | ✗ | ✗ |
| Propose / sanction for AI writes | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ |
| Source = git markdown | ✓ | ✗ | ✓ | ✗ | ✗ | partial |
| Auditable AI writes (EU AI Act-grade) | v0.3 target | partial (audit log) | ✗ | ✗ | ✓ (SaaS, org-bounded) | ✗ |
| Portable knowledge identity across employers | v0.3 prototype | ✗ | partial (vault copy) | ✗ | ✗ (org-bounded by design) | ✗ |
| Cross-workspace capability disclosure | v0.3 | ✗ | ✗ | ✗ | ✗ | ✗ |
Defensible moat: the intersection: local-first auditable AI second brain that the human owns across employers. SaaS competitors structurally cannot ship 3 of those 4 attributes together; their business model requires data lock-in. v0.3 makes the intersection legible.
5. Recommendation
Theme
“Auditable AI Second Brain: shipped before the deadline.”
Prioritised blocks
| # | Block | Justification | Cost | Defensible? |
|---|---|---|---|---|
| 1 | Audit trail + provenance: audit.ndjson append-only; frontmatter stamps x-ai-* on accepted proposals; syn audit show / list / replay <id> | EU AI Act Art. 50; C2PA text propagation; Notion / Glean already audit; this turns the existing pending queue into legal-grade output | Medium | Strong: git-diff-able audit, SaaS audit is vendor-locked |
| 2 | MCP-native expansion: resources (workspace/manifest, entity/{id}, audit/recent); prompts (/synapse:capture-decision, /synapse:daily-review, /synapse:weekly-brief); .synapse/server-card.json; Tasks extension on reindex / federated_search | MCP 2026-07-28 RC made these expected; window to ship before SaaS servers catch up | Medium | Strong: MCP-native is the moat |
| Cut by owner 2026-05-25: adapters depend on external APIs and runtime tokens, conflicts with local-first invariant. Deferred to post-v0.5 when the surface stabilises. The adoption-bottleneck argument is real but does not justify the trust-boundary cost right now. | - | - | ||
| 3 | Time queries: syn timeline <slug> (git log filtered by entity path); syn at <ref> -- <query> using indexed last_modified_commit | Replayability + temporal audit; primitive already shipped in SYN-68 | Low | Medium: only works because the entity is git-tracked |
| 4 | Cross-workspace stale advisory: syn audit stale --over <window> ranks open commitments across federation by likely cost of dropping | Cross-context pain validated (§2.1); no SaaS sees across workspaces it does not own | Medium | Strong: federation + capability disclosure required |
| 5 | syn capture: stdin → pending capture_fact in one keystroke | Removes the “I need to remember the syntax” barrier to capture; orthogonal to everything else; cheap | Low | Weak: UX win, easy to copy |
| 6 | Portable identity prototype (me.synapse): syn identity export|import|inspect; manifest declares the self person; optional ed25519 signing | See §6 Bet 1 expanded; owner greenlit prototype in v0.3 on 2026-05-25 | Medium | Strong: see Bet 1 explanation |
Explicit non-goals for v0.3
- Embeddings / semantic search (v0.5).
syn agentautonomous loop (v0.4).- TUI /
syn ui(backlog). syn-lspLSP companion (v0.4 or v1.0).- Schema declarable per workspace (v0.4 or v1.0).
- HTTP / SSE MCP transport (backlog).
- Full Slack / browser-history ingestion (deliberately rejected in §2.5).
6. Disruptive bets
Three to five non-obvious ideas the SaaS lane cannot easily copy:
Bet 1: Portable knowledge identity (me.synapse bundle)
Problem. A consultant or senior IC working across N employers / clients has their professional identity fragmented and re-typed in every workspace. cliente-alfa has person/romulo-febasi with email + role + expertise; cliente-beta has a re-typed version; personal-brain has a third. When the engagement ends, the professional memory (projects touched, decisions signed, expertise validated) stays trapped in a workspace that does not travel with them. Glean’s Personal Graph solves this conceptually but is SaaS: tied to the employer’s Glean subscription and lost at exit. LinkedIn is coarse, public, and not typed.
Solution. A signed bundle (~/me-romulo.synapse, ~10KB) carrying:
- The human’s canonical
personentity (email, social handles, bio, skills as tags). - Attestations of past involvement: “I worked on
api-pagamentosatcliente-alfain 2025 as tech-lead”: signed with an ed25519 key the human owns. - A history of roles that survives employment changes.
Flow.
# One-off: generate the bundle from the personal workspace
syn identity export --out ~/me-romulo.synapse
# In every new workspace
cd ~/work/cliente-novo
syn init
syn identity import ~/me-romulo.synapse
# → creates person/romulo-febasi pre-populated
# → links attestations as inbound references (read-only)
The MCP server surfaces that person as immediately-available context: no re-typing.
Why SaaS structurally cannot copy.
| Differentiator | Synapse | Glean | |
|---|---|---|---|
| Human owns the data | ✓ | ✗ (org-bound) | ✗ (public, platform-bound) |
| Crypto-signed verifiable attestations | ✓ | ✗ | ✗ |
| Works offline / no account | ✓ | ✗ | ✗ |
| Typed entities (not just “profile”) | ✓ | partial | ✗ |
| Survives an employment change | ✓ | ✗ | ✓ (coarse only) |
| Federates into workspaces without giving itself up | ✓ | ✗ | ✗ |
v0.3 scope (prototype).
syn identity export --out FILE: bundle generation from the workspacepersonthat the manifest marks asself: true.syn identity import FILE: create / update thepersonin the current workspace; attestations attach as inbound read-only references.syn identity inspect FILE: clear-text dump for audit (no crypto required to inspect).- Optional ed25519 in v0.3: bundles without a signature import as
unverified; signed bundles verify against a pinned public key. Mandatory signing lands in v0.4. - Workspace manifest gains
identity.consumes: [bundle-path]to declare federation intent. Read-only.
Out of v0.3 (deferred).
- Attestation revocation (v0.4).
- Sigstore-style transparency log (v0.5+).
- Cross-bundle merge (multiple bundles federated).
- HTTP discovery of bundles (backlog).
Bet 2: Decision-trace frontmatter as a first-class entity field
Every accepted AI proposal stamps the resulting markdown with:
x-ai-source: mcp:capture_fact
x-decision-trace-id: 0193af-...-a4b7
x-prompt-hash: sha256:f8e2c1...
x-model: anthropic/claude-opus-4-7
x-accepted-at: 2026-05-25T15:30:00Z
x-accepted-by: romulo@febasi.com.br
- Becomes the EU AI Act Article 50 receipt and a queryable edge in the graph.
- SaaS audit logs are vendor-locked; Synapse’s is a
git diff. - Stamps respect a
[provenance] inline = falseflag to keep the markdown clean when the user prefers the audit log alone.
Bet 3: Cross-workspace “stale + uncommitted” advisory
$ syn audit stale --over 14d
Across 7 workspaces, you have 4 open commitments older than 14 days:
[client-alfa] task/migracao-pagamentos 21d high risk: focal: maria-silva
[work-brain] project/refactor-auth 17d medium risk: no recent linked notes
[personal-brain] note/contratar-juniors 16d low risk
[client-beta] api/webhooks-v2 14d unknown: last contact unclear
- No SaaS sees across workspaces it does not own.
- Builds on federation + freshness primitives already shipped in v0.2.
Bet 4: Capability disclosure manifest at .synapse/server-card.json
Each workspace publishes what is readable cross-workspace and what is redacted:
{
"name": "client-alfa",
"exposes": ["project", "api", "person"],
"redacts": { "tags": ["confidential"], "types": ["note"] },
"federation_protocol": "synapse/v1"
}
- Aligns with MCP SEP-1649 trajectory, adds workspace-level capability scopes, a primitive no current MCP server has.
- Other Synapse instances negotiate the federation handshake reading this card.
Bet 5: syn pending replay <id>
Re-runs the original prompt against the original context snapshot stored alongside the proposal; shows the diff between the original output and the new one.
- Catches model drift, gives EU-AI-Act-grade reproducibility.
- SaaS cannot ship this: they do not keep the context snapshot client-side.
- Requires a new
pending.context_snapshotcolumn (added in SYN-114).
7. Ticket sketch (provisional IDs SYN-110..SYN-129)
These are sketches, not GitHub issues. Issues open only when the milestone is greenlit.
| Sketch ID | Block | Title | Size |
|---|---|---|---|
| SYN-110 | Audit | audit.ndjson schema + atomic appender + rotation | M |
| SYN-111 | Audit | Frontmatter stamps x-ai-* on accepted proposals | S |
| SYN-112 | Audit | syn audit show / list / prune CLI | S |
| SYN-113 | Audit | syn audit replay <id> (depends on SYN-114) | M |
| SYN-114 | Audit | pending.context_snapshot column + payload | S |
| SYN-115 | MCP | Resources surface (workspace/manifest, entity/{id}, audit/recent) | M |
| SYN-116 | MCP | Prompts (capture-decision, daily-review, weekly-brief) | M |
| SYN-117 | MCP | .synapse/server-card.json + capability disclosure schema | S |
| SYN-118 | MCP | Tasks extension on reindex (feature-gated) | M |
syn ingest github <repo> | - | ||
syn ingest linear <team> | - | ||
| SYN-121 | Time | syn timeline <slug> (git log filtered by entity path) | S |
| SYN-122 | Time | syn at <ref> -- <query> (subset: search + show) | M |
| SYN-123 | Stale | syn audit stale cross-workspace ranking | M |
| SYN-124 | Capture | syn capture (stdin → pending capture_fact) | S |
| SYN-125 | Identity | me.synapse bundle export / consume (prototype) | L |
| SYN-126 | ADRs | ADR-012: provenance frontmatter convention | M |
| SYN-127 | ADRs | ADR-013: audit.ndjson format + retention | M |
| SYN-128 | ADRs | ADR-014: portable identity bundle (me.synapse) | M |
| SYN-129 | Docs | docs/AUDITABILITY.md + docs/PROVENANCE.md | M |
Total after cuts: 18 sketched tickets in v0.3 (SYN-119 and SYN-120 deferred). One ticket carried to v0.4 in parallel: SYN-130: syn-lsp LSP companion (completions for slugs / types / tags in any editor that speaks LSP). v0.4 milestone opened so the issue has a home.
Estimate 4-6 weeks of focused execution at the v0.2 pace if the 2026-08-02 deadline lock-in holds.
8. Risk register
| Risk | Severity | Mitigation |
|---|---|---|
| Scope creeps past the 2026-08-02 EU AI Act window | HIGH | Cut blocks #5 (stale advisory) and #6 (capture) if behind schedule. Blocks #1–#4 are the core auditable and must ship. |
audit.ndjson grows without bound | MEDIUM | Default 90-day rotation per workspace; explicit syn audit prune |
| MCP spec 2026-07-28 is still RC at landing time | MEDIUM | Implement only parts marked stable in the RC: resources + prompts + server-card. Tasks ships behind a feature flag. |
| Decision-trace frontmatter pollutes user-edited markdown | MEDIUM | All stamps under x-ai-* namespace (extension-safe). [provenance] inline = false config moves stamps to audit.ndjson only. |
| GitHub / Linear ingestors become cloud-API dependencies | HIGH | Prefer user-side CLI delegation (gh, linear); never store API tokens; if HTTP is unavoidable, document the trust boundary clearly. |
| EU AI Act enforcement is delayed | LOW | Audit + provenance are still differentiating regardless of the deadline; the theme survives the schedule slip. |
me.synapse portable identity bundle leaks signed claims | HIGH | Sign only at export time; bundle is opt-in per command; clear-text inspection always possible (syn identity inspect <bundle>). |
| Ingestor adapters become a maintenance treadmill | MEDIUM | Each adapter ships in its own module with its own test fixture; CI gates a --no-network mode that runs against canned API responses. |
9. Open decisions
All resolved on 2026-05-25:
| # | Question | Decision |
|---|---|---|
| 1 | Deadline lock-in for the core auditable | Target 2026-08-02 (EU AI Act Article 50) for blocks #1, #2, #3 + ADRs |
| 2 | Portable identity scope in v0.3 | Prototype ships in v0.3 (export / import / inspect + optional ed25519). Mandatory signing + revocation push to v0.4. |
| 3 | Ingestor strategy | Cut from v0.3. External-API dependency conflicts with local-first invariant. Defer to post-v0.5 when the surface stabilises. |
| 4 | Version number | v0.3.0 (no skip). |
| 5 | Milestone opening | Open immediately: milestone created and 17 issues filed on 2026-05-25 along with the v0.4 placeholder milestone holding syn-lsp. |
The brief is accepted and actioned.
10. References
MCP ecosystem
- The 2026 MCP Roadmap: official roadmap blog.
- MCP 2026-07-28 release candidate: RC notes for the spec window v0.3 targets.
- MCP server discovery:
.well-known/mcp.json2026 guide (Ekamoira): SEP-1649 implementation walkthrough. - SEP-1649:
.well-known/mcpdiscovery (GitHub issue): spec discussion. - MCP ecosystem v1.27: Context Studios: ecosystem state.
Auditability and regulation
- Glean: Audit trail compliance for sensitive data: enterprise audit benchmark.
- Notion audit log: what Notion captures.
- EU AI Act Article 50: transparency obligations (HSF Kramer, 2026-03): legal analysis.
- EU AI Act August 2026 (Bria): pragmatic enterprise checklist.
- Human-in-the-loop AI agents: approvals 2026 (getclaw): batch approval as the new sweet spot.
Knowledge graphs and context
- Glean knowledge graph: incumbent SaaS reference.
- Glean doubles ARR to $200M (Futurum): competitive scale.
- Backstage vs Cortex vs Port: developer portal comparison 2026: engineering knowledge graph landscape.
- Enterprise search for engineering teams (Onyx): orthogonal incumbent.
- Context graphs: Foundation Capital: investor framing.
- Gartner context graphs (Atlan summary): analyst framing.
Integrations and adoption
- CodeRabbit Slack Agent launch (BusinessWire, 2026-04): adjacent product launch and adoption signal.
- AI browser landscape 2026 (DigitalApplied): Arc successors and capture patterns.
Pain points and ergonomics
- Tana alternatives 2026 (Storyflow): competitor pain-point coverage.
- Context switching costs (WaymakerOS): quantitative reference for multi-context tax.
v0.4: Refinement & extensibility: finish what we started
| Field | Value |
|---|---|
| Date finalised | 2026-05-25 |
| Author | Rômulo Febasi <romulo@febasi.com.br> |
| Scope | Roadmap brainstorm for Synapse v0.4.0: the consolidation cycle that closes v0.3’s acknowledged debts and opens the editor/companion extensibility surface, before v0.5’s semantic layer changes the centre of gravity. |
| Status | shipped 2026-05-26: 18/18 issues (12 core + 6 stretch); milestone #6 closed; tag v0.4.0 pending |
| Decisions log | Owner answered the §9 open questions on 2026-05-25: (1) cut syn agent (autonomous loop with batch proposals) from v0.4: defer to v0.6 so it lands after the semantic layer matures; (2) keep numbering as v0.4.0; (3) open milestone + issues immediately. The “refinement-first” framing was greenlit explicitly: v0.4 closes v0.3 dívidas before adding new product surface. |
| Inputs | The v0.3 brief (2026-05-25-v0.3-multi-context-developer.md); audit of every “deferred to v0.4” / “limitação documentada” / “follow-up” reference inside the v0.3 code, ADRs (012, 013, 014), and issue comments; the live v0.4 milestone (#6) which already held SYN-130 (syn-lsp) and SYN-150 (mcp/server.rs split) before the brief; internal critique of v0.3 ship vs promise. |
| Out of scope | Semantic search and embeddings (v0.5, gated by --features vector). syn agent autonomous loop (moved to v0.6). GitHub/Linear ingestors (deferred post-v0.5 by owner during v0.3). Sigstore-style transparency log, cross-bundle merge, HTTP discovery of identity bundles (v0.5+). Schema declarable per-workspace (v1.0 candidate). TUI / syn ui (backlog). Share entity granular (backlog). |
Executive summary
v0.3 shipped 18/18 tickets under EU AI Act deadline pressure. Three categories of work landed with explicit, documented limitations: accepted at the time as honest scope reductions, but they leave the product feeling “mal acabado” in production review: (a) syn audit replay does not actually re-run the model; (b) the identity bundle prototype has no mandatory crypto and no revocation; (c) MCP Tasks is a feature-flagged placeholder. Plus four primitives shipped half-way (tag-level federation redaction, syn at <ref> missing graph/list/info, resources pagination, x-ai-history on re-mutations).
v0.4 closes those debts before v0.5 changes the centre of gravity. Theme: “Refinement & extensibility: finish what we started, open the doors.” Two extensibility tracks join the cleanup: the mcp/server.rs split (ADR-015 mandates it) and the syn-lsp editor companion (highest-leverage adoption play that is not SaaS-shaped).
A bonus disruptive thread that the owner greenlit during the v0.3 ADR-014 review surfaces here as first-class: the portable identity bundle becomes a curriculum surface: syn experience add, skills auto-derived from workspace tag frequency, syn cv export rendering a deterministic Markdown CV from data the human already owns.
1. Context and premisses
What state is assumed (post-v0.3.0)
- v0.3.0 shipped 2026-05-25 with: 13 MCP tools + 4 resources + 3 prompts +
mcp_tasksfeature flag; audit trail (.synapse/audit.ndjson) with 90-day rotation;x-ai-*provenance stamps with[provenance] inline = falseescape; portable identity bundle prototype (syn identity export|import|inspect); capability disclosure (.synapse/server-card.json); time-travelsyn timeline+syn at <ref>subset; cross-workspacesyn audit stale;syn capture. - 99 tests green, clippy clean, cargo fmt clean. CI: release + mirror pipelines work; pre-push hook now enforces
cargo fmt --checkper the 2026-05-25 CI fix. - ADR index covers 001-015 (008 documented gap, 015 sets the file-size policy).
- Cargo.toml at
0.3.0. Milestone#6(v0.4) already holds two issues: SYN-130 (syn-lsp) and SYN-150 (mcp/server.rssplit).
What is explicitly off-limits in this brief
- Semantic search / embeddings: reserved for v0.5 behind
--features vector(SYN-80..89). syn agentautonomous loop with batch proposals: owner moved to v0.6 on 2026-05-25 so it lands on top of mature semantic search, not before.- GitHub / Linear ingestors: deferred post-v0.5 during v0.3 because external-API tokens conflict with local-first invariant.
- Sigstore transparency log, cross-bundle merge, HTTP discovery: deferred from ADR-014; revisit when there is real demand.
- Schema declarable per-workspace: v1.0 candidate; architectural cost outweighs current need.
- TUI /
syn ui, share entity granular: cut from v0.3 by the market scan; no signal to reverse. - Cursor / Copilot territory: Synapse remains the business context layer.
Triggers for this brief
- v0.3 closed 18/18 tickets under deadline pressure; several known limits stayed documented but unimplemented (audit replay, identity crypto, MCP Tasks placeholder, etc.). Production review of any of these blocks would fail.
- SYN-150 (mcp/server.rs split, ADR-015) was filed during v0.3 and blocks every new MCP feature in v0.4 until it lands.
- SYN-130 (
syn-lsp) was deferred to v0.4 with owner greenlight; it is the cheapest possible IDE integration play and the time to ship it is now. - Owner flagged in ADR-014 §Future direction that the identity bundle has a natural evolution into a curriculum-like surface: schema kept open precisely so that v0.4 (or later) could pick it up. v0.4 picks it up.
2. Audit of v0.3 limitations (the “mal acabado” inventory)
This section is the load-bearing piece of this brief. It is not a market scan: v0.4 is consolidation, not new-frontier. It is a systematic audit of every limitation v0.3 left documented, with severity and the v0.4 ticket that addresses it.
2.1 Audit / Provenance debt
| Item | Where it was deferred | Severity | v0.4 ticket |
|---|---|---|---|
syn audit replay does not re-run the model: only shows payload + snapshot + visual diff | cli/audit.rs doc, AUDITABILITY.md §2, SYN-113 commit | High: Bet 5 of v0.3 brief promised re-run; we shipped half | SYN-136 |
--no-network flag accepted as no-op | cli/audit.rs | Cosmetic until --execute ships | (subsumed by SYN-136) |
x-ai-history for entities mutated twice: second accept overwrites stamps; full history lives only in audit trail | ADR-012 §Open question | Medium: breaks repro for sequential mutations | SYN-137 |
prev_hash Merkle chain in audit | ADR-013 §Consequences | Low: slot reserved, no external demand | stays deferred to v0.5+ |
syn audit verify: re-hash pending payloads against audit.payload_sha256 | Not implemented in v0.3 | High: without verify, the hash is decorative; CI cannot gate | SYN-135 |
Stale ranking heuristic hand-tuned in risk_for() | cli/audit.rs | Medium: works, can be refined with richer signals (commits, blame) | stays as-is until user demand |
syn doctor --lint-provenance: validate consistency of x-ai-* stamps vs audit row | Not implemented | Medium: detects manual tampering or drift | SYN-146 |
2.2 MCP debt
| Item | Where it was deferred | Severity | v0.4 ticket |
|---|---|---|---|
MCP Tasks placeholder: mcp_tasks feature flag declares intent, does NOT implement progress reporting | SYN-118 commit + get_info().instructions | High: advertised but unfinished; users discover the gap | SYN-138 |
| Resources pagination via cursor | list_resources in mcp/server.rs | Medium: workspaces with thousands of entities suffer | SYN-139 |
| Resource templates (URI templates per spec) | Not implemented | Low: current per-entity listing works | stays deferred |
Tag-level federation redaction: only type-level shipped; SearchHit does not carry tags | SYN-117 commit | High: Bet 4 promised tags + types; only types shipped | SYN-140 |
2.3 Time-travel debt
| Item | Where it was deferred | Severity | v0.4 ticket |
|---|---|---|---|
syn at <ref> only search + show; graph, list, info subcommands missing | SYN-122 commit explicit | Medium: closes the surface | SYN-141 |
| Full as-of FTS rebuild: today filters current hits, NOT rebuilds index | SYN-122 commit + AUDITABILITY.md | High: current shape is misleading for compliance review | SYN-142 |
syn timeline --all (project-wide, not per-entity) | Not implemented | Low: workflow option | stays deferred |
2.4 Identity bundle debt
| Item | Where it was deferred | Severity | v0.4 ticket |
|---|---|---|---|
Mandatory ed25519 signing + syn identity keygen | ADR-014 §v0.3 explicit non-goals | High: without crypto the bundle verifies nothing | SYN-131 |
| Attestation revocation surface | ADR-014 | Medium: required before real-world use | SYN-131 (combined) |
Curriculum-like surface (syn experience add, auto-derived skills, exportable CV) | ADR-014 §Future direction; owner-flagged | High: owner asked explicitly, schema already kept open | SYN-132, SYN-133, SYN-134 |
| Cross-bundle merge | ADR-014 | Low | stays deferred to v0.5+ |
| HTTP discovery | ADR-014 | Backlog | stays deferred |
| Sigstore-style transparency log | ADR-014 | Low: no consumer asking | stays deferred to v0.5+ |
2.5 Federation debt
| Item | Status | Severity | v0.4 ticket |
|---|---|---|---|
| Cross-workspace write entity references (D3 v0.3: “kept read-only”) | Read shipped via SYN-117; write deferred | Medium | stays deferred: needs more user-side demand |
syn ws clone <git-url> lifecycle | Not implemented | Medium: adoption / portability story | SYN-143 |
syn ws fork, syn ws archive | Not implemented | Low | stays deferred |
2.6 Extensibility (new in v0.4)
| Item | Status | Source |
|---|---|---|
syn-lsp editor companion (slug / type / tag completions) | Already filed | SYN-130 (v0.4 milestone since 2026-05-25) |
mcp/server.rs refactor into submodules (ADR-015) | Already filed | SYN-150 (v0.4 milestone since 2026-05-25) |
2.7 Documentation debt
| Item | Status | v0.4 ticket |
|---|---|---|
docs/IDENTITY.md user-facing (covers bundle + curriculum) | Not written | SYN-149 |
| ADR-016: portable identity v2 (mandatory crypto + revocation) | Not written | SYN-147 |
| ADR-017: curriculum schema (experience kind, skill derivation, CV format) | Not written | SYN-148 |
3. Cross-check vs prior hypotheses
This brief was written against the v0.3 brief. The cross-check is shorter than v0.3’s because the goal is closing v0.3’s loops, not re-litigating direction.
| v0.3 deferral | Status in v0.4 | Reason |
|---|---|---|
| Block A: Time & audit | Completion: SYN-135 / 136 / 137 / 141 / 142 / 146 close the gaps | All flagged High severity in §2 |
| Block B: MCP resources + prompts + tasks | Completion: SYN-138 (Tasks real), SYN-139 (pagination), SYN-140 (tag redaction) | All flagged High/Medium in §2 |
| Block C: Ingestors | Stays deferred (post-v0.5) | Owner decision 2026-05-25: external-API conflict |
| Block D: Briefings | Stays as v0.3 shipped (syn audit stale is the briefing surface) | No new signal |
| Bet 1: Portable identity | Completion + expansion: SYN-131 (crypto + revoke), SYN-132 (experience), SYN-133 (CV) | Owner-flagged future direction in ADR-014 |
| Bet 2: Decision-trace frontmatter | Already shipped; refinement via SYN-137 (x-ai-history) | Open question from ADR-012 |
| Bet 3: Stale advisory | Already shipped; no change | Heuristic refinement out of scope |
| Bet 4: Capability disclosure | Shipped type-level; completion via SYN-140 (tag-level) | Was promised in bet, half-shipped |
Bet 5: syn audit replay | Shipped read-only; completion via SYN-136 (real --execute) | Was promised in bet, half-shipped |
D2: syn agent | Cut from v0.4 to v0.6 (owner decision 2026-05-25) | Lands after semantic layer matures |
syn-lsp (D4 in v0.3 cross-check) | Already filed as SYN-130 | Owner greenlit during v0.3 |
New thread (not in v0.3 brief)
mcp/server.rsrefactor (SYN-150) per ADR-015: file crossed 1,100 lines during v0.3. Architecture is now load-bearing; refactor lands first in v0.4 to unblock every other MCP-touching ticket.
4. Positioning
v0.4 does not move Synapse on the competitor matrix; the v0.3 matrix already captured the lanes Synapse occupies. v0.4 deepens the lanes:
| Axis | v0.3 state | v0.4 target |
|---|---|---|
| Auditable AI writes (EU AI Act-grade) | partial: replay is visual diff, no model re-run; no verify | full: syn audit verify gates CI; syn audit replay --execute round-trips through MCP client |
| Portable knowledge identity | prototype: no crypto, no revoke, no CV | production-grade: ed25519 mandatory, revocation list, syn cv export |
| Cross-workspace capability disclosure | type-level | tag + type level (SYN-140) |
| MCP-native surface (resources / prompts / tasks) | resources + prompts; Tasks placeholder | Tasks real (SYN-138); pagination (SYN-139) |
| Editor integration | none (syn open only) | LSP companion (SYN-130): completions in Cursor, Helix, Neovim, VSCode, Zed |
| Internal architecture | mcp/server.rs red-tier per ADR-015 | split per use case, all files green-tier (SYN-150) |
| Curriculum surface | none | first-class: syn experience add, skills, CV |
Defensible moat preserved. No competitor can ship “local-first auditable AI second brain that the human owns across employers”; v0.4 closes the half-shipped edges of that pitch and lets the user prove it (verify hash, re-run prompt, export CV) without trusting Synapse on faith.
5. Recommendation
Theme
“Refinement & extensibility: finish what we started, open the doors.”
Prioritised blocks
| # | Block | Justification | Cost | Defensible? |
|---|---|---|---|---|
| 1 | Refactor mcp/server.rs (SYN-150) | ADR-015 mandates it; blocks every other MCP ticket by merge-conflict risk. Lands FIRST. | L | Architecture: internal, no marketing |
| 2 | Identity completion: ed25519 keygen + mandatory signing + revocation (SYN-131); syn experience add + skills auto-derive (SYN-132); syn cv export Markdown (SYN-133); ADR-016 + ADR-017; docs/IDENTITY.md (SYN-149) | Bundle without crypto verifies nothing. Curriculum surface owner-flagged in ADR-014. Closes the bet that v0.3 only sketched. | L | Very strong: no SaaS can ship CV from data the human owns |
| 3 | Audit completion: syn audit verify (SYN-135); syn audit replay --execute (SYN-136); x-ai-history chain (SYN-137); syn doctor --lint-provenance (SYN-146) | Closes EU AI Act bet to real replay + hash verification. Without these, the audit story is decorative. | M+L | Strong: git-diff-able + CI-gateable |
| 4 | syn-lsp companion (SYN-130) | Cheapest possible IDE integration; one binary, every LSP editor. Adoption play. | L | Medium: anyone can build, but Synapse owns the workspace semantics |
| 5 | MCP completion: Tasks real (SYN-138); resources pagination (SYN-139); tag-level federation redaction (SYN-140) | All flagged High severity in §2 audit. Closes promises made on the v0.3 wire. | M+M+M | Strong: MCP-native is the moat |
| 6 | Time-travel completion: syn at <ref> graph/list/info (SYN-141); full as-of FTS rebuild (SYN-142) | Closes the time-travel surface. Compliance review of v0.3 shape exposes the “current hits at ref” misleading. | M+L | Medium: only meaningful in git-tracked workspaces |
| 7 | Workspace lifecycle starter: syn ws clone <git-url> (SYN-143) | Aligns with portable-identity story (human carries graph across machines). Forks / archives stay deferred. | M | Medium |
Explicit non-goals for v0.4
Same as §1.2 plus reinforced:
syn agentautonomous loop → v0.6 (owner decision).- Semantic search / embeddings → v0.5.
- GitHub / Linear ingestors → post-v0.5.
- Sigstore transparency log, cross-bundle merge, HTTP discovery → v0.5+.
- TUI /
syn ui, share entity granular → backlog. - Schema declarable per-workspace → v1.0 candidate.
6. Disruptive bets
Most of the disruption is already inside the recommended blocks (curriculum surface, real audit replay, mandatory crypto). Two adjacent bets worth keeping on the radar but not scoped for v0.4:
Bet 1: syn cv export --as pdf (v0.5 candidate)
Markdown CV ships in v0.4 (SYN-133). PDF rendering depends on a deterministic Markdown→PDF path: adding it now risks pulling in a heavy chain (typst, weasyprint, headless chromium). Reassess in v0.5 once the Markdown surface stabilises and user feedback narrows the formatting expectations.
Bet 2: syn audit verify as a GitHub Action
v0.4 ships syn audit verify as a local CLI returning a non-zero exit code on hash mismatch. A natural follow-up is a tiny GitHub Action wrapper that runs it in PR / nightly cron and posts a status check. Out of v0.4 (action / marketplace publication is its own work), but the CLI shape stays compatible.
7. Ticket sketch (provisional IDs SYN-130..SYN-149 + SYN-150)
These are sketches, not GitHub issues. Issues open with the milestone.
Core (must land for v0.4 to be honest)
| Sketch ID | Block | Title | Size |
|---|---|---|---|
| SYN-150 | Refactor | mcp/server.rs split into submodules (ADR-015) | L |
| SYN-130 | Extensibility | syn-lsp companion (slug/type/tag completions in any LSP editor) | L |
| SYN-131 | Identity | syn identity keygen + ed25519 mandatory signing + revocation list | L |
| SYN-132 | Identity | syn experience add + skills auto-derive from tag frequency | M |
| SYN-133 | Identity | syn cv export Markdown deterministic from bundle + workspace | M |
| SYN-135 | Audit | syn audit verify: re-hash pending vs audit.payload_sha256, CI-friendly | M |
| SYN-136 | Audit | syn audit replay --execute: real model re-run via MCP client | L |
| SYN-138 | MCP | MCP Tasks real (reindex + federated_search progress reporting) | M |
| SYN-141 | Time | syn at <ref> -- graph|list|info subcommands | M |
| SYN-147 | ADRs | ADR-016 portable identity v2 (mandatory crypto + revocation) | M |
| SYN-148 | ADRs | ADR-017 curriculum schema (experience kind, skill derivation, CV format) | M |
| SYN-149 | Docs | docs/IDENTITY.md user-facing + curriculum section | M |
Stretch (lands if window allows; otherwise rolls to v0.5)
| Sketch ID | Block | Title | Size |
|---|---|---|---|
| SYN-137 | Audit | x-ai-history chain on entity re-mutations | M |
| SYN-139 | MCP | MCP resources pagination via cursor | M |
| SYN-140 | MCP | Tag-level federation redaction (SearchHit carries tags) | M |
| SYN-142 | Time | Full as-of FTS index reconstruction (sandbox index) | L |
| SYN-143 | Lifecycle | syn ws clone <git-url> | M |
| SYN-146 | Audit | syn doctor --lint-provenance | M |
Total: 18 sketched tickets (12 core + 6 stretch). SYN-150 lands first to unblock every other MCP-touching ticket. Identity (SYN-131, 132, 133) is the second-priority lane because it carries the most owner-flagged work and the most disruptive surface (curriculum).
Estimate 6-8 weeks for core at the v0.3 pace; +2-3 weeks for stretch.
8. Risk register
| Risk | Severity | Mitigation |
|---|---|---|
| Scope creeps past 8 weeks | High | Stretch tickets (SYN-137/139/140/142/143/146) are explicit cuttables; core ships even if stretch slips to v0.5. |
| SYN-150 refactor introduces regressions in MCP wire surface | High | Acceptance criteria already require byte-stable JSON for tools/list, prompts/list, resources/list. Smoke compares before/after with jq. |
syn audit replay --execute (SYN-136) needs an MCP client roundtrip: clients differ in how they accept server-initiated calls | High | Implement against the rmcp 1.7 sampling API where supported; document explicit “not available with this client” fallback. |
| ed25519 mandatory signing breaks v0.3 bundle import path | High | v0.4 import accepts unsigned bundles with explicit --allow-unverified and a stderr warning. Export-side always signs. ADR-016 documents the migration. |
| Curriculum surface (SYN-132/133) ambition exceeds v0.4 | Medium | Markdown-only syn cv export; PDF deferred. Skills auto-derive uses only tag frequency in v0.4; richer signals (project depth, decision count) deferred. |
| MCP Tasks spec still RC at v0.4 ship time | Medium | Keep mcp_tasks feature flag opt-in. Implement against the RC as-shipped; document the version pinned in ADR-016 footnote. |
syn ws clone <git-url> shells out to git (CLAUDE.md forbids) | Low | Use gix::clone not subprocess; already in dep tree for SYN-68. |
syn experience add overlaps with capture_fact semantically | Medium | syn experience writes the attested_involvement array on the self: true person; capture_fact writes free-form entities. Different storage, different scope. ADR-017 spells it out. |
9. Open decisions
All resolved on 2026-05-25:
| # | Question | Decision |
|---|---|---|
| 1 | syn agent autonomous loop in v0.4? | Cut. Lands in v0.6 on top of semantic layer. |
| 2 | Curriculum surface (syn cv export): Markdown only or include PDF? | Markdown only in v0.4. PDF is a v0.5 candidate. |
| 3 | Identity v2: break v0.3 import path or stay backward-compatible? | Stay backward-compatible via --allow-unverified flag. ADR-016 documents the migration with explicit warnings. |
| 4 | Version number | v0.4.0 (no skip). |
| 5 | Milestone opening | Open immediately. Milestone #6 already exists; this brief fills it. |
The brief is accepted and actioned.
10. References
Inputs internos
- v0.3 brief: every “deferred to v0.4” cross-referenced.
- ADR-012 Provenance frontmatter: §Open question (x-ai-history).
- ADR-013 audit.ndjson: §Consequences (prev_hash deferred),
audit verifyimplicit. - ADR-014 Portable identity bundle: §Future direction (curriculum surface owner-flagged), §v0.3 explicit non-goals.
- ADR-015 File-size policy: SYN-150 mandate.
docs/AUDITABILITY.md: Phase 1 limitation onsyn at <ref>is documented and now closed by SYN-141 / SYN-142.docs/business/ROADMAP.md: v0.4 placeholder.docs/business/BUSINESS_RULES.md§12: no rule change required by v0.4.
Inputs externos (carry-over from v0.3 brief)
- MCP 2026-07-28 RC: Tasks extension spec watch (SYN-138).
- SEP-1649 .well-known/mcp discovery: capability disclosure trajectory (SYN-140 tag-level).
- EU AI Act Article 50:
syn audit verify(SYN-135) and replay (SYN-136) are the regulatory follow-throughs.
v0.4 closure & roadmap review: what shipped, what slipped back, what comes next
| Field | Value |
|---|---|
| Date finalised | 2026-06-02 |
| Author | Rômulo Febasi <romulo@febasi.com.br> (analysis assisted by Claude Code) |
| Scope | Post-mortem read of the v0.4.0 release (2026-05-27): what landed, what architectural debt re-grew, what the README failed to advertise, and the prioritised next-three-actions before v0.5 code starts. |
| Status | accepted: input to the v0.5 brief and to the README §Status refresh (commit b37a418) |
| Inputs | Repository walk on 2026-06-02; find src/ -name '*.rs' | xargs wc -l; cross-read of ADR-015 vs current file sizes; CHANGELOG.md from the auto-generated v0.4.0 section; live gh issue list --milestone "v0.5 Semantic layer"; live gh issue list --milestone "v1.0 Stable". |
| Out of scope | The v0.5 brief itself (separate document, same day). Code changes: only audit + recommendation. |
Executive summary
v0.4 shipped 18/18 tickets in two weeks (12 core + 6 stretch), four releases in that window, zero rollback. The product moved from “auditable AI second brain” to “auditable AI second brain with cross-editor LSP + portable identity v2 + real MCP sampling”. For a solo project that is delivery above the curve.
But the ride exposed three honest items that need to be flagged before v0.5
code begins: (1) src/mcp/server.rs regrew to 844 lines, breaking the
red-tier threshold of the very ADR-015 that v0.4 wrote to contain it: SYN-150
split the file, then SYN-136 (replay_proposal) and SYN-138 (reindex)
brought ~300 lines of new handlers back in. (2) the README §Status only listed
v0.1 + v0.2 until 2026-06-02: three releases were invisible to anyone
landing on the repo, including the entire LSP companion that is the most
differentiated single artefact of v0.4. (3) v0.5’s GitHub milestone has 11
issues but no brief: v0.4 worked because it had one; starting v0.5 code
without one is committing to architectural decisions inside the largest
backend module rewrite of the project so far.
1. Context and premisses
- v0.4.0 tagged + pushed 2026-05-27 03:31; mirror release synchronised 12 s
later (run
26489555101). Cross-platform assets present for aarch64 + x86_64 macOS, aarch64 + x86_64 Linux GNU, x86_64 Windows MSVC. Smoke test of the public mirror binary on the author’s laptop (aarch64-apple-darwin) confirms 15 MCP tools advertised includingreindexandreplay_proposal. - 121 unit tests pass on the full feature matrix (
mcp lsp mcp_tasks); zero clippy warnings under-D warnings; cargo doc clean. - v0.5 milestone holds 11 issues (
SYN-80..SYN-89+SYN-93bnetwork freshness) filed during the original v0.5 planning sprint in 2026-05-24. - v1.0 milestone holds 10 issues (
SYN-100..SYN-109). - No code changes are being proposed in this document, the changes that matter (re-split, brief, benchmark spike) are recorded as ticket sketches for the v0.5 brief that ships next to this file.
2. Architectural read: file by file
2.1 Red tier (>800 lines per ADR-015): one file
| Path | Lines | Notes |
|---|---|---|
src/mcp/server.rs | 844 | Carries 15 #[tool] handlers + ServerHandler impl + resource/prompt routing. SYN-150 reduced it from ~1380 → ~550 on 2026-05-25; SYN-136 added replay_proposal (~120 lines including the sampling builder), SYN-138 added reindex with the tasks progress callback shim (~80 lines), SYN-140 added the Redactable wiring at the call sites (~10 lines), plus the docstring expansion. Net: back to red tier two weeks after the split. |
What this means: ADR-015’s playbook (split when red, target < 500) was
followed once and then quietly disregarded by every subsequent MCP feature.
The pattern is recurring because #[tool_router] in rmcp 1.7 mandates one
impl block: every tool handler has to land in the same file, so the
attractor pulls every new MCP feature back here. The split that holds long
term is not a second pass of the same kind (extracting helpers) but a
dispatcher pattern: keep #[tool] shims thin (3–8 lines each) and move
all logic to core::mcp::* use case modules outside the macro’s reach.
2.2 Yellow tier (500–800 lines): five files
| Path | Lines | Trajectory |
|---|---|---|
src/core/identity/mod.rs | 650 | Stable. Concentrated crypto + bundle serde + canonical JSON. experience.rs already extracted. Split if v0.5 adds curriculum endorsements: otherwise leave it. |
src/cli/at.rs | 620 | Grew 280 lines in v0.4 (SYN-141 list/info/graph + SYN-142 sandbox wiring). Candidate for cli/at/{mod,search,show,list,info,graph}.rs split. |
src/cli/audit.rs | 611 | list/show/prune/stale/verify/replay. Six subcommands in one file. Candidate for cli/audit/{listing,verify,replay,stale,prune}.rs split. |
src/cli/ws.rs | 577 | Grew with syn ws clone (SYN-143). Still coherent. Don’t pre-emptively split. |
src/registry/mod.rs | 562 | Schema + queries + cache + freshness coexist. Stable; defer split until v0.6. |
2.3 Green tier (< 500): everything else
Notably core/at.rs (the SYN-142 sandbox builder, 339 lines) and
lsp/server.rs (437 lines) landed well within the green tier. The trait
Redactable (SYN-140) reduced what would have been ~60 lines of duplication
across three call sites into one trait + two impls. These are the v0.4 wins
that the file-size scoreboard rewards.
3. Surface-level adoption gaps
| Gap | Impact | Effort to close |
|---|---|---|
| README §Status frozen at v0.1+v0.2 until 2026-06-02 | Three releases invisible to the repo landing page; LSP companion (highest-leverage v0.4 single-feature) buried in the docs table | ✅ closed in b37a418 (commit before this doc) |
| No section “Editor integration” near top of README | The cross-editor LSP differentiator is the kind of thing visitors discover only by reading every line | ~1 h: add a short H2 with three editor logos + syn lsp --help snippet |
| No badges in README header (release, MSRV, license, CI) | Reduces credibility signal at first glance | ~30 min |
No demo asciinema / GIF for syn lsp and syn cv export | These are the v0.4 features that show well in motion and don’t show well in text | ~2 h |
Install path is cargo install only (no Homebrew, no curl pipe, no npm) | macOS first-time users are the largest single platform-bucket for Claude Code adoption, the highest-leverage friction point | SYN-101 (curl pipe) ~1 day; SYN-102 (Homebrew tap) ~2 days. Both filed under v1.0 but the cost of moving them earlier is zero. |
| No GitHub Pages docs site | README is ~150 lines and growing; new contributors land and don’t know where to start | SYN-109 v1.0 ticket: antecipar para v0.5 |
4. Strategic bets that v0.5 is choosing without a brief
There are at least four decisions baked into the v0.5 milestone issues
(SYN-80..SYN-89) that were not load-bearing-discussed before they were
opened on 2026-05-24:
- Embedding backend. ADR-008’s slot says “chosen default embedding
backend after benchmark” but no benchmark was run before the issues
were filed. Three real candidates: Ollama-as-runner (forces external
dep),
fastembed-rs(ONNX Runtime under the hood, ships small models), purecandle(HuggingFace’s Rust framework, no ONNX dependency). The trade-off: local-first vs perf vs binary size, needs a 1-day spike with numbers before SYN-83 starts. - Vector storage choice. SYN-81 says “sqlite-vec via load_extension”.
sqlite-vecis real, recent, used in production. But the combination ofrusqlite bundled+load_extensionhas known ABI gotchas (Alex Garcia documents them explicitly). If sqlite-vec fails the spike, the fallback islance(full vector DB) or a hand-rolled flat / HNSW index: both larger architectural decisions than the issue title implies. - Hybrid ranking algorithm. SYN-86 says “reciprocal rank fusion”.
RRF is the cold-start default (Elastic, Qdrant, Weaviate, OpenSearch
ship it). But 2026 papers show convex combination (`α * dense + (1-α)
- bm25`) outperforming RRF with as little as ~40 labelled relevance pairs. For a personal second-brain workspace that pair set is self-collectable. RRF is right for v0.5 ship; convex combination is the next-step polish.
- Search target. Is
syn searchhybrid by default, or vector-only behind--semantic? SYN-87 reads “vector-only mode” which suggests the second; SYN-86 reads “hybrid” which suggests the first. The two tickets are inconsistent. The brief needs to pick one.
5. Recommendation: the next three actions, ordered
| # | Action | Effort | Why it goes first |
|---|---|---|---|
| 1 | Write the v0.5 brief: docs/researchs/YYYY-MM-DD-v0.5-…: with the 4 strategic decisions above answered with reasons and a benchmark spike for the embedding backend | 2 days (1 day brief, 1 day spike) | v0.4 worked because the brief did the load-bearing thinking; v0.5 touches the largest single new module (vector index) and the biggest external choice (embedding backend): committing without a brief is committing to commit-driven design |
| 2 | File SYN-151: re-split mcp/server.rs to the dispatcher pattern (thin #[tool] shims, logic in core::mcp::*) before any v0.5 MCP feature lands | ½ day | ADR-015’s red tier exists for this exact file. Every new MCP feature in v0.5 (semantic_search per SYN-88, reindex_vectors) lands here. Pay now or pay 3× later. |
| 3 | Visibility polish: README §Editor integration section + Homebrew tap (antecipate SYN-102) + asciinema demo of syn lsp | 1–2 days | v0.4’s most differentiated single feature (cross-editor LSP) was undiscoverable until 2026-06-02. Polish converts what’s already built; it does not gate v0.5. |
6. Risk register
| Risk | Severity | Mitigation |
|---|---|---|
mcp/server.rs regrows again after the v0.5 dispatcher refactor because the macro contract still wants a single impl block | Medium | Document the dispatcher pattern in ADR-015 §Playbook so it survives the next contributor; add a CI lint that fails on mcp/server.rs > 600 lines |
| Embedding backend choice (Ollama vs fastembed vs candle) gets locked by accident through the first PR rather than by ADR | High | The v0.5 brief MUST close the open decision before SYN-83 is started. ADR-008 must land first. |
| Visibility polish slips because v0.5 code feels more urgent | Low–Medium | Make item #3 above explicitly a v0.4.1-bucket so it can ship independently of the v0.5 milestone |
tower-lsp-server (v0.4 dep, ADR-018) goes inactive like its upstream did | Low | The fork was active 2025-12; the LSP backend is ~440 lines; documented as portable to hand-rolled JSON-RPC in extremis |
v1.0 milestone has 10 issues but no plan for the x-ai-* schema freeze that v0.4 just expanded (SYN-137 added x-ai-history) | Medium | File a v1.0 ticket “SEMVER policy for the x-ai-* namespace”: SYN-104 covers frontmatter broadly but the AI provenance subset needs its own decision |
7. Open decisions for the v0.5 brief to close
- Which embedding backend: Ollama (~700 MB binary + ~280 MB model = ~1 GB
footprint, external dep) vs
fastembed-rs(~10 MB crate + ~33 MB BGE-small quantised, embedded inference via ONNX Runtime) vs purecandle(no ONNX, more flexibility, slower per-query)? Bias from the owner: local-first, no Ollama. Brief must show the trade-off with numbers. - Vector storage:
sqlite-vec(lean, in-process, recent),lance(full vector DB, heavier), or hand-rolled flat / HNSW? Spike before committing. - Hybrid ranking: RRF (cold-start safe) or convex combination (better with labelled data: does a single-user workspace have those?).
- Default mode for
syn search: hybrid by default, vector-only behind--semanticflag, or the inverse? - Insertion-time vs query-time enrichment. The owner explicitly stated
2026-06-02: “ao invés de usar puramente um LLM para isso, é organizar e
processar esses dados no momento que estamos inserindo eles”. This is a
business rule (BR-13 candidate) and changes the v0.5 architecture
meaningfully: entity linking happens on
syn project add, not onsyn search. The brief codifies this. - Adoption polish: does it ship as a hot-fix v0.4.1 cycle, parallel to v0.5, or after v0.5?
8. References
- ADR-015: modular files policy (yellow 500–800 / red 800+), the threshold
mcp/server.rsjust crossed. - ADR-018:
tower-lsp-server0.23 fork rationale. - v0.4 brief:
docs/researchs/2026-05-25-v0.4-refinement-and-extensibility.md. - CHANGELOG.md §0.4.0: list of 15 features merged in the cycle.
- README §Status: refreshed in
b37a418on 2026-06-02; this doc is the reasoning trail behind that refresh.
v0.5: Semantic layer & second-brain enrichment: make the graph think like the engineer
| Field | Value |
|---|---|
| Date finalised | 2026-06-02 |
| Author | Rômulo Febasi <romulo@febasi.com.br> (analysis assisted by Claude Code; market scan from public 2026 sources, footprint numbers measured against fastembed-rs 5.x, ort 2.x, sqlite-vec, BGE/Nomic/mxbai model cards) |
| Scope | Roadmap brief for Synapse v0.5.0: the cycle that adds a semantic retrieval layer (embeddings + hybrid ranking + lightweight entity linking) and codifies the new business rule that turns Synapse from a typed Markdown store into an active knowledge graph that thinks at insertion time. Includes the architectural-debt cleanup left by v0.4 (mcp/server.rs re-split, cli/at + cli/audit yellow tier splits) and the network-freshness Phase 2 deferred from v0.2 (SYN-93b). |
| Status | fully decided 2026-06-02: owner answered all six §11 questions; ticket numbering locked in §9 (existing SYN-80..89 + SYN-93b stay, new tickets fill SYN-151..154 and SYN-160..167); ready to file issues in milestone #7 |
| Inputs | v0.4 closure read (2026-06-02-v0.4-closure-and-roadmap-review.md); owner’s 2026-06-02 directive: “local-first sem Ollama; processar dados no momento da inserção; relacionar temas como o cérebro humano relaciona; benchmarking forte”; concrete use case scenarios provided by the owner (Carina / Firebase Remote Config / flag name); current v0.5 milestone (#7, 11 issues SYN-80..SYN-89 + SYN-93b); v0.4 brief that cut syn agent from v0.4 to v0.6 explicitly; ADR-008 reserved slot for the embedding-backend decision; ADR-013 audit trail + BR-12 privacy bundle; CHANGELOG.md §0.4.0. |
| Out of scope | syn agent autonomous loop with batch approval: deferred to v0.6 (owner decision on 2026-05-25 carried forward). Full Microsoft-style GraphRAG with community summaries: too expensive for v0.5, deferred to v0.6+. On-device generative LLM (Llama / Phi / Gemma in-process via candle): out of scope; Synapse continues to call MCP clients for generation, not embed a generator itself. Web UI / TUI: backlog. Cross-bundle merge of identity bundles: v0.6+. Sigstore transparency log for identity: kept at v0.5+ per ADR-016. |
Executive summary
v0.4 closed every documented debt of v0.3 and shipped a cross-editor LSP companion. v0.5 has a different shape: it changes what Synapse is, not just what it can do. The product moves from “typed Markdown store with FTS5 search and a great audit trail” to “active knowledge graph that recognises the same entity across notes the moment you write each one”.
The owner’s framing is what makes this brief different from a pure
“add embeddings” cycle: do the work at insertion time, not query time.
The brief codifies this as a new business rule BR-13 and structures
v0.5 around it. Embeddings ship as one signal among three (FTS, vector,
entity graph) combined by Reciprocal Rank Fusion. The local-first
invariant is preserved: the chosen embedding backend is fastembed-rs
via ONNX Runtime: ~70 MB total footprint, ~14× smaller than the
equivalent Ollama setup, runs on any CPU without external services.
Twenty tickets (twelve core + four arch-debt + four stretch). Six weeks of focused execution. The benchmark suite (SYN-90..SYN-93 corpus generator + P@k / nDCG@10 / MRR / latency budget) is core, not stretch: without it the choice of every algorithm in v0.5 is folklore.
1. The premise: what Synapse is for, restated for v0.5
Synapse’s target user has been consistent since v0.1: the multi-context developer of 2026, the engineer who runs 3-7 client projects in parallel because Claude Code / Cursor / Cline let them. The product compresses context-switching cost. v0.1..v0.4 made the store correct, auditable, and scriptable. v0.5 is the cycle that makes it associative: when the user writes a note about a meeting with Carina, the workspace should already know that “Remote Config” mentioned there is the Firebase tool from the other note. The query “flag do Firebase do dia X” finds both notes because the ingestion step wired them together; the search engine is just looking up the edge that was already drawn.
1.1 The canonical query that v0.5 must answer
The owner’s concrete example, restated:
Note A:
notes/2026-04-20-reuniao-carina.md, kindmeeting: “Reunião com Carina sobre a flagenable_v2_checkoutdo Remote Config. Acordamos rebatizar paracheckout-v2-eligibilityantes do próximo deploy.”Note B:
notes/firebase-stack.md, kindreference: “Remote Config é a ferramenta de feature-flags do Firebase. Os valores são entregues em tempo real para os clientes mobile.”Query (via Claude Code MCP, or
syn search): “Qual o nome da flag do Firebase do dia da minha reunião com a Carina?”
What it takes to answer this:
| Signal | Why it’s needed |
|---|---|
Temporal anchor (dia da reunião com a Carina) | Filter notes by meeting kind + Carina participant + date in the right month |
Concept bridge (Remote Config ↔ Firebase) | Note A doesn’t say “Firebase”; note B doesn’t say “Carina”. Without a bridge the two notes never co-occur |
| Specific extraction (the flag name) | Once both notes are surfaced, the LLM / CLI returns checkout-v2-eligibility from note A |
FTS5 alone, today, scores note B (literal match for “Firebase”) and maybe finds “Carina” in the participants index, but the link Remote Config → Firebase is invisible until both notes are seen together by the generation step: that means the LLM has to do the work, which means latency, cost, and non-determinism. v0.5’s job is to draw that edge once, when the user writes note B, so every future query that touches Firebase or Remote Config sees the link without paying for it again.
1.2 The new business rule BR-13: insertion-time enrichment
BR-13 (proposed for
docs/business/BUSINESS_RULES.md). Synapse processes entries at insertion time, not query time. Every CLI write (syn project add,syn person add,syn note add,capture_factvia MCP, …) hooks an enrichment pipeline that runs synchronously before the workspace transaction commits:
- Embed the entity’s
title + body + tagswith the configured embedding model; write the vector to theentities_vectable.- Extract candidate mentions: slugs of existing entities that textually appear in the new content, dates, person names with
@-prefix syntax, URLs, tag tokens, plus NER-detected spans (see §1.3 below for the language-per-workspace contract that drives the model choice).- Resolve mentions: every candidate is matched against the live workspace index. A hit becomes a typed
linkrow withrel = "mentions"orrel = "concept:<lemma>"; a miss becomes apendingproposal of kindlink_entity(existing) orcreate_entity_from_ner(new, when the NER pass discovered a span whose mapped Synapse entity type does not yet exist).- Latency budget: median ≤ 500 ms, p95 ≤ 800 ms on a 10k-entity workspace (M1 Pro reference). The pipeline runs the embedding and the NER passes in parallel inside the same
Workspace::txso the wall-clock cost ismax(embed, ner) + heuristic + link_resolve, not the sum. Background / async enrichment is explicitly out of scope for v0.5 and tracked as the v0.6 follow-up SYN-194.- Fail loud: if the enrichment step errors, the entity write aborts (atomicity preserved). The user can disable enrichment per workspace via
[enrichment] mode = "off"for the workspace that wants raw v0.4 semantics back.The principle: the IA is the memory co-pilot, not the real-time reranker. The work is paid once, on write.
This is the load-bearing decision of v0.5. Every other choice: vector backend, ranking algorithm, model size, benchmark target: flows from it.
1.3 Product principle: best models by default, per-workspace opt-down
Owner decision 2026-06-02: Synapse ships the strongest model in each slot by default. Smaller / English-only / faster variants are reachable through workspace config but the user does not have to know they exist to get a good first experience.
The corollary that matters for architecture: language is a per-workspace
property, not a per-binary one. A single Synapse install may host one
workspace where [enrichment] language = "pt-BR" and another where it is
"en-US"; both must run with equivalent retrieval quality and write
latency. The embedding backend and the NER model are selected per
workspace from a shared ~/.cache/synapse/models/ registry: the binary
itself stays language-agnostic.
The matrix this produces:
| Slot | Default (best) | Opt-down |
|---|---|---|
| Embedding | BGE-M3 multilingual (1024-dim, ~570 MB) | BGE-small-en-v1.5 Q (~33 MB, English only, faster): for workspaces that explicitly value binary footprint over recall |
| NER | GLiNER-multi-base Q (~150 MB) | GLiNER-base-en Q (~70 MB, English only): same trade-off |
| Vector storage | sqlite-vec (no choice: see §2.2) | - |
| Hybrid ranking | RRF k=60 (no choice for v0.5: see §2.3; convex combination is v0.6 polish) | - |
The configuration knob lives in <workspace>/.synapse/config.toml:
[enrichment]
language = "pt-BR" # or "en-US"
embedding_model = "bge-m3" # or "bge-small-en-v1.5-q"
ner_model = "gliner-multi" # or "gliner-base-en"
mode = "heuristic+ner" # or "heuristic" (skip NER), "off" (raw v0.4)
Defaults match the “best by default” row. The cascade config (user-level
~/.config/synapse/config.toml → workspace) is the same as for other
blocks (per ADR-011 §config policy).
2. Market scan: the four technical decisions in plain language
The brief uses the rest of the section to make the tooling choices defensible. Each subsection ends with a single line: What this means for Synapse.
2.1 Embedding backend: does it have to be Ollama?
In mid-2026 the realistic options for generating sentence embeddings from a Rust application are:
| Option | What it is | Cost on disk | Cost on first use | External deps |
|---|---|---|---|---|
| Ollama as runner | Synapse calls an Ollama HTTP server running locally; Ollama loads the embedding model. | ~700 MB Ollama binary + ~280 MB nomic-embed-text:v1.5 (q4) → ~1 GB minimum | User must install Ollama. First model download is ~280 MB. | Ollama running on localhost:11434. Synapse breaks if the user stops the daemon. |
fastembed-rs (wraps ort / ONNX Runtime) | Pure Rust crate; loads quantised ONNX models on demand; runs inference in-process on CPU. The model is downloaded from Hugging Face the first time it is used and then cached. | ~30 MB of ONNX Runtime statically linked + the model. BGESmallENV15Q is ~33 MB on disk. Total ~60–70 MB. | One-time ~33 MB model download. Cached under ~/.cache/fastembed/. | None at runtime. |
candle (HuggingFace pure-Rust ML) | Reads HF model weights directly (safetensors); no ONNX dependency. Maximum flexibility but slower per-query than ort. | ~10 MB crate + ~140 MB model (fp16). | Model download. | None at runtime. |
| Hand-rolled flat / HNSW + bag-of-words | No embeddings: return to BM25 + Levenshtein expansion. | 0 MB. | None. | None. |
The 2026 industry baseline for fast on-device embedding in a CPU-only Rust
application is fastembed-rs: Qdrant’s port of their original Python
library, in production at multiple Rust shops, supports the standard
embedding catalogue (BGE, Nomic, mxbai, MiniLM) including their quantised
ONNX variants ([fastembed-rs on crates.io], [Qdrant article on fastembed]).
The model the brief recommends as the v0.5 default: superseding the
original draft’s BGE-small-EN-v1.5 pick after the owner’s 2026-06-02
“best by default + per-workspace language” answer (§1.3): is BGE-M3
multilingual quantised: 1024-dimensional, ~570 MB on disk, MTEB
multilingual top-quartile (covers en + pt-BR + es + 100 + other
languages at near-state-of-the-art quality), embed latency ~80–200 ms
on M1 Pro CPU. For workspaces whose [enrichment] language = "en-US"
and that explicitly value binary footprint over recall, the
BGE-small-EN-v1.5-Q variant (~33 MB) is the opt-down: same trait
implementation, different model identifier passed to fastembed.
What this means for Synapse: Default fastembed-rs with BGE-M3
quantised, per-workspace selectable to BGE-small-EN-v1.5-Q. Ollama is a
configurable alternative ([embeddings] provider = "ollama") but not
the default. The local-first invariant survives. The binary itself stays
small; the model lives in ~/.cache/synapse/models/ and is downloaded on
first use of each workspace.
2.2 Vector storage: is sqlite-vec ready?
sqlite-vec is Alex Garcia’s loadable extension for SQLite that adds vector
columns, vec0 virtual tables, and L2 / cosine / dot distance functions in
~1.5 MB of native code. It is post-1.0, used in production, and Alex
Garcia maintains explicit Rust integration docs ([Using sqlite-vec in Rust]).
Two gotchas to plan for, both flagged in upstream issues and the rusqlite discussion threads ([rusqlite discussion #1423]):
rusqlitebundled+load_extensioninteraction. Bundled SQLite does not enable run-time loadable extensions by default, the user has to callconn.load_extension_enable()beforeload_extension(…), and on older SQLite versions thevtabfeature has to be enabled at therusqliteCargo level. The crate exposes the flag; documenting it in ADR-008 prevents a wasted afternoon.- No HNSW yet: flat scan only.
sqlite-veccurrently does brute-force scan; an HNSW-style index is on the roadmap but not landed. For a personal workspace of < 10k entities the linear scan is fast enough (~5 ms on a modern CPU). The v0.5 ceiling is set by this: if a user workspace exceeds ~50k entities, the recommendation is to chunk by workspace via federation rather than wait forsqlite-vecHNSW.
The cheap alternative is flat scan in pure Rust (Vec<f32> columns +
SIMD cosine): same time complexity, fewer abstractions, but reinvents the
storage layer. The expensive alternative is lance (Apache Arrow-based
columnar vector DB): full HNSW + ANN, but ~50 MB of code and a different
mental model from “everything lives in .synapse/index.db”.
What this means for Synapse: Adopt sqlite-vec. It keeps the
single-database invariant from BR-1. Document the load_extension setup in
ADR-008. Accept the flat-scan ceiling; revisit in v0.7 if a real user hits
it.
2.3 Hybrid ranking: RRF or convex combination, didactically
Once Synapse has three retrieval signals (FTS5, embeddings, entity-graph neighbourhood), the search command needs to fuse their results into a single ranked list. Two algorithms dominate the 2026 literature:
Reciprocal Rank Fusion (RRF)
The intuition. Each retrieval system independently ranks the documents. RRF ignores the scores (which are on incompatible scales: BM25 returns 0..∞ while cosine similarity returns -1..1) and uses only the ranks:
RRF_score(doc) = Σ_i 1 / (k + rank_i(doc))
rank_i(doc) is doc’s position in retriever i (1 if it’s the top hit, 2
if second, …). k = 60 is the standard tuning constant. A document that is
top-3 in BM25 and top-3 in vector search gets a high combined score; a
document that is top-1 in only one retriever still scores well but loses
to documents that show up in multiple.
Worked example: the Carina/Firebase query.
| Doc | FTS5 rank | Vector rank | Graph rank | RRF score (k=60) |
|---|---|---|---|---|
| Note B (Firebase reference) | 1 | 5 | 1 | 1/(60+1) + 1/(60+5) + 1/(60+1) = 0.049 |
| Note A (Carina meeting) | (not in top-10) | 2 | 2 | 0 + 1/(60+2) + 1/(60+2) = 0.032 |
| Random Firebase note | 2 | 12 | 25 | 1/(60+2) + 1/(60+12) + 1/(60+25) = 0.041 |
Both target notes surface in the top-3, even though FTS5 never saw note A and the random Firebase note ranked higher in literal text. RRF is safe out of the box and is the default of every major hybrid system shipped in 2026 (Elasticsearch, OpenSearch, Weaviate, Qdrant, [Hybrid search reference 2026]).
Convex Combination (a.k.a. score blending)
The intuition. Normalise each retriever’s score to [0,1], then take a weighted average:
score(doc) = α · vector_sim_normalised(doc) + (1-α) · bm25_normalised(doc)
α = 0.5 is the cold-start guess. The 2026 papers
([An Analysis of Fusion Functions for Hybrid Retrieval]) report convex
combination outperforms RRF when you have at least ~40 labelled query →
relevant-document pairs to tune α; below that, RRF wins because
normalisation breaks across queries.
The trade-off, sharply. RRF is the right cold-start default. Convex
combination is a polish step that requires the user to grade some search
results. A personal workspace owner can generate those labels (syn search foo --grade), and v0.6 should ship that. v0.5 does not.
What this means for Synapse: RRF with k = 60 for v0.5. Convex
combination becomes a v0.6 ticket gated on syn search --grade being shipped
first.
2.4 Entity linking at ingestion: small models, big payoff
The 2026 emerging best practice in retrieval pipelines is adaptive RAG: classify the query first, route simple factual queries to vector RAG and relationship queries to a knowledge-graph traversal ([GraphRAG vs Vector RAG 2026]). The Synapse twist, per BR-13, is to do the graph-building at ingestion time rather than wait for the query.
The choice of how the graph gets built drives binary size and latency:
| Approach | What it does at ingestion | Cost |
|---|---|---|
| Heuristic only (no model) | Scan the new entity’s text for: existing slugs (regex [a-z0-9-]+), ISO dates, @username mentions, URL hostnames, tag tokens. Match each one against the live index; emit pending proposals for confirmed concepts that need a new entity. | Free. ~5 ms per write. Catches the easy cases (e.g. the meeting note mentioning the literal slug firebase or remote-config). |
Small NER model (GLiNER-multi ONNX quantised, ~150 MB) | Run a lightweight zero-shot NER pass over the configured entity type list. Catches “Carina” without an @carina syntax, recognises “Remote Config” as a proper-noun span even when it doesn’t match an existing slug. | ~150 MB cached model. ~80–250 ms per write (inference). Parallelised with embedding so wall-clock cost is max(embed, ner). |
| LLM-driven extraction (call out to the user’s MCP client via sampling) | Best recall + best relation typing. | Network call per write. Breaks local-first. Rejected. |
What this means for Synapse: updated 2026-06-02 after owner decision:
v0.5 ships heuristic + GLiNER together as core. The original brief had
GLiNER as stretch; the owner promoted it on the rationale that (a)
pt-BR-grammatical content embeds proper nouns inside articles
("reunião com a Carina") which the heuristic-only path systematically
misses, and (b) the per-workspace language contract from §1.3 makes
GLiNER’s multilingual coverage a first-class need, not an optional one.
The promotion comes with three design specs that lock the budget:
- Model is
GLiNER-multi-basequantised (~150 MB) by default;GLiNER-base-enquantised (~70 MB) as the workspace opt-down per §1.3. - Embedding and NER run on parallel threads inside the same
Workspace::tx. Wall-clock cost ≈max(embed, ner). This is what keeps the BR-13 500 ms median budget reachable. - NER entity types are hard-coded by Synapse, not configured by
the user:
["person", "organization", "product", "feature_flag", "api_endpoint", "framework", "tool", "decision_topic"]. The mapping from NER type → Synapse entity type lives in the linker (SYN-91):person → person,api_endpoint → api,product | tool | framework | feature_flag | organization | decision_topic → entity. A surprise NER type out of this set is dropped; a new type requires a minor brief amendment, not a workspace config change.
The Carina case is now solved without any user discipline: even if the
user wrote "reunião com a Carina sobre a flag enable_v2_checkout do Remote Config" in raw pt-BR, NER tags Carina as person, Remote Config as product, enable_v2_checkout as feature_flag, and the
linker either creates the typed links or emits create_entity_from_ner
proposals to the pending queue.
The full-fledged GraphRAG with community summaries ([GraphRAG vs Vector RAG 2026]): 3–5× ingestion cost, requires LLM calls: remains out of scope. v0.5’s NER pass is the lightweight substitute; v0.6+ can evaluate whether the cost of community summaries pays off against the ingestion latency tax.
3. Cross-check vs the pre-research v0.5 backlog
The eleven issues already filed in milestone #7 are evaluated against the
brief’s recommendation:
| Open issue | Pre-research title | Status after brief | Reason |
|---|---|---|---|
| SYN-80 | feature flag vector + separate migration directory | Keep. | The migration story (migrations_vec/) is the right shape; vector becomes off-by-default for v0.5, default-on in v0.6. |
| SYN-81 | sqlite-vec integration: load_extension + entities_vec table | Keep, retitle to clarify the bundled+vtab setup. | Defaulted by §2.2. |
| SYN-82 | trait EmbeddingBackend (interface only) | Keep. | Right shape; the implementations come behind it. |
| SYN-83 | EmbeddingBackend implementations: Ollama, OpenAI, Voyage, configurable | Rewrite as “EmbeddingBackend implementations: fastembed-rs with BGE-M3 default + BGE-small-EN-v1.5-Q opt-down (per workspace); Ollama configurable”. OpenAI/Voyage move to stretch (SYN-95). | Local-first invariant per owner; per-workspace language contract from §1.3. |
| SYN-84 | EmbeddingBackend stub: deterministic, no network | Keep. | Needed for unit tests; deterministic SipHash-based fake vector. |
| SYN-85 | syn embed --all and syn embed <slug> | Keep, expand to also expose syn embed --check for verifying index integrity. | |
| SYN-86 | hybrid ranking: FTS5 + vector via RRF | Keep, finalise k=60. | Defaulted by §2.3. |
| SYN-87 | syn search --semantic (vector-only mode) | Reframe. v0.5 default = hybrid. --semantic becomes an explicit “vector-only, no FTS” debugging mode. --no-vector is the inverse. | The two existing tickets were inconsistent; this resolves the §4 open question of the closure doc. |
| SYN-88 | MCP tool semantic_search mirrors syn search --semantic | Reframe. The MCP tool stays search_knowledge (already exists); the hybrid mode is internal. semantic_search becomes a separate tool only if a client benefits from the vector-only mode; provisional kept as stretch. | |
| SYN-89 | ADR-008: chosen default embedding backend after benchmark | Keep. Brief mandates ADR-008 lands before SYN-83 code. | Governance. |
| SYN-93b | workspace freshness Phase 2 (network ls-refs via gix-protocol) | Keep. Independent of the semantic stack; lands as a parallel track. | Originally deferred from v0.2. |
4. Architectural debt cleanup: non-negotiable, lands first
These four tickets close the debts the v0.4 closure doc surfaced. They are core, not stretch. They lock the architecture before the semantic layer piles new modules on top:
- SYN-151:
mcp/server.rsdispatcher refactor. Move every#[tool]handler to a thin shim (3–8 lines) that delegates to a use-case module undersrc/core/mcp/(orsrc/mcp/handlers/). Acceptance: file drops below 500 lines and stays there; a CI lint rejects regrowth above 600 lines. ADR-015 §Playbook updated with the dispatcher pattern. - SYN-152:
cli/at.rssplit. Break the 620-line file intocli/at/{mod,search,show,list,info,graph}.rs. Pure code move, no behaviour change. - SYN-153:
cli/audit.rssplit. Same pattern:cli/audit/{listing,verify,replay,stale,prune}.rs. - SYN-154: file-size CI lint. A 30-line script in
.githooks/pre-pushor a GitHub Actions step: walksrc/, fail when any file exceeds 800 lines, warn at 600. ADR-015 enforcement.
5. The semantic layer: execution plan
The semantic layer ships as a sequenced track. Order matters because each ticket depends on a primitive established by the previous one.
SYN-89 (ADR-008)
↓
SYN-80 (feature flag vector + migrations_vec/)
↓
SYN-81 (sqlite-vec wired; entities_vec table; bundled+vtab+load_extension)
↓
SYN-82 (trait EmbeddingBackend)
↓
SYN-84 (deterministic stub) ← unblocks unit tests
↓
SYN-83 (fastembed-rs impl; Ollama impl behind config)
↓
SYN-85 (syn embed --all / --check / <slug>)
↓ ↓
SYN-86 (hybrid RRF) SYN-90 (BR-13 enrichment pipeline) ← writes hook this
↓ ↓
SYN-87 (syn search SYN-91 (heuristic entity-linker:
modes & flags) slugs + dates + @mentions + URLs)
↓ ↓
SYN-92 (benchmark suite: corpus generator + queries + metrics)
↓
SYN-93 (results report + thresholds for nDCG/P@k/MRR/latency)
Six new tickets join the eleven already-filed:
- SYN-90: pipeline:
core::enrichment::run_on_write(ws, entity)called by every write helper beforetx.commit(). Honours[enrichment] mode = "off" | "heuristic" | "ner". BR-13 codified. - SYN-91: heuristic linker module. No model dep. Documented detection
patterns. Emits
pendingproposals when a candidate is ambiguous (two slugs share a basename). - SYN-92: benchmark corpus + query bank. Generator produces a
synthetic workspace of 1k / 5k / 10k entities with planted cross-note
relations (the Carina/Firebase template generalised). Query bank with
ground-truth
relevant_slugsper query. - SYN-93: benchmark runner + results doc. CI artifact:
bench/results-<date>.json.
Stretch (lands if window allows, otherwise v0.6):
- SYN-94:
GLiNERNER backend behind--features ner. Comparable to the heuristic on the bench corpus. - SYN-95:
EmbeddingBackend::OpenAIandVoyageimpls (network-based, behind explicit opt-in). - SYN-96:
syn search --gradeto label query→relevant pairs for the v0.6 convex-combination polish. - SYN-97: MCP
semantic_searchtool, only if SYN-88 finds a client use case the unifiedsearch_knowledgecannot serve.
6. The benchmark suite: what “strong” means here, concretely
A benchmark suite for v0.5 has to do three things at once: produce a corpus that is representative of a Synapse user’s data, encode queries that are hard in the way the Carina example is hard, and report metrics that force a decision when algorithms compete.
6.1 Corpus generator (SYN-92)
The generator outputs a workspace as .synapse/index.db + notes/,
projects/, people/, apis/, entities/, tasks/. Three sizes (1k / 5k
/ 10k entities). Each generated workspace has a planted entity backbone:
- 5 fake “stacks” (e.g.
firebase-stack,aws-stack,vercel-stack,notion-stack,langchain-stack): each represented by anotes/<stack>.mdreference note that names 3–5 sub-tools (Firebase: Remote Config, Firestore, Auth, Functions, Storage). - 30–60 fake “meetings” with participants drawn from a pool of 12 fake people. Each meeting note mentions a sub-tool by name (without naming the parent stack) and proposes a flag rename / config change / API call / decision.
- 20–40 fake “decisions” (note kind
decision) referring back to a meeting and a stack sub-tool.
The cross-reference pattern is the Carina/Firebase pattern at scale. A query like “flag do Firebase do dia da reunião com Maria-Eduarda” has exactly one correct answer in the planted corpus; the benchmark measures whether the retrieval system finds it.
6.2 Query bank
50 queries per corpus, three difficulty tiers:
- T1: literal. “Carina” with no concept bridging: should be P@1 = 1.0 for everything (sanity check the FTS5 fallback).
- T2: single-hop concept (the Carina/Firebase case). The query names one concept (Firebase) and one anchor (a meeting participant or date); the correct answer is in a note that does not contain the concept word literally, only the sub-tool word. This is where RRF + embeddings + heuristic linking must beat plain FTS.
- T3: multi-hop / temporal. “What did we decide about retention in any AWS service in Q2?”: combines a temporal anchor with a parent-stack bridge with a decision-kind filter. RRF + embedding should surface the AWS-stack note + the relevant decision note; full GraphRAG would do better but is out of scope.
6.3 Metrics & thresholds
| Metric | Definition | v0.5 ship threshold |
|---|---|---|
| P@1 | Top-1 hit is in the ground-truth set | T1 = 1.0, T2 ≥ 0.6, T3 ≥ 0.3 |
| P@5 | Any of top-5 is correct | T1 = 1.0, T2 ≥ 0.85, T3 ≥ 0.6 |
| MRR | Mean reciprocal rank over the 50 queries | overall ≥ 0.55 |
| nDCG@10 | Normalised discounted cumulative gain | overall ≥ 0.6 |
| Write latency | Time for syn project add (median, 10k corpus, enrichment + NER on, default models) | ≤ 500 ms median, ≤ 800 ms p95 (revised 2026-06-02 from the original 200 ms target after the owner accepted the GLiNER core promotion + BGE-M3 default; the parallel embed+NER pipeline is what makes 500 ms reachable) |
| Search latency | Time for hybrid search (median, 10k corpus) | ≤ 150 ms |
| Binary size delta | target/release/syn size vs v0.4 baseline (binary only, models cached separately under ~/.cache/synapse/models/) | ≤ +60 MB |
| Cold-start model download | Total bytes fetched on first syn invocation in a fresh workspace with default models | ≤ 750 MB (BGE-M3 ~570 MB + GLiNER-multi-Q ~150 MB; visible in syn init output) |
Thresholds are codified in SYN-93’s acceptance: if a metric is missed, the v0.5 brief gets reopened.
7. Footprint, honestly: what the user installs
Sizes measured on aarch64-apple-darwin static release builds, refreshed
2026-06-02 after the BGE-M3 + GLiNER-multi defaults were locked.
| Build | Binary on disk | Models cached on first use | When |
|---|---|---|---|
syn (v0.4) | ~14 MB | none | today |
syn --features "mcp lsp vector" (v0.5 default, default models) | ~60 MB | ~570 MB BGE-M3 + ~150 MB GLiNER-multi = ~720 MB in ~/.cache/synapse/models/ | v0.5 ship |
Same binary, opt-down to English-only (workspace config: bge-small-en-v1.5-q + gliner-base-en) | ~60 MB | ~33 MB BGE-small + ~70 MB GLiNER-en = ~103 MB | per-workspace choice |
syn with Ollama instead of fastembed | ~14 MB binary + ~1 GB external (Ollama daemon + model) | requires Ollama daemon | configured opt-in |
The deal the user signs up for by accepting the default install:
- Binary itself stays ≤ 60 MB (versus 14 MB today, +46 MB for the ONNX Runtime statically linked plus the fastembed glue plus GLiNER plumbing).
- First
syninvocation in a fresh workspace fetches ~720 MB of models to~/.cache/synapse/models/. The cache is shared across workspaces of the same language config, so a user with three pt-BR workspaces pays the 720 MB once, not three times. - The English-only opt-down (target: low-resource laptops, CI workers) drops the cache to ~100 MB at the cost of multilingual coverage.
The Ollama-equivalent still costs 14× more on disk and adds a daemon dependency the user has to manage. The local-first invariant remains.
8. Risk register
| Risk | Severity | Mitigation |
|---|---|---|
sqlite-vec load_extension setup proves harder than expected on Windows MSVC | Medium | Spike before SYN-81; if blocking, fall back to a pure-Rust flat scan (slower path, same SQL surface) so the rest of the stack is unblocked |
| Quantised BGE-small accuracy is too low on Portuguese content (T2 threshold missed) | Medium | The brief reserves BGE-M3 as the multilingual swap behind [embeddings] model = "bge-m3"; the bench corpus generator includes pt-BR variant prompts |
Insertion-time enrichment introduces a write-latency regression that hurts syn capture UX | High | BR-13 already mandates the enrichment runs inside the workspace transaction; the 200 ms write threshold is the brief’s ship gate. If exceeded, enrichment moves async with a 5-second eventual-consistency window: explicitly out of scope for v0.5 ship |
| Benchmark numbers reveal RRF underperforms convex combination even at α=0.5 | Low | The brief documents this explicitly; SYN-96 (convex combination + --grade) is the v0.6 polish path |
Re-split of mcp/server.rs (SYN-151) breaks an rmcp macro contract | Medium | The dispatcher pattern keeps every #[tool] shim inside the macro’s impl block; the move is the body of each shim into a free function. Smoke tests against Claude Code stay the verification gate |
| Owner runs out of context-window mid-v0.5; the cycle drags into v0.6 | Medium | The brief breaks v0.5 into the 4 arch-debt tickets (SYN-151..154) shippable independently and the 17 semantic-layer tickets sequenceable; the architecturally hardest decisions (ADR-008 spike) front-loaded |
9. Ticket sketch: provisional numbering
This is the proposed full v0.5 milestone after the brief lands. Provisional
numbers in SYN-150 range expand into the unused SYN-151..199 block;
existing SYN-80..89 + SYN-93b are kept with the retitles from §3. The
new tickets fill the v0.2-adjacent free slots SYN-94..98 plus the
expansion block.
9.1 Architecture-debt cleanup (lands first)
| ID | Layer | Size | Title |
|---|---|---|---|
| SYN-151 | mcp | M | refactor mcp/server.rs to dispatcher pattern (thin #[tool] shims, logic moves to core::mcp::*) |
| SYN-152 | cli | S | split cli/at.rs into cli/at/{mod,search,show,list,info,graph}.rs |
| SYN-153 | cli | S | split cli/audit.rs into cli/audit/{listing,verify,replay,stale,prune}.rs |
| SYN-154 | ci | XS | CI lint rejecting src/** files above 800 lines (red), warning at 600 (yellow): ADR-015 enforcement |
9.2 Semantic layer: core (must land)
| ID | Layer | Size | Title |
|---|---|---|---|
| SYN-89 | docs | M | ADR-008: chosen default embedding backend after benchmark spike (default: fastembed-rs + BGE-M3 multilingual quantised; opt-down: BGE-small-EN-v1.5-Q; both per-workspace per §1.3) |
| SYN-80 | index/build | M | feature flag vector + separate migrations_vec/ migration dir |
| SYN-81 | index | L | sqlite-vec integration via bundled+vtab+load_extension; entities_vec virtual table; ADR-008 §gotchas |
| SYN-82 | core | M | trait EmbeddingBackend { fn embed(text: &str) -> Result<Vec<f32>> } + trait NerBackend { fn extract(text: &str, types: &[&str]) -> Result<Vec<NerSpan>> } (the parallel-pipeline contract is one trait per slot) |
| SYN-84 | core / tests | S | deterministic EmbeddingBackend + NerBackend stubs (SipHash-based fake vector, regex-based fake NER) for unit tests |
| SYN-83 | core | M | EmbeddingBackend impls: fastembed-rs with BGE-M3 default + BGE-small-EN-v1.5-Q opt-down per [enrichment] embedding_model; Ollama configurable |
| SYN-164 | core | M | NerBackend impl: GLiNER-multi-base-Q default + GLiNER-base-en-Q opt-down per [enrichment] ner_model. Promoted from stretch to core 2026-06-02. Hard-coded entity types per §2.4 spec. |
| SYN-85 | cli | M | syn embed --all / syn embed <slug> / syn embed --check / syn embed --reset-cache |
| SYN-160 | core | L | enrichment pipeline core::enrichment::run_on_write invoked by every write helper; runs embedding + NER in parallel threads inside the same Workspace::tx; honours [enrichment] mode; BR-13 codified. Owns the median-500 ms / p95-800 ms latency budget. |
| SYN-161 | core | M | heuristic entity-linker + NER-span resolver: slug / date / @mention / URL / tag patterns plus the GLiNER-typed spans from SYN-164 → matched against the live index → either typed link rows or pending proposals (link_entity + new create_entity_from_ner) |
| SYN-86 | core / index | L | hybrid ranking via RRF with k = 60 over FTS + vector + graph-neighbourhood signals |
| SYN-87 | cli | S | syn search default = hybrid; --semantic = vector-only; --no-vector = FTS+graph only |
| SYN-162 | core / tests | L | benchmark corpus generator (1k / 5k / 10k) + query bank (50 queries × 3 difficulty tiers): pt-BR + en-US variants, since the language contract from §1.3 mandates equivalent quality. |
| SYN-163 | core / tests / docs | M | benchmark runner + results report; CI artefact; thresholds enforce v0.5 ship gate (T2 P@5 ≥ 0.85, MRR ≥ 0.55, write median ≤ 500 ms, search median ≤ 150 ms) |
| SYN-167 | docs / cli | S | README §Editor integration section promoting syn lsp near the top + cross-link to docs/EDITOR-INTEGRATION.md. Owner decision 2026-06-02: lands inside v0.5 cycle (not as a parallel v0.4.1). Homebrew tap explicitly out of v0.5; asciinema demo registered for v0.5.1. |
9.3 Stretch (lands if window allows; otherwise rolls to v0.6)
| ID | Layer | Size | Title |
|---|---|---|---|
| SYN-165 | core | M | EmbeddingBackend impls for OpenAI + Voyage (network-based, behind explicit opt-in) |
| SYN-166 | cli / core | S | syn search --grade UX for capturing query → relevant labels; foundation for v0.6 convex-combination tuning |
| SYN-88 | mcp | S | MCP semantic_search tool: only if a client benefits over search_knowledge’s hybrid mode |
| SYN-194 | core | L | (v0.6 follow-up: registered explicitly so it does not get lost) async / background enrichment with eventual-consistency window for users who want to remove the 500 ms write penalty entirely |
9.4 Network freshness Phase 2: parallel track
| ID | Layer | Size | Title |
|---|---|---|---|
| SYN-93b | store / mcp | L | workspace freshness Phase 2: network ls-refs via gix-protocol, ADR-011 §Phase 2 closure |
9.5 New BR + ADR
- BR-13: insertion-time enrichment principle. Landing in
docs/business/BUSINESS_RULES.mdas part of SYN-90. - ADR-008: embedding backend choice + sqlite-vec setup notes. Landing as part of SYN-89 before SYN-81 code starts.
10. Cuts assumed (registered explicitly)
syn agentautonomous loop with batch approval: kept at v0.6, owner decision from 2026-05-25 carried.- Microsoft-style GraphRAG with community summaries: too expensive (3–5× ingestion cost); v0.6+ or v0.7. Lightweight heuristic + optional GLiNER is the v0.5 substitute.
syn cv export --as pdf: already cut from v0.4; still v0.5+1 stretch or later.- Web UI / TUI: backlog.
- Cross-bundle merge of identity bundles: kept at v0.6+, ADR-014/016 cuts carried.
- Sigstore transparency log for identity: kept at v0.5+ per ADR-016 §Alternatives considered, lands when there is a public exchange to protect.
11. Decisions log
Decided 2026-06-02
- ✅ Default embedding model:
BGE-M3multilingual. Owner answered the §2.1 question explicitly: ship the strongest model by default;BGE-small-EN-v1.5-Qis the per-workspace opt-down. Combined with the new product principle from §1.3, the binary itself stays language-agnostic; the model is selected per workspace. - ✅ GLiNER promoted from stretch to core. Owner answered the §2.4
question with option B:
GLiNER-multi-base-Qdefault,GLiNER-base-en-Qopt-down per workspace. Hard-coded entity types, parallel embed+NER pipeline insideWorkspace::tx, three mitigations from §2.4 locked as design specs. SYN-94 moved from §9.3 to §9.2; was retitled accordingly.
- ✅ BR-13 latency budget: write median ≤ 500 ms, p95 ≤ 800 ms. Owner raised the original 200 ms ceiling explicitly to make room for the GLiNER pass; async / background enrichment registered as the v0.6 follow-up (SYN-194) so the trade-off is not lost.
- ✅ Per-workspace language contract: owner declared the product
is not pt-BR-primary; each workspace’s
.synapse/config.tomlpicks itslanguage = "pt-BR" | "en-US", and both must perform equivalently. Codified as §1.3 product principle; benchmark mandates pt-BR + en-US query variants in SYN-92.
Decided 2026-06-02 (second pass)
- ✅
syn searchsyntax confirmed. Default = hybrid (FTS + vector + graph via RRF).--semantic= vector-only mode.--no-vector= FTS + graph only. Codified in §2.3, §3 (SYN-87 retitle), §9.2. - ✅ Ticket numbering: SYN-160..167 expansion block. Owner picked
the safer remapping over reusing the v0.2-adjacent slots. Final
allocation:
SYN-160enrichment pipeline (BR-13)SYN-161heuristic + NER linkerSYN-162benchmark corpus + queriesSYN-163benchmark runner + reportSYN-164NerBackend impl (GLiNER multi default + en-only opt-down)SYN-165(stretch) OpenAI + Voyage EmbeddingBackend implsSYN-166(stretch)syn search --gradeSYN-167README §Editor integration adoption section- Plus the architecture-debt block already at
SYN-151..154.
- ✅ README §Editor integration lands inside v0.5 as SYN-167.
Owner explicitly rejected the v0.4.1 parallel-cycle idea. Homebrew
tap is out of v0.5 entirely (decision carried to v1.0 backlog
SYN-102). Asciinema demo of
syn lspregistered for v0.5.1, not in this cycle but mapped. - ✅ Six-week window accepted as the upper bound, owner expects earlier delivery.
12. References
- fastembed-rs on crates.io: Rust embedding generation, ONNX-backed.
- Qdrant’s FastEmbed article: design rationale and CPU-first benchmarks.
- Using sqlite-vec in Rust: Alex Garcia: the integration walkthrough.
- Hybrid Search: BM25, Vector & Reranking Reference 2026: 2026 industry baseline.
- An Analysis of Fusion Functions for Hybrid Retrieval, the original convex-combination vs RRF analysis carried forward in 2026 work.
- GraphRAG vs Vector RAG: Knowledge Graph AI Guide 2026, the adaptive RAG framing the brief borrows.
- Best Embedding Model for RAG 2026: Milvus blog: model size + MTEB comparison feeding §2.1.
- Entity Linking and Relationship Extraction With Relik in LlamaIndex: Neo4j Developer blog: small-model NER at ingestion baseline.
- rusqlite discussion #1423:
modern_sqlite+vtabwith loadable extensions: the sqlite-vec gotcha called out in §2.2. - v0.4 closure read:
docs/researchs/2026-06-02-v0.4-closure-and-roadmap-review.md. - v0.4 brief:
docs/researchs/2026-05-25-v0.4-refinement-and-extensibility.md: pattern this brief follows. - ADR-013: audit trail (still binds the insertion-time pipeline).
- ADR-015: modular files policy (enforced by SYN-154 lint).
- ADR-018:
tower-lsp-serverchoice (precedent for picking active forks over abandoned upstreams).
Vector storage: is sqlite-vec really the best fit for Synapse?
| Field | Value |
|---|---|
| Date finalised | 2026-06-02 |
| Author | Rômulo Febasi <romulo@febasi.com.br> (analysis assisted by Claude Code) |
| Scope | Owner-prompted critical review (2026-06-02) of the ADR-008 decision to adopt sqlite-vec. Survey of the 2026 alternative options: LanceDB, SurrealDB embedded, DuckDB + VSS, vectorlite, usearch, Qdrant embedded: measured against Synapse’s actual constraints, not generic benchmarks. |
| Status | accepted: confirms ADR-008 stays as written; registers vectorlite as the documented upgrade path for v0.7+ if we hit the flat-scan ceiling |
| Inputs | ADR-003 (rusqlite bundled); ADR-008 (this week’s decision); v0.5 brief §2.2; live walks of asg017/sqlite-vec, lancedb/lancedb, surrealdb/surrealdb, 1yefuwang1/vectorlite, unum-cloud/usearch repositories; 2026 benchmark write-ups on slashdot, peerspot, thedataquarry, lancedb.com; Alex Garcia’s roadmap statements on the sqlite-vec issue tracker. |
| Out of scope | Migrating FTS5, the audit trail, Workspace::tx, the pending queue, or any other SQLite-resident feature out of SQLite; this brief evaluates the vector storage slot only. |
Executive summary
Three honest answers to the owner’s question:
- For Synapse’s actual workload (≤ 10k entities per workspace,
federation handles scale by chunking),
sqlite-vecis the right pick. It is recent, maintained, and fits our existing.synapse/index.dbmodel cleanly. ADR-008 stays as written. - For a 2026 greenfield project starting from scratch with no
SQLite baggage, the answer would be LanceDB. It is the
architectural option born for the workload. We are not greenfield.
Migrating from SQLite to LanceDB costs more than the upside
delivers at our ceiling:
Workspace::tx, FTS5 ranking, audit trail, pending queue, federation registry are all tightly coupled to the SQLite engine. Re-implementing them is a v1.0-shaped task, not a v0.5 spike. - The honest upgrade path exists. If a Synapse user ever exceeds
~100k entities per workspace and the flat-scan latency becomes
visible, the drop-in replacement is
vectorlite(HNSW-based, ~10× faster searches thansqlite-vec’s flat scan, same loadable- extension shape, still SQLite). Registered as the v0.7+ pre-staged ticket so the option is not lost.
1. The constraint set that actually matters here
The vector storage choice has to satisfy seven hard constraints before any benchmark number is relevant. Most “is X better than Y” articles online ignore these because they are project-specific:
| Constraint | Source | What it eliminates |
|---|---|---|
Atomic FS + index transactions (Workspace::tx) | BR-6, ADR-003 §Consequences | Any backend without a transaction model that composes with the filesystem write |
| Append-only audit trail living next to the index | BR-12.3, ADR-013 | Any backend that requires a separate process or separate storage mount |
| FTS5 ranking already shipped in 121 tests | v0.4 codebase | Any backend that forces a re-implementation of full-text from zero |
| Single binary, no daemon | BR-1, BR-12, ADR-018’s “no second runtime” thread | Anything that requires localhost:<port> |
| Cross-platform: aarch64+x86_64 macOS, aarch64+x86_64 Linux, x86_64 Windows | v0.4 release matrix | Anything that ships only on a subset of OSes |
| Liberal OSS license (MIT or Apache 2.0) | Synapse is MIT | Anything that ships proprietary or AGPL |
| Workspace ceiling 10k entities (federation chunks beyond) | v0.5 brief §6.3 | Anything optimised exclusively for ≥ 1M vectors at the cost of complexity |
Anything that satisfies all seven is a candidate. The list of candidates that pass the gate is short.
2. The candidate matrix
The matrix below grades each option against the Synapse constraint set, not against synthetic benchmarks. Each row ends with the yes/no on whether a v0.5 migration to that option would pay back.
| Option | What it is | Hard-no for Synapse? | Soft-no for Synapse? | Worth migrating from sqlite-vec? |
|---|---|---|---|---|
sqlite-vec 0.1.9 | Alex Garcia’s loadable SQLite extension; flat brute-force scan; ~1.5 MB native code; rusqlite integration documented. Author roadmap: v1.0 within a year, then maintenance mode. ANN index (“eventually”) still in design. | - | Flat scan is the v0.5 ceiling; documented at 10k entities | baseline |
vectorlite 0.2.x (beta) | hnswlib-based HNSW index packaged as a SQLite loadable extension. 2×–40× faster search, 10× faster inserts vs sqlite-vss according to the author’s bench (2025). Still beta as of 2026. | - | Beta status + smaller maintainer; HNSW tuning adds knobs we don’t need at 10k entities | No for v0.5 (too early); yes if we ever exceed ~100k vectors per workspace |
| LanceDB embedded | Apache Arrow-based vector lakehouse. Rust core. In-process. Has Tantivy-backed FTS, hybrid search, rerankers, S3 backing. ~50 MB extra binary. 1.5M IOPS bench 2026. Multi-modal (vectors + tables + text + images). | - | Would force re-implementing Workspace::tx, the pending queue, the audit trail, the federation registry, and all FTS5 callers because LanceDB has no SQL transactions in the SQLite sense; it is Arrow-native, columnar. Atomic write-after-write semantics are different. | No: migration cost is v1.0-shaped, not v0.5. Right answer if we were greenfield in 2026. We are not. |
| SurrealDB embedded | Multi-model DB (graph + document + relational + vector + KV + time-series + geospatial). Rust-native. Embedded + server. Custom query language (SurrealQL). | - | Same as LanceDB plus: replaces SQL entirely. Loses 100% of our existing SQL tests + migrations + the queries 21 commits in v0.4 just wrote. Conceptually attractive (graph + vector + KV in one engine maps onto Synapse’s data model nicely) but the migration is a complete reskinning. | No: even more costly than LanceDB. Reconsider only on a v2.0 reset. |
| DuckDB + VSS extension | Columnar OLAP database with a vector extension. Embedded. Multi-platform. | - | Wrong workload shape: DuckDB is analytical (read-heavy aggregations) not transactional (one row, one write). Synapse is transactional. DuckDB also breaks Workspace::tx for the same reasons LanceDB does. | No: DuckDB is the answer for a different product. |
usearch | Single-file HNSW index library by Unum. ~1 MB. C++/Rust/multi-language. No storage layer: just the index. | - | Would have to wire its own storage and we’d lose SQLite’s transaction binding | No as a standalone replacement; could complement sqlite-vec if we ever wanted HNSW without leaving SQLite, but vectorlite already does that and ships as a SQLite extension |
| Qdrant embedded mode | Real vector DB that has an embedded crate. Rust-native. HNSW. | - | Same daemon-shaped split as Ollama: heavy binary growth, separate API surface, ~80 MB | No: same reason we rejected Ollama-as-runner for embeddings |
sqlite-vector by sqlite.ai | Commercial-ish (sqliteai.com) vector extension; HNSW. | License unclear (BSL? Commercial?) | - | No until license clarity |
| Tantivy + custom vector | Lucene-style FTS engine in Rust. Quickwit underpins it. Not a vector store on its own. | - | We’d add Tantivy alongside SQLite + a separate vector layer. Three storage engines, three transaction surfaces, three failure modes | No: we’d be inventing LanceDB’s stack ourselves |
| Hand-rolled flat scan + SIMD cosine | Pure Rust, no dep. | - | Reinvents sqlite-vec for no upside; lacks the ANN index path we’d want at scale | No |
The matrix collapses to three real options: stay on sqlite-vec, upgrade to vectorlite (HNSW within the SQLite shell), or migrate to LanceDB (different shell entirely).
3. Cross-check vs the architectural promises Synapse already made
Synapse’s data model is eight entity types + tags + links + FTS +
pending + audit + provenance + federation cache, all atomic per
Workspace::tx. The matrix below tabulates how much code each
backend would force us to re-write, taking the v0.4 codebase as the
baseline (Cargo.toml at synapse 0.4.0, 121 lib tests, 10k+ LOC).
| Subsystem | LOC today | Stays on sqlite-vec | Moves to vectorlite | Moves to LanceDB |
|---|---|---|---|---|
Workspace::tx (atomic FS + SQLite transaction) | ~150 | unchanged | unchanged | rewrite: different transaction surface |
| FTS5 ranking + snippet | ~120 | unchanged | unchanged | replace with Tantivy: different ranker, snippet format, tests |
Audit trail (.synapse/audit.ndjson lives outside the DB but the pending queue and the payload_sha256 integrity check are SQLite-backed) | ~450 | unchanged | unchanged | rewrite the queries: Arrow columnar reads, no SQL |
| Pending queue | ~530 | unchanged | unchanged | rewrite |
Federation registry + manifest cache (separate SQLite DB at ~/.config/synapse/workspaces.db) | ~560 | unchanged | unchanged | migrate or keep on SQLite alongside: two DBs, two query languages |
Schema migrations (0001..0004) | ~200 (incl. SQL) | unchanged | unchanged | rewrite as Arrow-native migrations |
FTS5 MATCH … ORDER BY rank callers | ~80 in core::search, cli/at, mcp | unchanged | unchanged | rewrite all of them |
core::reindex (4 migration tables, 6 entity types) | ~310 | unchanged | unchanged | rewrite |
| Net delta | ~0 lines | ~30 lines (FFI to vectorlite or migration script) | ~2,400 lines rewritten + ~100 tests rewritten |
Migrating to LanceDB is a v1.0-shaped commitment in a v0.5 cycle. Migrating to vectorlite is a ~30-line swap (the loadable-extension boundary is the same as sqlite-vec). Staying on sqlite-vec costs zero.
4. Where each option really beats sqlite-vec
The honest read on each alternative, not biased by what we already have:
LanceDB
- Multi-modal (vectors + images + tables + Parquet-importable) by
design. We do not need this today. If Synapse ever ships a
syn entity import <csv>for 10M-row enterprise CRM dumps, LanceDB becomes the right answer. - Tantivy-backed FTS is more flexible than SQLite FTS5 (custom analyzers, faceting, BM25 score tuning). FTS5 is enough for our workload size; Tantivy would matter at corpora 100× ours.
- 1.5M IOPS on cloud-backed storage; we are local-first, the IOPS number is meaningless for us.
- Native hybrid search with
query_type="hybrid", but we are building RRF ourselves on top of three signals (FTS + vector + graph), so LanceDB’s hybrid doesn’t subsume our SYN-86 ticket.
vectorlite
- HNSW index → sub-millisecond search on millions of vectors. The one real performance win over sqlite-vec for large corpora.
- Same SQLite shell, same
load_extensionshape, same FFI integration pattern we’re already taking with sqlite-vec in SYN-81. - Beta status as of 2026: author is one maintainer, less mature than sqlite-vec’s ecosystem.
SurrealDB
- The data model fits Synapse better than SQL does: graph edges +
documents + KV + vectors are exactly what entities + links + tags
- embeddings need. If we were starting from scratch in 2026 with Synapse’s spec in hand, SurrealDB is the architectural option to consider seriously.
- But we are not greenfield. We have 121 tests pinning SQL behaviour.
5. The conditional logic that would force a re-evaluation
Three triggers would re-open this decision. Recording them so the next reader does not have to re-do the survey:
| Trigger | What to do |
|---|---|
A real user workspace exceeds ~100k entities and reports flat-scan latency on syn search | Migrate to vectorlite (HNSW). ~30 lines of FFI changes. Same SQL queries. Same FTS5. No business logic change. Pre-staged ticket: SYN-203 (file in v0.7 milestone when created). |
| Synapse decides to support multimodal capture (images, audio embeddings, large attachments) | Evaluate LanceDB seriously. It owns multimodal natively; bolting it onto SQLite is unhealthy. This would be a v2.0 conversation. |
| sqlite-vec project goes inactive | Switch to vectorlite (same shell) immediately. Synapse already had the same risk play out with tower-lsp → tower-lsp-server and managed; the rerun is documented in ADR-018. |
| Synapse opens an enterprise CRM / SaaS-integration import path with 1M+ rows | Migrate to LanceDB: same conversation as the multimodal trigger above, just for a different scale axis. |
6. Risk of staying on sqlite-vec: the parts the ADR didn’t fully
The ADR-008 §Consequences lists three risks. The exhaustive read:
| Risk | Severity now | Mitigation already in place |
|---|---|---|
| Flat scan O(N) becomes visible at ≥ 50k vectors per workspace | Low | Brief §6.3 caps the v0.5 workspace at 10k entities; federation chunks beyond |
| sqlite-vec author single-maintainer (Alex Garcia) goes inactive | Medium | Switch to vectorlite (same shell); C extension is ABI-stable so the source can be vendored under src/index/vendor/ if upstream disappears |
| ANN index promised “soon” never materialises | Low | Doesn’t block v0.5; if it never lands, vectorlite is the upgrade path |
| sqlite-vec v1.0 introduces breaking changes during the maintenance handoff | Medium | We pin a specific version + take FFI direct path (per ADR-008 production wiring decision), insulating us from crate-level API churn |
rusqlite ^0.31 vs our 0.39 collision | Low | Resolved in ADR-008 by FFI-against-C-extension path; no Rust crate dependency |
None of these justify a v0.5 migration away from sqlite-vec.
7. Recommendation
Confirm ADR-008 as written. No change to v0.5. The decision matrix says sqlite-vec is the right call for our workload size, codebase shape and constraint set. The alternatives are either wrong-workload (DuckDB), too-early (vectorlite beta), or greenfield-only (LanceDB, SurrealDB).
Two follow-ups that this report recommends filing:
| ID | What | When |
|---|---|---|
| SYN-203 (placeholder) | Document vectorlite migration path as the v0.7+ swap; vendor sqlite-vec C source under src/index/vendor/ so we are insulated from upstream churn | Open ticket now, no milestone: moves into v0.7 when that milestone exists |
| ADR amendment | Append to ADR-008 §Consequences: “If a workspace ever exceeds 50k indexed entities, switch to vectorlite via the same load_extension FFI path: no business-logic change required.” | Inline edit to ADR-008: small enough to do now |
8. References
- Releases · asg017/sqlite-vec: v0.1.9 stable, v1.0 roadmap statements.
- Introducing sqlite-vec v0.1.0: Alex Garcia’s blog: design goals + flat scan limitations.
- vectorlite: Fast SQL-powered HNSW SQLite extension: 2×–40× faster searches vs sqlite-vss (predecessor benchmark).
- LanceDB GitHub: Rust core, in-process, embedded.
- LanceDB vs SQLite: slashdot.org comparison (2026): feature parity table.
- LanceDB Documentation: Hybrid Search:
query_type="hybrid"design. - SurrealDB embedded mode docs: multi-model in one engine.
- DuckDB vs SQLite: Motherduck: analytical vs transactional workload framing.
- usearch crate: HNSW-only index, no storage layer.
- Embedded databases (3): LanceDB and the modular data stack, The Data Quarry: independent embedded-DB landscape read for 2026.
- ADR-003:
rusqlite bundled(the constraint that binds the storage decision to SQLite). - ADR-008: embedding backend + vector storage + NER (the decision this brief stress-tests).
- v0.5 brief:
2026-06-02-v0.5-semantic-layer-and-second-brain.md§2.2.
CLI output UX & agent-friendliness
| Field | Value |
|---|---|
| Date finalised | 2026-06-05 |
| Author | Rômulo Febasi romulo@febasi.com.br (Claude-assisted) |
| Scope | Is Synapse’s CLI output well-structured and visually modern (vs Vercel/gcloud/gh/cargo)? Where are the AI-running-the-CLI affordances? Grounded in the v0.5 code. |
| Status | draft |
| Inputs | Audit of src/output.rs, src/cli.rs, src/exit.rs, i18n strings; clig.dev (Command Line Interface Guidelines); 2026 “CLI for AI agents” literature. |
| Out of scope | The MCP door (covered in the agent-ergonomics brief); a full TUI (ratatui is backlog SYN-203). |
Executive summary
Synapse’s CLI is functionally correct and predictable but visually flat. Output goes through one clean chokepoint (src/output.rs), --format json exists, and exit codes are already sysexits-structured (better than most CLIs) (but the only colour in the whole tool is the red error: label. There are no success/warn glyphs, no bold/dim, no tables, no NO_COLOR/--no-color, no progress on TTY, and no non-interactive flag) so an agent driving the CLI in a pseudo-TTY can block on the syn init model-download prompt. The fix is cheap because the output module is centralised: add a TTY-gated styling layer (clig.dev rules), a --no-input/--quiet pair, finish --format json coverage, and emit machine-readable errors. None of this should change the data an existing script sees on a pipe.
1. Context and premisses
Audited reality (v0.5):
output::println/println_stderrare bareprintln!/eprintln!. The only styled output isprint_error→ rederror:label, gated onstderr.is_terminal().- Globals:
--verbose,--format text|json,--lang,--no-freshness. No--no-color,--quiet,--yes/--no-input. --format jsonis implemented in ~13 of 27 command files (reads); writes/actions print text only.ExitCode(BR-8.3):Success=0, UserError=1, InternalBug=2, Usage=64, DataError=65, IoError=74, Transient=75: sysexits-aligned, but category-level (not-found and already-exists both map toUserError=1).NO_COLOR/TERM=dumbare not honoured anywhere.- Prompts exist in
syn setupand thesyn initonboarding; they gate onis_terminal()but have no flag to force non-interactive.
2. Market scan
2.1 clig.dev: colour & TTY discipline
Disable colour when: not a TTY (check stdout/stderr individually), NO_COLOR is set (non-empty), TERM=dumb, --no-color is passed, or an app-specific MYAPP_NO_COLOR is set. No animations when stdout is not a TTY (progress bars become “christmas trees” in CI logs).
What this means for Synapse: Synapse does the TTY check for one label and ignores the other four signals. A real styling layer must honour all of them in one place (the output module).
2.2 Modern CLIs: the visual bar
| Capability | Vercel | gcloud | gh | cargo | Synapse today |
|---|---|---|---|---|---|
| Colour + glyphs (✓/✗/▲) | ✓ | ✓ | ✓ | ✓ | only red error: |
| Bold headings / dim secondary | ✓ | ✓ | ✓ | ✓ | ✗ |
| Tables for lists | - | ✓ | ✓ | - | manual spaces |
| Spinner/progress on TTY | ✓ | ✓ | ✓ | ✓ | plain text lines |
NO_COLOR/--no-color | ✓ | ✓ | ✓ | ✓ | ✗ |
--json machine output | ✓ | ✓ | ✓ (+--jq) | - | ~half the commands |
| Non-interactive flag | ✓ | ✓ (-q) | ✓ | ✓ | ✗ |
| Structured exit codes | partial | partial | partial | ✓ | ✓ (sysexits) |
What this means for Synapse: the contract basics (json, exit codes, clean output module) are ahead of the pack; the presentation (colour, glyphs, tables, progress, NO_COLOR) is well behind. The asymmetry is the story.
2.3 CLI-for-AI-agents (2026)
Agents want: deterministic, machine-readable output (--json), no ANSI / no prompts / no spinners in machine mode, distinct/branchable exit codes, machine-readable errors, and a frozen, additive-only output contract once they depend on it. The CLI is increasingly the lowest-friction door for an agent (less context than MCP tool schemas).
What this means for Synapse: the CLI is a first-class agent surface (complementary to MCP). Two real gaps: (a) no --no-input means a prompt can block an autonomous run; (b) --format json doesn’t cover writes, and errors are never JSON, so an agent parsing output gets structured reads but unstructured actions/errors.
2.4 Current output, captured (the honest picture)
Real output from the binary (v0.5 formatters):
$ syn project list
project cliente-alfa Cliente Alfa ← manual space columns, no header/color/border
$ syn search alfa
person maria-silva Maria Silva
project cliente-alfa Cliente Alfa
## Context ← snippet dumps RAW markdown + blank lines (looks broken)
$ syn person show maria-silva
---
type: person
id: maria-silva
... ← raw YAML frontmatter dump, not a styled card
$ syn graph project cliente-alfa
project/cliente-alfa
inbound:
<--[focal]-- person/maria-silva ← ASCII arrows (decent) but no unicode tree, no color
$ syn info
workspace: /private/tmp/syn-ux
project 1 ← plain key:value, no total, no bar/panel
What this means for Synapse: there is no unicode/box-drawing, no tables, no colour anywhere; show dumps raw YAML; search snippets render raw markdown (## Context) and look broken; graph is the only “structured” output and it’s plain ASCII. This is functional but well below a premium (“AAA”) modern-CLI bar. Three concrete polish targets beyond the styling layer: rich rendering (unicode graph tree, entity panels/cards, clean snippets) and a premium loading/animation experience.
3. Recommendation
| # | Block | Justification | Cost | Notes |
|---|---|---|---|---|
| 1 | TTY-gated styling layer (colour, ✓/✗/▲, bold/dim) honouring NO_COLOR/--no-color/TERM=dumb/SYNAPSE_NO_COLOR | Closes the whole visual gap in one module; clig.dev-compliant | M | Needs a colour crate → ADR (anstyle/anstream, the clap/cargo stack, ~0 transitive weight) |
| 2 | Non-interactive mode: --no-input (alias --yes/-y) + --quiet/-q; every prompt respects it | Agents/CI must never block on a prompt | S | The syn init onboarding + syn setup are the prompts to gate |
| 3 | --format json to 100% + machine-readable JSON errors (stderr) in json mode | Agents get structured actions + errors, not just reads | M | Today print_error is always plain text |
| 4 | Finer exit-code branching (not-found vs already-exists vs conflict) | Agents branch on outcome without parsing text | S | Keep sysexits categories; add/Document sub-codes |
| 5 | Tables + visual structure for list/search/info/doctor on TTY | The human-readability win; plain columns on pipe | M | Light table crate or hand-rolled; gate on TTY |
| 6 | TTY progress (spinner/bar) for embed --all/reindex/watch; plain lines off-TTY | Long ops feel alive; clig.dev: no animation off-TTY | S | indicatif or minimal; keep the current plain stream for pipes |
Non-goals: a full TUI (SYN-203 backlog); colour in JSON mode (never); changing the data a piped consumer already parses.
4. Disruptive bets
--no-inputas a first-class “agent mode” that also flips output to compact + no-colour + no-progress in one switch (aSYNAPSE_AGENT=1env too), the single flag an autonomous runner sets.- A documented, frozen CLI output contract (alongside the v1.0 frozen MCP surface): agents can depend on field names + exit codes; changes are additive-only. This is a trust feature, not just hygiene.
5. Ticket sketch (provisional IDs)
| Sketch ID | Block | Size |
|---|---|---|
| SYN-186 | TTY-gated styling layer + NO_COLOR/–no-color/TERM compliance (ADR for colour crate) | M |
| SYN-187 | --no-input/--yes + --quiet; prompts respect it (agent/CI never blocks) | S |
| SYN-188 | --format json 100% coverage + JSON errors + frozen output contract | M |
| SYN-189 | Finer exit-code branching for agents (not-found/already-exists/conflict) | S |
| SYN-190 | Tables + structured layout for list/search/info/doctor on TTY | M |
| SYN-191 | TTY spinner/progress for embed –all / reindex / watch | S |
| SYN-192 | Premium loading & animation experience (branded spinners, multi-phase bars, polished completion): AAA tier | M |
| SYN-193 | Rich rendering: unicode graph tree (├─└─), entity panels/cards for show, clean search snippets (strip raw markdown) | M |
| SYN-195 | Rendering opt-out contract: default AAA/rich; --plain/--raw + SYNAPSE_PLAIN for raw/token-economy; single is_rich() gate | S |
6. Risk register
| Risk | Severity | Mitigation |
|---|---|---|
| Colour crate = new dep | Low | anstyle/anstream are the clap stack (already transitively present); still write an ADR |
| Styling leaks into pipes/JSON, breaking scripts | High | Single chokepoint; gate on TTY + NO_COLOR; never style JSON; snapshot-test plain output |
| Output-field changes break agents | Med | Freeze the contract (block 3); additive-only after v1.0 |
7. Open decisions
- Colour crate:
anstyle(style only, BYO writer) vsanstream(auto-stripping stream)? Recommendanstream(handles the strip-on-pipe rule for free). --no-inputdefault in non-TTY: should the absence of a TTY imply--no-input(recommended) or still require the flag? (Recommend: non-TTY implies it; flag forces it even on a TTY.)- Tables: add a crate (
comfy-table/tabled) or hand-roll? (Hand-roll is dep-light but more code.)
8. References
- Command Line Interface Guidelines (clig.dev): colour, TTY, NO_COLOR, animation rules.
- NO_COLOR standard
- “You need to rewrite your CLI for AI agents” · “Designing a CLI for AI agents” (Arcjet)
- Code:
src/output.rs,src/cli.rs,src/exit.rs.
MCP & agent ergonomics: token-efficiency and missing affordances
| Field | Value |
|---|---|
| Date finalised | 2026-06-05 |
| Author | Rômulo Febasi romulo@febasi.com.br (Claude-assisted) |
| Scope | How to make Synapse’s MCP surface cheaper and easier for Claude (and other agents) to drive: token savings, missing tools/affordances, plugin/skill packaging. Grounded in the v0.5 code. |
| Status | draft |
| Inputs | Code audit of src/mcp/ + src/core/mcp/; Anthropic “Writing effective tools for AI agents”, “Code execution with MCP”, “Advanced tool use”; MCP 2026 spec (structuredContent/outputSchema, resources, pagination); the v0.5 SKILL.md/LLM.md work. |
| Out of scope | The semantic layer internals (ADR-008); the identity onboarding (SYN-170..173); anything requiring a protocol change to rmcp. |
Executive summary
Synapse’s MCP surface is correct and safe (propose/accept is solid) but not yet optimised for token cost or agent ergonomics. Three cheap fixes pay off immediately: bound the one unbounded read (list_by_type), return structuredContent + compact JSON instead of pretty-printed text, and attach ResourceLinks to reads (entities are already exposed as resources, but tools don’t link to them). Beyond that, a small set of new affordances (a batch read, a workspace-overview digest, a recent_changes delta tool, and batch proposals) would cut the round-trips and exploratory reads that dominate an agent’s token budget. The strategic bet is the Anthropic code-execution-with-MCP pattern (intermediate data never enters context) and shipping Synapse as a one-install Claude plugin (skill + MCP + hooks).
1. Context and premisses
v0.5 ships 15 MCP tools (4 local read, 4 federation read, reindex, replay_proposal, 5 write proposals). Responses are built by mcp::helpers::json_content → serde_json::to_string_pretty wrapped as a text content block. Entities, the manifest, the server-card and recent audit are exposed as resources (synapse://entity/{type}/{id}, paginated). The propose/accept invariant (BR-12.3) is enforced in core::pending::enqueue. The freshness banner is conditional (only when a noteworthy signal exists): already well-behaved.
Assumed: agents are the primary consumer of the MCP door, and their token budget is the scarce resource. The SKILL.md/LLM.md (v0.5, in synapse-releases) already teach correct usage; this brief is about making the server cheaper, not just the docs.
2. Market scan
2.1 Anthropic: writing effective tools for agents
Core principles: avoid tool overload / overlapping tools; namespace related tools; implement response constraints (pagination, range, filtering, truncation with sane defaults) for anything that could flood context; tool descriptions are load-bearing and should be prompt-engineered.
What this means for Synapse: list_by_type violates the response-constraint principle: it takes only type + status, no limit/cursor (src/mcp/args.rs ListArgs), so a big workspace returns everything. search_knowledge and federated_search already cap with limit; list_by_type is the outlier. Fix first.
2.2 MCP 2026 spec: structuredContent, outputSchema, resource links
Tools may return structuredContent (machine-readable JSON conforming to an outputSchema) alongside text; clients parse it programmatically instead of the model re-reading a text blob. Results may carry ResourceLink/EmbeddedResource blocks pointing at resources the client can fetch (and cache) lazily.
What this means for Synapse: today every tool returns pretty-printed JSON as text (json_content). Two wins: (a) emit structuredContent + a tool outputSchema, and drop the pretty-print (whitespace is pure token waste); (b) since entities are already resources, have search_knowledge hits and get_entity return a ResourceLink to synapse://entity/{type}/{id} so a resource-caching client can avoid re-fetching, and the agent can defer reading the body until it’s sure it needs it.
2.3 Anthropic: code execution with MCP (the 98% pattern)
Presenting MCP servers as a code API the agent writes against: loading only the tools it needs and keeping intermediate results in the execution environment: cut token usage by up to 98% in Anthropic’s tests. The “Tool Search Tool” discovers tools on demand (~85% reduction) so the full tool catalogue isn’t loaded upfront.
What this means for Synapse: the CLI analog already exists and is the right shape: syn export --as json | jq '…' filters/aggregates outside the model’s context. The MCP door has no equivalent: an agent must pull data into context to filter it. A constrained, read-only query tool (a safe expression over the workspace JSON, or a thin jq-like projection) would let agents compute answers without the rows entering context. This is the highest-leverage bet.
3. Cross-check vs the current server
| Affordance | Status today | Gap |
|---|---|---|
| Bounded reads | search/federated cap; list_by_type does not | Add limit + cursor |
| Machine-readable results | JSON as pretty text, no outputSchema | structuredContent + compact JSON |
| Lazy/cacheable reads | entities are resources, but tools don’t link them | ResourceLink on hits + get_entity |
| Batch reads | one get_entity per call | get_entities / resource multi-read |
| Field projection | get_entity returns the whole markdown | optional fields projection |
| Orientation | none (agent must list+search to learn the shape) | workspace_overview digest |
| Deltas | none (re-reads everything each session) | recent_changes {since} |
| Batch writes | one proposal per call | propose_batch → one accept |
| Pre-validation | update_status validates at accept time | valid_statuses/dry-run to cut rejects |
| Compute-out-of-context | CLI has `export | jq`; MCP has nothing |
4. Positioning
The moat is local-first + auditable propose/accept. No SaaS competitor can match “the model proposes, the human signs off, every write is provenance-stamped and replayable” while keeping data on the laptop. Token-efficiency and agent ergonomics are table stakes that make the moat usable; they are not the moat. Lane to occupy: “the second brain agents drive cheaply and can’t silently corrupt.”
5. Recommendation
| # | Block | Justification | Cost | Defensible? |
|---|---|---|---|---|
| 1 | Bound list_by_type (limit+cursor) | Closes the one unbounded read; Anthropic principle #1 | S | Hygiene |
| 2 | structuredContent + compact JSON | Token + parse win on every read; spec-aligned | M | Hygiene |
| 3 | ResourceLink on reads | Lazy/cacheable reads; entities already are resources | S | Leverages existing resources |
| 4 | workspace_overview digest tool | Kills the explore-cost; one call to orient | M | Product |
| 5 | recent_changes {since} delta | Recurring agents pull deltas, not the world | M | Product (audit/timeline exist) |
| 6 | get_entities batch + fields projection | Fewer round-trips; read only needed fields | M | Hygiene |
| 7 | propose_batch (one bundle, one accept) | Fewer round-trips; one human sign-off | M | Fits propose/accept |
| 8 | read-only query tool (compute-out-of-context) | The 98% pattern; biggest token lever | L | Moat-adjacent |
Non-goals for now: protocol-level changes; a full code-execution sandbox; dropping any existing tool.
6. Disruptive bets
- Compute-out-of-context
query: a safe, read-only projection/aggregation over the workspace (jq-subset or a whitelisted expression) so an agent answers “how many blocked tasks per owner” without a single row entering its context. The local-first design makes this safe (no remote data exfil) in a way a SaaS MCP cannot casually offer. - Synapse as a one-install Claude plugin:
.claude-plugin/plugin.jsonbundling the skill + the MCP server config + hooks (e.g., aSessionStarthook that runssyn doctor/syn pending list). Onecp/marketplace install wires the whole loop. Agents that don’t speak MCP still get the skill. - Proposal “context snapshot” as a first-class agent contract: every write tool already accepts
context; lean into it sosyn audit replaycan re-run a proposal against a newer model and flag drift. This is an auditability feature no hosted assistant offers: sell it. recent_changesas the agent’s memory diff: a recurring agent (daily standup, weekly review) reads only what changed since its last run; pair with a server-issued cursor so the agent is stateless.
7. Ticket sketch (provisional IDs)
| Sketch ID | Block | Size |
|---|---|---|
| SYN-176 | list_by_type limit + cursor | S |
| SYN-177 | structuredContent + outputSchema + compact JSON on all reads | M |
| SYN-178 | ResourceLink to synapse://entity/... on search hits + get_entity | S |
| SYN-179 | workspace_overview digest tool (counts + key/recent) | M |
| SYN-180 | recent_changes {since} delta tool (over audit/timeline) | M |
| SYN-181 | get_entities batch + optional fields projection | M |
| SYN-182 | propose_batch (one pending bundle, one accept) | M |
| SYN-183 | read-only query tool (compute-out-of-context): spike first | L |
| SYN-184 | Synapse Claude plugin package (skill + MCP + hooks) | M |
8. Risk register
| Risk | Severity | Mitigation |
|---|---|---|
structuredContent not supported by all clients | Low | Keep the text block too (dual emit); spec says clients ignore unknown fields |
query tool = injection / DoS surface | High | Read-only, whitelisted ops, row/time caps; spike (SYN-183) before commit |
| Tool sprawl (15 → 20+) dilutes the catalogue | Med | Namespace (synapse_*); lean on the skill + Tool Search; retire overlaps |
| Batch tools complicate the audit trail | Med | One pending row per logical write even under propose_batch; bundle only the accept UX |
9. Open decisions
- Adopt
structuredContentnow, or wait until the dominant client (Claude Code) renders it? (Recommend: dual-emit now: cost is low.) - Is the
querytool worth the security surface, or issyn export --as json | jq(CLI) enough and we just teach agents to shell out? (Spike SYN-183 to decide.) - Namespace the tools (
synapse_*) now (breaking for existing configs) or at v1.0 with the frozen MCP surface? - Should
workspace_overviewandrecent_changesbe tools or resources (or both)? Resources are cacheable; tools are discoverable.
10. References
- Writing effective tools for AI agents: Anthropic
- Code execution with MCP: Anthropic
- Advanced tool use: Anthropic
- MCP spec: Tools (structuredContent / outputSchema)
- Agent Skills open standard · llms.txt
- Code:
src/mcp/{server,helpers,resources,freshness,args}.rs,src/core/mcp/*.rs.
Agent-memory field comparison: where Synapse is ahead, behind, and should adapt
| Field | Value |
|---|---|
| Date finalised | 2026-06-14 |
| Author | Rômulo Febasi romulo.silva@oboi.com.br |
| Scope | A critical survey of how the agent-memory problem space solves the same problems Synapse is solving: temporal facts, contradiction, decay, write-governance, provenance, storage, typed schemas, retrieval, scale, extraction, evaluation, and what that implies for our roadmap. Design approaches are described abstractly (as problem-space techniques), grounded in public research/benchmarks. No external code informs this brief; it is our own analysis. |
| Status | draft |
| Inputs | Public research literature and benchmarks (temporal KGs, forgetting curves, memory governance, retrieval fusion, vector indexing); the published critiques of agent-memory benchmarks; our own ADRs 022/026/027/028/029/030 and the v0.9 re-scope. |
| Out of scope | Implementation of any item here (each becomes its own slice/ADR). Multi-user/concurrent-write features (deliberately off-thesis). Chasing QA leaderboards. |
Executive summary
The field validates Synapse’s four load-bearing bets: invalidation-not-deletion, provenance + reversibility, deterministic non-ML trust, and local-first Markdown-as-truth, and several are being independently rediscovered as necessary in the 2025–26 literature. It also exposes a handful of concrete, low-risk improvements we should adopt. The single most important reframing: operation-level benchmarks now measure that auto-extraction and auto-update are where memory systems corrupt (extraction accuracy <62 %, correct-update rate <26 %: HaluMem). That is exactly the stage Synapse’s human-accept gate sidesteps, but only if we reframe the gate from “a human approves every write” (friction the market rejected) to “auto-apply the reversible majority, gate only the costly minority”, which ADR-022 already half-encodes. The five highest-leverage adoptions are below; none requires abandoning the thesis.
1. Context and premisses
Synapse is a local-first, single-user, Markdown-as-truth knowledge graph (Rust; CLI + MCP). Markdown files are the source of truth; a disposable SQLite index holds FTS + vectors + a typed graph. The moat is governance: AI proposes a fact → a human accepts it; accepted facts carry inline provenance + a history chain; an append-only, tamper-evident, git-anchored audit; reversibility via git and at <ref>. We have just repositioned the brand around provenance + reversibility + tamper-evidence (not the gate) and applied a roadmap cut list. This brief asks: given that positioning, what has the rest of the field figured out that we should learn from, and what have we gotten right that the field is still catching up to?
2. Market scan
2.1 Temporal model: valid-time vs transaction-time
The mature pattern is a four-timestamp bitemporal fact, not a two-timestamp one: valid-from/valid-until (when it was true in the world) and recorded-at/expired-at (when the system believed it). The two axes are decoupled on purpose; a job change that happened in March may be learned in June. “Currently true” resolves to valid-until IS NULL AND expired-at IS NULL; historical queries filter the valid axis, audit queries the transaction axis. A heavier variant reifies time itself as graph nodes (Timestamp/Interval) for native “during/overlaps/before” reasoning: overkill for a personal KB.
What this means for Synapse: ADR-027’s plan (valid-time in frontmatter, transaction-time derived from git) is the good half of the mature model, but deriving “when we stopped believing it” purely from git commit boundaries is fragile under batch commits (a commit that both adds B and closes A collapses both transaction-times). We should store an explicit belief axis (x-recorded-at / x-invalidated-at) on the fact and reserve git for the tamper-evident audit axis where it is uniquely strong. Adopt three deterministic supersession rules: new-after-old → close old; older-than-known → born already-expired (out-of-order ingestion); disjoint intervals → keep both, no contradiction. Materialise a current flag in the index so the hot read path is an indexed lookup, not an interval scan.
2.2 Contradiction detection: structural → semantic candidates → judge
The disciplined approach is layered, cheapest-first: (a) structural match (same subject+predicate, different object) with an exact-normalised-text short-circuit for pure duplicates; (b) semantic candidate retrieval: hybrid search nominates the top-K related facts as invalidation candidates (catches reworded contradictions like “lives in Berlin” vs “moved to Munich” that a same-field check misses); (c) an LLM-as-judge over only that small candidate set returns duplicate and contradicted index lists. The brittle anti-pattern is a single LLM call emitting ADD/UPDATE/DELETE/NOOP that fuses detection and resolution, over-deletes, and has no temporal axis. Cosine distance should nominate candidates, never decide.
What this means for Synapse: SYN-242 should be: structural + exact-dedup (deterministic, no model) → semantic candidate retrieval → escalate to the pending queue with the candidate set attached rather than auto-adjudicate. The human gate is the right home for the expensive, non-deterministic judgment the field tries (and fails, <26 %) to automate. Once a human confirms supersession, the interval-closing is pure date arithmetic.
2.3 Decay, forgetting, consolidation
Three shapes dominate: write-time reconciliation (forgetting as a side effect of an LLM add/update/delete verb: its named failure mode is catastrophic forgetting: silently overwriting still-valid facts); background consolidation (“sleep-time” passes every N episodes that cluster and abstract old detail into compact gist: destructive of originals unless archived); and genuine time-decay as a retrieval-rank multiplier (Ebbinghaus/ACT-R curves; retrieval reinforces, disuse fades), usually with importance tiers and a TTL only on the long tail. Notably, several systems fence destructive forgetting behind explicit confirmation, and one keeps automated time-decay as a paid/hosted-only feature its open SDK refuses, a tell that automated forgetting is considered risky.
What this means for Synapse: Non-destructive (advisory stale, never deletes) is the correct, under-marketed call; the field is painfully relearning that destructive consolidation corrupts memory. Two reversible gaps to close: (1) a compaction path that writes a new summary fact linking to (not replacing) its sources: bounded retrieval cost without losing reversibility; (2) ensure decay actually moves retrieval rank, not just a list: stale and ranking must share one decay function (ADR-029 already specifies the curve; wire it into fusion, see §2.8). The “profile vs log” distinction (current-state-overwrite vs append-and-enrich) maps cleanly onto per-type policy (SYN-248).
2.4 Write governance / human-in-the-loop
Essentially no shipped agent-memory system gates memory writes behind human approval; the category’s whole value is frictionless capture. The closest analogue gates tool calls generically (requires_approval → pause/persist/resume), which validates the mechanism but positions it as an optional safety guardrail, not the default. Where there is no human, bad writes are prevented by machinery: dedup-before-write, write-time contradiction resolution, confidence thresholds, periodic “memory reviewer” hygiene passes, and (in the recent governance literature) an explicit write-validation gate as a truth-maintenance system that auto-rejects a write failing an NLI contradiction check. So the governance idea is being independently rediscovered as necessary; Synapse is early, not wrong.
What this means for Synapse: An absolute every-write human gate is friction the market rejected; keep ADR-022’s tiered auto-apply and push it further: auto-apply the reversible majority (git + at <ref> make a wrong write cheap to undo, which removes the main justification for blocking), and reserve the gate for new entities, irreversible/identity changes, and contradictions of high-trust facts. Add a syn-native memory-hygiene review (surface duplicates/contradictions/stale/low-confidence into the pending queue): it converts the field’s “reconcile-later” into our accept-gate idiom and gives the gate high-value work. Messaging matters: lead with “every AI write is auditable and reversible,” not “AI proposes, human disposes.”
2.5 Provenance / audit / attribution / reversibility
In shipped memory systems the ceiling is a per-fact actor_id + role + created/updated_at and a plain mutable history table: captures who/when/what-changed, but not which-model/why, and is not tamper-evident (an ordinary table a compromised process can rewrite). The Markdown-native siblings stamp frontmatter lineage but have no history chain, no signing, no audit log, no undo command. Cryptographic, tamper-evident provenance (signed receipts, hash-chained custody, content-hash manifests, external witnesses) is an active theme in the adjacent agent-governance and content-authenticity literature, but is absent from shipped agent-memory products.
What this means for Synapse: Our stack (hash-chain + ed25519 signed checkpoint + git Merkle DAG + identity-bound commit signing + portable signed receipts + audit verify) matches what the governance literature prescribes and exceeds what any memory product ships. The moat is real but time-boxed; the right framing is “Synapse already ships what the governance papers are still writing,” and durability comes from binding it to local-first Markdown + identity, not from the crypto alone (which others can copy).
2.6 Storage model
Three camps: graph-DB-backed (max query power: native multi-hop, community detection: at the cost of a DB dependency and no human-editable artifact); pluggable vector-store + metadata (max operational/multi-tenant flexibility, but the store is a black box with no inspectable truth); and Markdown-as-truth + disposable index (ours). The mature Markdown analogue’s own trajectory is the warning sign: to stay correct under concurrency it bolted on a server/Postgres index path and a cloud bidirectional-sync layer with git-style conflict modes; i.e. multi-writer is exactly where files-as-truth breaks, and the fix is to stop being files-as-truth.
What this means for Synapse: Markdown-as-truth is a strength for our single-user, auditable, git-friendly thesis and a genuine differentiator (every claim traces to a diffable, revertible file), and the multi-writer tax that tears the seam is a cost we do not incur by design. The standing weakness is query power: relations resolved as lazy forward-references and deep traversal simulated in SQL rather than native. The portable lesson from the field’s sync engines: a naive “scan tree, diff checksums” loop is what feels O(n²); the principled fix is a persisted scan watermark + mtime → size → checksum gating (checksum only when mtime/size already differ) + content-hash move/rename detection. This directly extends the reindex work in §2.9.
2.7 Typed / open schemas
Four designs: typed-model-as-schema (custom types as typed classes; field annotations declare which fields are embeddable/indexed and which are identity fields that deterministically derive the node id via a namespaced hash: free, lookup-less dedup); frontmatter-bag + parsed body (open vocabulary of categories/relations expressed in prose, indexed via a JSON bag: low friction, no validation, no fast typed columns); reference-ontology overlay (validate against an external RDF/OWL vocabulary: powerful for real taxonomies, heavy for a personal KB); and fixed memory blocks with char limits (an anti-pattern for an open typed graph).
What this means for Synapse: Our closed-core + EAV-side-table + open-frontmatter-bag (ADR-028) is sound and injection-safe, and binding field names as values (never interpolating) is the correct invariant. The single idea worth adopting: identity fields → deterministic hashed id: declaring which fields define an entity’s identity and hashing them to a stable id gives free dedup/merge, directly useful for contradiction detection (SYN-242) and merge-safe multi-author segments (SYN-249). “Typed observations in the body” is a lower-friction capture path worth considering, but it trades away validation.
2.8 Retrieval / ranking
Plain RRF (k=60) is a respectable, recall-leaning floor, not the ceiling. k is a precision/recall dial (low k rewards the top hit; high k rewards cross-list consensus). The well-trodden upgrades, cheapest first: weighted RRF (per-source weights); additive normalised scoring (semantic + sigmoid-normalised BM25 with query-length-adaptive steepness + entity-match boost: strictly more expressive, at the cost of per-signal normalisation, which is the brittleness RRF dodges); graph rerankers (anchor/node-distance: “things near your focal entity rank higher”; mention-frequency as a corroboration proxy; MMR for diversity); and a cross-encoder final stage (SOTA precision, ~80–120 ms for ~20 docs, but its marginal gain shrinks once stage-1 hybrid is already good, which ours is). A no-new-dependency variant uses a general LLM as a boolean relevance classifier and reads the logprob of the “true” token as the rerank score.
What this means for Synapse: The single cheapest precision upgrade is to fold ADR-029’s deterministic trust score into the fusion step as a per-candidate boost: it currently lives only in blame/display, and the field stores importance on the record and uses it in ranking. Next: an anchor-distance graph reranker for focal-entity queries (we already fuse a graph signal; promoting it is dependency-free). Expose k / per-signal weights. Treat a cross-encoder (or the LLM-logprob variant via MCP) as an opt-in final stage only: its gain over good hybrid is real but bounded.
2.9 Indexing / scale
Consensus: flat vector search is genuinely fine exactly where Synapse lives (thousands to low-hundreds-of-thousands of vectors); it gives deterministic, linearly-growing latency and only becomes painful around 10⁵–10⁶. HNSW adds build cost, recall-tuning params, and non-determinism for no gain below ~100k, a post-100k lever, not a now-lever. Ingestion is kept sub-linear by change-set work, not clever indexes: watermark + incremental scan, cheap-op-gating before checksums, batch + bounded concurrency, embedding in one batched pass over only content-changed entities, and separating FTS indexing from embedding (index text now, vectorise later) so a missing model doesn’t fail or re-scan everything.
What this means for Synapse: Our reindex O(n²) fix (prepare_cached + insert-only bulk path + single bulk FTS rebuild) is the same instinct and already paid off (10k full reindex 7.9 s → 0.4 s). The remaining wins, in order: (1) batch the embedding call across all content-changed entities and only re-embed when an embeddable field changed (tie to §2.7’s indexed-field declaration so a frontmatter-only edit skips re-embedding); (2) watermark + mtime/size-before-checksum scan (the structural fix for scan cost at scale); (3) content-hash move/rename detection so a renamed note keeps its id and links; (4) hold HNSW until SYN-227 budgets actually cross the threshold: they won’t at single-user scale.
2.10 Extraction: and why “agent proposes, no auto-extraction” is mostly right
The field’s dominant pattern is a two-call extractor (extract atomic self-contained facts → reconcile against existing memory with an add/update/delete verb), governed by enormous prompts encoding hard-won rules: atomicity, pronoun/speaker resolution, specificity preservation (“416 pages”, not “about 400”), no-echo (don’t re-extract the assistant’s restatement), no-meta-extraction (store the content, not “user shared a document”), and observation-date grounding (resolve “last week” to an absolute date at write time). HaluMem now measures the failure: extraction recall <60 %, accuracy <62 %; update correctness <26 %; the system misses real facts and fabricates them, and the update stage is the weakest link. Root causes are structural: recall-maximising “when in doubt, extract”, speaker misattribution, echo, and meaning-inversion (“used to love hiking” stored as present-tense).
What this means for Synapse: By not running an auto-extractor, Synapse structurally avoids the stage where the whole field scores <62 %. The human gate is a precision filter no competitor has, and the benchmarks now validate it. But there are two honest gaps: (1) the work doesn’t vanish, it moves to the MCP client agent; if that agent runs a naive extractor we inherit the <62 % before the human sees it, and the gate just makes a human triage noise. We should publish a proposal-quality contract / prompt-guidance doc for client agents encoding the field’s extraction craft; this raises the floor on what reaches the gate without building an extractor. (2) Human-gated capture does not scale to “ingest 10k chat turns”; capture/ambient-git are the pressure valves, and precision-over-recall-at-scale is the right trade for governed memory.
2.11 Dedup / merge on ingest
Three strategies; the best layer them: LLM-as-reconciler (the <26 % stage, a single judgment that can DELETE, unrecoverable without versioning); deterministic-first, LLM-as-fallback (the strongest idea: exact-normalised match → MinHash/LSH fuzzy match over character-n-gram shingles, gated by a Shannon-entropy filter so short/low-entropy names skip straight to the model → LLM only on true ambiguity: most dedup never touches an LLM); and ADD-only + link, defer merge (never merge on ingest; store new facts with linked_memory_ids, resolve later: trades storage for never silently destroying a memory).
What this means for Synapse: When SYN-242 ships, internalise deterministic-first-LLM-last tiering and invalidate-don’t-delete: both align with our existing NER/mention-linking and ADR-027, and both avoid the destructive auto-merge the benchmarks punish. The identity-hash idea (§2.7) is the deterministic backbone for the first tier.
2.12 Evaluation
The public benchmarks measure auto-extract-and-auto-answer pipelines: LoCoMo (end-to-end QA over multi-session dialogue; single/multi-hop, temporal, adversarial), LongMemEval (extraction, multi-session + temporal reasoning, knowledge updates, abstention), HaluMem (operation-level hallucination: extraction/update/QA scored separately). Documented criticisms: LoCoMo conversations are short enough (~16–26k tokens) that “memory” is partly just long-context reading; gold labels are sometimes wrong; SOTA claims are contested and configuration-sensitive (critique); and the deepest flaw is the wrong unit of measurement: QA scores “did the model answer given context”, not “did the memory store/retrieve correctly”, letting systematic storage failures hide behind decent QA.
What this means for Synapse: Do not adopt these benchmarks; they assume a pipeline we deliberately lack, and chasing them would distort us toward auto-QA. Build a property/invariant harness instead, because governed memory’s quality is verifiable where the field can only estimate: (1) provenance correctness (every fact traces to an accepted proposal; unbroken x-ai-history); (2) reversibility integrity (replaying the audit reconstructs current state exactly; audit ⟷ store ⟷ index agree); (3) no-silent-corruption (no accepted change overwrites a fact without a recorded supersession, the one thing the field fails at <26 % and we can prove); (4) temporal correctness (at <ref>/--as-of return the state that held: deterministic, where the field scores worst); (5) door parity (CLI ⟷ MCP identical); (6) round-trip fidelity (reindex reproduces the index; export→import lossless). The reframing is itself marketing: “we can prove our memory didn’t corrupt; the field can only score whether a judge liked the answer.”
3. Cross-check vs prior hypotheses
| Hypothesis (our prior stance) | Status | Reason |
|---|---|---|
| Invalidation-not-deletion + supersession (ADR-027) | Validated | The mature temporal-KG consensus; the destructive camps are the documented failure mode. |
| Provenance + reversibility is the moat, not the gate (v0.9 re-scope) | Validated | The field’s governance literature is racing toward primitives we already ship; reversibility dissolves the gate’s main objection. |
| Deterministic, non-ML trust score (ADR-029) | Validated | Mirrors the ACT-R/forgetting-curve research while staying auditable; the field buries importance in opaque weights or has none. |
| Markdown-as-truth + disposable index | Validated (single-user only) | A real differentiator for our thesis; the seam that tears is multi-writer, which we don’t incur. |
| Flat vector search, defer HNSW | Validated | Field consensus: flat is correct below ~100k; HNSW is a post-100k lever. |
| The accept-gate as headline | Challenged | An absolute every-write gate is market-rejected friction; reframe to auto-apply-reversible, gate-the-costly. |
| Transaction-time derived purely from git | Challenged | Fragile under batch commits; store an explicit belief axis, keep git for the tamper-evident audit axis. |
| Contradiction handled by overwriting a field | Adds | Needs a semantic-candidate-retrieval step (reworded contradictions) and escalate-to-gate, not just same-field detection. |
| Trust score is a display feature | Adds | Cheapest precision win is to inject it into retrieval ranking, not just blame. |
| No eval harness | Adds | Build a property/invariant harness (provenance/reversibility/temporal), not a QA leaderboard. |
| Extraction is the agent’s job, undocumented | Adds | The craft is real, transferable IP; publish a proposal-quality contract for client agents. |
4. Highest-leverage adoptions (recommended, ranked)
- Inject the ADR-029 trust score into retrieval fusion: cheapest precision upgrade, reuses shipped work; trust currently only displays.
- Reframe SYN-242 contradiction as deterministic-structural + semantic-candidate-retrieval → escalate to the gate (not LLM auto-resolve); pure date-arithmetic supersession on confirm.
- Batch embedding + watermark/mtime-gate scan + move-detection: extends the reindex fix; the field’s proven sub-linear-ingestion patterns.
- Proposal-quality contract for MCP client agents: encode atomicity / observation-date / no-echo / no-meta-extraction / attribution; closes our one real extraction gap without building an extractor.
- Property/invariant eval harness: provenance, reversibility, no-silent-corruption, temporal correctness, door parity, round-trip; makes our quality provable.
Secondary: explicit belief transaction-time axis (§2.1); reversible compaction-with-links (§2.3); identity-hash dedup (§2.7); per-type “profile vs log” decay policy (SYN-248); anchor-distance graph reranker (§2.8).
5. Disruptive bets (a SaaS competitor cannot easily copy)
- “Provable, not estimated” memory quality. Ship the invariant harness and surface its assertions (
audit verify, adoctor-style integrity report). A hosted multi-tenant memory service cannot let a user prove its store didn’t corrupt theirs; we can, because the truth is their files in their git. - Reversibility-first governance. “Auto-applied but always reversible, gated only when it matters” is a posture that needs a content-addressed, user-owned substrate, which a server-side store structurally lacks.
- Git-native bitemporality. Tamper-evident transaction-time from git’s Merkle DAG, uniform across every type and file, bound to identity; the field’s temporal systems store transaction-time as a mutable DB column.
- The proposal contract as a standard. Publishing the extraction-craft contract for MCP clients makes Synapse the quality bar other agents target, without us running the lossy extractor.
6. Ticket sketch (provisional)
| Sketch | Block | Size |
|---|---|---|
| Trust score → retrieval fusion boost | core/search | S |
| SYN-242 contradiction: structural + candidate-retrieval → gate | core | M |
| Batch embedding over content-changed entities only | core/index | S |
| Watermark + mtime/size-before-checksum scan; move-detection | core/index | M |
| Proposal-quality contract doc for MCP clients | docs/mcp | S |
Property/invariant eval harness (syn verify or test-suite) | core/ci | M |
Explicit belief transaction-time axis (x-recorded-at/x-invalidated-at) | model/core | M |
| Reversible compaction: summary-fact-with-source-links | core | M |
| Identity-hash dedup backbone | core/model | S |
| Anchor-distance graph reranker | core/search | S |
7. Risk register
| Risk | Severity | Mitigation |
|---|---|---|
| Trust-in-ranking changes search results subtly / regresses recall | Med | Behind a flag; add a search-quality property test before defaulting on. |
| Contradiction escalation floods the pending queue | Med | Deterministic + dedup short-circuit first; only genuinely ambiguous cases escalate. |
| Belief-axis fields bloat frontmatter / confuse users | Low | Stamp lazily on mutation (like provenance); keep git as the audit axis. |
| Eval harness over-fits to our own assumptions | Med | Make invariants, not scores; cross-check with doctor/audit verify. |
| Provenance moat is time-boxed (others copy the crypto) | Med | Durability is the local-first + identity binding, not the crypto; keep shipping the integrated stack. |
8. Open decisions
- Do we add the explicit belief transaction-time axis now (sharpens SYN-223/242) or keep deriving from git for v1.0 and revisit?
- Is the property/invariant harness a
syn verifycommand (user-facing) or a CI test-suite (internal), or both? - Does the proposal-quality contract ship as a doc, or also as a machine-readable schema the MCP server advertises to clients?
- Per-type “profile vs log” decay (SYN-248): worth the
[policy.<kind>]surface, or defer past v1.0?
9. References
- HaluMem: operation-level hallucination evaluation for memory systems (arXiv 2511.03506)
- LongMemEval (arXiv 2410.10813)
- LoCoMo overview
- Temporal knowledge graphs for LLM agent memory (Zep paper)
- “Is Mem0 really SOTA?”: benchmark critique
- Governing Evolving Memory: SSGM framework (arXiv 2603.11768)
- ACT-R-inspired remembering/forgetting in LLM agents (ACM)
- Understanding Reciprocal Rank Fusion in hybrid search
- Weighted RRF (Elastic)
- Search reranking with cross-encoders (OpenAI Cookbook)
- Scaling vector search: flat vs HNSW
- sqlite-vec stable release
v0.11 issue-direction study: question, reshape, and sequence the open work
| Field | Value |
|---|---|
| Date finalised | 2026-06-15 |
| Author | Rômulo Febasi romulo@febasi.com.br (4 specialised research subagents) |
| Scope | Critically question every open v0.11 issue (market, technical state-of-art, user-problem, adversarial), then reshape/sequence each. Implementation follows. |
| Status | accepted |
| Inputs | 4 parallel subagents (market/competitor with web; technical state-of-art with web; user-problem strategist; adversarial critic: all read the repo + the 2026-06-14 field study). |
| Out of scope | Cutting any v0.11 issue entirely (owner directive: adjust, don’t drop). Multi-user/Commercial milestone design. |
Executive summary
After v0.10 the governance moat is built; the proof of it is parked. Four independent subagents converge on three things: (1) several v0.11 items are multi-user features mis-filed under a single-user phase (SYN-249C, parts of SYN-254/248): keep them but scope them honestly to opt-in/minimal; (2) two items are already partly shipped (SYN-259 per-field delta authorship landed with SYN-256; the SYN-245 self-kNN “blocker” is already proven in a test): strike the stale halves; (3) the highest-value, most copy-proof artifact in the whole field (syn verify (provable non-corruption, SYN-253)) is buried in v1.0 while the market commoditises Synapse’s crypto primitives (Anthropic ships editable-Markdown-with-audit; Microsoft/MemLineage publish Ed25519+Merkle protocols; Memoir/Memoria ship “git for memory”). Reshape v0.11 to deliver the provable-quality thread (reshape SYN-258 into a temporal-correctness invariant, the seed of syn verify) while finishing the disciplined scale/graph slices that survive the “prove-the-need” ruler. Implement all nine, adjusted.
1. Context and premisses
v0.10 shipped (governed memory-quality: both-doors, contract finish, trust-in-ranking, proposal contract, contradiction→gate). v0.11 (#14) was a holding milestone: explicitly to be re-scoped when it became active. It is now active. Pillars are non-negotiable: local-first, single-user by design, Markdown-as-truth, governed+provable, git-native, reversible. Ruler: deterministic > ML; prove the need before building; no dep without an ADR; collapse forks, don’t extend them.
2. Market scan
2.1 The crypto-provenance moat is being commoditised: fast
Anthropic (2026-04) shipped editable Markdown agent memory with audit logs + rollback/redact as the default substrate; Microsoft “Portable Agent Memory” and “MemLineage” publish BLAKE3/Ed25519 Merkle-DAG provenance protocols; Memoir/Memoria/Beads ship “git for AI memory” with cryptographic integrity. Synapse’s hash-chain + ed25519 + receipts are now a published, copyable pattern.
What this means for Synapse: the durable moat is no longer the crypto primitives; it is local-first + git + identity-binding + the ability to prove non-corruption to the user (which a hosted store structurally cannot). Lead with syn verify, not “we also have hashes.”
2.2 Bitemporality and contradiction are now table-stakes; auto-resolution still fails
Zep/Graphiti bitemporal edges and Mem0’s temporal gains are the reference; HaluMem still measures auto-update correctness <26%. What this means for Synapse: our valid-time + supersession + contradiction→gate (don’t auto-resolve) posture is correct and now mainstream-validated. Temporal correctness we can prove (replay-as-invariant) is the differentiator, not temporal features per se.
2.3 Scale pressure moved to token-efficiency, not ANN speed
The field optimises tokens-per-retrieval and reports at-scale degradation (BEAM −25% at 1M–10M tokens); nobody hits flat-vs-HNSW pain at single-user scale. What this means for Synapse: the flat-vector/defer-HNSW ruler is vindicated. SYN-225/246 stay deferred; SYN-251’s value is ingestion correctness (don’t re-embed unchanged fields), not query speed.
3. Cross-check vs the open v0.11 issues
| Issue | Subagent consensus | Decision (this study) |
|---|---|---|
| SYN-260 hygiene | Contains 2 real latent bugs (inert pre-commit fmt gate; unwrap_used unenforced despite CLAUDE.md). Elevate. | DO FIRST. Split: fix fmt-hook PATH + add clippy::unwrap_used deny (staged) + split queries.rs (ADR-015). cliff noise → cosmetic. |
| SYN-259 retrieval | Part (2) per-field delta authorship already shipped (core::changes). Part (1) valid-time pushdown is a real latent bug (fts_rank/vector_rank ignore as_of). | RESHAPE. Strike (2). Keep: push valid_at_predicate into FTS+vector candidate queries; add search --type <custom> (the documented gap). pattern regex → its own dep+ADR, slip. |
| SYN-251 ingestion | Real win = batch-embed only changed embeddable fields + content-hash move detection. Watermark is a multi-writer lesson. | RESHAPE. Ship the two high-ROI slices; keep the existing (mtime,size) ledger. |
| SYN-254 identity dedup | Scope to deterministic fingerprint + collision→gate; never derive id/filename from hash (pillar risk); opt-in. Fuzzy/MinHash deferred. | RESHAPE. Internal identity fingerprint for dedup comparison; collision routes to the gate (detect-don’t-merge); never touches the slug/id. ADR. |
| SYN-245 semantic edges | Self-kNN blocker already resolved (test exists). Build explainable mutual-kNN + per-edge provenance; JGF is the consumer. | RESHAPE. Remove stale blocker; build mutual-kNN + local-baseline rescale + provenance (cosine,k,shared_terms); drop-semantic-first under limits. |
| SYN-255 anchor rerank | Dependency-free (reuse graph_rank); bench-gate. | KEEP, bench-gated. Reuse the existing graph leg; ship behind a recall check; cheap either way. |
| SYN-248 per-type policy | Reshape to the “profile vs log” (overwrite vs append / decay) axis, not a write_policy/pii/link matrix. Only hardens the gate. | RESHAPE. Minimal [policy.<kind>] with a decay/mutability axis; deterministic; can only raise the gate. ADR. |
| SYN-249C sig verify | Multi-user value; opt-in; ssh-key + ADR (pre-authorised by ADR-030); rank last. | KEEP, opt-in, last. ssh-key SSHSIG verify of the commit gpgsig vs non-revoked identities; doctor/audit verify flag unsigned/unknown. ADR. |
| SYN-258 temporal replay | Don’t build a replay engine (~zero daily solo use). Reshape to the temporal-correctness invariant feeding SYN-253. | RESHAPE. A property/invariant test asserting at <ref> -- … --as-of <date> reconstructs the state that held, the seed of syn verify. No replay command. |
4. White space / disruptive opportunities (fit the pillars)
syn verify: provable non-corruption as a command (promote SYN-253 to the next milestone’s headline). A hosted store cannot let a user prove its store didn’t corrupt theirs; Synapse can, because the truth is the user’s git. The single most on-brand, copy-proof artifact. SYN-258 is reshaped as its first invariant.- “Import your AI’s memory, then govern it”: one-way ingest of Claude/ChatGPT memory exports into governed, signed, git-versioned Markdown (extends SYN-228). Anthropic normalised memory export; Synapse becomes the provable home.
- Decay-as-a-daily-value-moment: fold a “re-confirm aged high-trust facts” prompt into
syn digest, routed to the accept queue (builds on SYN-250 + ADR-029). Turns the gate from pure cost into recurring value.
5. Decision: v0.11 stays, reshaped; v1.0 = “Provable, daily, done”
Implement all nine v0.11 issues, reshaped per §3, in the dependency-aware order:
SYN-260 → SYN-259 → SYN-251 → SYN-254 → SYN-245 → SYN-255 → SYN-248 → SYN-249C → SYN-258.
Recommend the next milestone be v1.0-hardening headlined by syn verify (SYN-253), the import-and-govern wedge, and the decay-value-moment, not more scale/graph. SYN-249C/254-merge/248-multi-actor value ultimately points at the Commercial/team wedge; shipped here only in their honest single-user-minimal form.
| Hypothesis (pre-study) | Status | Reason |
|---|---|---|
| v0.11 = scale & graph is the right next theme | challenged | 3/4 agents: the moat’s proof (syn verify) outranks scale/graph for a single-user tool; reshape toward provable-quality. |
| All v0.11 issues are net-new work | challenged | SYN-259(2) shipped; SYN-245 blocker resolved: reconcile before building. |
| HNSW / graph-UI deferral | validated | Market moved to token-efficiency; flat-vector correct < ~100k. |
| Deterministic-first contradiction/dedup | validated | HaluMem auto-update <26% unchanged; gate-don’t-resolve is correct. |
Provable non-corruption: the foundations of syn verify (v1.0)
| Field | Value |
|---|---|
| Date finalised | 2026-06-16 |
| Author | Rômulo Febasi romulo@febasi.com.br (3 specialised research subagents, web) |
| Scope | Whitepapers, projects, and theory for reproducible, auditable, testable non-corruption: to define the v1.0-hardening issues headlined by syn verify. |
| Status | accepted |
| Inputs | 3 parallel subagents (theory/whitepapers; real-systems prior-art; eval/testability), all web-enabled + repo-grounded. |
| Out of scope | Implementation (this brief defines the issues); multi-user/Commercial crypto (SYN-261). |
Executive summary
Every integrity system in the literature proves one of three things: (A) content integrity: this artifact is the one attested (hash/signature); (B) history integrity: the append-only log was not reordered, truncated, or rewritten (consistency proof); (C) derived-state integrity: the output was reproducibly derived from the input (deterministic rebuild). Synapse is uniquely positioned to prove all three at once, offline, per-fact, because the truth is the user’s Markdown in their git and the SQLite index is a pure function of it. A hosted competitor structurally cannot offer (C); there is no user-side source to reproduce from. syn verify operationalises the brand line “provable, not estimated” into a command that emits a signed, reproducible attestation. The governing discipline, inherited from the ADRs: borrow the invariant, refuse the engine (no servers, no heavy deps, deterministic over ML, tamper-evident not tamper-proof).
1. Theory → adoption verdict (condensed)
| Theory | Verdict | Use in Synapse |
|---|---|---|
| Merkle DAG / content-addressing (git, IPFS/IPLD) | ADOPT (have it via git) | Lean on git’s DAG (gix verify_checksum); per-fact payload_sha256 keyed to the introducing commit’s blob. Do not build a parallel Merkle tree. |
| CT / transparency-log inclusion + consistency proofs (RFC 6962/9162; Crosby–Wallach) | ADAPT the proofs, reject the network | “Is this fact in history” (inclusion) + “history B is an unmodified prefix of A” (consistency) computed over git topology. |
| Transparent logs for skeptical clients (Cox tlog; Go sumdb; Sigsum) | ADAPT | A cached signed checkpoint ({head, count, root, sig}); verify the current state extends it: “the verifier is the log operator” = the offline single-user case. |
| Reproducible builds (rebuild-and-compare; diffoscope) | ADOPT: the conceptual heart | The disposable index is a reproducible build of the Markdown: digest(reindex(md)) == digest(live). Compare a canonical logical manifest, not raw .db bytes. |
| Datomic/XTDB immutability + deterministic replay | ADOPT as invariant | “DB is a pure function of an immutable log”; supersession = invalidation-not-deletion; replay reconstructs state. |
| Property-based + metamorphic testing (beats the oracle problem) | ADOPT | No oracle for “memory is uncorrupted”, but strong oracles for “reindex twice == once” and “CLI == MCP”. |
| in-toto / SLSA / DSSE attestation | ADAPT the data model | The proof artifact = in-toto Statement shape (subject digest + predicate invariant table), signed with the existing ed25519 identity (reuse SYN-247 receipts; no new crypto). SLSA’s ladder is whitepaper framing only. |
git verify-commit + --allowed-signers | ADOPT (offline!) | Export me.synapse.json keys → .synapse/allowed_signers so git’s native verify path is Synapse’s: resolves ADR-030’s open allowlist question. |
| TLA+/Alloy/Stateright; Rekor/Fulcio; Trillian witness-nets; RDF/OWL PROV; IPFS/CID | INSPIRATION / REJECT | Single-writer (no concurrency to model-check at runtime); offline-first (no online OIDC/log server); git is already the Merkle DAG. Author invariants with them; don’t ship them. |
2. The invariants syn verify proves (the catalogue)
Each is deterministic (recomputable, no ML/judge) and contributes a line to the signed report.
- INV-1 Disposability / reindex fixpoint:
digest(reindex(markdown)) == digest(live index)over a canonical logical dump (ordered rows; rowids/FTS-internal/float-vectors excluded or pinned tomodel@precision). The keystone; the index is a reproducible build of the files. - INV-2 Content integrity: every fact’s
payload_sha256matches the Markdown blob at the commit that introduced it; no orphan facts, no orphan audit rows. - INV-3 History integrity: hash-chain (
seq/prev) + ed25519 signed checkpoint intact (Tier-2), or git DAG intact + per-author monotonic segments (Tier-1). (Shipped asaudit verify; formalise.) - INV-4 Reversibility: replaying the audit reconstructs the current store state (frontmatter-generic). (Riskiest: see §4.)
- INV-5 No silent corruption: no fact’s value changed without a recorded
x-supersedes+ an appended interval close (invalidation-not-deletion). The HaluMem <26%-auto-update failure made a provable invariant, the headline differentiator. - INV-6 Port parity: CLI ≡ MCP for every read (metamorphic; the AGENTS.md “two doors, one core” rule as a test).
- INV-7 Temporal correctness:
at <ref> -- search --as-of <date>reconstructs the state that held. (Seeded by SYN-258.) - INV-8 (opt) Round-trip:
import(export(w)) ≅ w.
Proof artifact: a signed synapse.verify-report/1 (in-toto-shaped): {subject:{workspace, git_ref, inputs_hash}, predicate:{tool_version, invariants:[{id,status,evidence}], checkpoint, witness_status, ts}, sig, pubkey}. Reproducible: anyone with the repo at git_ref re-runs syn verify and gets a bit-identical inputs_hash + the same verdicts.
3. Opportunities (white space, ranked)
- O1:
syn verify(headline). All three integrity classes, offline, exit-coded, signed report. Mostly assembly of shipped primitives (audit verify+reindex+at/--as-of) reframed as proofs → low new code, category-defining claim. - O2: Verifiable-rebuild attestation (
--rebuild). “Your memory is a reproducible build of your files; delete the index, rebuild it, get the same bits: if you can’t,syn verifytells you which fact.” Literally true, and uncopyable by a hosted store. - O3: Offline consistency proof + publishable witness (
syn audit witness+syn verify --since <ref>). Cache a signed tree-head; prove the current history is an append-only extension (git ancestry + segment-prefix hash) → catches history-rewrite locally, no remote. A published checkpoint lets a third party hold the owner to their history (recovers part of the witness property ADR-030 says only a remote gives). - O4: git as the trust root. Export identity →
.synapse/allowed_signers(git-native verify),gix verify_checksumfor the DAG. Resolves ADR-030’s open allowlist; no reinvention. - O5: The invariant harness (CI). proptest (already a dev-dep) + metamorphic + differential (CLI≡MCP) + fuzz the untrusted boundary. The “test-suite half” of SYN-253. Only new dep:
cargo-fuzz(opt-in, own ADR).
4. Critical caveats / risks (be honest)
- Honest ceiling: tamper-evident, not tamper-proof. A lone local clone cannot detect its own owner’s rewrite (no witness).
syn verifymust print the trust-root caveat (local-only vs remote-witnessed vs checkpoint-witnessed). The Memoir lesson: be specific where competitors hand-wave “cryptographically secure.” - Canonicalisation is the one real prerequisite. The index isn’t byte-deterministic (rowids, FTS internals, float vectors). Verify against a canonical logical manifest; spike it first (SYN-262).
- Replay (INV-4) rests on an unverified ADR-027 assumption (generic frontmatter reconstruction + supersession-close-as-append). Verify that before claiming
--deepreplay; else a frontmatter-generic replay refactor is needed first. - Don’t over-engineer. Reject Rekor/Fulcio/OIDC, Trillian/witness-nets, runtime model-checking, RDF/OWL, IPFS/CID. Borrow the invariant, refuse the engine.
5. Proposed v1.0 issues (next free SYN-262)
| ID | Title | AC (one line) |
|---|---|---|
| SYN-262 | core::verify engine + canonical logical-store dump (foundation + report schema ADR) | dump(ws) byte-stable across calls and a reindex --full |
| SYN-263 | syn verify runtime command + signed report (both doors): headline | clean ws exits 0 + signed report; corruption → exit 1 + offending invariant |
| SYN-264 | Port-parity differential suite (CLI ≡ MCP) | adding a door to one side without the other fails CI |
| SYN-265 | Invariant proptest suite (coherence + governance: I-1/2/3/5/7/9/10) | incremental ≡ full + round-trip over ≥256 cases |
| SYN-266 | Deterministic replay invariant + clock/identity seam (verify ADR-027 prereq first) | replay-into-empty == current store |
| SYN-267 | Stateful supersession + temporal proptest (I-8/11) | order-independence + bitemporal axis-independence |
| SYN-268 | Fuzz the untrusted boundary (proptest-first; cargo-fuzz opt-in + ADR) | arbitrary bytes never panic / never violate round-trip over ≥1024 cases |
| SYN-269 | Verifiable-rebuild attestation (syn verify --rebuild, diffoscope-style localized diff) | rebuild manifest mismatch names the diverging facts |
| SYN-270 | Witness checkpoint + offline consistency proof (syn audit witness, verify --since) | history-rewrite detected offline against a cached signed head |
Whitepaper/positioning (separate doc): “Provable non-corruption”: the invariants, the proof artifact, and a SLSA-style ladder (L1 audited → L2 hash-chained → L3 git-witnessed → L4 signed-identity).
6. Cross-check vs prior hypotheses
| Hypothesis | Status | Reason |
|---|---|---|
syn verify is genuine white space | validated | No precedent emits a signed, reproducible integrity report from a property/metamorphic harness for a local store (3 agents, web). |
| It’s mostly assembly of shipped primitives | validated | audit verify (content+chain), reindex (rebuild), at/--as-of (temporal), receipts (signing) already exist. |
| Crypto provenance is being commoditised | reinforced | Anthropic editable-Markdown-audit; Microsoft/MemLineage Ed25519+Merkle; Memoir “crypto integrity” (unspecified). Durable moat = local-first + per-fact + provable derived-state, not the primitives. |
| Replay is the hard part | validated | ADR-027’s generic-frontmatter assumption is unverified; gate SYN-266 on it. |
Benchmark redesign: scaling, resources, and correctness-at-scale
| Field | Value |
|---|---|
| Date | 2026-06-21 |
| Author | Rômulo Febasi (4 specialised research subagents, web) |
| Scope | Replace the useless single-latency chart (SYN-290) with benchmarks that answer “will it handle MY project, and where does it break?”: scaling curves, resource curves, and correctness-under-load, premium and reproducible. |
| Status | accepted (drives a v1.1 benchmark-v2 plan + 2 code-finding tickets) |
| Inputs | 4 parallel web-enabled subagents: scaling/bottlenecks, resource measurement, premium presentation, workload-profiles/correctness. |
Executive summary
The current README benchmark: median 7.97 ms / p95 10.21 / p99 11.53 on a 300-doc
stub corpus: proves the renderer works, not that Synapse scales. It has no
scale axis, no resources (RAM/CPU/disk), no correctness-under-load, no workload
profiles, no breaking point, and no honest comparison. Every credible benchmark in
the field (ClickBench, DuckDB, ann-benchmarks, Qdrant, Meilisearch, TigerBeetle, uv)
replaced bars with curves over a scaling variable + a floor-vs-slope resource
model + percentiles + machine stamp + raw data + one-command reproduce.
The redesign: benchmark across 5–6 scale tiers (T1 daily ~500 → T4/T5 stress 50k–1M), measure per-subsystem latency, RAM (floor vs slope), disk, reindex, throughput, and recall-at-scale, render curves with confidence bands and a breaking-point band, and compare Synapse only to itself (FTS / vector-flat / hybrid; fp32 / int8; cold / warm; flat / future-HNSW) plus ripgrep as a labelled naive floor: never to a hosted cluster, never at unequal recall.
Two real code findings surfaced en route (each → its own ticket): the FTS5 table is self-content despite a comment claiming external-content (~doubles the text footprint, ~3 KB/entity waste); and the “flat vector correct below ~100k” rationale (SYN-225 deferral) is conservative by ~5–10×: flat is fine to ~500k–1M at 1024-dim on a laptop; the real wall is page-cache, not 100k.
1. Why the current benchmark is useless (the critique)
- No scale axis. A single N=300 number. The reader’s question is latency × N: a curve from 1k → 10k → 100k. Without it, “will it handle my vault?” is unanswerable.
- No resources. Nothing on RAM (the BGE-M3 model floor alone is ~2.5 GB!), CPU, or
.synapse/index.dbsize. For a local tool, “does it fit on my machine?” is the question. stubhides the real path. Stub latency skips the semantic path (embeddings + sqlite-vec). And vector search is flat brute-force O(N·d): it degrades linearly; that is exactly where it breaks and the chart never shows it.- No correctness under load. “Fast” is worthless if retrieval stops surfacing the right fact as the graph grows. Recall/precision × N is the proof of usable.
- No workload profiles, no breaking point. No “daily (medium)” vs “extreme (stress)”, no cliff.
- No comparison. 8 ms vs what?
2. The cost model (verified against the source)
Let N = entities (= FTS rows = vectors), E = edges, W = federated workspaces, d = 1024 (vector dim).
| Subsystem | Complexity | Class | Note |
|---|---|---|---|
| Flat vector KNN | O(N·d) | linear | the headline wall: full scan every query (hybrid.rs embedding MATCH … ORDER BY distance) |
| FTS5 BM25 | ~O(matched·log N) | sub-linear | tracks the result set, not N: essentially never the bottleneck |
| Graph 1-hop | O(log E + deg) | ~log | index-backed (idx_links_from/to) |
Ego-graph / connections | O(Σ deg over frontier) | O(N) on hubs | degree-driven; a mega-hub is the only graph cliff |
| EAV filter | O(log F + matches) | ~log | index-backed |
| RRF fusion | O(pool) | ~O(1) in N | trivial |
| Federated search | O(W·N·d) | linear in W | each ws pays its own full vector scan, serially |
| Full reindex | O(N) parse + O(N·d) embed | linear | embed dominates; content-hash-gated incremental (SYN-251) |
| DB size | O(B + N·d) | linear | §4 |
The two that bite, in order: flat vector KNN (linear in N), then federation (multiplies it by W). Everything else is sub-linear or free until N is enormous.
Crossover estimates (1024-dim fp32, laptop): to be confirmed by measurement
Anchored on the sqlite-vec author’s own M1 benchmarks (GIST 500k×960 = 41 ms; 100k×1536 = 105 ms):
| N vectors | flat-search median (est.) |
|---|---|
| 10k | ~1 ms |
| 50k | ~4 ms |
| 100k | ~9–30 ms |
| 500k | ~44 ms |
| 1M | ~100–250 ms (IO-bound) |
- < 10 ms (snappy): ~50–100k vectors.
- < 100 ms (interactive): ~500k–1M.
- < 1 s (usable): 1M+.
- Breaking point: ~1M vectors or whenever the fp32 vector blob (4 KB × N) outgrows page cache → every query becomes disk-bandwidth-bound and jumps a tier. This validates that the “100k flat ceiling” is conservative; the bench must locate the real page-cache cliff.
3. Workload tiers (grounded in real vault data)
Anchors: Obsidian forum self-reports (median ~5,500 notes; large 18k–20k; 60k+ “approaching indexing failure”); Luhmann’s Zettelkasten ~90k notes / 40 yrs (~2,250/yr), the lifetime single-user ceiling.
| Tier | Persona | Entities | Edges | People/Projects | Words | WS |
|---|---|---|---|---|---|---|
| T0 Smoke | (current bench) | 300 | ~600 | 20 / 5 | ~150k | 1 |
| T1 Daily | solo dev, months in | ~500–2k | ~3–6k | 40–150 / 8–20 | 150k–1M | 1–3 |
| T2 Power | heavy user @ 1yr | ~6k–20k | ~20k–80k | 250–1k / 30–100 | 1.5M–10M | 3 |
| T3 Heavy | multi-year, near ceiling | ~20k–100k | ~200k–500k | 5k / 500 | 10M–50M | 5 |
| T4 Stress | synthetic stress | 100k–500k | ~3M | 20k / 2k | 250M | 10 |
| T5 Break | past every wall | 1M (+ a 10k-degree hub) | ~8M | 50k / 5k | 500M | 25 |
Realistic single-user upper bound: ~50k–100k entities. T1–T3 are must-pass; T4/T5 are the “does flat still hold or do we need HNSW (SYN-225)” probe.
The corpus must look like a real graph, not uniform random
- Scale-free (Barabási–Albert, γ≈2.2–2.5), mean degree ~4–10, with a few hub projects/people carrying 100–1,000+ edges. Uniform-random graphs lie: they have no near-miss distractors, so recall@k looks artificially perfect.
- Log-normal note lengths (median ~150–400 words, p95 ~2,000+, a long tail).
- Zipfian tags; 20–50% AI-authored (exercise trust-ranking + audit under load);
supersession chains (exercise
--as-of+ “stale shouldn’t outrank current”).
4. Index / disk growth (.synapse/index.db)
Per-entity (≈500 words ≈ 3 KB text), under current defaults (fp32, self-content FTS):
| Component | per entity | note |
|---|---|---|
| Vectors (fp32) | 4.0 KB | 1024×4 B; int8 → 1 KB; binary → 128 B |
| FTS5 (self-content) | ~4 KB | text copy + inverted index: ~3 KB is waste vs true external-content (Finding #1) |
| core row + 5 indexes | ~1 KB | |
| links (deg 10) | ~1.2 KB | |
| EAV + meta | ~0.5 KB | |
| Total | ~10.7 KB/entity |
| N | DB ≈ | int8 vec | + external-content FTS |
|---|---|---|---|
| 1k | ~11 MB | ~8 MB | ~8 MB |
| 10k | ~107 MB | ~77 MB | ~74 MB |
| 100k | ~1.07 GB | ~770 MB | ~740 MB |
| 1M | ~10.7 GB | ~7.7 GB | ~7.4 GB |
Vectors + self-content FTS are 75% of the DB. Two cheap wins: true
external-content FTS (−28%) and int8 vectors (~−28%) ≈ roughly halve the index.
And disk size is the latency story, the slowdown happens when that blob outgrows page cache.
5. Resource measurement methodology (RAM/CPU/disk)
The honesty trap: (a) don’t report peak RSS as “required RAM” (page cache is opportunistic/evictable: Qdrant: “10 GB RSS ≠ needs 10 GB”); (b) don’t use dhat for the headline number: it sees only the Rust global allocator and is blind to the 2.5 GB ONNX model (off by ~100×).
Floor vs slope (the only honest method):
RSS(N) ≈ baseline + Model_floor(~constant) + SQLite_working_set(N) + Vector_set(N)
- Baseline: RSS of a no-op (
syn --version). - Floor: RSS with the model loaded on an empty workspace → report as “baseline RSS with model loaded (fp32 / int8)”. This is the dominant, ~constant cost.
- Slope: Δ RSS per 10k entities (vectors = +4 KB/entity fp32, +1 KB int8). Subtract the floor: never attribute the 2.5 GB to “100k entities”.
Measurement stack: /usr/bin/time -v (Linux) / -l (macOS) for peak RSS envelope;
in-process VmHWM / getrusage phase checkpoints (after model-load, after index-N,
after search); dhat only for the Rust working-set slope (labelled, not as total);
heaptrack (Linux) for one-off full-process attribution incl. ORT.
Landmines (verified): ru_maxrss is KiB on Linux, bytes on macOS (1024× error
if unbranched: sanity-assert peak > model floor); measure index.db only after
OPTIMIZE + VACUUM (fragmentation over-reports); don’t gate wall-time in CI (~50%
variance): gate memory-peak regressions (stable) and run timing on a pinned box,
reporting CPU-seconds + thread/core count, not bare wall time.
CPU/throughput: warmup-then-median; cold (model load + first embed) vs warm (steady state) labelled separately; embedding throughput in entities/sec + tokens/sec with thread count stamped; reindex in CPU-s/entity (machine-comparable) + wall-s (felt).
The “will it fit?” headline this produces: the model is the floor (~2.5 GB fp32 / sub-GB int8); each 10k entities adds ~41 MB (fp32) of vectors, so on a 16 GB machine you fit millions of entities; the binding constraint is the model, not your data.
6. The chart catalogue (curves, not bars)
x-axis = N (log scale) on the scaling charts. Every chart: median line + p5–p95 confidence band (≥5 reps), breaking-point band (green/amber/red threshold rules), dark/light via CSS, and a caption with the machine stamp + reproduce command + commit + date.
| # | Chart | y | Proves | Hero? |
|---|---|---|---|---|
| 1 | Search latency vs N | p50 & p95 (log): 3 lines: FTS / vector-flat / hybrid | how each mode degrades; where flat goes linear | ★ |
| 2 | Scale-tiers strip | banded usable·interactive·breaking over N tiers | one-glance “what tier am I in” | ★ |
| 3 | RAM vs N | peak RSS: floor line + slope, fp32 vs int8 | the resource ceiling / fit | ★ |
| 4 | Index size vs N | stacked FTS / vector / graph | disk cost + what dominates | |
| 5 | Reindex time vs N | wall: full vs incremental | the disposable-index claim is credible only if bounded | |
| 6 | Embedding throughput | docs/sec, fp32 vs int8, cold-load separate | onboarding cost | |
| 7 | Recall/quality vs N | recall@k / nDCG@10 vs a labelled gold set | retrieval stays correct as the graph grows | ★ |
| 8 | Latency CDF (small multiples per tier) | the tail, not just p50 | the worst case the reader feels |
Charts 1, 2, 3, 7 are the hero set. SVG (crisp, diffable, themeable), confidence
bands, annotated knee (“≈42k entities: p95 crosses 200 ms”), committed results.json
beside the SVGs, --quick/--full reproduce tiers.
7. Correctness-at-scale (the premium differentiator)
The claim to prove: the right fact still ranks top-k when 50k entities compete. Measure rank-aware IR metrics as a curve over N, not a single number:
- recall@{1,5,10,20}, MRR, nDCG@10, precision@k, computed per tier for FTS / vector / RRF separately (so degradation is attributable).
- Tier-stable planted-needle query bank (≥100 queries/tier): synthesize the graph, so truth is known; re-run the same queries as the corpus grows → pure haystack-growth degradation (the LongMemEval “same question, bigger haystack” design).
- Hard negatives: 2–5 near-miss entities per needle sharing the hub/tags/words.
- Abstention queries (invalidated/never-existed): test it declines vs hallucinates.
- Watch RRF k=60 × corpus-size interaction: verify RRF doesn’t win at T1 but lose at T3 purely from a small-scale-tuned k.
Agent metric: Recall@k_budget(N) under a declared context budget (e.g. top-10 ≈ 3k tokens), reported separately from end-to-end task success (decouple “was it retrieved” from “did the LLM use it”) + distractor load = noise fraction of the window. Field anchor: LongMemEval shows a 30–60% accuracy drop without targeted retrieval: exactly what this catches.
8. Honest comparison (and what to refuse)
Ship: Synapse vs itself: FTS / vector-flat / hybrid-RRF; fp32 vs int8; cold vs warm; flat vs future-HNSW (flat = the recall=1.0 ceiling once SYN-225 lands); ripgrep as a labelled naive floor (ripgrep should win raw latency and lose relevance: state the asymmetry).
Refuse: comparing at unequal recall (Qdrant’s “fixed precision threshold” rule); warm-only with cold hidden; cherry-picked N as a bar; tuned-vs-untuned asymmetry; hosted-cluster comparisons (apples-to-oranges for a single-user embedded tool); mean-only latency.
9. Two code findings (→ tickets)
- FIND-1: FTS5 is self-content, not external-content.
migrations/0001__initial.sqldeclaresfts5(…)with nocontent=clause, but the comment claims external-content. The table stores its own copy of title/body/tags → ~doubles the text footprint (~3 KB/entity, ~28% of the DB at 100k). Fix → expand-contract migration to a contentless/external-content FTS5 table. A genuine, measurable storage win. - FIND-2: the “flat correct below ~100k” rationale is conservative ~5–10×. Per the sqlite-vec numbers, flat 1024-dim search stays interactive (<100 ms) to ~500k–1M on a laptop; the real wall is the page-cache cliff. The HNSW deferral (SYN-225) is more justified than stated, but the specific “100k” figure in CLAUDE.md / the ADR should be re-validated by chart #1 before HNSW is ever built.
10. Recommended implementation phases (→ plan 021 / a benchmark-v2 ticket)
This supersedes the thin SYN-290 chart (which proved the renderer, not the product).
- Phase A: scaling latency + DB-size curves (highest value, ships first). Scale-free corpus generator (BA, hubs, log-normal lengths); per-subsystem timing (FTS / vector / hybrid / graph) at T1–T4; median+p5–p95 bands; chart #1, #2, #4; committed raw JSON.
- Phase B: resources. Floor-vs-slope RSS, disk (post-VACUUM), reindex, throughput;
/usr/bin/time+VmHWMcheckpoints; chart #3, #5, #6; fp32/int8 series. - Phase C: correctness-at-scale. Planted-needle tier-stable query bank + hard negatives; recall@k / nDCG / MRR per tier per ranker; chart #7; the agent Recall@k_budget metric.
- Phase D: premium polish + CI. Confidence bands, breaking-point bands, dark/light,
captions;
--quick/--full; pinned-runner release workflow + artifact; README embeds the hero trio. - Parallel: FIND-1 (external-content FTS) + FIND-2 (re-validate the flat ceiling) as their own tickets.
Discipline: real, reproducible, honest: curves not bars, cold+warm, percentiles, floor-vs-slope, equal-recall comparisons, raw data + one-command reproduce, machine stamp on every figure. Quality over a pretty single number.
Sources (selected)
sqlite-vec benchmarks https://alexgarcia.xyz/blog/2024/sqlite-vec-stable-release/index.html · SQLite FTS5 https://sqlite.org/fts5.html · ClickBench https://benchmark.clickhouse.com/ · DuckDB db-benchmark https://duckdblabs.github.io/db-benchmark/ · ann-benchmarks https://ann-benchmarks.com/ · Qdrant benchmarks https://qdrant.tech/benchmarks/ · getrusage(2) https://man7.org/linux/man-pages/man2/getrusage.2.html · dhat https://docs.rs/dhat/latest/dhat/ · BEIR https://arxiv.org/pdf/2104.08663 · LongMemEval https://arxiv.org/abs/2410.10813 · Obsidian vault sizes https://forum.obsidian.md/t/how-many-notes-do-you-have/36987 · Luhmann Zettelkasten https://www.ernestchiang.com/en/posts/2025/niklas-luhmann-original-zettelkasten-method/ · pythonspeed CI benchmarking https://pythonspeed.com/articles/consistent-benchmarking-in-ci/.
Agent-in-the-loop benchmark for governed memory
| Field | Value |
|---|---|
| Date | 2026-07-03 |
| Author | Rômulo Febasi (2 web research agents + a live proof-of-concept) |
| Scope | Design a robust benchmark that drives a real AI agent (Claude Code) through the full governed-memory loop over MCP: insert / correct / contradict / retrieve, and grades what actually happened, not the transcript. |
| Status | accepted (drives plan 022 / SYN-298; Phase 0 root-cause fix filed as SYN-297) |
| Inputs | claude-code-guide agent (headless Claude Code facts, verified against claude v2.1.197) + a general research agent (agent-memory eval methodology, ~20 cited sources) + a working PoC on this machine. |
Thesis (three load-bearing claims)
-
Grade the final STATE of the memory, not the transcript. Every serious agent benchmark (τ-bench hashes the mutated DB; SWE-bench runs tests against the end state; Terminal-Bench verifies the container) converges here. Synapse is uniquely ready:
syn verifyalready emits a canonical logical dump, a drop-in analog of τ-bench’sget_data_hash(). -
The contradiction-surfacing metric is un-benchmarked, and it’s our positioning. Across HaluMem / MemoryAgentBench / Mem0 / Letta, every system either silently auto-resolves conflicts or grades only “did the latest value win.” Nobody rewards “detect the conflict and route it to a human gate”: exactly Synapse’s
contradictssignal + BR-12.3. We get to define the metric. Do NOT grade contradiction as “latest wins” (that’s a correction); grade the surfacing. -
Decouple retrieval from generation or the numbers are uninterpretable. A wrong answer has three distinct causes: the fact was never written, was written but not retrieved, or was retrieved but misused. A single end-to-end number can’t tell them apart. Our loop has a third stage the chat benchmarks lack, the write:
WRITE (gate) → RETRIEVE (search) → READ (answer) right fact + surfaced among used correctly right op? N distractors? (+ abstains when absent)?Report a number per stage + an oracle-context ceiling (feed the gold fact directly → the generation bound).
Reusable designs (what to steal)
- τ-bench / τ²-bench (2406.12045, 2506.07982): canonical state hashing; ground-truth by action-replay (stays in sync with schema); a composable reward basis (DB-state ∧ invariant ∧ communicated-string ∧ tool-trace). Introduced pass^k.
- SWE-bench Verified (2310.06770):
FAIL_TO_PASS (must flip true, the fix landed) + PASS_TO_PASS (must stay true, nothing corrupted). Maps perfectly: assertions that flip true after the write plus
syn verifyINV-1/2/3/5 that must stay green (the non-corruption story). And the “Verified” lesson: hand-audit the task set: broken graders are the norm. - LongMemEval (2410.10813): 5 abilities
(extraction, multi-session reasoning, knowledge-update, temporal, abstention);
staged indexing→retrieval→reading with recall@k reported separately from QA;
_oraclesplit = generation ceiling. The template for scenarios B and D. - HaluMem (2511.03506): operation-level metrics, extraction (Recall / False-Memory Resistance / F1), updating (Update Accuracy / Hallucination Rate / Omission Rate). Exactly how to grade “did the RIGHT facts land.”
- MemoryAgentBench (2507.05257): the only one with a Conflict Resolution competency: counterfactual-edit pairs (MQuAKE) ordered later-supersedes-earlier. Steal the construction; note it grades resolution-given-“prefer-later”, not autonomous detection: our gate is the novel part.
- LoCoMo (2402.17753): persona + event-graph generation recipe (causally-coherent histories). Do NOT copy its grading: an audit found ~6.4% of its answer key wrong and a judge accepting ~63% of intentionally-wrong answers. The cautionary tale.
- RULER / NoLiMa (2404.06654, 2502.05167): non-parametric needles (coined names/uuids the model can’t answer from pretraining) with low lexical overlap to the question (force associative, not keyword, retrieval) + hard in-distribution distractors.
The four scenarios + how to grade each
Ground rules for all: fixed seeded corpus, pinned agent model + pinned judge snapshot,
N≥5 trials/task, grade final state via syn verify dump + direct index queries.
Task = {seed_state, user_messages, target_mutations(replay), assertions, gold_answer+evidence}.
- A: Insertion. User message with K plantable facts + M non-facts (chit-chat, hypotheticals) that must NOT be written. Grade: extraction precision/recall (query the result), False-Memory Resistance (the M non-facts don’t appear), operation correctness (capture_fact vs link_entity vs update_status: from the tool trace), and state∧INV after accept.
- B: Correction/update. Plant F1 early; later a user corrects it to F2 (counterfactual
pair, F2 after F1). Grade: chose update/supersession (not a duplicate); F1
invalidated-not-deleted (x-valid-until / x-supersedes); “what is X now?” → F2, and
--as-of <before>→ F1 (bitemporal correctness: nobody else tests this). - C: Contradiction (NOT correction). Plant F1; later user asserts F1′ that conflicts,
framed as a fresh claim (no “actually/I was wrong”). Grade the governance, not which
value won:
contradictsfires ∧ routed to the gate (no auto-apply even underauto_apply=safe) ∧ no silent overwrite AND no silent duplicate. New metric: Contradiction Surfacing Rate + Contradiction Precision on a hard-negative control (compatible facts that look conflicting but aren’t: without it, a flag-everything system scores 100% and the metric is vacuous). - D: Retrieval Q&A under noise. Question whose answer is one planted needle among N distractors (N sweep: 1k/10k/100k). Grade: retrieval leg (recall@k/MRR, isolated) + oracle leg (ceiling) + end-to-end (LLM-judged) → attribute the failure. Include abstention (a fraction whose answer was never planted → correct = “I don’t know”).
Grading mechanics
- Deterministic wherever possible (A/B/C): outcomes are checkable by querying DB state
- hashing the
syn verifycanonical dump (τ-bench style) + INV invariants. No judge variance. Lean on this.
- hashing the
- LLM-as-judge only for open answers (D), with discipline (Zheng 2023 2306.05685): reference-guided (supply gold + evidence); judge with a DIFFERENT model family than the agent (preference-leakage, 2502.01534); pin the judge snapshot; audit judge-human agreement and report it before trusting (if <~80%, the judge is noise).
- Trajectory grading (τ² ACTION basis): assert the MCP tool-call sequence contains the expected op-kinds.
Non-determinism + cost protocol (honest reporting)
Agents are stochastic + expensive; single-run numbers are dishonest (Kapoor, 2407.01502).
- pass^k (reliability) is the headline for a tool whose pitch is “can’t silently corrupt”: it requires the identical correct mutation across all k trials, surfacing nondeterministic corruption pass@1 hides. Report pass@1 (capability) + pass^k (reliability) on write/contradiction scenarios.
- N≥5 trials/task; 95% CIs, cluster SEs on the randomization unit (Anthropic 2411.00640: naive SEs run ~3× too small).
- Pin the substrate: exact agent model snapshot in the run name, fixed seeded corpus, pinned judge, disclosed temperature/step caps.
- Cost as a first-class axis: a cost-vs-accuracy Pareto (HAL/Kapoor), tokens + USD per
run (Claude Code reports
total_cost_usdper headless run). Bound with step caps. - Not CI-able (credentials + cost + variance): owner-run, like the real-backend bench.
Driving Claude Code headlessly (verified + corrected against claude v2.1.197)
Getting write tools to fire headless means clearing three stacked gates: a fact the
PoC learned the hard way (see below). --permission-mode bypassPermissions clears none
of them (it governs the permission prompt, not MCP approval or tool-search):
- Approval / trust. A project
.mcp.json(or inline--mcp-config) server sits at⏸ Pending approvalin headless and its tools never load; only its resources do. Since v2.1.196enableAllProjectMcpServersin project settings is ignored in an untrusted folder: it must live in~/.claude/settings.json. Fix: register the server local-scoped:claude mcp add --transport stdio --scope local synapse -- <abs>/syn mcp(auto-approved;--scope userhas open bugs #32939/#54803). - Schema validation. Claude Code ≥2.1.x validates each tool’s
outputSchemaand drops the entire tool list if any fails. Synapse tripped this (see PoC / SYN-297). Fix landed insrc/mcp/output_schema.rs. - Tool-search deferral. ≥2.1.7 defers all MCP tools behind
ToolSearch. Fix:ENABLE_TOOL_SEARCH=false+--allowedTools "mcp__synapse__*"(eager-load + pre-approve).
Working recipe (proven E2E on this machine: capture_fact enqueued a real pending proposal):
claude mcp add --transport stdio --scope local synapse -- "$(command -v syn)" mcp # once, per workspace
ENABLE_TOOL_SEARCH=false claude -p "<user turn>" \
--allowedTools "mcp__synapse__*" \
--output-format stream-json --verbose --model <pinned>
Other confirmed flags: --bare (skip CLAUDE.md/settings → clean context), --strict-mcp-config,
--output-format (json/stream-json), --model/--effort/--no-session. Each -p is a
fresh session. stream-json events carry type: tool_use (name+input) → trajectory; the
result event carries final text + total_cost_usd + token usage. No temperature/seed
control → average over N runs. Robust alternative for the harness: the Claude Agent
SDK (Python/TS) takes mcp_servers + allowed_tools in code and asserts
init.mcp_servers[].status == "connected" before the first call: bypassing the CLI trust /
tool-search machinery entirely. Recommended when the driver must be fully non-interactive.
Proof-of-concept findings (run on this machine, 2026-07-03 → root cause 2026-07-04)
A live PoC (claude v2.1.197 + the synapse MCP over a fresh workspace) validated the harness mechanics and caught a real, shipping bug: on ourselves, exactly as intended.
- ✅ The harness works. We drove
claude -pagainst the synapse MCP, captured the fullstream-jsontrajectory (tool calls + inputs + final answer + cost) and can inspect the resulting workspace state. This is the eval loop. - ✅ Read resources connect even when tools don’t (they follow a separate gate),
synapse://workspace/server-cardreturned live data, which is what first proved the server was not “off”. - ❌→✅ Write loop was BLOCKED, now fixed. First read as “Claude Code 2.x defers all MCP
tools behind
ToolSearch” (the agent calledToolSearch16× andmcp__synapse__propose_batchreturned “No such tool available”). Deferral is real (gate 3), but it was masking the true root cause. Once the approval gate was cleared with--scope local, the status becameConnected · tools fetch failedwithInvalid input (at tools.2.outputSchema.properties.result): Synapse’s SYN-177 read envelope emittedoutputSchemawithresult: true(the boolean schemaserde_json::Valuederives). Draft 2020-12 permitstrue; Claude Code’s strict (Zod) validator rejects it and drops the whole 24-tool list → the agent sees zero Synapse tools. This breaks any current-Claude-Code user, not just the bench. Fixed insrc/mcp/output_schema.rs(a permissiveAnyValue→{}, nevertrue; regression test); filed as SYN-297. After the fix + the working recipe above, a headlessclaude -pcalledmcp__synapse__capture_factand enqueued a real pending proposal: end-to-end.
This is exactly what the benchmark is for. The eval’s first act was to find a
governed-write regression the unit tests missed (they assert schema shape, not
strict-client acceptance). Phase 0 of the harness is that fix (done) plus a
syn verify-style strict-schema conformance test so it can’t regress.
Corpus design (the haystack)
Extend the deterministic generator (synapse-bench/src/corpus.rs) + the persona fixtures
(SYN-296): non-parametric planted needles (coined companies/uuids: no pretraining
leak), low question↔needle lexical overlap (associative retrieval), hard
in-distribution distractors, causally-coherent correction/contradiction pairs
(persona + event graph), each with a recorded intent label (correction vs contradiction)
so the gold governance behavior is known. Keep it in-repo as the example workspace
(synapse-bench/fixtures/example-workspace/): deterministic + versioned; a separate repo
only if it balloons past ~50 MB.
Top pitfalls (the critique)
- One end-to-end number (hides which stage failed). 2. Single-run scores. 3. Grading the
transcript, not the state. 4. Same model generates corpus + answers + judges (preference
leakage). 5. Trusting the answer key (LoCoMo’s ~6.4%-wrong key). 6. Conflating
contradiction with correction: grading “latest wins” measures the auto-overwrite the gate
exists to prevent. 7. No hard-negative control for the gate/abstention (flag-everything
scores 100%). 8. No cost axis. 9. Ignoring the bitemporal
--as-ofcorrectness (our strongest, least-tested story).
Sequencing → plan 022 / SYN-298 (Phase 0 fix = SYN-297)
Phase 0: make the headless write loop work, the invalid-outputSchema fix is done
(SYN-297); harden it with a strict-schema conformance test + document the working recipe. Phase 1: synapse-bench loop
mode: task spec + a driver that runs claude -p per user turn, captures the trajectory,
and grades final state via syn verify dump + index queries. Phase 2: the four scenarios +
the deterministic graders; ship Contradiction Surfacing Rate with a hard-negative control
(the novel, on-brand number). Phase 3: retrieval/oracle/end-to-end decoupling for Q&A + an
LLM judge (different model, audited). Phase 4: pass^k + cost-Pareto reporting with CIs over
N≥5; a committed example workspace with planted needles + scripted corrections/contradictions.
Sources (selected)
τ-bench https://arxiv.org/abs/2406.12045 · τ²-bench https://arxiv.org/abs/2506.07982 · SWE-bench https://arxiv.org/abs/2310.06770 · LongMemEval https://arxiv.org/abs/2410.10813 · HaluMem https://arxiv.org/abs/2511.03506 · MemoryAgentBench https://arxiv.org/abs/2507.05257 · LoCoMo https://arxiv.org/abs/2402.17753 + audit https://dev.to/penfieldlabs/we-audited-locomo-64-of-the-answer-key-is-wrong-and-the-judge-accepts-up-to-63-of-intentionally-33lg · RULER https://arxiv.org/abs/2404.06654 · NoLiMa https://arxiv.org/abs/2502.05167 · LLM-as-judge https://arxiv.org/abs/2306.05685 · AI Agents That Matter https://arxiv.org/abs/2407.01502 · statistical evals https://arxiv.org/abs/2411.00640 · Claude Code headless https://code.claude.com/docs/en/headless.md.
Agent-in-the-loop benchmark: first empirical findings
| Field | Value |
|---|---|
| Date | 2026-07-04 |
| Author | Rômulo Febasi |
| Scope | First real runs of the synapse-bench drive headless driver (SYN-298, plan 022) against Claude Code, over the insert and contradict scenarios. Owner-run. |
| Substrate | claude 2.1.197 (Claude Code), default model; syn release build; each run a fresh zero-context workspace; MCP over the SYN-297 three-gate recipe. |
| Status | findings: drives the next plan-022 iteration. |
Headline numbers
| Scenario | N | pass@1 | pass^k | cost | note |
|---|---|---|---|---|---|
| insert-01 (plant a person amid chit-chat) | 15 | 80% | no | $3.65 | structural extraction (entity landed) = 13/15 (87%) |
| contradict-01 (conflicting claim vs seeded fact) | 10 | 100% non-corruption | - | $3.58¹ | the agent surfaced the conflict itself; nothing silently corrupted |
¹ contradict cost is the sum of the two N=10 passes (bald + directive turn). pass@1 shown is for the reframed non-corruption grade (see below); the first assertion set mis-measured.
Finding 1: capability (80%) ≠ reliability (pass^k = no)
insert-01 held pass@1 = 80% at both N=5 and N=15: a stable rate, not sampling noise. The three failures decompose into three distinct causes, which is the whole point of grading state per-run:
- 1/15 infra flake (run 7): the agent surfaced zero Synapse MCP tools
(
agent invoked no Synapse MCP tool). This is residual SYN-297-class flakiness: even with the fix + the local-scope +ENABLE_TOOL_SEARCH=falserecipe, headless tool-loading fails intermittently (~7%). Actionable: the driver should detect-and-retry a zero-tool turn. - 1/15 agent no-op (run 3): the agent explored (
search_knowledge,list_by_type) and wrote nothing. Genuine agent unreliability: sometimes it reasons and gives up. - 1/15 brittle assertion (run 1): the person was created (via
capture_fact), but the body didn’t contain the literal “data engineer”: the agent authored in pt-BR on some runs. This measured phrasing, not extraction. Fixed: dropped the substring check; grade the structural fact (entity exists). By that measure capability is 13/15 (87%).
pass^k = no and 12 distinct state-hashes across 12 passing runs: every successful capture
produced a unique syn verify dump (timestamps + agent-authored prose + tags all vary). This
validated redefining pass^k on the graded assertions, not on a bit-identical hash: the
τ-bench hash-equality trick doesn’t transfer to Markdown-with-timestamps + freeform prose.
Finding 2: the contradiction is caught by the AGENT, not (only) the gate
The marquee, on-brand result, and it inverts the naïve expectation. Told “record that Rafael is the lead of the Prefetcher work stream” over a workspace where Marina is the seeded lead, the agent wrote nothing in 10/10 runs (bald framing and directive framing). Not a failure: reading its final answer shows why:
“Antes de registrar, preciso te alertar sobre um conflito direto… A entidade da Marina diz explicitamente… ‘Marina is the lead of the Prefetcher work stream.’… Não quero sobrescrever isso silenciosamente… Como toda escrita no Synapse passa pela fila de aprovação (
pending), nada é aplicado até você confirmar.”
The agent searched, found the conflict itself, refused to overwrite, laid out the options (leadership transition / co-lead / mistake), and deferred to the human. So:
Governed memory has two independent layers of contradiction defence:
- The agent’s own judgement: a capable agent surfaces the conflict and asks. This is the first line and, with Claude 2.1.197, it fired every time.
- Synapse’s gate (SYN-242 + BR-12.3): the second line, for when the agent does not notice: a less-capable/more-autonomous agent, a subtler conflict, or too much data to spot it. This is precisely where the deterministic gate earns its keep.
Implication for positioning: don’t pitch the gate as the thing that stops a careless
write in the common case: a good agent already hesitates. Pitch it as the non-bypassable
floor that holds when the agent’s judgement doesn’t. And the strongest, cleanly-true claim
the runs support today is non-corruption: across every insert and contradict run, no
existing fact was ever silently overwritten and no unsanctioned write was ever applied
(accept_pending=false kept any proposal gated; INV-1/2/3/5 held after a rebuild).
Implication for the benchmark: to measure SYN-242 in isolation we must bypass the
agent’s judgement: a direct-MCP probe that files a conflicting update_status/link_entity
with no agent reasoning, then asserts the contradicts signal fires and the write is gated.
That is the next plan-022 increment (a gate-probe mode distinct from the agent-in-the-loop
drive).
Harness corrections this shook out (all landed)
- pass^k keyed on assertions, not bit-identical state (timestamps + prose vary).
file-containscase-insensitive; dropped a brittle substring assertion: grade structural facts, not LLM prose (multilingual output makes literal matching noise).- Reindex after the gate before grading:
accept’s incremental index can diverge from a full rebuild (INV-1) though the Markdown is correct, the index is disposable, so rebuild. contradict-01reframed to grade non-corruption (which holds), not “a proposal was queued” (which assumed a write the agent chose not to attempt).
Cost
~$0.24/run (insert), ~$0.18/run (contradict, read-heavy). $3.65 for N=15. A full four-scenario suite at N=15 with a judge on scenario D will run tens of dollars per sweep: consistent with “owner-run, not CI” (ADR-041).
Round 2: all four scenarios + gate isolation + MCP-only isolation
After the round-1 fixes, added: MCP-only isolation (a per-turn system prompt + a
--disallowedTools block for Read/Grep/Bash/Web* → the agent cannot read the host repo’s
files/docs; it must use Synapse’s MCP tools, the benchmark measures MCP-driven memory, not the
agent reading project docs), a zero-tool + crashed-CLI retry, 95% Wilson CIs +
cost-per-pass, and two new scenarios + the gate-isolation probe.
| Measurement | N | result | reads what it should |
|---|---|---|---|
| insert-01 (A) | 15 | pass@1 80% (CI 55–93%) | extract the fact, resist the chit-chat |
| correct-01 (B) | 8 | pass@1 88% (CI 53–98%) | agent chose update_status on the existing entity → status archived; no duplicate |
| abstain-01 (D leg) | 8 | pass@1 88% (CI 53–98%) | “Não encontrei registro de Camila Duarte”: abstains, fabricates nothing |
| contradict-01 (C, agent) | 10 | non-corruption 100% | agent surfaces the conflict itself, asks; existing fact intact |
| gate-probe (C, isolated) | det. | detect + gate 100% | SYN-242 flags active→archived, queues it; same-value control → no flag |
The recurring failure is claude -p crashing (exit 1), not Synapse: one crash in each N=8
run (started around a host suspend/resume). The driver now retries a crashed turn, so future
pass@1 reflects Synapse, not CLI flakiness. With that, the governance behaviours
(update-on-existing, abstention, non-corruption, gate detect+gate) are effectively clean.
Scenario B works reliably: the agent consistently searched → get_entity → update_status
on the existing Aurora entity, never spawning a duplicate project. Scenario D abstention
works: given a never-planted fact the agent said (in pt-BR) it had no record and wrote
nothing: false-memory resistance held. Gate isolation is the clean, deterministic
Contradiction-Surfacing number the agent-in-the-loop path can’t produce (because a capable
agent hesitates before ever filing the write).
Round 3: bitemporal correction: a real gap the benchmark surfaced
Building the scenario-B --as-of bitemporal assertion (“correct a fact, and --as-of <before> still returns the old value”) exposed that it cannot be driven by the agent’s
natural correction, and why:
update_statusoverwrites the field in place; it is NOT bitemporal. After an agent correction (Auroraactive → archived), the entity row isvalid_from=<created>, valid_until=NULL, supersedes=NULLandsearch --as-of <a date before the correction>returns the entity with the current status (archived), not the old one. The old value survives only in the git/audit trail, not the queryable valid-time axis. So the “correct it →--as-of before→ the old value” story holds for fact supersession (x-supersedes+x-valid-from), not for field-levelupdate_statuscorrections. Filed as a gap (SYN-301).- Supersession closure runs on the insertion path, not bulk
reindex. A hand-authored F1 + F2(x-supersedes: F1) reindexed shows the--as-offilter working before F2 (F1-only ✓), but after F2 both facts return: F1’svalid_untilwas never closed, because the “new-after-old closes F1” rule (ADR-031) fires when a fact is inserted (propose/accept), not when pre-existing Markdown is bulk-reindexed. A deterministic bitemporal-probe must therefore go through the write path (likegate-probe), not a reindex of fixtures.
Consequence for the benchmark: the bitemporal-correctness signal is best measured by a
deterministic bitemporal-probe on the write path (propose F1, propose F2 with
x-supersedes, assert --as-of before→F1 / after→F2 and F1 invalidated-not-deleted), the
agent-driven scenario B can only assert the durable corrected state, not the --as-of
reconstruction. And the marketing pillar should be scoped honestly: bitemporal --as-of is
a fact-supersession property, not something a field update_status gives you.
Round 4: bitemporal-probe + noise-scale sweep (both landed)
bitemporal-probe(deterministic, write-path): propose F1, then F2 withx-supersedes- a later
x-valid-from, accept both (the accept’sresolve_supersessioncloses F1), rebuild, and assert F1 closed-not-deleted, F2 open,--as-ofbefore→F1 / after→F2, INV-5 holds. PASS: the on-brand bitemporal-correctness signal the agent scenario can’t produce (SYN-301). This is how the “temporal story nobody tests” is actually tested.
- a later
- Scenario D under noise (
qa --distractors N): recall@10 and the oracle ceiling stay at 100% through +8 000 distractors (e2e ~100%; the one dip is a 5-needleclaude -pblip). The agent’s token cost is ~flat in N (it searches via MCP), so only the embed scales, the 100 000 tier is a ~2 h one-time embed, documented not run. Retrieval does not degrade at the low-thousands a real brain holds.
Next (plan 022)
- ✅
gate-probe: SYN-242 detect+gate in isolation. Done. - ✅ Driver zero-tool + crash retry: absorbs the headless flakes. Done.
- ✅ Scenario B / D: abstention + three-leg judge +
--distractorsnoise sweep + the deterministicbitemporal-probe(the--as-ofsignal the agent path can’t drive). - ✅ 95% Wilson CIs + cost-per-pass. Done.
- Remaining: the 100 000-distractor tier (a ~2 h embed) on demand; a judge–human
agreement audit before trusting scenario-D e2e as a headline; a possible product fix so
update_statuscorrections become valid-time-reconstructable (SYN-301).
Behavior-learning search ranking (KNN / learning-to-rank): does it fit Synapse?
Date: 2026-07-06 Method: three specialized subagents (technical/ML, operational/systems, product/strategy), each doing repo grounding + 2026 web research, synthesized here. Question: can KNN or a learning-to-rank / online behavior model improve and personalize Synapse search over time from the user’s own signals (which results they open/accept/use), and does it fit what Synapse is?
TL;DR: Do not build behavior-LTR. The kernel of the idea already ships, the on-brand way.
All three lenses converge, independently, on the same conclusion: classical LTR (LambdaMART/
RankNet), online LTR (DBGD/interleaving), contextual bandits, and per-query KNN do not fit
Synapse: the single-user signal is 2–4 orders of magnitude too sparse, the methods need a
training pipeline or heavy Rust ML deps (fail the no-dep-without-ADR bar), and their
exploration/SGD/RNG state is incompatible with “same bytes → same index” and signed
syn verify. The only surviving sliver is a deterministic, bounded, opt-in access
reinforcement, and even that is marginal, costs a new logged-signal subsystem, widens the
unverified surface, and is expected to miss the empirical bar SYN-255 already set. Recommended:
drop it; spend the effort on SYN-292 (undo Phase 2), SYN-271 (reindex enrichment correctness),
and distribution (SYN-101/102/103), which strengthen the actual moat.
Why the heavy forms don’t fit (technical lens)
- Data scale. A heavy personal-KB user produces ~10–50 opens/day, mostly on distinct, never-repeated queries. LambdaMART/RankNet train on 10³–10⁶ judged rows; online interleaving needs thousands of impressions per ranker comparison to clear noise. Per-query models get ~1 example per context. Only low-parameter, query-independent methods are statistically honest at this scale.
- Determinism. Synapse promises a reproducible index and signed
syn verify. The discriminator is sharp: convex + batch + fixed-iteration + no RNG + logged input → a pure function of the bytes, reproducible; anything with online RNG state or non-convex SGD → not. Bandits/DBGD/GBDT-SGD land on the wrong side and also put opaque ML on the retrieval path, which the ethos forbids near governance (ADR-029). - Rust reality (2026). The available LTR/GBDT crates (
lightgbm3-rs,linfa,smartcore) are training-pipeline / native-lib deps that fail the “no heavy ML runtime” bar. A hand-rolled deterministic re-weighter is the only viable path, and it needs zero new crates. - The one narrow form that is technically sound: a per-entity familiarity prior computed
as a batch fold over a logged interaction signal, reusing ADR-029’s existing ACT-R base-level
decay, applied as a bounded
1 + λ·familiaritymultiplier at the same site and in the same spirit as the trust factor: opt-in, post-fusion, feeding neither trust nor the gate.
Why even the narrow form is a liability (operational lens)
- Signal capture is thin and noisy. No browser, no click-stream, no dwell (a
syn openblocks on$EDITOR: that is lunch time, not attention). The one usable event, “search → open same slug,” has no session binding across the CLI/MCP doors and is confounded by position bias that a single user can never de-bias. - No cheap home for the learned state. In Markdown → pollutes source of truth and breaks the
verify fixpoint. In SQLite only → not rebuildable (violates the disposable-index rule). The
only defensible home is a second append-only, audit-class log in
.synapse/: a full new durable subsystem with its own retention, rotation, crash-safety, and privacy defaults. - It widens the unverified surface. A behavior model must be excluded from the canonical
syn verifydump (like vectors andmentions:*), which means the component deciding what the user and agent see first sits permanently outside the corruption proof. A bad model can silently down-rank a true, superseding fact without tripping any invariant, a corruption of what-gets-surfaced thatverifyis constitutionally unable to detect. - Privacy. Search/open history reveals intent more than the notes do; on a Markdown+git
substrate a
git add .would publish it. It would need gitignore-by-default (opposite of the audit trail) and an ADR.
Why it dilutes the product (product lens)
- Wrong axis. The benchmark shows retrieval is already the strongest leg (recall@10 100%, e2e 100% under thousands of distractors). The demonstrated pain is insertion (pass@1 80%) and correction: write-side governance, not read-side ranking.
- Already hedged. SYN-255 (anchor reranker) shipped with an explicit “if < 2–3% MRR, make it a no-op” gate. Behavior-LTR starts from less signal than that deterministic reranker and aims at the same marginal band.
- Anti-positioning. “Search that quietly changes based on your behavior” is, definitionally, a black box that drifts: the exact property v0.10–v1.0 spent eliminating (“prove nothing changed silently”; “no opaque ML in the governed path”, ADR-029). ADR-029 already rejected a per-user ranking model in spirit (EigenTrust over one user’s facts is rank-1/trivial).
- Landscape. Behavior-LTR is a moat for aggregate, multi-user, engagement-framed tools (Glean, web search) and is absent from every local-first single-user peer (Obsidian, Logseq). The closest AI-memory rival (mem0) does retrieval fusion: which Synapse already ships, not behavior-LTR. Where behavior-tracking met a privacy brand (Rewind/Limitless) it added creepiness.
The on-brand half already exists
Synapse already expresses the true kernel of the idea: “things you touch more, more recently,
rank higher”: the verifiable way: ACT-R base-level activation over recorded write/edit
touches (x-ai-history), folded into the deterministic trust factor and shared by
blame/stale/the ranker. It is transparent, banded, and reproducible by syn verify. That is
the ceiling of what behavior-adaptivity should ever be here.
If the owner still wants something (the only acceptable shape)
An explicit, opt-in, off-by-default access-reinforcement that stamps a syn show/get_entity
open as a recorded, inspectable reinforcement event into the existing history channel, reuses
the one ACT-R decay curve, and surfaces in the banded trust display: never “search learns from
you,” always “boost what you actually use, transparently.” The hard test: if syn blame can’t
print it and syn verify can’t reproduce it from the repo alone, it does not ship. Gate it
behind the same recall/MRR precondition SYN-255 carries; expect it to fail that bar.
Recommendation
Do not add behavior-LTR/KNN ranking. It is the anti-Synapse in miniature: opaque, drifting, outside the proof, on a leaky substrate, aimed at the one axis already at 100%. Reinvest in the differentiators the same effort would cost:
- SYN-292:
syn undoPhase 2 (reversibility pillar, still half-built). - SYN-271: reindex regenerating
mentions:*enrichment (a correctness gap in the reproducible-build keystone the provability moat depends on). - SYN-101/102/103: installer / Homebrew / npm (v1.1.0’s adoption theme; for a proven tool, reach is the binding constraint, not ranking cleverness).
Addendum: “continuous learning” of the embedding model (data adapts, the model does not)
A follow-up question distinguished embedding of data from continuous learning of the model. They land on opposite sides of the moat:
- Embedding of data: already shipped and adaptive. Synapse embeds everything with BGE-M3
on-device, at passage granularity (SYN-301), and re-embeds when content changes (SYN-251
skips unchanged content;
reindex/embed --allregenerate). So the vector representation of the brain continuously tracks the data, deterministically: same text → same vector. This is the on-brand form of “learning continuously.” - Continuous learning of the model (online fine-tuning of BGE-M3 on the user’s corpus): does
NOT fit, for the same three reasons as LTR: (1) it breaks “same bytes → same index” (a note’s
vector would depend on training history, not just the note) and so
syn verifystops reproducing; (2) a few hundred single-user notes overfit any fine-tune; (3) it puts a drifting, opaque model on the retrieval path, outside the corruption proof. - The acceptable, already-designed form: ADR-008 versions vectors by the model
identifier. Adopting a better model is a discrete, versioned, deterministic choice (swap → re-embed → fully reproducible), not continuous opaque drift. A future domain-adapted model could be an opt-in, but it pays the same determinism/verify cost and is likely not worth it.
Decision: continuous learning of the embedding model: no. Deterministic re-embed of data + deliberate, versioned model upgrades: yes (already how it works).
Logical-contradiction invariant for syn verify (spike, SYN-283 / #271)
| Field | Value |
|---|---|
| Date finalised | 2026-07-07 |
| Author | Rômulo Febasi romulo.silva@oboi.com.br |
| Scope | Decide whether a deterministic logical-contradiction invariant (INV-13) is worth adding to syn verify, and if so, scope it. Design/spike: no production feature here. |
| Status | accepted (recommendation: GO, ship in verify --deep, own follow-up issue) |
| Inputs | src/core/contradiction.rs (SYN-242 propose-time detection), src/core/verify/ (INV engine), ADR-027/031 (bitemporal), plan 012 |
| Out of scope | Semantic/NLI contradictions (need a model: “borrow the invariant, refuse the engine”); multi-user merge conflicts (SYN-285/#273) |
Executive summary
syn verify proves storage coherence (index = a reproducible build of the
Markdown) but not logical coherence. The one gap that matters: a contradiction
introduced by a direct Markdown edit that bypasses the propose/accept gate, the gate’s SYN-242 detection never sees it, and storage-coherence invariants
pass because the index faithfully mirrors the (now-inconsistent) Markdown.
Recommendation: GO. A small, deterministic, O(N) invariant (INV-13) over the
already-indexed valid-time columns catches the decidable cases without any model.
Ship it in syn verify --deep (opt-in, like INV-4) as a follow-up; this doc is
the design.
1. Context and premisses
Assumes v1.0/v1.2 shipped: the bitemporal model (ADR-027/031, valid-time columns
entity_field_historyfrom ADR-046) and the verify engine (INV-1/2/3/5, plus--deepINV-4 and--sinceINV-12). SYN-242 already detects contradictions at propose-time and forces the gate; there is no invariantverifycan attest over the historical graph.
2. Analysis
2.1 What counts as a deterministic contradiction
Decidable without judgement, relative to valid-time (ADR-027):
- C1: two live mutually-exclusive values. The same entity carries two
open (
valid_until IS NULL) values of a single-valued field (status) over overlapping valid-time. After ADR-046 the live value is one column + a closed history, so C1 reduces to: a history row withvalid_until IS NULL(a history entry must always be closed) or an overlap between a history interval and the live interval. Pure column check. - C2: supersession cycle / dangling close.
x-supersedesforms a cycle, or a fact is closed (valid_untilset,x-invalidated-at) but nothing supersedes it and it is not a correction, an orphaned invalidation. Graph check over thesupersedescolumn. - C3: present-and-tombstoned. An entity both exists as a live row and is referenced as invalidated/removed by another fact. Column check.
Explicitly excluded (need a model): “the note says X but the linked decision implies not-X”, any natural-language disagreement. Out of scope by discipline.
What this means for Synapse: the decidable set is exactly the overlap /
cycle / orphan checks over entities.valid_from/until/supersedes and
entity_field_history: all already indexed. No new schema.
2.2 Reuse of core::contradiction
The propose-time rules (update_status field-flip, link rel-flip) are the pairwise
core of C1/C3. The historical sweep differs only in that it runs over the whole
index rather than one proposed pair, and adds the valid-time-overlap predicate.
So: lift the comparison, add the interval overlap, a thin new function in
core::verify that calls the same equality/flip logic. No duplication of the
detection semantics.
What this means for Synapse: ~one new InvariantResult producer + a helper;
the “what is a contradiction” definition stays single-sourced.
2.3 Cost
C1/C3 are O(N) with a GROUP BY (type,id,field) over entity_field_history +
one pass over entities. C2 is O(E) over the supersedes edges (a forest, not a
dense graph). No unbounded pairwise scan. Fits the verify --deep budget.
What this means for Synapse: bounded and cheap; safe to gate behind --deep
(where INV-4 already lives) so the default verify stays fast.
2.4 Single-user value vs the gate
SYN-242 was scoped as “the human sees the diff at the gate”, which covers
proposed writes. INV-13’s incremental value is the bypass path: a user (or
a script, or a bad merge) editing Markdown directly never hits the gate, so a
direct edit that opens a second live status is invisible today: verify
passes because the index mirrors the Markdown. INV-13 is the only check that
catches it. That is genuine, on-brand value (“the memory your agents can’t
silently corrupt” extends to “…or you can’t silently corrupt by hand”).
What this means for Synapse: the invariant is not redundant with the gate; it guards the un-gated edit path, single-user included.
3. Recommendation
GO: ship INV-13 in syn verify --deep (opt-in), as a follow-up issue
(SYN-316, v1.4). Deterministic, O(N), model-free, reuses core::contradiction
semantics, guards the bypass path. Signed into the synapse.verify-report/1 like
the other invariants. Discipline held: decidable cases only; semantic
contradiction stays rejected. Not shipped in this spike (design-only per plan 012);
the implementation is its own gated change with proptests over planted overlaps.
4. Open questions (for the build)
- Exact remediation UX when INV-13 fails: report the conflicting (type,id,field,
intervals) like INV-1’s
--rebuildlist; never auto-resolve (that is the gate’s job). - Whether a genuine real-world overlap (two truly-concurrent valid facts of a
multi-valued field) must be distinguishable from a single-valued clash: key
off the field’s cardinality in the registry (
statusis single-valued).
Multi-user / team governance seam (spike, SYN-285 / #273)
| Field | Value |
|---|---|
| Date finalised | 2026-07-07 |
| Author | Rômulo Febasi romulo.silva@oboi.com.br |
| Scope | Turn the latent multi-user seams (per-author audit, identity dedup, signed commits) into a concrete MVP design + non-goals, so the team-governance decision is deliberate. Design/spike: no production code. |
| Status | accepted (recommendation: Commercial-line MVP, git-merge model; do not build in open-core v1.x) |
| Inputs | ADR-026 (per-author audit segments), ADR-030/034 (signed commits), ADR-032 (identity dedup), src/core/audit/, src/core/identity/, ROADMAP “Commercial optionality”, plan 014 |
| Out of scope | E2EE sync transport (SYN-230); a hosted network-auth service (STOP condition); open-core inclusion |
Executive summary
Multiple writers sharing one governed workspace is the most plausible monetization wedge, and several v1.0 decisions already lean that way. The design that respects the invariants: git is the merge substrate, the bitemporal append-not-overwrite model (ADR-027) already turns most “conflicts” into coexisting facts, per-author audit segments (ADR-026) merge over git’s DAG without coordination, and identity is bound to signed commits (ADR-030). The MVP is therefore small in new mechanism but large in productization + support, so it belongs on the Commercial line, not open-core v1.x. STOP conditions respected: the design needs neither abandoning Markdown-in-git nor a network auth system for the MVP.
1. Context and premisses
Assumes v1.0 invariants shipped (they are the multi-user foundation). The
substrate already built multi-user-aware: .synapse/audit/<author>.ndjson
per-author segments (merge-safe, ADR-026), identity-fingerprint attribution and
dedup (ADR-032), commit-signature posture (ADR-030/034). The gap is a committed
design for concurrent writers, not missing primitives.
2. Analysis
2.1 Source of truth & merge model
The Markdown-in-git stays the truth. Concurrent edits resolve via git merge,
and the bitemporal model does most of the work for free: a “correction” appends a
new fact / closes an interval (ADR-027/046) rather than overwriting, so two
authors editing the same entity usually produce coexisting facts, not a
textual conflict. Genuine same-line conflicts (two authors set status to
different values in the same commit window) fall back to a normal git merge
conflict the human resolves: no CRDT, no Synapse-level auto-merge. Last-write-
wins is explicitly rejected (it silently corrupts: off-brand).
What this means for Synapse: the merge model is “git + append-not-overwrite”; new mechanism required ≈ 0. The cost is productizing it (clear conflict UX).
2.2 Permission model for the gate
Minimal viable: the propose/accept gate stays per-clone (each author’s
pending queue is local), and acceptance authority is expressed as who may sign
an accepting commit (ADR-030). A shared workspace defines a set of non-revoked
identities allowed to accept; verify/doctor already check commit signatures
against known identities. So permissions = the signed-commit posture generalized
to a team roster, not a new ACL engine.
What this means for Synapse: permission = “accepted writes must be signed by a roster identity”, verifiable offline. No network auth for the MVP.
2.3 Attribution + verify in multi-user
Already covered: per-author audit segments + witness checkpoints (ADR-026/037)
give per-author, merge-safe, tamper-evident history; the remote is a witness
(ADR-011). syn verify --since already proves append-only extension across a
shared history. Cryptographic SSHSIG verify (SYN-261, deferred to Commercial) is
the one piece that becomes load-bearing in multi-user and should ship with it.
What this means for Synapse: the audit/verify story is multi-user-ready by construction; the missing bit (SSHSIG verify) is already Commercial-scoped.
2.4 Which v1.0 invariants must hold unchanged
All of them: INV-1/2/3/5 are per-clone and unaffected; INV-12 (append-only extension) becomes more important; the gate (BR-12.3) stays absolute. Multi-user must add roster + signature enforcement, never relax an invariant.
3. Recommendation
Commercial-line MVP, git-merge model. Build order when pulled in: (1) a team roster of accepting identities in the shared config; (2) enforce accept-must-be-signed-by-roster (lift ADR-030 posture to enforcement, SYN-261); (3) conflict UX doc for the git-merge fallback. Do not add a CRDT, a network auth service, or a hosted gateway for the MVP, those are separate Commercial bets (SYN-230/231/235). Keep it out of open-core v1.x: the value is team/ compliance, not the single-user promise. STOP conditions respected (Markdown-in- git intact, no network auth for MVP).
4. Non-goals (explicit)
- No CRDT / operational-transform layer.
- No hosted multi-tenant service (that is SYN-235 / Commercial).
- No relaxation of the gate or any v1.0 invariant.
- No last-write-wins auto-merge.
Market positioning and Febasi substrate role for Synapse
| Field | Value |
|---|---|
| Date finalised | 2026-07-14 |
| Author | Rômulo Febasi romulo.silva@oboi.com.br |
| Scope | 2026 competitive map of AI-memory/agent-context tools, emerging themes and new problem vectors, and whether Synapse is a tool/API/substrate for the other Febasi products (Soma, Eved, and the rest). |
| Status | draft |
| Inputs | Three parallel research agents (competitive landscape with web sources; emerging-themes/new-problems scan; internal Febasi portfolio evaluation from the repos on disk). Prior: strategic-direction audit 2026-06-07, competitor note (Memoria owns “Git for AI memory”). |
| Out of scope | Executing a rebrand (the headline reposition is recorded here as a recommendation, not applied). Shipping any listed feature. Legal review of licensing. |
Updated direction 2026-10-12: this draft’s exclusivity and buyer-demand assertions are hypotheses, not validated adoption evidence. The knowledge framework defines interoperability, scoped integrity/identity claims and a bounded Soma pilot. It supersedes the competitive-replacement premise and expands optional source monitoring.
Executive summary
The “provable non-corruption plus governed write path” axis is genuinely
uncontested: every funded competitor (Mem0, Zep/Graphiti, Letta, Cognee,
Supermemory, LangMem, native OpenAI/Anthropic memory, Memoria) competes on
recall, latency, and token cost, and resolves conflicts with a non-deterministic
LLM that writes automatically. Three dated 2026 forces push demand toward
Synapse’s exact strengths: OWASP ASI06 (Memory and Context Poisoning) named the
threat, the EU AI Act Article 12 logging mandate lands 2 August 2026, and the MCP
ecosystem has a trust vacuum (about 30 CVEs in 60 days). The recommendation:
lead with syn verify plus the gate (not bitemporal, which is table stakes, and
not undo, which is crowded), aim at regulated engineering teams, and make the
proof legible to buyers who do not live in a terminal. Internally, Synapse is a
clean substrate for Soma (local, single-user, MCP-native) and a shared contract
(not a runtime dependency) for Eved.
1. Context and premisses
Synapse is at v1.4.0 (v1.5.0 in flight). It is local-first, git-native governed
memory for AI agents: Markdown plus YAML source of truth, disposable SQLite
index, one core behind a CLI (syn) and an MCP server (25 tools). Moat: governed
write path (propose/accept, never silent overwrite), syn verify (signed,
offline, reproducible proof of non-corruption including logical-contradiction
detection), provenance plus tamper-evident git audit, bitemporal --as-of,
reversibility (syn undo). Closed-source binary, public MIT mirror, npm
@febasi/synapse, cosign-signed. Hard non-goals: no hosted data UI, no
accounts/login, no non-SQLite engine, no proprietary format.
2. Market scan
2.1 The competitor map
Every serious player competes on recall/latency/token-cost and writes memory automatically with an LLM. None proves non-corruption.
| Player | Positioning | Hosted/Local | OSS/Closed | Integrity story |
|---|---|---|---|---|
| Mem0 | “Universal memory layer” | Hosted + self-host + OpenMemory MCP | Apache core, cloud closed | Estimate. LLM add/update/delete, most-recent-wins, mutable. Audit = access logs. |
| Zep / Graphiti | “Agent memory at enterprise scale,” temporal graph | Zep Cloud primary; Graphiti self-host | Graphiti Apache, Zep closed | Closest neighbor. Real bitemporal + episode provenance, but contradiction is LLM-judged, no crypto verify, no gate. |
| Letta (MemGPT) | Stateful agent runtime, agent is its memory | Cloud + self-host | Apache open-core | None. LLM self-edits its own memory. A Merkle-audit request was closed as spam. |
| Cognee | “Open-source memory platform,” graph ECL | Self-host + cloud | Apache lib, cloud closed | Estimate. Best trust primitive is a boolean ontology_valid. Building a Rust edge engine. |
| Supermemory | “The memory API,” Memory Router | Hosted + offline single binary | MIT open-core | Estimate. Timestamp recency-wins, TTL decay. Closest local philosophy. |
| LangMem (LangChain) | LangGraph long-term memory SDK | SDK + hosted platform | SDK MIT, runtime Elastic 2.0 | None. Conflict = LLM upsert. “Time travel” is checkpoint replay. |
| Anthropic memory tool | Client-side file ops in /memories | Client-side, you hold data | Contract open, weights closed | None; explicitly your problem. Punts versioning to “git recovery on your side.” |
| OpenAI/ChatGPT memory | Saved memories + history mining | Fully hosted, closed, no export | Closed | None; opposite. Server store the model rewrites silently. |
| Memoria (MatrixOrigin) | “The World’s First Git for AI Agent Memory” | Cloud + self-host on MatrixOne | Apache, Rust | Estimate, git-as-metaphor. Snapshots are COW/MVCC, not a commit DAG or Merkle chain; not tamper-evident. |
| Engram (newcomer) | “AI that knows your org” | Hosted, closed | Closed | None stated; pitch is token-cost efficiency. |
| Memvid (newcomer) | No-DB append-only .mv2 file | Local file | OSS | Partial. Content-hashed append-only primitives, but no signing/invariants/gate. |
External validation of the thesis: Microsoft Security (Feb 2026) documented “AI Recommendation Poisoning” and that vector stores have “no integrity check, no provenance model.” Atlan’s “AI Agent Memory Governance” enumerates the exact five gaps Synapse fills and names no vendor as solving them. MemStrata (arXiv 2606.26511) finds cosine similarity separates contradictions from duplicates at only AUROC 0.59, the argument for a deterministic gate over similarity heuristics.
What this means for Synapse: the proof/governance axis is empty. Lead there.
2.2 Emerging themes to ride (dated tailwinds)
- OWASP ASI06 “Memory and Context Poisoning” (2026) entered the Agentic Top 10, framed as persistent, silent corruption. The market now has a category name for the exact problem Synapse attacks.
- EU AI Act Article 12 requires automatic event-logging with retention of at least 6 months for high-risk systems; full application 2 August 2026; penalties up to 35M EUR or 7% of turnover.
- MCP trust vacuum: roughly 9,650 registry servers, about 30 CVEs in 60 days, tool-description poisoning disclosed by Microsoft; the official roadmap prioritizes a “verified” server directory.
- ISO/IEC 42001 is becoming a procurement gate;
syn verifyoutput is evidence a buyer hands an auditor. - Content provenance generalizing to text: C2PA v2.3 (Feb 2026) added manifests for LLM text output; SLSA/in-toto/Sigstore now applied to AI models.
What this means for Synapse: three independently verified forces (a named threat, a dated mandate, a trust vacuum) all reward governed writes and offline proof, which competitors structurally lack. The bet is timing, not direction.
2.3 Pricing and GTM reality
The category monetizes on usage-based consumption (Mem0, Zep credits, Cognee,
Supermemory) plus enterprise compliance envelopes (SOC 2 Type II, HIPAA BAA, SSO,
retention, BYOC). Open-core is universal: permissive OSS as funnel, hosted
platform as revenue. Given Synapse’s non-goals, consumption billing and hosted
sync are blocked (no accounts, no hosted meter, no data UI). What is open: a
per-developer/per-enterprise license (closed source, cosign-signed npm), a
self-managed team/enterprise license where the compliance story is the product,
and a paid synapse-check CI/gate plan. Synapse is structurally a priced tool,
not a memory-infrastructure SaaS. That caps TAM but sidesteps the recall and
token-cost races entirely, which is the correct trade given the non-goals.
What this means for Synapse: price the tool; do not chase the SaaS meter.
3. Cross-check vs prior hypotheses
| Hypothesis | Status | Reason |
|---|---|---|
Moat is the gate + syn verify, not sync | Validated | Proof/governance axis is empty across all competitors; sync is copyable and off-limits by non-goal. |
| Do not lead with “Ctrl+Z” / undo | Validated | Memoria owns rollback, Anthropic defers to git recovery, LangGraph has checkpoint replay. |
| Bitemporal is a differentiator | Challenged | Zep/Graphiti productized valid-time invalidation; it is table stakes, not a wedge. |
| Regulated teams are the buyer | Validated + sharpened | Both the competitive and themes scans converge on regulated/high-stakes engineering teams. |
| Local-first Rust is a durable edge | Challenged | Cognee building Rust edge engine, Supermemory ships offline binary, Anthropic memory is client-side. Local alone will not hold; proof will. |
4. Positioning
Sharpest defensible position: Synapse is the only AI-agent memory that gives you an offline, cryptographically signed proof it was not silently corrupted; every write is proposed, gated, and reversible on your own machine. Lead with verification plus the governed gate. The moat pillar to defend is the offline, reproducible, signed verification, because retrofitting proof onto a mutable hosted vector store is architecturally hard (it requires Markdown-as-truth plus a disposable index). Adversarial note: Zep is one feature away on three of four pillars (it already has bitemporal + provenance + SOC 2 + HIPAA + audit); if it adds a deterministic invariant checker plus an approval mode it matches them with hosted compliance Synapse cannot offer. Own “proof” loudly and now.
Wedge audience: regulated/high-stakes engineering teams (fintech, health, legal) building coding or ops agents, for whom recommendation poisoning and “reconstruct what the agent knew at decision time” are audit requirements.
5. Recommendation
| # | Block | Justification | Cost | Defensible? |
|---|---|---|---|---|
| 1 | Reposition headline to proof (syn verify + gate) | Defends the one pillar Zep cannot cheaply copy; rides ASI06 + Article 12 | S (docs/brand) | Yes |
| 2 | “Memory CI” + syn contradictions as top-of-funnel | Devs adopt a check faster than a database; the Action already exists | M | Yes |
| 3 | Standard attestation envelopes (syn verify --format in-toto, C2PA-text) | Makes proof legible to existing verifiers; fixes “proof only means something to who generated it” | S/M | Yes |
| 4 | EU AI Act / ISO 42001 evidence pack (syn verify --report ...) | Maps existing audit fields to controls; template not engineering | S | Yes |
| 5 | Soma x Synapse integration (MCP stdio) | Dogfood; first proof of Synapse-as-agent-substrate; no new infra | M | Yes |
| 6 | Shared verify/provenance contract with Eved | One governance vocabulary across the portfolio without a runtime dependency | S | Yes |
Explicit non-goals to reaffirm: no blockchain/ZK verifiable-AI (git + ed25519 is the advantage), no hosted vector cloud, no hosted data UI, no runtime guardrail/prompt-injection product, no telemetry.
6. Disruptive bets
- Reposition from “personal memory” to “the poisoning-proof memory of
record,” with
syn verifyas the product, not a feature. The market handed over a category name (ASI06) and a structural gap. Risk: the proof must be legible to non-CLI buyers, so envelopes (block 3) and a CI comment (block 2) are the mitigation; a hosted verification UI is the non-goal not to cave on. - Ship “Memory CI” as the top-of-funnel product and let the store follow. A
free Action that fails PRs on knowledge contradiction/poisoning/provenance
breaks needs no accounts. Risk: a noisy or slow check gets disabled day one, so
ship
syn contradictions(near-zero false positive) first. - Portable signed developer credential, assembled from three Febasi parts.
Synapse identity +
syn cv exportfor the crypto,febasi-api-authfor issuance/verification, Vero for activity attestation. Rides AI resume-fraud. Risk: unproven demand at individual-developer scale; validate before building. - MCP-native “ground truth” service other agents query. As MCP trust collapses, one server whose brand is “human-vetted, provenance-stamped, verifiable.” Risk: a two-sided network play that fights the single-user, no-accounts DNA; only after bets 1 and 2 land, and gated on SYN-235.
7. Ticket sketch (provisional IDs)
| Sketch ID | Block | Size |
|---|---|---|
| SYN-318 | Memory CI: synapse-check fails a PR on newly introduced contradiction/poisoning/provenance break, with a human-readable diff comment | M |
| SYN-319 | syn contradictions first-class CLI/MCP verb (narrow, demo-able wedge) | S |
| SYN-320 | syn verify --format in-toto (ITE-6) and a C2PA-text manifest option | M |
| SYN-321 | Soma x Synapse: register syn MCP in the agent Soma spawns; capture run ADRs/diffs as governed memory | M |
| SYN-322 | EU AI Act Article 12 / ISO 42001 evidence-pack report mode | S |
| SYN-323 | Shared verify-report + x-ai-* provenance contract adopted by Eved audit_logs (spec, not binary) | S |
8. Risk register
| Risk | Severity | Mitigation |
|---|---|---|
| Market not yet asking for proof | High | Ride dated mandates (Article 12, ISO 42001); lead top-of-funnel with a free check, not a paid proof. |
| Zep adds gate + invariant checker | High | Own “offline reproducible signed verify,” the pillar hard to retrofit on hosted stores; move on positioning now. |
| Proof illegible to non-CLI buyers | Medium | Emit standard envelopes and a CI comment; do not rely on a bespoke report. |
| TAM capped by non-goals | Medium | Price as a tool/enterprise license; accept the trade to avoid the recall/hosting races. |
| Hosted spine erodes local-first | Medium | Keep any hosted mode an opt-in team tier gated on SYN-235; never the core. |
| Competitor funding/traction figures unreliable | Low | Treat all vendor numbers as directional; the regulatory/OWASP tailwinds are the verified part. |
9. Open decisions
- Greenlight the headline reposition (proof-led) for the next brand/docs pass, or keep as recorded-only for now? (User decision 2026-07-14: recorded-only.)
- Is “Memory CI” a free growth product or a paid team plan from day one?
- Does the Soma integration ship as an opt-in Soma feature or a documented recipe first?
- Is the developer-credential bet a Synapse feature or a separate Febasi product?
10. References
- OWASP Agentic AI Top 10, ASI06 Memory and Context Poisoning (2026).
- EU AI Act Article 12 (logging) and Article 19 (retention); full application 2026-08-02.
- MCP adoption and security: official MCP 2026 roadmap; MCP CVE reports (2026).
- Microsoft Security, “AI Recommendation Poisoning” (Feb 2026).
- Atlan, “AI Agent Memory Governance.”
- Mem0, “State of AI Agent Memory 2026.”
- MemStrata, arXiv 2606.26511 (contradiction vs duplicate AUROC 0.59).
- Zep/Graphiti, arXiv 2501.13956 (temporal knowledge graph).
- C2PA v2.3 (text manifests, Feb 2026); SLSA/in-toto/Sigstore model signing.
- Internal: Febasi portfolio repos (soma, febasi-web-eved, febasi-api-analytics, febasi-api-auth, febasi-proxy-gateway);
docs/business/ROADMAP.md(SYN-231/235, Commercial optionality).
Typed decisions, small NLI and trust: an evaluation protocol
Date: 2026-10-12 · Status: research/design; no model benchmark executed
Author: Rômulo Febasi · Baseline: 3ceddd0
Question
Can a narrow decision model reduce review cost for prose changes and contradictions while preserving deterministic acceptance authority and explicit integrity boundaries? User reference: Jev by TypeSafe AI, not JEPA.
Findings from primary sources
TypeSafe documents Jev as a hosted System One model returning Choice, Score and Noul rather than generated prose. It describes focused questions, typed answers and confidence-aware routing. This is an appropriate architectural reference for triage and source relevance; it does not establish a small local artifact or proven Synapse performance. Introduction.
The current model page identifies versioned jev-1.13.0 and moving aliases. Pin resolved versions when evaluating thresholds; do not adopt vendor latency/cost comparisons as product results. No deployable local weights or parameter count were established in this research. Model specification.
Known limitations include literal/negation interpretation, arithmetic/date handling, irrelevant context, hostile input and option-order effects. Keep exact calculations in code and test rubric/label perturbations. Typed output can still be a wrong decision. Official limitations, confidence.
cross-encoder/nli-deberta-v3-small is an Apache-2.0 English NLI reference, trained for contradiction/entailment/neutral. Its task-specific scores are not business-domain calibrated probabilities. Evaluate compatibility with the existing ONNX runtime before use. Model card.
MoritzLaurer/mDeBERTa-v3-base-mnli-xnli is a MIT multilingual NLI comparison candidate. Multilingual metadata is insufficient to assert Portuguese architectural/API quality; measure PT fixtures explicitly. Model card.
Two recent preprints investigate typed decision robustness and paired System One evaluation. They motivate tests rather than a conclusion about Synapse or a proven replacement engine. These papers are emerging evidence, not independently reproduced here: type/label robustness, paired evaluation.
Recommended experiment
- Define a finite relation vocabulary and allowed actions. Model suggests review; code controls permission/acceptance.
- Produce a human-reviewed EN/PT corpus of architecture and shared API facts, with source/environment/version/date scopes.
- Split by source/project/claim families to prevent near-duplicate leakage. Freeze test data before threshold tuning.
- Compare exact rules + retrieval, local English NLI, multilingual NLI and optional consented Jev under identical candidate sets.
- Measure candidate retrieval recall separately from pair-classifier recall. Test joint pipeline as well as oracle candidate pairs.
- Evaluate both pair directions when entailment matters; distinguish genuine conflict from later valid change or separate environments.
- Freeze model/rubric/schema/input digests and hardware stamp; retain sanitized artifacts and repo commit.
- Choose thresholds on validation only; review low-confidence/unsupported cases, then report held-out outcomes and intervals.
Cases: negation, allowed exceptions, multiple clauses, v1/v2 coexistence, prod/staging, dates/counts, source wording drift, implicit architectural consequences, missing evidence, unrelated text, adversarial instructions, renamed/permuted labels and source access loss.
Metrics: alert precision/recall, candidate recall, macro-F1 by language/relation, false-positive review minutes, abstention/coverage, Brier score or calibration curves where probabilities are available, p50/p95, cold-start/RAM, query/model input cost and timeout rate. Report estimates separately from official measured usage. Treat all thresholds as domain-specific.
Proposed pilot gate: precision >=0.90 for conflict-review alerts and scoped conflict recall >=0.80, with sample size and bootstrap confidence intervals reported. These targets are not claims of achieved reliability; choose final workload budgets and review budget before benchmarking. A failed gate means keep the adapter experimental or do not ship it.
Action contract
- Same fact: suggest deduplication/relation; never silently merge identities.
- New scoped fact: propose creation with source revision.
- Later valid fact: propose temporal supersession/update; preserve previous validity.
- Potential conflict: show both evidence spans and require review.
- Missing/ambiguous context: abstain and request context or reviewer input.
- Suggested patch: a generative model or deterministic template drafts it separately; Jev does not write prose. Apply only with upstream revision precondition and explicit write capability.
A successful classifier does not add a universal semantic Pass to syn verify. Integrity proof evaluates exact records; a classifier estimates relations between claims.
Deployment posture
Local rules remain available without models. Local model downloads require explicit configuration, artifact digest/license/runtime checks and defined memory/latency budget. A local model swap changes disposable model-derived data, not accepted truth.
Jev is an optional edge adapter, not a default dependency. Read source ACL/egress policy before sending content, minimize data, keep credentials outside knowledge, set deadlines/rate/budget, pin returned model and fail to abstention/review. No paid API calls, downloads, training or local benchmark were performed for this document.
Integrity, identity and blockchain analysis
Useful blockchain-related ideas are authenticated append-only records, hash links, content addressing, signatures and independently witnessed heads. Existing Synapse already has some of them. The gap is coverage of actual mutations and preserved receipt/trust material, not the absence of a distributed consensus network.
OIDC login, device key possession, membership authorization and independent witnessing solve distinct problems. A blockchain address does not map itself to an employee, and a checkpoint cannot prove that the stored fact was true. Begin with sealed transitions and independent checkpoints; benchmark/log-volume evidence may later justify Merkle proofs. No on-chain content/identity; optional private/public anchor adapters require privacy and operational evaluation.
Standards consulted: OIDC Core, OAuth native apps, OAuth BCP, RFC-9162, RFC-8785, Sigstore bundles. Their mechanisms are inspiration/compatibility targets; Synapse does not claim to implement them merely by referencing them.
Decision and revisit trigger
Accept the provider abstraction and the evaluation protocol. Defer choosing a default model, training a new architecture, adding a second native runtime or operating a blockchain until the experiment demonstrates a benefit or a customer provides a concrete trust requirement. See ADR-050 through ADR-053 and the linked implementation issues.
Scoped EN/PT decision evaluation: 2026-10-13
Decision: ship a conservative read-only provider seam and experimental adapters; enable no new model default. Neither evaluated model reached the proposed 90% precision / 80% scoped-conflict recall gate. Model probabilities are diagnostic evidence, never acceptance or integrity proofs.
Measured evidence
The original synthetic corpus has 96 cases, 24 topic families, 32 training cases and 64 heldout cases (32 EN, 32 PT). Entire family variants remain in one split. The corpus hash/split were fixed before threshold selection. Labels were reviewed by the coding agent against the narrow engineering contract, not independently human-labeled or obtained from a Febasi pilot. Negation, exceptions, environments, versions, dates, changed option names, distractors, injected instructions and window overflow are represented.
| Provider | Heldout alert precision | Conflict recall | Abstention | Warm model-only p95 | Peak process RAM | Cold model initialization |
|---|---|---|---|---|---|---|
| Exact-text + scoped filters | undefined (no alerts) | 0% | 87.5% | not applicable | 25 MiB | not applicable |
| English DeBERTa small | undefined (disabled by training threshold) | 0% | 87.5% | 47.79 ms | 932 MiB | 4.18 s |
| Multilingual MiniLM six layers | 100% (9 alerts) | 25% | 50.0% | 10.90 ms | 1269 MiB | 4.58 s |
| Jev | not measured | not measured | not measured | not measured | hosted | not run: no credentials |
Multilingual recall: EN 22.2% (4/18), PT 27.8% (5/18). Whole-family bootstrap 95% pooled recall interval is 7.1%–46.4%; language intervals are EN 0%–50%, PT 7.1%–50%. All emitted alerts in this small sample are positive, so the empirical bootstrap precision interval degenerates to 100%–100%; this is not a population guarantee. English training selected 1.01 (abstain-all), not a deployable threshold. The conservative baseline uses the same exact-text rule as production; its non-alert results principally come from explicit context filters.
Candidate recall was 100% among the explicitly supplied synthetic conflict pairs. This says nothing about finding unknown conflicts across a whole company. Review load for MiniLM was 65.6% EN / 62.5% PT. Raw uncalibrated reliability ECE was 0.161 for MiniLM and 0.210 for DeBERTa; no calibrated confidence is exported. Resource numbers include framework/model initialization and hash verification. The final reproducible run overlapped other local verification workloads; timings characterize this development host rather than an isolated performance lab. Warm pair latency excludes CLI startup, cold model loading and the second judgment direction. The native adapter cold-loads for each invocation and has a bounded total operation deadline.
Artifact and runtime checks
Pinned MiniLM revision 0a71e92a985b6e1ad1828cf67ce9c459639c1dca is MIT; pinned DeBERTa revision fa2804872c3b4bd748f38c0185cc85775361e735 is Apache-2.0. Public file hashes and sizes are retained in the fixture manifests. Safetensors and reviewed local-only code were used; no custom remote model code ran. The multilingual model card’s published XNLI table does not include PT, so Portuguese quality is measured here rather than inferred from its name.
Both pinned ONNX artifacts loaded in Python ONNX Runtime 1.23.2, arm64 CPU. MiniLM’s two bounded probes agreed with PyTorch and differed by at most 3.5e-6 logits. DeBERTa ARM64 int8 agreed on the English probe but changed the Portuguese top class, with a 4.25 logit difference. Successful loading does not establish behavioral compatibility. Native Rust ONNX execution was not validated and no native default is shipped.
The actual Synapse binary ran the local pinned MiniLM on an accepted source fixture through CLI and MCP; both returned the same revision-bound advisory result with null calibrated confidence. Source Markdown hashes remained unchanged, and unaccepted source drift failed closed. Repository CI runs the default baseline/process contract without downloading models; the opt-in experiment receipt is separate.
Reproduction and remaining research
Raw per-case measurements, grouped uncertainty and provenance are sanitized and contain no credentials or source secrets. Reproduction commands and host configuration describe fixed corpus/model hashes, runtime versions, source/egress boundaries and budgets. Jev has official-contract tests for model identity, closed option names, distribution/confidence validation and consent fallback; these are not live quality measurements.
Before recommending a default: obtain independent domain/human labels, expand families and realistic retrieval misses, evaluate calibrators without heldout leakage, measure full operation latency and test the actual native runtime artifact. The current evidence supports experimentation and explicit review, not a general contradiction verifier or measured field ROI.
Primary sources: MiniLM model, DeBERTa model, TypeSafe Choice API, confidence contract.
Synapse governed-state predicate v1
Predicate type: https://github.com/romulofebasi/synapse/blob/main/docs/spec/governed-state-predicate-v1.md.
The opt-in export uses in-toto Statement v1.
Its single subject identifies the actual bytes of synapse-state-evidence.json,
not the source head hint or a filename. digest.sha256 hashes those exact bytes.
The predicate includes their standard-base64 representation so independent consumers
can verify the subject without reproducing Rust JSON serialization.
The artifact contains the original accepted records, detached native receipts, original shared seals and explicitly selected public trust/team/witness inputs. Missing or unreadable material remains null with an explicit issue. Markdown bodies, credentials and private signing keys are excluded. Native verification and collection share one writer lease; the artifact is immutable after export.
assurance preserves the actual native synapse.state-assurance/2 report, including
basic/strict profile, skipped checks, required-coverage verdict, limited baseline,
covered range, receipt checks, freshness, identity, witness and excluded surfaces.
new_operation_authority=not_granted and general_semantic_truth=not_proven apply
even when an outer export signature verifies. Earlier history, unseen revocations
and arbitrary agent behavior do not become proven through serialization.
Optional DSSE
uses application/vnd.in-toto+json and the specified UTF-8 byte-length PAE over the
exact serialized payload. The CLI adapter selects Ed25519 out of band. A verifier
pins the export key independently; keyid remains an unauthenticated hint. Both
standard and URL-safe base64 are accepted. Verified bytes are parsed once and
passed to the application; the envelope is not reread after signature verification.
Use
syn state attest --profile basic
syn state attest --profile strict-local --trust /private/trust.json \
--trust-sha256 REVIEWED_SHA --since /private/witness.json --trusted-key PUBLIC_KEY \
--dsse-key-dir /private/export-key --artifact-output /private/new-evidence.json
syn state envelope-verify --input /private/envelope.json \
--trusted-key INDEPENDENT_EXPORT_PUBLIC_KEY --artifact /private/new-evidence.json
Existing native report formats remain unchanged. Export is a separate command;
failed native policy still produces its truthful statement and a failing exit code.
MCP state_attest returns an unsigned standard statement through the same core.
MCP never accepts a private signing-key path. state_envelope_verify shares the
signature/subject verification core and reports that native source verification
was not rerun. Optional CLI artifact output must be a new file outside the source.
DSSE authenticates the selected export key’s statement; it does not authenticate an organization, install source, supply missing native receipts or grant approval. Evaluate native evidence against independent current trust/policy inputs when those guarantees are required. C2PA and external blockchain anchoring remain separate compatibility/use-case studies rather than prerequisites or compliance claims.