Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Synapse

Lil Syn characters connected by amber sparks 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.

Rust MCP Release npm Signed releases License

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.

ResponsibilityWhat Synapse provides
Preserve useful contextTyped Markdown records for projects, APIs, people, decisions, tasks and runbooks
Govern changesProposals, policy checks, explicit review and supported reversal
Inspect evidenceRecorded provenance, retained history and scoped verification reports
Retrieve for the next taskLocal 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.

A result enters a proposal, exact review and accepted Markdown, with retained source context, history and explicit verification for the next task.

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.

EvidenceWhat to inspectBoundary
Recorded changessyn blame, syn diff, syn audit verifyAttribution text alone is not authenticated team identity
Storage projectionsyn verifyA skipped check means missing prerequisites, not a pass
Governed source suffixsyn state verify and selected assurance profileExplicit baseline, retention, trust and witness inputs determine coverage
Historical statesyn at <ref>, syn audit replayReplay determinism and complete source reconstruction are distinct checks
Structured contradictionsScoped diagnostics and deep verificationExact 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.

CLI and MCP call one shared core. Soma uses a pinned CLI process contract. The core works with Markdown, a search projection and separately retained 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.

Operators and agents use Synapse directly or through Soma. Owned Markdown and read-only Git, Markdown or optional GitHub adapters have distinct responsibilities.

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.

Internal synthetic retrieval fixture: first-result recall ranges from 75 to 90 percent; top-ten recall is 95 percent at 77 entities and 100 percent at the five larger measured corpus sizes.

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 evidenceWhat it establishes
Agent-in-the-loop benchmarkBounded insertion, correction, structured conflict and retrieval scenarios; inspect sample sizes and confidence intervals
Verification scopeNamed invariants, skipped checks, selected trust and explicit exclusions
Durable transactionsCrash recovery from retained prepare data and exact SQLite decision markers
Governed source historyAcknowledged limited baseline, retained images and independently selected witness checks
Shared integrationCurrent 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

IntentStarting point
Capture a project factsyn capture "Maria now owns the Payments API"
Inspect recent changessyn digest
Review queued proposalssyn pending list
Reverse a supported local writesyn undo
Query supported recorded timesyn search payments --as-of 2026-05-01
Check knowledge changes in CIMemory CI guide

Review the command contract and selected policy before accepting proposals in bulk.

Documentation

Start with a taskReference
First workspace and assistant connectionOnboarding, tutorial, MCP
Install or verify an artifactInstallation, signed release verification
Integrate a host such as SomaKnowledge process contract, shared source
Monitor external knowledge changesSource adapters, source evolution
Understand assurance and evidenceProvability, provenance, auditability
Inspect system designArchitecture, ADRs, business rules
Find every command and optionCLI reference, configuration, models
Assess measured behaviorBenchmarks, 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.

Lil Syn with heart eyes
Built by Rômulo Febasi at Febasi.

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 is edge → core → data.

If something goes wrong, syn doctor is the first stop. Lil Syn looks confused 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:

  1. detects uname -s / uname -m,
  2. resolves the latest release tag (or SYNAPSE_VERSION if set),
  3. downloads the matching tarball and attempts the documented best-effort checksum/signature checks,
  4. extracts and drops syn into INSTALL_DIR (default /usr/local/bin),
  5. on macOS, clears the Gatekeeper com.apple.quarantine attribute 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

VariableDefaultPurpose
SYNAPSE_VERSIONlatest published tagPin an explicitly reviewed release tag.
INSTALL_DIR/usr/local/binWhere to drop syn. Use ~/.local/bin for no-sudo installs.
SKIP_QUARANTINEunsetSet 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:

PlatformTarget
macOS Apple Silicon (M1, M2, M3, …)aarch64-apple-darwin
Linux x86_64x86_64-unknown-linux-gnu
Linux ARM64aarch64-unknown-linux-gnu
Windows x86_64x86_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 no syn binary 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 synapse name is taken on npm by an unrelated package, so Synapse publishes under the @febasi organization scope.

How it is published (SYN-103): the npm packages are built and published from the public synapse-releases mirror (.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 the synapse-releases repo + that workflow. The launcher source lives in the mirror under npm/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.

FeatureAddsCost
v05_backendsdefault-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).
vectorthe 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_backendon-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_backendon-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_backendsopt-in: OpenAI / Voyage hosted embeddings (sends content to a third party: see IDENTITY.md). Implies vector.tiny; no local model.
test_backendopt-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 init onboarding prompt, or running syn embed --all (or syn embed <slug>). Until then, writes save without embedding and syn search degrades to FTS + graph: nothing blocks and nothing downloads silently. syn embed --check is read-only and reports models_ready false without downloading. Budget ~2.5 GB for the multilingual defaults, ~325 MB for the English opt-downs; the cache (overridable via SYNAPSE_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: syn is 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.

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

Lil Syn waving

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


Lil Syn satisfied

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 json is always machine-clean and unstyled.

Workspace lifecycle

CommandWhat 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 infoWorkspace 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 contextPrint 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 reindexRebuild the SQLite index from the Markdown source of truth. Idempotent.
syn doctorRead-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 explicit syn embed --all (FTS-only until then): SYN-251. Declared pattern (regex) field constraints are parsed but not yet enforced (needs a regex dep + 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 at DATE (YYYY-MM-DD or RFC-3339), using the SQL:2011 closed-open [x-valid-from, x-valid-until) interval. Facts with no x-valid-* stamps always pass. Applies in every mode (FTS and hybrid); local-only (conflicts with --all). Composes with syn 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 blame use), 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 link rows for mentions that resolve to existing entities (surfaced by syn graph);
  • queues a create_entity_from_ner proposal 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

VariableEffect
SYNAPSE_LANGLocale override (en, pt-BR). Same as --lang.
SYNAPSE_PLAINAny non-empty value forces raw, unstyled output (same as --plain).
SYNAPSE_NO_COLOR / NO_COLORDrop ANSI colour, keep structure. TERM=dumb does the same.
SYNAPSE_AGENTMark the caller as an AI agent: implies --plain and --no-input (no styling, no prompts).
SYNAPSE_PROFILEPath to the global self-identity profile.toml (overrides the default location; used by tests + advanced setups).
SYNAPSE_NO_FRESHNESSAny non-empty value silences the freshness advisory for the invocation.
SYNAPSE_ENRICHMENT_MODEOverride [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_CACHEOverride 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

CodeMeaning
0success
1user error (validation, not found)
2internal bug (panic caught)
64usage error (bad clap args)
65data format error (frontmatter, YAML)
74I/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/; syn finds it walking up from cwd.

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

AdapterCurrent deliverySeparate integration obligations
Git/Markdownpinned local commit reads/poll/current-state reconciliationreviewed upstream write-back remains disabled
GitHub Markdownoptional official Contents/commit API readsApp installation/token lifecycle remains host-owned
ObsidianMarkdown in a selected Git repository works through Git adapterunversioned vault monitoring is not implemented
Notiontracked under SYN-335 extension scopepage/block revision and per-user ACL mapping; not implemented
Jiratracked under SYN-335 extension scopeissue/version/event visibility and ACL mapping; not implemented
Confluencetracked under SYN-335 extension scopepage 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.

Three Lil Syn characters connected by amber sparks

Synapse: Architecture

Two interfaces, one governed core, inspectable knowledge on disk.

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

CLI and MCP share core use cases. Hosts such as Soma invoke a pinned CLI knowledge process. The core uses Markdown content, SQLite retrieval and operational state, and separately retained evidence.
LayerResponsibilityDependency rule
EdgesCLI parsing, MCP transport, host/source/model adaptersCall typed core use cases; keep credentials and transport concerns outside core
CoreRetrieval, proposals, review, source decisions and verificationShare behavior across CLI and MCP; no clap, rmcp or direct transport dependencies
DataMarkdown store, SQLite index, configuration and evidence metadataProvide 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

A proposed result is reviewed against exact context before acceptance, then retained with source and history evidence for later retrieval. Verification reports its own coverage.

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

StateRolePreservation requirement
User MarkdownCurrent knowledge content and typed frontmatterKeep in the user’s selected Git/backup workflow
SQLite search projectionFTS, links, tags and optional vectorsRebuild derived retrieval data with syn reindex
Operational queuePending payloads, context and review guardsRetain separately; Markdown cannot reconstruct lost pending rows
Governed source metadataAccepted transitions, retained images, original receipts and sealsPreserve with the governed workspace; missing evidence reduces or fails coverage
Independently selected trust and witnessesVerifier roots, pins and observed checkpointsKeep 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.

StageDurable decision and recovery behavior
AcquireTake the cooperative writer lease and recover any preceding intent
PrepareStage writes/deletes, retain preimages and fsync a versioned prepare document
InstallInstall the prepared source while retaining recovery material
CommitCommit index changes and an exact prepare-digest marker in the same SQLite transaction
Publish evidencePublish the retained original receipt, source record, shared seal where required, and accepted head
RecoverA 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 facetWhat it can establishWhat it cannot establish alone
syn verifyNamed projection, history and supported structured invariantsUniversal truth, complete receipt coverage or authenticated team authority
Source manifests and transitionsSelected raw bytes and causal ordering from the acknowledged baselineUnknown pre-baseline history or independent non-rewrite evidence
Stored receipt signaturesIntegrity of available original receipt bytesA trusted employee/device identity from an embedded key alone
Strict-local assuranceRequired selected source, receipt, trust and witness inputsCurrent organizational permission
Strict-team assuranceRequired original shared evidence with independently selected roots, lifecycle observations and witness coveragePermission 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

Synapse provides memory to direct clients or Soma, with local knowledge separate from fresh read-only external source adapters. Planned authoring-tool adapters are labeled separately.

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

ConcernEntry point
CLI and MCPsrc/cli.rs, src/cli/, src/mcp/
Bounded host knowledge contractsrc/core/knowledge/
Governed source and shared bridgesrc/core/governed_state.rs, src/core/shared_source.rs, src/core/source_candidates.rs
Durable persistencesrc/store/transaction.rs, src/store/transaction/prepare.rs, src/store/governed_state.rs
Exact diagnostics and verificationsrc/core/exact_contradictions.rs, src/core/verify/
Retrieval and modelssrc/core/search/, src/core/embeddings/
Portable public authority contractscrates/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

  • adr: Resolve the v1.3.0 spikes + scoping decisions (e6250e0)

  • changelog: Release v1.2.0 (26c456b)

🧹 Chores

[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

[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

[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

[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

[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

  • release: V0.8.0 (0b9a451)

  • hooks: Block Portuguese in commits and gh issue/pr commands (cb34328)

[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

  • release: V0.7.0 (2f6a4c0)

  • deny: Ignore RUSTSEC-2026-0173 (proc-macro-error2 unmaintained) (d1926a3)

[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

[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

[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

[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

[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

  • release: V0.2.0 (e91761e)

  • fmt: Cargo fmt sweep after federation work (54b651d)

[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

VariantVibeUse it when…
classicdancing, arms upGeneric header, default avatar, neutral context
stackthree Lil Syns connected by amber sparksThe hero. Whenever you want to show the metaphor: README top, ARCHITECTURE, “two doors, one core”
firingarms high, gold particles burstingCelebration. Install completed, release notes, milestone shipped
thinkingone arm raised toward a tiny sunReasoning, AI work, MCP docs
heartsheart-shaped eyes, floating pink heartsAffection. Footers, “built with care”, thank-you notes
sleepyeyes closed, soft ZzZIdle states. Empty workspaces, (no entities), watcher resting
confusedarms wide, orange ? floatingErrors, 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

TokenHexWhere
Indigo#5B3EE0Brand colour. Lil Syn body. Headings, links, badges.
Cream / Paper#F6F4EFLight backgrounds. Lil Syn eyes.
Mustard#FFB454Accent. Lil Syn’s question mark. Warnings, callouts.

Extended (variant-specific)

TokenHexWhere
Gold#FFD23FParticles in the firing variant. Celebration accent.
Pink#FF6B9DHearts 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

SurfaceVariant
README.md: herostack
README.md: footerhearts
docs/INSTALL.mdfiring (you’re about to ship)
docs/QUICKSTART.mdclassic
docs/MCP.mdthinking
docs/ARCHITECTURE.mdstack (same metaphor, second appearance)
docs/COMMANDS.mdclassic
docs/CONFIGURATION.mdclassic
docs/CONTRIBUTING.mdhearts (welcome the contributor)
Inside syn doctor output (future)confused for ERRORs, classic for clean
Inside syn search empty result (future)sleepy
synapse-releases READMEclassic

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:

LayerFileQuestion it answers
Audit trail.synapse/audit.ndjson (+ audit-archive/<yyyy-mm>.ndjson after rotation)Did this decision happen, and when?
Provenance frontmatterx-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 classes syn verify turns 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…"
}
  • id matches syn pending show and survives the row’s deletion.
  • decision is accepted | rejected | pruned.
  • payload_sha256 ties 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 prune is 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 pending so 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:

ObligationSynapse mechanism
Machine-readable provenance on AI-authored contentx-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)
Reproducibilitysyn 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.


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:

Twenty fixed synthetic questions: recall at ten is 95 percent at 77 entities and 100 percent at the five larger measured corpus sizes; first-result recall is 75, 90, 85, 85, 85 and 85 percent.

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)

ScenariomeasuresNresult
A: insertion (insert-01)extract the fact, resist chit-chat, land it15pass@1 80% (CI 55–93%); structural extraction 87%
B: correction (correct-01)update the existing entity (no duplicate), status corrected8pass@1 88%: agent chose update_status on the existing entity every time
C: contradiction, agent (contradict-01)non-corruption when a conflicting claim arrives10100% 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 isolationdet.detect + gate 100%; hard-negative control (same value → no flag)
D: retrieval Q&A under noise (qa)three-leg attribution + LLM judge10recall@10 80%, e2e 100%, oracle 100%

Two findings that shape the positioning:

  1. 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-probe measures that floor directly: it detects and gates a conflicting update_status, 100%, with a hard-negative control.

  2. 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 search top-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:

distractorsrecall@10e2eoracle
0 (persona)80%100%100%
+2 000100%80%¹100%
+8 000100%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:

  1. Scaling, with deterministic offline measurements of latency, size and resources.
  2. 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

ScenarioWhat it measuresSampleRecorded result
A: InsertionExtract the target fact, resist chit-chat and save it15 runs80% pass@1 (12/15; 95% CI about 55% to 93%); structural extraction 87%
B: CorrectionUpdate the existing entity without a duplicate8 runs88% pass@1 (7/8); the agent selected update_status on the intended entity in every run
C: Conflict, agentPreserve the existing fact when the fixture supplies a conflicting claim10 runs10/10 preserved the existing fact; the agent raised the conflict and requested clarification
C: Conflict, gateDetect and hold the supported conflicting update_status in isolationDeterministic fixtureAll tested conflicting writes were held; the same-value negative control did not flag
D: Retrieval Q&ARaw retrieval, reference-injected oracle and MCP agent answer10 questions80% 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 distractorsRecall@10e2e answer scoreOracle answer score
080%100%100%
2,000100%80%100%
8,000100%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)

  1. 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.
  2. 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.
  3. Saturated metrics. Several samples reach 100%. More diverse cases, harder negatives and repeated seeds are needed before drawing broader conclusions.
  4. 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.
  5. 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.
  6. Stub scaling is mechanical evidence. The large scaling curves use nonsemantic vectors. Their latency/size measurements do not establish semantic retrieval quality.
  7. 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

  1. 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.
  2. 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.
  3. 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.
  4. Agent-driven temporal correctness remains a separate coverage question. The original agent benchmark did not assert complete --as-of correctness. The bitemporal-probe checks the supported write path; later native tests and current source contracts must be inspected independently. Do not infer a completed agent-loop temporal proof.
  5. 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 verify on 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

InputDefaultWhat it does
version1.0.0Synapse 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--rebuildFlags 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 syn for the runner’s OS/arch from the public synapse-releases repo; supported runners: Linux (x64/arm64), macOS (arm64), Windows (x64).
  • Pin @v1 (or a tag) on the action and pin version: 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:

  1. Compiled-in defaults (zero config).
  2. System config: /etc/synapse/config.toml (POSIX only; skipped on Windows).
  3. Global user config: see Paths below for the OS-specific location.
  4. Workspace config: <workspace>/.synapse/config.toml.
  5. 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

LayermacOSLinuxWindows
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.tomln/a
Workspace config<workspace>/.synapse/config.tomlsamesame
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

VariableEffect
SYNAPSE_LANGLocale override (en, pt-BR). Same as --lang.
SYNAPSE_NO_FRESHNESSAny non-empty value suppresses the freshness advisory for this invocation.
SYNAPSE_MODEL_CACHEOverride the model cache directory (see Paths).
SYNAPSE_ENRICHMENT_MODEOverride [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

VariablePurpose
SYNAPSE_LANGLocale override (see precedence above).
SYNAPSE_PLAINForce raw, unstyled output (same as --plain/--raw).
SYNAPSE_NO_COLOR / NO_COLORDrop ANSI colour, keep structure (TERM=dumb too).
SYNAPSE_AGENTMark the caller as an AI agent: implies --plain and --no-input.
SYNAPSE_PROFILEPath to the global self-identity profile.toml (overrides the default location).
SYNAPSE_MODEL_CACHEOverride the model cache directory (see Paths).
SYNAPSE_ENRICHMENT_MODEOverride [enrichment] mode for one invocation (off / heuristic / heuristic+ner).
SYNAPSE_NO_FRESHNESSWhen set (any value), suppresses the freshness advisory for this invocation (same as --no-freshness). CI-friendly.
RUST_LOGStandard tracing-compatible log filter (e.g. RUST_LOG=synapse=debug).
INSTALL_DIRUsed 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:

KnobWhereDefaultEffect
freshness.enabledglobal / system / workspace configtrueMaster kill-switch. false means no check ever fires and the advisory is silent.
freshness.interval_minutessame30Minimum minutes between two checks on the same workspace. Two parallel commands seeing a stale stamp may both fire; the race is benign.
freshness.silent_clisamefalseWhen true, the CLI stderr line is suppressed even when a workspace is behind/diverged. The MCP enrichment is independent.
freshness_disabledper-workspace, in the registry0Toggle with syn ws freshness on|off <name>. Set to opt this workspace out without disabling the feature globally.
SYNAPSE_NO_FRESHNESSenv varunsetPer-invocation kill (cascades over every config).
--no-freshnessCLI flag (global)absentSame 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:

SettingPurposeTicket
editor$EDITOR override for syn openSYN-70 (v0.2)
mcp.transportPick stdio / HTTP for the MCP serverSYN-207 (backlog)
watcher.debounce_mssyn watch debounceSYN-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.toml and native file locking)
  • POSIX shell for the git hooks

Recommended (the bootstrap script lists what’s missing):

  • cargo-nextest: faster test runner
  • cargo-deny: license/advisory/ban audit
  • cargo-shear: orphan dep detector
  • typos: pre-commit typo catcher
  • git-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}
  • SCOPE is the SYN-N ticket (e.g. SYN-60) or an area (cli, mcp, index, store, core, model, i18n, config, deps, ci, hooks)
  • subject is 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

  1. 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).
  2. Business rule exists? Check docs/business/BUSINESS_RULES.md. If your change contradicts a rule, the rule changes first (PR + ADR).
  3. 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.
  4. 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.
  5. Tests. Unit tests next to the code; integration tests in tests/cli.rs; snapshot tests with insta. Property tests on parsing/encoding code.
  6. Smoke test the release binary. After clippy + unit tests pass, build --release and exercise the feature end-to-end against a temp workspace. cargo test covers logic; smoke covers integration (filesystem, CLI parsing, MCP stdio). Don’t mark the ticket done without it.
  7. Translate. Every user-visible message gets a key in i18n/en/synapse.ftl and i18n/pt-BR/synapse.ftl, in the same commit as the feature.

File-size policy (ADR-015)

Code-review caps to keep the project legible:

TierLinesWhat it means
Green< 500Healthy. No action.
Yellow500-800The reviewer of any PR touching the file flags it. A refactor proposal is welcome, not forced.
Red800+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_yaml or serde_yml: both are banned in deny.toml (ADR-004).
  • Don’t use unwrap() in library code; doctests use ? with # Ok::<_, Error>(()).
  • Don’t shell out to git from production code: use gix if 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).

Lil Syn celebrating

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:

FieldValue
TypeCNAME
Namesynapse
Targetromulofebasi.github.io
ProxyDNS 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 .md file in a workspace. Build with the lsp feature, point your editor at the syn lsp binary, 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 .md files:
    • 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.
  • 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

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-local trusted_keys allow-list. Revocation is in-band: a bundle’s revoked array 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_keys is empty, any embedded public_key is accepted. This is the single-user mode: you sign your own bundle, you import it on another machine, it Just Works.
  • If trusted_keys is non-empty, the bundle’s public_key must 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 stamps verified: false so 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:

  1. # {name} + optional _headline_
  2. Born: {birthdate}: detailed style only, when the self person has a birthdate (v0.6, SYN-205)
  3. Contacts line (email · github · linkedin · website)
  4. ## Summary: the person body
  5. ## Skills: top N from syn skills (counts in detailed only)
  6. ## Experience: attested_involvement, newest first
  7. ## Recent decisions (detailed only): last 5 notes with kind: 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 init prints 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

ToolPurposeInputs
search_knowledgeSearch 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_entityReturns the frontmatter + body of one entity as {"markdown": "<full text>"}.type, id
get_entitiesBatch 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_typeLists 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_graphInbound + outbound link neighbours of an entity.type, id, optional rel
workspace_overviewOne-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_healthAgent-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)
blameAI 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
diffPer-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
queryRead-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)
verifyProve 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)

ToolPurposeInputs
list_workspacesEvery workspace the user opted in for cross-workspace queries. Identity fields (description, topics, tags, language) come from each workspace’s SYNAPSE.md.none
federated_searchKeyword 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_fromRead one entity from a specific opt-in workspace.workspace, type, id
get_workspace_manifestRead 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.

ToolPurposeInputs
capture_factPropose creating a new entity (note, decision, person, project, …).type, title, optional body/tags/fields/reason
link_entityPropose linking two entities with a labelled relation.from_type, from_id, to_type, to_id, rel, optional reason
update_statusPropose updating an entity’s status field (task done, project paused).type, id, status, optional reason
add_workspace_topicPropose appending a topic to an opt-in workspace’s SYNAPSE.md.workspace, topic, optional reason
add_workspace_tagPropose appending a tag to an opt-in workspace’s SYNAPSE.md.workspace, tag, optional reason
propose_batchBundle 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:

  • result is always present (the uniform envelope, SYN-203); only the freshness block is conditional; it is attached when there is something to say, and omitted for up_to_date/unknown statuses (silence is the polite default).
  • behind_count = -1 in Phase 1 means “histories differ; the precise count lands with SYN-93b”.
  • advice is always a concrete git -C <root> pull command 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"
    }
  }
}
  • command is whatever syn you installed (which syn to confirm).
  • cwd is 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

CapabilityTicket
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: guided capture_fact flow 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 via recent_changes since a given instant, not a full re-read.
  • synapse:resolve-contradiction (SYN-312): when a capture_fact returns a contradicts signal, 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 → offer syn 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:

  1. Atomicity: one subject–predicate–object per proposal; never bundle unrelated claims.
  2. Observation date: x-valid-from is when the fact became true in the world, not when you learned it (transaction-time comes from git; don’t duplicate it).
  3. No echo: search first; propose deltas, not duplicates of what’s already there.
  4. Supersede, don’t duplicate: a changed fact carries x-supersedes: <old-id> (the old interval is closed on accept: invalidation, not deletion, SYN-223).
  5. No meta-extraction: facts about the domain, not about the conversation.
  6. Attribution: cite the source; provenance (x-ai-*) records who/when automatically.
  7. 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)

CapabilityTicketPhase
HTTP/SSE transport (today: stdio only)-backlog
Lil Syn dozing

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 reindex if 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 (syn subcommands 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 an ort release 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

  1. Update the binary (see docs/INSTALL.md; the synapse-check Action pins a version for CI).
  2. Open a workspace. If syn doctor or syn verify flags anything, run syn reindex. That is the entire migration.
  3. (Optional) MCP clients: syn mcp install now wires Synapse into your client config for you (see docs/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.

JobDefault modelDim / outputCached sizeLanguagesSource repo (Hugging Face)License¹
Embeddings (vector search)bge-m31024-dim~2.2 GBmultilingual (en, pt, es, fr, de, it, ja, ko, zh)BGE-M3 (BAAI), fetched by fastembed from Hugging FaceMIT
NER (entity discovery)gliner-multispans~350 MBmultilingualonnx-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:

Profileembedding_modelDimCached sizeNotes
Quality (default)bge-m31024~2.2 GBfp32, maximum recall
Speedbge-m3-int81024~560 MBint8 (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:

JobOpt-down modelDimCached sizeNotes
Embeddingsbge-small-en-v1.5-q384~65 MBEnglish-only, quantised, ~5× faster
NERgliner-base-enspans~260 MBEnglish-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 fastembed crate resolves BGE-M3 to its Hugging Face repo and downloads the ONNX file + tokenizer.
  • NER: Synapse calls the Hugging Face hub client (hf-hub) directly for onnx/model_quantized.onnx + tokenizer.json from 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):

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


The models are pulled only on an explicit action: never silently on your first syn project add:

  1. syn init onboarding: on an interactive terminal, a fresh syn init asks “Download the models now? [Y/n]”. Decline and it explains exactly what you keep and lose; you can change your mind later.
  2. 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_CACHE at a pre-populated shared path. The .synapse-ready-* sentinels travel with it.
  • Run without models. Set [enrichment] mode = "off" (or SYNAPSE_ENRICHMENT_MODE=off per command) to skip the pipeline entirely, and use syn search --no-vector for keyword + graph search with zero model load.

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 inferred mentions / references edges 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)

PathLocal backend (default)Hosted backend (opt-in)
Model downloadExplicit HTTPS GET from Hugging Face when required assets are absent or changeNo local embedding model required
Embedding a write or queryCached local-model inference does not send entity/query text to a model providerSends 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-darwin is 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 on ubuntu-24.04 runners: 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.
Lil Syn with a stack

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: syn is on your PATH. 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 init runs 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 with SYNAPSE_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 ws can 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 …”.)

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 with syn 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 setup to capture it: the next syn init seeds the self person. Or set self: true by hand on the entity that represents you. (cv/identity export will tell you to do exactly this if no self person 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 → global profile.toml) and seeded as exactly one self person per workspace on syn init (BR-14 / SYN-171/172). cv/identity export require that single self person and point you at syn setup if 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-sheetQUICKSTART.md
The full AI workflow (MCP, propose/accept, federation)TUTORIAL.md
Common workflows (CSV import, weekly review, briefings)RECIPES.md
Everything about the semantic modelsMODELS.md
Config, locales, env varsCONFIGURATION.md

If something looks off, syn doctor is always your first stop.

Lil Syn happy
Lil Syn pondering

Synapse: provable non-corruption

Provable, not estimated.

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:

InvariantClassWhat 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:

  • --deep adds 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.
  • --rebuild makes 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-of reconstructs 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 verify therefore prints its trust root: local-only, checkpoint-witnessed, or remote/published-witness. A tracked remote, or a published syn 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 --deep is 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

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 is git diff-readable. The escape hatch is [provenance] inline = false; the audit trail in .synapse/audit.ndjson keeps 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
FieldMeaning
x-ai-sourceHow the proposal entered the pending queue. Conventional prefixes: mcp:, cli:. Examples: mcp:capture_fact, cli:capture (SYN-124).
x-ai-decision-trace-idStable trace identifier. v0.3 uses pending:<row-id> because the row is monotonic per workspace and matches what syn pending show shows.
x-ai-modelBest-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-hashsha256:<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-atRFC-3339 timestamp of the acceptance.
x-ai-accepted-byIdentifier 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_entity mutates the links table.
  • add_workspace_topic / add_workspace_tag mutate SYNAPSE.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.ndjson still 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-history of 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 for SHA256SUMS.

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.

FileResponsibility and evidence
memory-foundation.svgStable content model: Markdown/Git, derived local search and CLI/MCP access. Suitable for the distribution mirror without newer integration claims.
governed-memory-loop.svgLocal proposal, exact review, accepted note and separately scoped evidence. The knowledge process contract always queues; other paths follow their configured policy.
one-core-topology.svgCurrent shared core and its interfaces. Retained evidence and operational queue state are distinct from rebuildable search data.
knowledge-in-context.svgSynapse 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 variantRecorded 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

  1. 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.
  2. 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.
  3. 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.
  4. Require independent review of the workflow permissions, exact tag/source selection, source/notes receipt and retained platform/feature restrictions.
  5. 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 mutationOriginal six casesStrengthened 15 cases
core-leasesurvivor-acceptance-suiteassertion-killed
terminal-stateassertion-killedassertion-killed
pending-review-digestinvalid-runtimeassertion-killed
payload-digestinvalid-runtimeassertion-killed
retry-auditassertion-killedassertion-killed
retry-timestampassertion-killedassertion-killed
first-audit-duplicatesurvivor-acceptance-suiteassertion-killed
audit-failure-ignoredsurvivor-acceptance-suiteassertion-killed
root-scopesurvivor-acceptance-suiteassertion-killed
guid-scopesurvivor-acceptance-suiteassertion-killed
read-fact-scopesurvivor-acceptance-suiteassertion-killed
shared-source-denialsurvivor-acceptance-suiteassertion-killed
mcp-agent-authoritysurvivor-acceptance-suiteassertion-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::reject while a native WriterLease remains 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_required and 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

DeclarationSource evidenceCandidate change
anstreamNo direct imports; clap still owns its transitive stream adapterRemove direct runtime declaration
anyhowThe actual binary reports typed synapse::Error; build script returns a boxed standard errorRemove direct runtime declaration
fluentThe loader is imported through i18n_embed::fluentRemove direct runtime declaration
i18n-embed-flNo fl! or crate imports; fluent_language_loader! expands through i18n_embedRemove unused macro declaration
gray_matterFrontmatter code uses the maintained yaml_serde boundary directlyRemove direct runtime declaration
tracing-subscriberNo direct subscriber initialization or importsRemove direct runtime declaration
rstestNo imports or attributes across production, tests, examples or build scriptRemove unused dev declaration and old roadmap reservation
build clap, clap_complete, clap_mangenbuild.rs uses only vergen_gix; actual generation imports are in src/cli/generate.rsRemove build declarations; preserve all three runtime declarations
cargo-shear ignore tableFour entries already have real source use; rstest is an obsolete reservationRemove 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.

GateActual result
Formatting, file-size and diff checksPassed
Dependency inventory and valid unused-declaration controlCandidate 0, unused declaration 1, restored fixture 0
Clippy, all targets and all features, warnings deniedPassed in 2m 38s on Rust 1.95.0
Full all-feature Rust tests, including doctests555 passed, zero failed, nine existing ignored
Native guarded rejection and direct core contracts15 passed, zero skipped
Native Memory CI contractsSeven passed, zero skipped
API docs, all features, warnings deniedPassed
Cargo deny advisories, bans, licenses and sourcesPassed
Pre-push Git environment and required-gate regressionPassed
Rust 1.89.0 offline metadataPassed; no higher declared dependency MSRV
Rust 1.89 actual compilationDeferred 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_run can 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 variantOriginal 14 methods
Omit the newly added diagram ADR from the generated chapter setSurvived
Omit canonical Quickstart from the generated chapter setSurvived
Run CLI diagram verification only for the release-pipeline chapterSurvived
Accept a missing diagram containerAssertion kill
Accept raw Mermaid code fencesAssertion kill
Accept initializer references before the runtimeAssertion kill
Identify the Mermaid runtime by filename instead of pinned bytesAssertion kill
Normalize canonical source bytes during preparationAssertion 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.

BoundaryActual result
Source releaseRun 39103431019 passed tagged inputs, four platform builds and publication.
Public mirrorRun 39104711966 passed automatically. All seven names, sizes, API SHA256 digests and notes match the source release.
Asset admissionSeven complete nonzero assets and the exact five-entry manifest were checked before any archive parsing.
SignatureReal cosign verified SHA256SUMS against the exact source workflow identity for refs/tags/v1.5.1 and issuer https://token.actions.githubusercontent.com.
Canonical documentsREADME, 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 publicationRun 39104728647 passed automatically. A fresh complete acquisition checked five exact package names and versions, SHA512 SRI and advertised provenance.
Binary comparisonAll four npm platform binaries equal their authenticated source archive binaries in size and SHA256.
Host launcherA 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.

ADRTitleStatus
001Rust 1.85 + edition 2024 as floorAccepted
002MCP via official rmcp 1.7Accepted
003rusqlite 0.39 with bundled featureAccepted
004YAML via serde_yaml_ng (serde_yaml/serde_yml banned)Accepted
005Git hooks via core.hooksPathAccepted
006MSRV policy: piso 1.85, subida via ADRSuperseded by 009
007Release pipeline: git-cliff + dist + cargo-releaseAccepted
008Embedding 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 §ConsequencesAccepted
009Subir MSRV para 1.88 (ecosystem floor real)Accepted
010Workspace federation: leve, opt-in, registry-basedAccepted
011Workspace freshness: advisory git remote drift detectionAccepted
012Provenance frontmatter (x-ai-*) on AI-authored entitiesAccepted
013.synapse/audit.ndjson format + retention policyAccepted
014Portable identity bundle (me.synapse)Accepted (prototype)
015File-size threshold + modular split policy (yellow 500-800 / red 800+)Accepted
016Portable identity v2: mandatory ed25519 + revocationAccepted (supersedes ADR-014 §v0.3 non-goals)
017Curriculum schema: attested_involvement + skills + CV formatAccepted
018LSP companion via tower-lsp-server 0.23 (active fork)Accepted
019Terminal styling via anstream + anstyle; rich-by-default, --plain opt-outAccepted
020Self-identity: capture once globally, seed the self person per workspaceAccepted
021TTY progress via indicatif, gated by is_rich() (stderr, hidden off-TTY)Accepted
022Evolve the propose/accept gate (BR-12.3) into a confidence-tiered write policyAccepted
023Read-only query tool: structured aggregation, no raw SQL (injection/exfiltration-proof)Accepted
024SYN-185: batch approval over syn pending (accept/reject --all), not a syn agent runnerAccepted
025SYN-220 syn capture --from-git: harvest people+project (not decisions) as proposals; bot-filtered, idempotentAccepted
026SYN-239 two-tier tamper-evident audit: git-versioned per-author segments → Merkle DAG; unversioned → self-chained + ed25519 signed checkpointAccepted
027SYN-223 bitemporal: store valid-time only (x-valid-* + x-supersedes); transaction-time from git; supersession = invalidationAccepted
028SYN-234 open entity types via .synapse/types.toml validation registry (closed-world, ADR-023-safe); EAV for indexed fieldsAccepted
029SYN-241/245 deterministic per-fact trust (WPM: Confidence × Freshness × Supersession × Provenance); mutual-kNN edge weightAccepted
030SYN-249 git as integrity substrate + ed25519 identity bound to SSH-format git commit signing; recommend-and-verify defaultAccepted
031SYN-223 belief-time axis (x-recorded-at/x-invalidated-at): third axis distinct from git transaction-time; deterministic supersession (invalidation-not-deletion). Amends ADR-027Accepted
032SYN-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-028Accepted
033SYN-248 per-type policy reshaped to the profile-vs-log decay axis ([policy.<kind>] decay); advisory only, never bypasses the gate. Extends ADR-029/028Accepted
034SYN-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-030Accepted
035SYN-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/030Accepted
036SYN-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-027Accepted
037SYN-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-proofAccepted
038SYN-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-008Accepted
039SYN-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-guardedAccepted
040SYN-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-benchAccepted
041SYN-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-040Accepted
042SYN-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-029Accepted
043SYN-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/012Accepted (MVP)
044SYN-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 RRFAccepted
045SYN-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/042Proposed

| 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 e rustfmt.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 uv faz com 1.93): corta packagers que ainda não pegaram a versão.

Consequences

  • + acesso a let-else em mais posições, async closures, melhorias no async/await, gen blocks (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:

  1. rmcp: SDK oficial Anthropic sob modelcontextprotocol/rust-sdk. Atualmente em 1.7.0 (lançado 2026-05-13), 78 releases, 1.x estável semver, edition 2024.
  2. mcp-protocol-sdk / mcp-sdk-rs: alternativas comunitárias.
  3. 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 flag mcp (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.rs enxuto. 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"]
  • bundled compila 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_extension necessário para futuro sqlite-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.
  • − tokio precisa de wrapper spawn_blocking nas 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.
  • rusqlite sem bundled: 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 release 0.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 check rejeita.
  • serde_yaml_ng (acatton): fork mínimo do último commit do dtolnay, mantido ativamente, ~686k downloads/mês, em transição para libyaml-safer.
  • yaml-serde (yaml org): replacement “oficial” em desenvolvimento, ainda não ecossistema-default.

Decision

  • Usar serde_yaml_ng = "0.10".
  • Banir serde_yaml e serde_yml em deny.toml.
  • Isolar todas as operações YAML em crate::store::frontmatter para que swap futuro (para yaml-serde quando maturar) seja um one-file change.

Consequences

  • + sem advisory bloqueando releases.
  • + API idêntica à do serde_yaml original: sem custo de migração.
  • − gray_matter (usado para split de frontmatter) pode trazer YAML parser próprio. Auditar via cargo tree -d periodicamente; 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 .githooks no scripts/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-commit faz cargo fmt --check (rápido) e typos (se instalado).
  • pre-push faz cargo 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 lefthook para 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 no cargo test. Surpresa para devs que rodam cargo build e 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-version no Cargo.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

  1. Manter rust-version = "1.85" na v0.x. É o piso de edition 2024, o limite inferior natural. Não baixar.

  2. 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 que rustup update instala: fricção sem benefício.

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

  4. Job msrv no 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.

  5. 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 msrv no CI é mais uma matriz para manter.

Alternatives considered

  • Manter rust-version = "1.95" (exato). Rejeitado: auto-fura o pé toda vez que rustup update instala 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:

  1. 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.
  2. Vector storage lives in the same .synapse/index.db to keep BR-1.5 (“one workspace, one source of truth”) intact.
  3. Hybrid ranking uses RRF in v0.5: no labels yet, no place for convex combination.
  4. 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-vec integrate with a rusqlite-backed connection without breaking the bundled SQLite that ADR-003 mandates?
  • Does fastembed-rs 5.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.

SlotDefault (best)Opt-downCrate
Embedding inferenceBGE-M3 multilingual quantisedBGE-small-EN-v1.5-Qfastembed 5.x
Vector storagesqlite-vec vec0 virtual table(none: single backend)sqlite-vec 0.1.x
NER (entity linking)GLiNER-multi-base-QGLiNER-base-en-Qalso via fastembed-rs (ships GLiNER as ner_model)
Hybrid fusionRRF 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

CandidateWhy 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 APIsBest 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 storageFull 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 cosineTempting (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 summaries3–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

  1. rusqlite version conflict: AMENDED 2026-06-02 during SYN-81. The spike flagged that sqlite-vec 0.1.9 declares rusqlite ^0.31 while the main crate runs on rusqlite 0.39 (ADR-003). On closer inspection of sqlite-vec’s Cargo.toml, the rusqlite dep 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’s sqlite3_auto_extension, which is ABI-stable and version-agnostic. Decision: skip path B (vendor the C source) entirely. We depend on sqlite-vec 0.1 directly as a runtime dep behind feature = "vector" and register it via rusqlite::ffi::sqlite3_auto_extension. Total cost: ~10 lines in src/index/vector.rs and one OnceLock. The original path A (downgrade) was never viable; path B was real engineering work the spike’s surface read mis-attributed.

    • unsafe exception. Wiring sqlite3_auto_extension requires a single transmute of a C function pointer. The crate’s #![forbid(unsafe_code)] is relaxed to #![deny(unsafe_code)] so that src/index/vector.rs can scope a documented #![allow(unsafe_code)]. Every other file in the crate still fails the build on unsafe. This is the only unsafe site introduced by v0.5 and the only one allowed by the project’s architectural rules going forward.
  2. 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 the EmbeddingBackend and NerBackend on 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 int8 gpahal/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 in docs/MODELS.md.

  3. sqlite-vec is flat-scan only in v0.1.9: no HNSW index yet. For Synapse’s ≤ 10k entities v0.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 in docs/CONFIGURATION.md; revisit in v0.7 if a real user exceeds it.

  4. Fastembed cache discovery: the spike’s du heuristic reported 0 MiB because fastembed-rs 5.x writes under ~/.cache/fastembed_cache/ (lowercase, no leading dot) rather than the ~/.fastembed_cache/ the spike probed. Cosmetic for the spike, but core::embeddings (SYN-83) must read FASTEMBED_CACHE_PATH first, then fall back to the platform-correct path (XDG on Linux, ~/Library/Caches/fastembed_cache on macOS, %LOCALAPPDATA% on Windows), and Synapse should surface the chosen path in syn embed --check so 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/fastembed on macOS, XDG on Linux, %LOCALAPPDATA% on Windows). The fastembed-owned FASTEMBED_CACHE_PATH env var is not read; one knob, one owner. syn embed --check will print the resolved path so users can du -sh it themselves.
  5. GLiNER via gline-rs blocked by ort RC 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 crate gline-rs 1.0.1 pins ort = "=2.0.0-rc.9" exactly while fastembed 5.15 pins ort = "=2.0.0-rc.12" exactly: Cargo cannot resolve two pinned RCs of the same crate. Three paths were considered:

    • A. Vendor / fork gline-rs ourselves 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-rs 0.2.0: Kreuzberg’s Apache-2.0 fork of gline-rs whose published crate re-pins ort to =2.0.0-rc.12 and ships an aligned kreuzberg-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 RegexNer until upstream gline-rs re-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) with kreuzberg-gliner-rs = "0.2" + fastembed = "5.15" resolved to a single ort 2.0.0-rc.12 in Cargo.lock. Both backends share the same ORT copy: no binary bloat. The earlier +50 MB claim came from misreading the fork’s Cargo.toml: the published 0.2.0 carries ort =rc.12, even though the fork’s master branch 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 against kreuzberg-gliner-rs; we revisit when upstream gline-rs re-aligns its ORT pin (issue #156’s exit criterion).

Consequences

  • +1 dep family: fastembed 5.x pulls ort (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: sqlite-vec does not get added to synapse’s Cargo.toml AMENDED 2026-06-02. Per the Findings amendment above, SYN-81 adds sqlite-vec = { version = "0.1", optional = true } as a normal optional runtime dep gated by feature = "vector". No vendoring; the upstream Cargo dep ships the C source the build links in. The dev-deps-only rusqlite ^0.31 constraint inside sqlite-vec is 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.md and docs/CONFIGURATION.md.
  • Migration story: opening a v0.4 workspace with a v0.5 binary triggers syn embed --all automatically on the first write (or runs it explicitly when the user types syn embed --all); the vector migration ships as migrations_vec/0001__entities_vec.sql per 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 to vectorlite (HNSW-based SQLite loadable extension by 1yefuwang1, ~10× faster searches than sqlite-vec’s flat scan, same load_extension shape, 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 rewriting Workspace::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 vector feature flag at build time omits the entities_vec table cleanly. v0.4 binaries opening a v0.5 workspace ignore entities_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.5 instead 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 = Relik or spaCy over 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, and syn embed --check all 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 the syn init onboarding prompt and syn embed. Without the models the tool is a fully functional keyword + graph brain; the models add meaning-search and NER discovery on top.
  • network_backends and test_backend stay 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 leaked test_backend into shipped binaries.

References

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 pelo rust-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

  1. MSRV = 1.88 em Cargo.toml (rust-version = "1.88") e em clippy.toml (msrv = "1.88").
  2. CI job msrv instala 1.88 (não 1.85).
  3. README e ADRs atualizados.
  4. 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-version precisa 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 por cargo update reabriria 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.23 exige 1.88; ficar entre 1.87 e 1.88 imediatamente reabriria a discussão na próxima minor do darling.
  • Trocar etcetera por directories/dirs: tecnicamente possível, mas etcetera é 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_knowledge across 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.md mirrors the README.md convention, obvious, discoverable, hand-editable.
  • .gitignore rules 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 cached last_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>]: open SYNAPSE.md in $EDITOR. On save, re-cache.
  • syn ws refresh [<name>]: re-read SYNAPSE.md, update cache. No args = refresh all known workspaces.
  • syn ws topic add|rm <topic>: append-only mutation of the topics list, atomic via Workspace::tx.
  • syn ws tag add|rm <tag>: same for tags.

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_workspaces over MCP becomes routing-useful: the AI picks the right workspace before even searching.
  • + Identity is versioned in git alongside content. Diffs of SYNAPSE.md are 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 init bootstrapping 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.db is recoverable by re-running syn init in each root.
  • + Preserves the v0.1 CLI/MCP contract: federation lives behind --all and 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’s index.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 store federated flag or git_remote auto-detected.
  • Federation always-on (no opt-in). Rejected. Clients with confidential data deserve a default of “do not leak”. --all defaulting 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 behind or diverged.
  • MCP: every tool response that returns data sourced from a workspace embeds a freshness field; the AI assistant decides whether to surface it to the human.

Design rules

  1. 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.
  2. 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.
  3. 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.
  4. 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).
  5. 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 local refs/heads/<branch> SHA with the refs/remotes/origin/<branch> SHA: both read directly from the filesystem, no subprocess, no network, no auth surface. Phase 2 (SYN-93b, deferred) layers a real gix-protocol ls-refs call on top to detect drift the user hasn’t fetched yet, with the 3s timeout and unreachable fallback. The two phases share the same status enum and the same registry columns.
  6. Network path is opt-out, not opt-in. Default is enabled when a workspace has a git_remote. Disable per-workspace with syn ws freshness off <name> or globally in config: [freshness] enabled = false. The SYNAPSE_NO_FRESHNESS=1 env var disables for the current process only (CI-friendly).
  7. No fetching. Synapse never runs git fetch. Recommendation output suggests it to the user; execution belongs to the user.
  8. 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_remote already 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:

valuemeaning
unknowncheck never ran, or no git_remote
up_to_datelocal HEAD matches remote HEAD of the tracked branch
behindremote has commits the local doesn’t (count in remote_behind)
aheadlocal has commits not pushed (often fine for solo workflows)
divergedboth have commits the other doesn’t
unreachablelast 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/HEAD to find the current branch, then compares refs/heads/<branch> with refs/remotes/origin/<branch>. behind_count is reported as a precise count when the local tip is reachable from the remote tip (or vice versa); when histories have diverged, status is diverged and 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_remote layers a real ls-refs handshake over Phase 1 via gix (no shelling out). It runs on a worker thread under a hard 3s timeout; any failure folds into unreachable, and the runner then retains the previously known status on the row (only bumping last_remote_check) so a transient offline moment never erases a real “behind”. behind_count is precise when the remote commits have been fetched (a bounded 1000-commit gix::rev_walk), and a count-less Behind when 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 pull that orchestrates git 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 syn exits. Mitigated by a 3s timeout and a detach-on-exit policy.
  • − Workspaces with auth-locked remotes will sit at unreachable forever 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 fetch instead of git 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 syn in 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 diff shows 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 = false lose 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.ndjson is 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 diff ergonomics, 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 stays pending. Next syn pending list shows 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 --prune cannot 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_hash field 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: pending is already SQLite; the audit trail needs to be readable by non-Synapse tools (compliance reviewers running jq, 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 prune is 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 keygen creates the user’s keypair under ~/.config/synapse/identity/.
  • syn identity export signs the canonical JSON (bundle minus the signature field) with the configured key.
  • syn identity import verifies against a pinned public key declared in [identity] trusted_keys = ["<base64>"].

v0.3 explicit non-goals

  • No mandatory crypto: signature field 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:

  • attestations schema is intentionally extensible (open record); future commands add skill, accomplishment, certification kinds.
  • Bundle root kind is namespaced (identity-bundle); a future curriculum-bundle can layer on top with shared schema.
  • The person.frontmatter field accepts arbitrary keys today, a future experience array fits there without a schema break.

Consequences

  • + Human owns their professional graph; survives any employment change.
  • + Bundle is inspectable in jq and 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_protocol field. When breaking changes accumulate, importer needs to learn migration. Acceptable for the prototype; ADR follow-up if synapse/v2 arrives.

Alternatives considered

  • Tar + zstd archive. Rejected: too small to benefit from compression, kills jq ergonomics.
  • 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:

TierLinesAction
Yellow500-800A reviewer must call out the file in the next PR that touches it. Refactor proposal welcome but not forced.
Red800+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:

  1. 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).
  2. 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.
  3. 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.
  4. Land in one commit per module where possible, smallest first. Each commit must pass cargo clippy --all-targets -- -D warnings and cargo test. No big-bang.
  5. Document. Update docs/ARCHITECTURE.md (module tree) and docs/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 concrete ServerHandler.
  • 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.

SymptomPatternReference
A subcommand dispatcher (syn at, syn audit, syn ws, …) crosses 500 lines because the verbs keep growingConvert 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 implDispatcher 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 helperThe 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-unverified flag that stamps verified: false on 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 signature field omitted.
  • The public_key field 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):

  1. If signature is absent: succeed in “unverified” mode iff --allow-unverified was passed; otherwise error. The imported person stamps verified: false.
  2. If signature is present: public_key must also be present and appear in the [identity] trusted_keys list configured in ~/.config/synapse/config.toml (or the per-workspace cascade). Verification uses ed25519_dalek::VerifyingKey::verify_strict.
  3. 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 import and reading the verified flag 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-only ed25519-dalek, base64, rand_core.
  • Importers without trusted_keys configured default to accepting any embedded public_key (single-user mode). This is documented in docs/IDENTITY.md and is what --allow-unverified is 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-unverified is 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 export on 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 at syn experience rm to replace.
  • list renders 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: active counts more than one on status: archived).
  • Decision count. Boost tags that recur in note kind: decision frontmatter (the person is making calls in this space, not just observing).
  • Owner-resolved attribution. Filter to entities where the self person appears in memberships or owner. 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:

  1. # {person.title} heading.
  2. Optional headline (frontmatter field headline).
  3. Contacts line (email, github, linkedin, website).
  4. ## Summary from the person body, trimmed.
  5. ## Skills: detailed shows name (Nx); minimal shows comma list. Top N from the bundle’s skills.
  6. ## Experience: attested_involvement newest-first by since, each entry ### {title}: {role} + date range + ref: {type}/{id}.
  7. ## Recent decisions (detailed only): last 5 notes with kind: decision, sorted updated 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_involvement becomes the canonical place to record what the human wants to claim. The bundle’s attestations array 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 skills exposes 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) and capture_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_involvement from memberships. 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: Backend struct implementing LanguageServer. Single file because tower-lsp-server’s macro, like rmcp’s, wants one impl block.
  • src/cli/lsp.rs: syn lsp subcommand. 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 six EntityType variants.
    • Inside tags: [<cursor>: distinct tags from the workspace.
  • 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 via file:// URI.
  • textDocument/publishDiagnostics: on didOpen, didChange, didSave: dangling [[slug]] references are flagged as Warning with source: "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 of tokio. 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 in mcp lsp builds.

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 for lsp-types security 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; anstream strips 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, or TERM=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 raw println!) so the gate is honoured, a discipline to enforce in review. anstream replaces direct println! 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_COLOR detection and not already in the tree; we would re-implement what anstream gives for free.
  • termcolor: solid (used by ripgrep/cargo) but a heavier API and not the clap stack; would be a new dependency.
  • colored / console: colored mutates global state and lags on NO_COLOR; console (from indicatif) 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:

  1. 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.
  2. CI/agents must never block. syn init runs 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).
  3. 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 init requires 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? }. affiliations is deliberately distinct from memberships (BR-4.4 reserves the latter for person→project). Employers materialise as entity(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/experience work 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 alongside config.toml and the registry. The seed adds work to init’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-DD shape 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 memberships for employers. Rejected: BR-4.4 reserves memberships for person→project; collect_links/cv export already assume a project key. A separate affiliations field keeps both semantics clean.
  • Run BR-13 enrichment during seed. Rejected: init runs 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 or syn 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 \r spam).
  • 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) indicatif is newly compiled (small); in the default build it is already present. indicatif 0.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_COLOR correctly is exactly what indicatif already 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:

  1. 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 accept vira 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.

  2. 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 accept humano: 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 agent e 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 + their context_snapshot), the audit-adjacent rows, embeddings_meta, and (via ATTACH) the global workspaces.db registry of every workspace on the machine, including confidential, non-federated ones (BR-12.4). A raw SELECT * FROM pending or an ATTACH lets 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_extension can 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 of type | 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 on type | status | owner | priority | tag. Every filter value is a bound SQL parameter, never interpolated.
  • metric: count only (v0.7).
  • The query only ever reads entities and tags. It cannot name pending, the audit tables, embeddings_*, or ATTACH the registry.
  • limit is 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/tags surface 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 + ATTACH of 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:

  1. 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. A syn agent command 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 one syn pending accept <id> at a time.

  2. 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 safe tier auto-applies low-risk, reversible writes (a link_entity between two existing non-self entities), stamped auto-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 --all sanctions 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_batch row (SYN-202), which is the unit when atomicity across several writes is wanted.
  • There is no syn agent command. The autonomous loop is the external MCP agent, unchanged.

Consequences

  • Scope change (recorded): SYN-185’s syn agent noun and any “Synapse-runs-the-agent” reading are dropped. The issue’s deliverable, “human approves N at once”: is met by syn pending batch 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; --all is 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 safe tier (opt-in, default manual) auto-applies the low-risk, reversible class. Batch approval covers exactly what the policy leaves queued.
  • 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 run orchestrator 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 via syn mcp.
  • A dedicated syn agent review command 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_batch already 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 a person proposal carrying a contributor membership 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 project proposal, queued first so syn 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_fact is 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 with syn 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 init in (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 --all to 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 git author 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_batch for 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_batch stays 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 prev chain 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 prev chain 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. seq is monotonic per author; the global order is the git commit topology (record timestamp is 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 verify becomes 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 blame on 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.json checkpoint ({origin, size, head_hash, timestamp, sig}) refreshed at the end of every mutating command. verify walks the chain, verifies the checkpoint signature, and asserts count/head_hash, catching edit, reorder, mid-delete, and end-truncation.
  • syn doctor warns 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.ndjson into 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, else git log author 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 until syn setup runs); whether audit verify should 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:

AxisMechanismSurface
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 indexnew --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 replay and at must 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 replay reconstructs 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 entities table keeps its fixed columns; user fields never become new columns. Every entity is one row regardless of type.
  • A query’s kind is checked against the registered slug set (a HashSet lookup) and then bound as a parameter: never string-interpolated. Unknown kind → rejected before SQL.
  • indexed fields project into a narrow whitelisted entity_fields(entity_id, field_name, value) table; group_by/filter resolve field_name against the registry’s indexed list 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 Kind enum 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 indexed fields → 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 binding field_name as a value preserves ADR-023 more strongly than columns would. EAV keeps types.toml purely 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; extends composition 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 / pii flag, 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_name never 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-extends is 90% of the value at 10% of the cost.
  • An Avro-grade automated data-migration engine. Expand-contract by convention + reindex is 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 r independent corroborations, s contradictions, prior weight W = 2, and base rate a keyed to the ADR-022 tier: Confidence = (r + a·W) / (r + s + W), with a = 0.60 auto-accepted, 0.85 human-accepted, 0.95 signed/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 decay d = 0.5: B = ln(Σⱼ tⱼ^(−d)), Freshness = σ((B − B₀)/s). The O(1)-state approximation B ≈ ln(n/(1−d)) − d·ln(L) (store only count n and first-seen age L) is permitted when per-access timestamps aren’t kept. Recency and frequency compose in one validated formula.
  • SupersessionPenalty: γ^k, γ = 0.5, where k is supersession depth (the current head fact has k = 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.0 cryptographically signed, 0.9 human-accepted unsigned, 0.8 auto-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 B doubles 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₀/s from 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.email and git commit --author=… are unauthenticated. Plain git log proves 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/identities file?); 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, gix 0.71 / gix-object 0.48): gix reads commit signatures (via CommitRef::extra_headers, which carries the gpgsig header) but does not verify them: its only verify is verify_checksum (object-hash integrity). Native SSH-signature verification would need a new dependency (ssh-key to 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 an ssh-key ADR, 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 commit that 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-at is stamped on every governed capture_fact write.
  • x-invalidated-at is 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’s x-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 as update_status for custom: SYN-234 contract finish). A custom or dangling x-supersedes pointer 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-at as 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:

  1. Never auto-merge. Detect, don’t resolve: the human decides (consistent with core::contradiction; auto-merge is the <26%-correct stage HaluMem measures).
  2. 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.
  3. 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.toml knob; the O(n) candidate scan per propose (fine at single-user scale; revisit only under a SYN-227 budget breach). Identity fields must be indexed, 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, fighting git 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.toml section 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 mutability field on [types.<kind>]. Can’t express policy for the RESERVED built-ins (e.g. mark person as 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:

  1. 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.
  2. The crypto path is not smoke-testable in this environment. Full SSHSIG verification needs the ssh-key dependency, 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 doctor reports the signature posture of the workspace’s HEAD commit: signed vs unsigned: by scanning the raw commit object for a gpgsig header (covers GPG and SSH; gix-version-independent). Advisory Info: 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-key SSHSIG verify of the signature against the non-revoked identity allowlist (ADR-016 revocation), with audit verify / doctor rejecting unsigned/unknown-signer commits. This carries the new ssh-key dependency 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-key dep + 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-key for 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 verify headline, SYN-253). Companion to ADR-026 (audit), ADR-027 (bitemporal), ADR-030 (git substrate). Grounded in docs/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 from entities;
  • pending: the proposal queue; mutable state reindex does not reproduce, with a non-content AUTOINCREMENT rowid;
  • reindex_ledger: a machine-local (mtime, size) skip cache;
  • sqlite_* internals and the feature-vector float 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_ref recomputes a bit-identical inputs_hash); a divergence is localized to a named fact, not “the .db differs”.
  • Worsens / limits: syn verify proves the integrity of the deterministic projection of the Markdown, not of model-derived enrichment (embeddings, NER edges). Covering enrichment would require pinning model@precision and is deferred.
  • Real drift surfaced (open): building the dump exposed that the create path writes mentions:* enrichment links that reindex does not regenerate, so the live index and a reindex --full index diverge on those edges (and --full silently drops them). We exclude mentions:* from the dump (same class as float vectors: heuristic/model-derived, not a pure function of reindex(markdown)). The underlying disposability gap, reindex not 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 .db bytes. 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. Pin model@precision later if they must be covered.
  • Make reindex re-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:

  1. The audit log is hash-only. AuditEntry stores payload_sha256, not the payload. The replayable payloads live in the pending table: part of the disposable index, and prunable (syn audit prune). So the audit log is not an event journal you can replay.
  2. The wall clock is baked in, not journaled. apply stamps created/updated (via Frontmatter::now_iso) and the belief axis (x-recorded-at) from now() / the accept-time stamps, and accepted_by from the environment. Two of these are fresh now() calls recorded nowhere a replay can read, so a replay cannot reproduce them byte-for-byte.
  3. 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). apply is 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::replay reuses pending::apply so the replay and accept engines cannot drift.
  • Worsens / limits: --deep does 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 pending journal 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_at parameters through every create_* and apply. Heavier and more invasive than a single thread-local clock seam, for the same determinism. Rejected in favour of pin_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,

  1. git ancestry (when versioned): HEAD is the witnessed commit or descends from it (gix rev-walk). A force-push that drops the witnessed commit fails here.
  2. segment prefix stability: for every witnessed segment, the current author segment has >= size entries and its first size entries re-hash to the witnessed prefix_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. verify surfaces this in the trust-root caveat.
  • Open: a future audit witness --publish could 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) pins ort = "=2.0.0-rc.12" exactly.
  • canonical gline-rs (the NER crate) pins ort = "=2.0.0-rc.9", which conflicts; the kreuzberg-gliner-rs fork 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 (and kreuzberg-gliner-rs) publish versions depending on ort 2.0.0 final, adopt it in a 1.0.x patch: bump the three together, run cargo build --release --all-features && cargo test --all-features && cargo deny check, re-run synapse-bench to confirm no embedding/NER regression, and ship. Until then, no action (the pin cannot move).
  • Do not bump Cargo.toml/Cargo.lock speculatively: 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.0 final 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 adopting ort 2.0.0 final). Track it with the dependency-health cadence; this ADR is the exit criterion of record.

Alternatives considered

  • Option B: wait for ort 2.0.0 final before tagging v1.0. Rejected for now: adoption is not in Synapse’s hands (it requires fastembed + 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 undo reverses 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:

  1. 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). So undo must not be “apply the logical inverse from the audit log.”
  2. 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 (not pending, rejected, or already undone).
  • The created entity must still exist and not have been re-mutated after the accept (a non-empty x-ai-history or 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 from undo_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: undo is 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-bench harness (SYN-162/163). Records the methodology contract that makes the numbers honest and the decision on how charts are produced for the README and synapse-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):

  1. 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.
  2. Distributions, not means. Report median + p95 + p99 (added here). Never a bare mean.
  3. Machine/version stamp. Every results file carries a SpecStamp (os, arch, cpus, backend, synapse-bench version, git commit): printed in the chart footnote.
  4. 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 under stub); quality charts require a real run.
  5. Publish raw + reproduce. The results.json is committed next to the SVG, with the exact command to regenerate it.
  6. 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 real run 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, revisit poloto (uv’s choice) or vl-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.
  • hyperfine for 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. hyperfine stays 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-json yields 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 called capture_fact and enqueued a real pending proposal).
  • Claude Agent SDK (Python/TS). Takes mcp_servers + allowed_tools in code, bypasses the CLI trust/tool-search machinery, and lets the harness assert init.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:

  1. The synapse-bench driver shells out to claude -p … --output-format stream-json per user turn, using the SYN-297 three-gate recipe, and parses the stream-json stream for tool_use events (trajectory) and the final result event (answer + total_cost_usd + tokens). No new dependency.
  2. The driver is isolated behind a small internal interface (a Driver trait / one module) so swapping in an Agent-SDK-backed driver later is a localized change, not a rewrite of the harness.
  3. Every run records the exact claude version 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 -p session per turn gives clean zero-context isolation for free; total_cost_usd comes straight from the result event, 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 claude version 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_SEARCH or 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@1 0.75 → 0.85, no recall@10 regression (a clean 1.0 across the whole curve); average recall@1 across the six tiers 0.783 → 0.842. Track B (cross-encoder reranker) shipped opt-in, off by default: on this corpus chunking already captured the recall@1 gain, 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-bench measured 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:

  1. One vector per whole entity. src/core/embed.rs embeds each entity as a single entities_vec row (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.

  2. 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, weaker recall@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 existing ort/fastembed/sqlite-vec stack: zero new charting/runtime deps.
  • Worsens / limits: chunking multiplies entities_vec rows (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@1 directly). 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@1 at 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 + the capture_fact / link_entity tool descriptions); the agent declares the source class via enriched_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 by pending 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:

  1. 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 description fields (capture_fact, link_entity, propose_batch) and the server instructions string. 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-252 synapse:proposal-contract prompt carries the deep version for explicit user invocation.

  2. Advisory only, topology-blind. MCP exposes no primitive to enumerate sibling servers: the initialize handshake advertises the client’s own capabilities, never what other servers are connected, and the sole cross-server hook (sampling includeContext:"allServers") is deprecated (SEP-2577/2596) and client-discretionary. So Synapse recommends, it cannot detect. The recommendation lives in the instructions string, 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).

  3. 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 verify dump. 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 enriched and is never eligible for auto_apply=safe and never applied by pending accept --all; it requires an individual, deliberate human accept, regardless of [proposals] auto_apply. (The enriched marker 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 the safe-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/self edges, 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, the context_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 like x-ai-source (ADR-012). Synapse never saw the source and attests only that the agent claimed context. Free text in existing x-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), never mentions:* enrichment edges (excluded from the dump): otherwise the enriched edge is verify-invisible. Docs must state: syn verify clean ≠ 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 verify intact.
  • 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 safe auto-apply tier should keep auto-applying links at all once enrichment is encouraged; exact clause wording; how syn doctor surfaces 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 sampling from 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):

  1. 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.
  2. 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 = mean 1/(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):

  1. RRF-k sweep. persona-bench --features real (real BGE-M3, the 6-persona haystack growing 77 → 463 entities, 20 fixed needles) swept k ∈ {20,40,60}:

    krecall@1 (final tier)recall@10 (final tier)recall@1 across tiers
    600.851.000.75/0.90/0.85/0.85/0.85/0.85
    400.851.00identical to k=60
    200.851.00identical to k=60

    recall@1 is identical for every k at every tier; recall@10 differs only at the smallest, noisiest tier (k=20: 1.00 vs 0.95, a single needle out of 20) and never on the headline recall@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_K remains a bench-only knob.

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

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

  1. Parallel legs. Run the independent legs (FTS, vector) concurrently, each on its own SQLite connection (rusqlite Connection is not Sync), 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.
  2. 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).
  3. 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 if persona-bench shows a systematic per-leg bias. Default stays equal weights (the honest prior). No global title>body field weighting.
  4. RRF-k sweep. Treat k (currently 60) as a bench-tuned global constant; sweep {20,40,60} on persona-bench and 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, and fastembed does 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.**
  1. 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.
  2. ONNX/ort substrate gate. A candidate must run on the existing ort/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.
  3. 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 and passage: on stored chunks. Ship E5 only with that seam and a persona-bench --features real win: never as a bare fastembed enum swap (which would degrade silently).
  4. 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).
  5. 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-until on 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 into list/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.

  1. Frontmatter is the source of truth (disposable-safe). On a correction that actually changes the value, update_status appends the prior value to a reserved x-field-history list in the entity’s frontmatter: {field, value, valid_from, valid_until} where valid_from is the field’s prior start (the entity’s x-valid-from or created) and valid_until is the correction’s belief-time. The live frontmatter field holds the current value over [valid_until, ∞). Nothing is deleted; history lives in the Markdown, so syn reindex rebuilds it (disposability, BR-1.4).
  2. Index projection. Migration 0009 adds entity_field_history(type, id, field, value, valid_from, valid_until). Reindex parses x-field-history into it (a pure function of the Markdown, so INV-1/verify stays honest).
  3. Read overlay. A read with --as-of D overlays the field: if a history row covers D (valid_from <= D < valid_until), its value replaces the current column; otherwise the current value stands. The entity’s own row keeps valid_from = created, valid_until = NULL: visibility is unchanged.
  4. Scope. Applies to the fields update_status governs (the indexed status, extensible to other corrected core fields). Full per-field temporal tables for arbitrary custom fields are out of scope; this closes the headline status gap 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) and verify-honest (history is a pure function of the Markdown). Composes with syn 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-of read path gains an overlay step (only for entities that have history). Custom non-status field 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 --workspace flag). 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.db at a platform-standard path) IS the per-user catalog: every syn init registers the workspace (id, name, root, git remote, cached SYNAPSE.md identity). syn ws list/show/include/exclude/refresh/forget/gc manage 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 --workspace bakes that into the client config (#287), so a client in any directory reaches the pinned brain with no cwd dependency.
  • Attributed cross-workspace reads. list_workspaces, federated_search, get_entity_from, get_workspace_manifest return workspace-attributed results, gated by the opt-in federated flag (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 --workspace install 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-bench write 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 = async with 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

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

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

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

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

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

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

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

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:

  1. Unicode NFD.
  2. Remove diacríticos (̀-ͯ).
  3. Lowercase ASCII.
  4. Substitui qualquer não-[a-z0-9]+ por -.
  5. Colapsa hífens duplos.
  6. 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:

  1. Move o arquivo (e a pasta, no caso de project).
  2. Reescreve id no frontmatter.
  3. 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:

  1. Abre transação SQLite.
  2. Escreve arquivos em *.tmp paralelo aos finais.
  3. Em sucesso: rename atômico de cada *.tmp → final, depois commit SQLite.
  4. 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.

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

  • 0 sucesso.
  • 1 erro de uso (args inválidos, entidade não encontrada, validação).
  • 2 bug interno (panic capturado, erro inesperado).
  • 64+ reservados, semântica BSD sysexits.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 --all ou 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á comando syn 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_entity entre 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 como auto-accepted (distinto de um accepted humano). 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 via SYNAPSE_NO_FRESHNESS=1, --no-freshness, syn ws freshness off <name> ou [freshness] silent_cli = true.
  • MCP: campo freshness no payload de tool results quando status ∈ {behind, diverged, unreachable}. Omitido para up_to_date e unknown.

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:

  1. Embedding: title + body + tags é vetorizado pelo modelo configurado em [enrichment] embedding_model (default BGE-M3 multilingual; opt-down BGE-small-EN-v1.5-Q). O vetor entra na tabela entities_vec da mesma index.db.
  2. 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 (default GLiNER-multi-base-Q; opt-down GLiNER-base-en-Q). Tipos NER são hard-coded por Synapse: person, organization, product, feature_flag, api_endpoint, framework, tool, decision_topic.
  3. Resolução: cada candidato bate contra o índice live. Match conhecido vira link tipado (rel = "mentions" ou rel = "concept:<lemma>"); span NER sem entidade correspondente vira pending de kind create_entity_from_ner para revisão humana (BR-12.3 continua valendo: MCP/CLI não cria entidade sem aceite).
  4. 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).
  5. 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, ou mode = "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 8601 YYYY-MM-DD (validada na captura).
  • headline: opcional, profissão; alimenta syn 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-N que 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:


Convenções

ColunaValores
IDSYN-N, sequencial, imutável
Layercli, core, store, index, mcp, i18n, config, ci, release, docs
SizeXS (≤ 1h), S (≤ ½ dia), M (1 dia), L (≥ 2 dias)
Statustodo, wip, done, blocked, cut
DependsOnIDs que precisam estar done antes

Status (2026-05-26):

  • v0.1 Foundation: 53/53 done. Tag v0.1.0 em 2026-05-24.
  • v0.2 Daily polish: 24/24 done. Tag v0.2.0 em 2026-05-25.
  • v0.3 Auditable AI Second Brain: 18/18 done. Tag v0.3.0 em 2026-05-25.
  • v0.4 Refinement & extensibility: 18/18 done (12 core + 6 stretch); milestone GitHub #6 fechado em 2026-05-26. Tag v0.4.0 pendente: Cargo.toml ainda em 0.3.0, release roda via cargo release. Brief em docs/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: wip 2026-06-02. Brief em docs/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.0 em 2026-06-21 (SYN-262..270 + pre-release /improve plans 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-check Action) done + SYN-290 (bench charts, ADR-040) done + SYN-291 (docs currency) done + SYN-292 (syn undo Phase 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:

FaseFaixa em usoSlots livres
v0.1 FoundationSYN-1..SYN-53SYN-54..SYN-59
v0.2 Daily polishSYN-60..SYN-79 + SYN-90..SYN-93 (manifest + freshness) + SYN-99 (uninstall)-
v0.3 Auditable AI Second BrainSYN-110..SYN-129 (ingestors SYN-119/120 vagos, deferidos)SYN-131..SYN-149
v0.4 Refinement & extensibilitySYN-130..SYN-149 + SYN-150 (refactor); cortes: SYN-134, SYN-144, SYN-145SYN-151..SYN-199
v0.5 Semantic layer & second-brainSYN-80..SYN-89 + SYN-93b + SYN-151..SYN-154 (arch-debt do v0.4) + SYN-160..SYN-168 (expansion)-
v0.6 Personal onboarding & autonomySYN-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 foundationSYN-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 autonomySYN-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 & adoptionSHIPPED (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-qualityRe-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 engineSYN-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 stableSYN-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
BacklogSYN-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 search encadeá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

IDLayerSizeTitleDependsOnSource
SYN-1core/cliMAppDeps + Dispatch trait, eliminando god-match em commands/mod.rs-03-critique §4.1 §4.2
SYN-2storeMWorkspace::find (sobe diretórios procurando .synapse/)SYN-102-arch §commands
SYN-3storeLWorkspace::tx atômico: temp + rename + transação SQLiteSYN-203-critique §4.5
SYN-4coreScore::CreateProject use case (sem I/O direto)SYN-303-critique §4.4
SYN-5coreScore::Slugify + validação de slug-01-schema §convenções

Schema, índice e migrations

IDLayerSizeTitleDependsOnSource
SYN-6indexMDDL inicial: entities, tags, links, FTS5: com PK composta (type,id)SYN-303-critique §3.5
SYN-7indexXSToda conexão executa PRAGMA foreign_keys = ONSYN-603-critique §3.1
SYN-8indexSFK explícitas em links (CASCADE) e tasks.projectSYN-703-critique §3.1
SYN-9indexMMigration runner (versionado, idempotente)SYN-602-arch §index
SYN-10indexSColunas explícitas para campos quentes: status, owner, priority, dueSYN-603-critique §3.6
SYN-11indexMsyn reindex: varredura idempotenteSYN-9 SYN-1001-schema §reindex

Frontmatter & parsing

IDLayerSizeTitleDependsOnSource
SYN-12storeMParser/serialize de frontmatter (gray_matter + serde_yaml_ng)-03-critique §5 / ADR-004
SYN-13storeSRound-trip preserva ordem dos campos no YAML (UX de diff)SYN-1202-arch §store
SYN-14storeSValidação de schema por type (campos obrigatórios)SYN-1201-schema

Tipos de entidade: CLI

IDLayerSizeTitleDependsOnSource
SYN-15cli/coreMsyn project add|list|show|rm|renameSYN-4 SYN-11README
SYN-16cli/coreMsyn person add|list|show|rm|renameSYN-15README
SYN-17cli/coreMsyn person link (cria membership N:N project ↔ person)SYN-1601-schema §person
SYN-18cli/coreMmemberships em vez de roles (renomeação semântica)SYN-1703-critique §3.3
SYN-19cli/coreMsyn entity add|list|show|rmSYN-1501-schema §entity
SYN-20cli/coreMsyn api add|list|show|rmSYN-1501-schema §api
SYN-21cli/coreMsyn task add|list|show|rm|done (id sequencial)SYN-1501-schema §task
SYN-22cli/coreMNovo tipo note (decision | runbook | meeting | snippet | reference)SYN-1503-critique §3.2
SYN-23cli/coreSsyn tag list|rename|merge (operações globais sobre tags)SYN-1102-arch §commands

Busca

IDLayerSizeTitleDependsOnSource
SYN-24core/indexMcore::Search: FTS5 + filtros --type --tag --ownerSYN-1101-schema §FTS5
SYN-25cliSsyn search <query> com formatação text/jsonSYN-24README
SYN-26core/indexMcore::Graph::neighbors: base p/ “quem depende de X”SYN-1101-schema §links
SYN-27cliSsyn graph project <slug> (quem aponta, quem é apontado)SYN-2602-arch §commands

Saúde & meta

IDLayerSizeTitleDependsOnSource
SYN-28cli/indexMsyn doctor: referências quebradas, slugs órfãos, frontmatter inválidoSYN-1101-schema §reindex
SYN-29cliXSsyn info: resumo do workspace, contagem por typeSYN-1102-arch §commands
SYN-30configMCascade config (system → global → workspace → runtime) com etcetera-02-arch §config
SYN-31cliSsyn setup (interativo: locale, editor padrão)SYN-3002-arch §setup
SYN-32cliSsyn init [PATH] cria .synapse/ e estrutura baseSYN-3002-arch §init

Erros & saída

IDLayerSizeTitleDependsOnSource
SYN-33coreSError::fluent_key() separado de Display (i18n sem perder log)-03-critique §4.3
SYN-34cliSoutput::render text/json centralizado-02-arch §output
SYN-35cliXSCódigos de saída padronizados (1 user error, 2 bug, 64+ reservados)SYN-3402-arch §main

Internacionalização

IDLayerSizeTitleDependsOnSource
SYN-36i18nMLoader i18n-embed + Fluent, com detecção via sys-locale-02-arch §i18n
SYN-37i18nS--lang (CLI) e SYNAPSE_LANG (env)SYN-3602-arch §i18n
SYN-38i18nMCobertura completa pt-BR e en para todas as mensagens v0.1SYN-3602-arch §i18n

MCP (leitura)

IDLayerSizeTitleDependsOnSource
SYN-39mcpMServidor rmcp 1.7 sobre stdio, feature-gatedSYN-24ADR-002
SYN-40mcpSTool search_knowledge (mesma função que syn search)SYN-3903-critique §4.4
SYN-41mcpSTool get_entity (project / person / api / task / note)SYN-3902-arch §mcp
SYN-42mcpSTool list_by_type com filtrosSYN-3902-arch §mcp
SYN-43mcpSTool project_graph (vizinhança)SYN-2602-arch §mcp
SYN-44mcpSLazy init de tokio só quando syn mcp é chamadoSYN-3903-critique §4.6

CI / qualidade / build

IDLayerSizeTitleDependsOnSource
SYN-45ciSWorkflow ci.yml (fmt, clippy, nextest matrix, deny, shear, MSRV)-feito em SYN-0: validar
SYN-46ciSWorkflow commit-lint.yml valida PRs-feito em SYN-0: validar
SYN-47ciXSJob cargo doc -D warnings no CISYN-4503-critique §7
SYN-48buildXSbuild.rs com vergen-gix (versão + git hash + rustc)-feito em SYN-0: validar
SYN-49coreXS#![warn(missing_docs)] + intra-doc links no lib.rs-feito em SYN-0: validar

Testes

IDLayerSizeTitleDependsOnSource
SYN-50testsSFixtures de workspace mínimo + assert_cmd round-tripSYN-3202-arch §testes
SYN-51testsSSnapshots insta para saída text/jsonSYN-2502-arch §testes
SYN-52testsMProperty tests proptest para slugSYN-502-arch §testes
SYN-53testsMTeste de transação Workspace::tx (falha em índice → rollback do FS)SYN-303-critique §4.5

Fase v0.2: Daily polish

Meta: experiência sem fricção no dia-a-dia. Completions, watcher, export, MCP que escreve.

IDLayerSizeTitleDependsOnSource
SYN-60mcpMTool capture_fact: IA propõe gravação; CLI confirmaSYN-3903-critique §2.2
SYN-61mcpMTool link_entity (cria vínculo project↔person, etc.) com confirmaçãoSYN-6003-critique §2.2
SYN-62mcpSTool update_status (task done, project pausado)SYN-60README v0.2
SYN-63cliSCompletions bash/zsh/fish/elvish/powershell via clap_completeSYN-45README v0.2
SYN-64cliSMan page man syn via clap_mangenSYN-63README v0.2
SYN-65cliMWatcher mode (syn watch) com notify 8: reindex em mudançaSYN-11README v0.2
SYN-66cliMsyn export --format=html (browser-friendly do workspace)SYN-24README v0.2
SYN-67cliSsyn export --format=json (single-file)SYN-24README v0.2
SYN-68core/indexMIndexar last_modified_commit (sem implementar --at ainda)SYN-1103-critique §3.4
SYN-69cliSsyn config get|set|unset no nível workspaceSYN-3002-arch §commands
SYN-70cliSsyn open <slug> abre o arquivo no $EDITORSYN-32UX comum
SYN-71cliSsyn rm confirma com prompt; --force para scriptSYN-1502-arch §commands
SYN-72docsMTutorial passo-a-passo (docs/tutorial.md)v0.2 fimUX adoção
SYN-73docsMReceitas (docs/recipes.md): casos de uso comunsv0.2 fimUX 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.

IDLayerSizeTitleDependsOnSource
SYN-74cliSsyn uninstall (binary + --purge config + --everything registry; nunca toca workspaces)-UX gap apontado pelo usuário
SYN-75store/indexMWorkspace registry: workspaces.db global + auto-register no syn init + git remote auto-detect + refresh last_seen-ADR-010 §F1
SYN-76cliSsyn ws list|show|forget|gc|include|excludeSYN-75ADR-010 §F1
SYN-77core/cliM--all em search/info/graph: federated read sobre workspaces opt-inSYN-75 SYN-76 SYN-79ADR-010 §F2
SYN-78mcpMMCP tools: list_workspaces, federated_search, get_entity_from(ws, type, id)SYN-77ADR-010 §F3
SYN-79store/cliSPrivacy gate: federated=0 default, opt-in explícito, BR-12.4SYN-75ADR-010 (privacy)
SYN-90store/coreMSYNAPSE.md identity card spec + I/O + bootstrap no syn init (BR-1.5)SYN-75ADR-010 §F7
SYN-91cliSsyn ws edit|refresh|topic add|tag add (curadoria do manifest)SYN-90ADR-010 §F7
SYN-92mcpMMCP tools get_workspace_manifest, add_workspace_topic, add_workspace_tag (fila de aprovação)SYN-90 SYN-60ADR-010 §F7
SYN-93store/cli/mcpLWorkspace freshness: git ls-remote async + advisory em CLI/MCP (BR-12.5)SYN-75ADR-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.

IDLayerSizeTitleDependsOnSource
SYN-110core/indexMaudit.ndjson schema + atomic appender + rotação (90d default)-research §5 #1
SYN-111coreSFrontmatter stamps x-ai-* em propostas aceitasSYN-110bet 2
SYN-112cliSsyn audit show|list|pruneSYN-110research §5 #1
SYN-113core/cliMsyn audit replay <id> contra context snapshotSYN-114bet 5
SYN-114core/indexSColuna pending.context_snapshot + payloadSYN-110bet 5
SYN-115mcpMMCP Resources (workspace/manifest, entity/{id}, audit/recent)-research §2.2
SYN-116mcpMMCP Prompts (capture-decision, daily-review, weekly-brief)-research §2.2
SYN-117mcpS.synapse/server-card.json + capability disclosure schema-bet 4
SYN-118mcpMMCP Tasks extension em reindex (feature-gated)-research §2.2
SYN-121core/cliSsyn timeline <slug> (git log filtrado por path da entity)SYN-68research §5 #3
SYN-122core/cliMsyn at <ref> -- <subquery> (subset: search + show)SYN-68 SYN-121research §5 #3
SYN-123core/cliMsyn audit stale --over <window> (ranking cross-workspace)SYN-77bet 3
SYN-124cliSsyn capture (stdin → pending capture_fact)SYN-60research §5 #6
SYN-125core/cliLme.synapse bundle: syn identity export|import|inspect (prototype, ed25519 opcional)SYN-90bet 1
SYN-126docsMADR-012: provenance frontmatter conventionSYN-111governança
SYN-127docsMADR-013: audit.ndjson format + retention policySYN-110governança
SYN-128docsMADR-014: portable identity bundle (me.synapse)SYN-125governança
SYN-129docsMdocs/AUDITABILITY.md + docs/PROVENANCE.md (user-facing)SYN-111 SYN-112UX 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)

IDLayerSizeTitleDependsOnSource
SYN-150mcpLRefatorar mcp/server.rs em submódulos focados (tools/{read,federation,write}, resources, prompts, freshness, helpers); surface MCP byte-stable. LANDS FIRST.-ADR-015
SYN-130cli/lspLsyn-lsp companion: completions de slug/type/tag em qualquer editor LSP (Cursor/VSCode/Helix/Neovim/Zed)SYN-115brief bet: adoption
SYN-131core/cliLsyn identity keygen + ed25519 obrigatório + revocation listSYN-125ADR-014 deferred
SYN-132core/cliMsyn experience add + skills auto-derive from tag frequencySYN-125ADR-014 §Future direction
SYN-133core/cliMsyn cv export Markdown deterministic from bundle + workspaceSYN-132brief disruptive: curriculum surface
SYN-135core/cliMsyn audit verify: re-hash pending payloads vs audit.payload_sha256SYN-110ADR-013 implicit gap
SYN-136core/cli/mcpLsyn audit replay --execute: real model rerun via MCP clientSYN-113brief bet 5 closure
SYN-138mcpMMCP Tasks real (reindex + federated_search progress)SYN-118SYN-118 placeholder closure
SYN-141core/cliMsyn at <ref> -- graph|list|info subcommandsSYN-122SYN-122 surface completion
SYN-147docsMADR-016: portable identity v2 (crypto + revocation)SYN-131governance
SYN-148docsMADR-017: curriculum schemaSYN-132 SYN-133governance
SYN-149docsMdocs/IDENTITY.md user-facing (bundle + curriculum)SYN-131 SYN-133UX adoption

Stretch (lands if window allows; otherwise rolls to v0.5)

IDLayerSizeTitleDependsOnSource
SYN-137coreMx-ai-history chain on entity re-mutationsSYN-111ADR-012 §Open question
SYN-139mcpMMCP resources pagination via cursorSYN-115SYN-115 documented limit
SYN-140mcp/coreMTag-level federation redaction (SearchHit carries tags)SYN-117brief bet 4 closure
SYN-142core/cliLFull as-of FTS index reconstruction (sandbox)SYN-122SYN-122 documented limit
SYN-143core/cliMsyn ws clone <git-url> lifecycle starter-brief §2.5 partial
SYN-146core/cliMsyn doctor --lint-provenanceSYN-111brief §2.1 audit

Cortes assumidos (registrados no brief, §9)

  • syn agent autonomous 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)

IDLayerSizeTitleDependsOnSource
SYN-151mcpMrefactor 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-152cliSsplit cli/at.rs (620 linhas, yellow tier) em cli/at/{mod,search,show,list,info,graph}.rs-ADR-015
SYN-153cliSsplit cli/audit.rs (611 linhas, yellow tier) em cli/audit/{listing,verify,replay,stale,prune}.rs-ADR-015
SYN-154ciXSCI lint rejeitando src/** acima de 800 linhas (red), warning em 600 (yellow): ADR-015 enforcement-ADR-015 §Playbook

Core: camada semântica

IDLayerSizeTitleDependsOnSource
SYN-89docsMADR-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-80index/buildMFeature vector em Cargo.toml + migration separada migrations_vec/*.sql-03-critique §3.7
SYN-81indexLIntegração sqlite-vec (bundled+vtab+load_extension + tabela entities_vec + gotchas documentadas em ADR-008)SYN-80brief §2.2
SYN-82coreMtrait EmbeddingBackend + trait NerBackend (contracts para o pipeline paralelo)SYN-8003-critique §3.7
SYN-84core/testsSStubs determinísticos para EmbeddingBackend (SipHash fake vector) e NerBackend (regex fake NER)SYN-82testes
SYN-83coreMEmbeddingBackend impls: fastembed-rs (BGE-M3 default, BGE-small-EN-v1.5-Q opt-down per workspace); Ollama configurávelSYN-82 SYN-89brief §2.1
SYN-164coreMNerBackend impl: GLiNER-multi-base-Q default + GLiNER-base-en-Q opt-down per [enrichment] ner_model; tipos NER hard-codedSYN-82 SYN-89brief §2.4
SYN-85cliMsyn embed --all / syn embed <slug> / syn embed --check / syn embed --reset-cacheSYN-81 SYN-83brief §9.2
SYN-160coreLPipeline 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 codificadaSYN-83 SYN-164brief §1.2 + §9.2
SYN-161coreMHeuristic entity-linker + NER-span resolver (slug/data/@mention/URL/tag + spans GLiNER → links tipados ou pending proposals link_entity / create_entity_from_ner)SYN-160brief §2.4 + §9.2
SYN-86core/indexLRanking híbrido RRF (k=60) sobre FTS + vector + graph-neighbourhoodSYN-81 SYN-85 SYN-161brief §2.3
SYN-87cliSsyn search default = híbrido; --semantic = vector-only; --no-vector = FTS+graph onlySYN-86brief §2.3
SYN-162core/testsLBench corpus generator (1k/5k/10k entities) + query bank 50q × 3 tiers, variantes pt-BR + en-USSYN-83 SYN-164brief §6.1
SYN-163core/tests/docsMBench runner + results report; CI artefact; thresholds gate (T2 P@5 ≥ 0.85, MRR ≥ 0.55, write median ≤ 500 ms, search median ≤ 150 ms)SYN-162brief §6.3

Adoption inside v0.5

IDLayerSizeTitleDependsOnSource
SYN-167docs/cliSREADME §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-168core/cli/docsMSemantic 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-outSYN-160 SYN-164 SYN-85owner decision 2026-06-04

Stretch (lands if window allows; otherwise v0.6)

IDLayerSizeTitleDependsOnSource
SYN-165coreMEmbeddingBackend impls para OpenAI + Voyage (network-based, opt-in explícito)SYN-83brief §9.3
SYN-166cli/coreSsyn search --grade para capturar pares query → relevant; foundation para convex combination tuning v0.6SYN-86brief §9.3
SYN-88mcpSMCP tool semantic_search: somente se algum cliente precisar do modo vector-only diretoSYN-86brief §9.3

Track paralelo (independente da semantic stack)

IDLayerSizeTitleDependsOnSource
SYN-93bstore/mcpLWorkspace freshness Phase 2: ls-refs real via gix-protocol (3s timeout, unreachable fallback, walk de commits para behind_count preciso)SYN-93ADR-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 self no person (a “pessoa-você” canônica, src/core/identity) existe e é lida por cv export / identity export, mas não há como criá-la, nem CLI, nem onboarding. E o syn setup é opcional, então o idioma/identidade nunca são definidos de forma garantida.

Onboarding obrigatório & identidade própria

IDLayerSizeTitleDependsOnSource
SYN-170cli/coreMSetup 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-32owner 2026-06-05
SYN-171cli/coreLOnboarding 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 workspaceSYN-170 SYN-131owner 2026-06-05
SYN-172coreMSemear 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-32owner 2026-06-05
SYN-173core/docsMSchema 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-172owner 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

IDLayerSizeTitleDependsOnSource
SYN-174build/ciMRestaurar 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 custoSYN-83 SYN-164v0.5 release finding 2026-06-05

Integração com agentes (skill / llms.txt)

IDLayerSizeTitleDependsOnSource
SYN-175cliMsyn 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.

IDLayerSizeTitleDependsOnSource
SYN-176mcpSBound list_by_type: limit + cursor. É a única read ilimitada (search/federated já capam); viola o princípio de response-constraint da AnthropicSYN-150brief §2.1
SYN-177mcpMstructuredContent + 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-150brief §2.2
SYN-178mcpSResourceLink para synapse://entity/{type}/{id} nos hits de search_knowledge e no get_entity: entidades já são resources (SYN-115); leitura lazy/cacheávelSYN-115brief §2.2
SYN-179mcp/cliMworkspace_overview: tool/resource de digest (contagens por tipo + entidades-chave/recentes) numa chamada; mata o custo de exploração do agenteSYN-150brief §5
SYN-180mcp/coreMrecent_changes {since}: delta sobre audit/timeline; agente recorrente puxa só o que mudou desde a última execução (cursor server-issued)SYN-110brief §6
SYN-181mcpMget_entities batch + projeção fields: buscar N entidades / só os campos necessários numa chamadaSYN-150brief §3
SYN-182mcp/coreMpropose_batch: múltiplas propostas → um bundle pending → um accept. Uma linha pending por write lógico; agrupa só a UX do acceptSYN-151brief §5
SYN-183mcp/coreLquery 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 commitarSYN-86brief §2.3 + §6
SYN-184cli/distMSynapse como plugin Claude: .claude-plugin/plugin.json empacotando skill + config MCP + hooks (ex.: SessionStart → syn pending list). Um install fia o loop inteiroSYN-175brief §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).

IDLayerSizeTitleDependsOnSource
SYN-186cli/outputMCamada 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 planoSYN-34brief §2.1
SYN-187cliSModo não-interativo: --no-input/--yes + --quiet; todo prompt (onboarding do init, setup) respeita. Non-TTY implica no-input. Agente/CI nunca travaSYN-170brief §2.3
SYN-188cli/outputM--format json 100% + erros JSON machine-readable (stderr) + contrato de output congelado (aditivo-only p/ agentes). Hoje json cobre ~metade e print_error é sempre textoSYN-34brief §2.3
SYN-189core/cliSExit codes mais granulares p/ agentes (not-found / already-exists / conflict): hoje sysexits category-level (ambos UserError=1)-brief §1
SYN-190cli/outputMTabelas + layout estruturado p/ list/search/info/doctor no TTY; colunas planas no pipeSYN-186brief §2.2
SYN-191cliSSpinner/progress no TTY p/ embed –all / reindex / watch; stream plano off-TTY (clig.dev: sem animação off-TTY)SYN-186brief §2.1
SYN-192cli/outputMLoading & 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 premiumSYN-186 SYN-191brief §2.4
SYN-193cli/outputMRich 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 glyphsSYN-186 SYN-190brief §2.4
SYN-195cli/outputSContrato 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/193SYN-186brief §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.

IDLayerSizeTitleDependsOn
SYN-196outputMoutput/brand.rs: mascote (constelação) full/compact/glyph + wordmark; fundação do clusterSYN-186
SYN-197cli/outputMRevelação animada no onboarding (setup/init, 1x, pulável, no-op off-TTY): o “cérebro online”SYN-196 SYN-170
SYN-198cli/outputSBanner branded em syn (help) e --version: estático, rich-onlySYN-196
SYN-199core/outputSEstados vazios/saudáveis charmosos: mascote em list/search vazios e doctor limpo (estático)SYN-196
SYN-200cli/outputMExperiência de download branded (~920 MB): constelação “carregando” sobre a barra; reusa SYN-191SYN-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).

IDLayerSizeTitleDependsOn
SYN-202coreLpropose_batch atômico: uma tx compartilhada nas apply paths (hoje best-effort sequencial; falha parcial trava o bundle). Follow-up da SYN-182SYN-182
SYN-216coreMAccept de proposal único crash-safe / idempotente: apply+audit+mark numa tx (hoje 3 separadas; crash entre apply e mark re-aplica)SYN-202
SYN-217index/coreLReindex incremental por dirty-tracking (mtime+hash): substitui o DELETE *; rescan full; pré-requisito de sync e do swap HNSW-
SYN-218coreLProposals por tier de confiança + auto-apply de baixo risco reversível (ADR-022). Mata o rubber-stamping e destrava autonomia seguraSYN-202, ADR-022
SYN-219mcpMMigrar auditoria de MCP Sampling → Tasks extension (SEP-2577 depreca Sampling/Roots/Logging; janela ~12 meses)-
SYN-183mcp/coreLquery read-only compute-out-of-context. Spike de segurança ANTES (SQL-injection/exfiltração): bloqueante-
SYN-214docs/coreSReconciliar 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).

IDLayerSizeTitleDependsOn
SYN-185cli/coreLsyn 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-220cli/coreLCaptura ambiente do git: autores→pessoas, repos→projetos, merges→decisões candidatas, como proposals. A maior alavanca de adoçãoSYN-218
SYN-221cli/coreMPromover syn capture "<texto>" como caminho padrão: frictionless + estruturação diferida pelo agente-
SYN-222cli/coreSsyn digest: “o que aprendi esta semana” (cria o prompt que falta; força retrieval)-
SYN-229docs/brandMReposicionar 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 unsafe injustificadas.
  • 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 schemas memory.json/basic-memory nã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)

IDLayerSizeTitleDependsOn
SYN-234store/indexMTipos 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)

IDLayerSizeTitleDependsOn
SYN-223store/indexLFatos 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-027SYN-234
SYN-224core/indexMDecay / 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-239core/storeMAuditoria à 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-249core/cli/mcpMAuditoria 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-030SYN-239
SYN-240core/cli/mcpMsyn 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-241core/cli/mcpMTrust/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-242coreMDetecçã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 apertarSYN-223
SYN-247core/cli/mcpMAlinhamento 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. StretchSYN-239
SYN-248coreMPolí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. StretchSYN-234

3. Grafo vivo (adoção / showcase): staged

Visão do dono: syn expõ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.

IDLayerSizeTitleDependsOn
SYN-243core/indexMMotor 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-244cli/mcpMsyn 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 commitSYN-243
SYN-245index/coreMArestas 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-246cli/mcp/releaseLUI 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.0SYN-244

4. Performance: medir antes de otimizar

IDLayerSizeTitleDependsOn
SYN-227ci/indexSBudgets 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áriaSYN-217
SYN-225indexMSwap 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 dispararSYN-227

5. Ergonomia

IDLayerSizeTitleDependsOn
SYN-226mcpSTool ú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 com at <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_changes per-field deltas (reusa core::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).

IDLayerSizeTitleDependsOn
SYN-256cli/mcpSBoth-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-257core/mcpMSYN-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-242coreMContradiçã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 confirmSYN-223
SYN-250core/searchSTrust 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ávelSYN-241
SYN-252docs/mcpSContrato 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).

#IDSizeTitle (reformulado)DependsOn
1SYN-260SHygiene + 2 bugs reais: corrigir PATH do pre-commit fmt (gate inerte); clippy::unwrap_used deny (escalonado); split de queries.rs (ADR-015)-
2SYN-259SReformulado: 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óprioSYN-223, SYN-257
3SYN-251MReformulado: 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
4SYN-254SReformulado: 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. ADRSYN-234
5SYN-245MReformulado: 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
6SYN-255SAnchor-distance reranker reusando graph_rank: bench de recall como pré-condição; se <2-3% MRR, vira no-op de baixa prioridadeSYN-243, SYN-250
7SYN-248SReformulado: só o eixo profile-vs-log (mutabilidade/decay) por tipo; só endurece o gate; sem matriz write/pii/link. ADRSYN-234
8SYN-249CMVerificaçã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 + ADRSYN-239
9SYN-258MReformulado: 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-253SYN-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.

IDLayerSizeTitleDependsOn
SYN-230core/releaseLsyn sync: vault sync E2E-criptografado opcional (hospedado + self-hostable). Supersedes #91/#105SYN-217
SYN-231coreLTime / workspace compartilhado com edições de IA governadas (por assento): o wedge B2BSYN-230
SYN-232docs/releaseSDeclarar fronteira open-core (governança/compliance edition) cedo e transparente-
SYN-233coreMCamada de identidade/CV verificável (atestações assinadas checáveis)-
SYN-235mcpMMCP 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.

IDLayerSizeTitlePor que aqui
SYN-194coreMAsync / 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-174build/ciMRestaurar 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.

IDLayerSizeTitleDependsOnSource
SYN-100releaseMdist init + workflow gerado; primeiro release real v0.1.0SYN-45ADR-007
SYN-101releaseSInstaller shell (curl … | sh) hospedado em PagesSYN-100ADR-007
SYN-102releaseMHomebrew tap romulofebasi/synapseSYN-100README v1.0
SYN-103releaseS(opcional) Pacote npm para npx synapseSYN-100ADR-007
SYN-104docsMPolítica de semver para o schema de frontmatterSYN-15..22governança
SYN-105coreMMigration de schema entre versões (forward + reversa básica)SYN-10401-schema
SYN-106mcpMCongelar a tool surface MCP (toda mudança via ! em commit)SYN-39..43governança
SYN-107docsMMIGRATING.md 0.x → 1.0SYN-104onboarding
SYN-108ciSRelease assinada (cosign ou sigstore)SYN-100segurança
SYN-109docsMSite simples em GitHub Pages (estilo Fumadocs / mdBook)SYN-100adoção
SYN-215brand/docsSResolver o mascote duplo (Lil Syn × constelação de grafo) antes do freeze de marca; alinhar README/BRAND.md e output/brand.rsSYN-196audit 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)TitlePor que não está numa fase
SYN-331 (#90)Encryption-at-rest do .synapse/index.dbYAGNI no MVP; usuário pode usar disco encriptado
SYN-332 (#93)TUI (ratatui) interativaterceira 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 MCPaguardar caso de uso
SYN-337 (#106)Federation remota (syn remote add <git-url>)aguardar demanda
SYN-236 (#225)Legacy numbering hygieneCompleted allocation SYN-331..337; historical commits retained
SYN-238Higiene: 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ênciameta-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

  1. Cada ticket aqui vira issue no GitHub com label phase:v0.1 (etc.) e layer:<camada>. Issues novas devem nascer com formato SYN-<ID> no título.
  2. Mudar prioridade ou cortar item: editar este arquivo numa PR; sem discussão paralela em Slack/Notion.
  3. Tickets de mais de 1 dia (L) devem ganhar uma sub-issue com plano de implementação antes do wip.

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

  1. O markdown é a fonte da verdade. O SQLite é um índice descartável, reconstruível com syn reindex. Nada vive só no banco.
  2. 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.
  3. IDs estáveis e legíveis. O nome do arquivo (slug) é o ID. maria-silva.md é referenciado como maria-silva. Renomear é uma operação explícita (syn person rename) que atualiza os vínculos.
  4. 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:

CampoTipoObrigatórioDescrição
typeenumsimproject, person, entity, api, task
idstring (slug)simIgual ao nome do arquivo sem extensão
titlestringsimNome legível (“Maria Silva”, “API Pagamentos”)
tagslistanãoTags livres, normalizadas em minúsculas
createddata ISO 8601simPreenchido na criação
updateddata ISO 8601simAtualizado 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);

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:

  1. Apaga e recria as tabelas (ou faz upsert por updated).
  2. Varre projects/, people/, entities/, apis/, tasks/.
  3. Para cada .md: faz parse do frontmatter (YAML → JSON), extrai corpo.
  4. Popula entities, tags, links, entities_fts.
  5. Reporta contagens e inconsistências (ex: owner apontando para pessoa inexistente): esse relatório é o que o syn doctor consome.

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

CamadaComo testar
store::frontmatterunit: round-trip YAML ↔ struct
store::slugunit: casos de normalização e validação
index::queryintegração: banco em memória (:memory:) com fixtures
commandsintegração: workspace temporário em tempfile::tempdir()
CLI completotests/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:

  1. config/ + store/workspace.rs + cli.rs mínimo → syn setup, syn init
  2. model/ + store/frontmatter.rs → entidades existem como arquivos
  3. index/ (schema + reindex + query FTS) → syn search funciona
  4. commands/{project,person,api} add/list/show/rm → base utilizável
  5. i18n.rs + .ftl → tudo traduzido (estruture as chaves desde o passo 1)
  6. mcp/ → Claude Code consulta a base
  7. Resto: task, tag, doctor, export, completions

Synapse: Crítica Profissional e Decisões de Arquitetura

Documento de revisão dos arquivos 01-data-schema.md e 02-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

CamadaVereditoAção
IdeiaSó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 dadosBem 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 RustBorda → 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.
StackVersõ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çãoNã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/commitsNã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 = ON em toda conexão (rusqlite não habilita por default).
  • Em links, adicionar FOREIGN KEY (from_id) REFERENCES entities(id) ON DELETE CASCADE e o mesmo para to_id.
  • Para o tipo task, adicionar FOREIGN KEY (project) REFERENCES entities(id): mesmo o frontmatter sendo 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):

CrateBinárioBuildUXVeredito
clap v4~574 KiB~3 senv vars, completions (5 shells), mangen, derive madurousar
bpaf 0.9~256 KiB~830 mscombinator + derive, completion dinâmica, sem mangen, ecossistema menorconsiderar em v2 se compile time doer
argh~38 KiB~4 slast release 2023, 63 issues abertasevitar
lexopt~37 KiB~330 mshand-written, zero ergonomia prontaevitar
pico-args~24 KiB~300 mshelp fraco, frágilevitar

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):

FerramentaVersãoFunção
cargo-nextest0.9test runner em CI (paralelismo melhor que cargo test)
cargo-deny0.19licenses + advisories + bans + sources
cargo-shear1.12detector de deps órfãs (parser-based)
cargo-dist0.31release multi-plataforma
committedlatestlinter Conventional Commits (Rust, sem Node)
typoslatestcatador 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/:

HookFaz
commit-msgValida formato Conventional via regex shell ou committed --commit-file $1
pre-commitcargo fmt --check (rápido) + typos (catador de typos)
pre-pushcargo 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:

  1. lib.rs e 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.
  2. 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.
  3. Intra-doc links sempre: [`Workspace`], [`Db::open`] em vez de prosa.
  4. Exemplos usam ?, nunca unwrap().
  5. CI: cargo doc --no-deps --all-features -D warnings.
  6. lib.rs declara:
    #![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)

ItemPor que não agora
sqlite-vec / embeddingsCrate 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áquinasGit já resolve. Documento de sync entra quando alguém pedir colaboração.
PluginsYAGNI. 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/ embeddingsAtrelado 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

AreaShipped baselineNext contract
ContentTyped Markdown, disposable SQLiteSame source principle, revisioned external references
ReadsCLI JSON/MCP, hybrid/federated searchBudget/purpose/ACL/version-aware evidence envelope
WritesProposals, policies, audit/supersessionDurable sealed transition coverage and receipts
Logical checksStructured gate; INV-13 intervals/cyclesSeparate probabilistic prose diagnostics
INV-5Declared predecessor closureDetect unjournaled current-source drift from a sealed baseline
ReceiptsExport-on-demand; INV-8 SkipPersisted signed receipts with profile-aware verification
SigningReport signature may be absent; commit postureGenuine signature/trust/authority checks
CollaborationPer-user catalog/federation; future team workOptional authority/roster/witness adapters
SourcesAgent-composed context; import/exportOptional 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

ProfileRequired evidenceMissing prerequisite
Basic/compatibleAvailable storage/history checks with explicit exclusions/rangeReport partial/Skip; preserve v1 behavior
Strict-localSealed suffix, payload coverage, persisted receipts, pinned local signer trustFail with a stable reason
Strict-teamStrict-local plus subject/key binding, approval grant/policy, revocation freshness and selected independent witnessFail 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.

MechanismWhat it can establishWhat it does not establish
Digest/hash chainBytes/links relative to retained expected headAuthor identity, correctness, unseen truncation without witness
SignaturePossession of an explicitly trusted keyOrganizational membership by itself
OIDC binding + scoped rosterSubject/key relationship and evidenced approval authorityTruth of a claim or permanent future permission
Independent checkpoint/Merkle proofInclusion/extension relative to trusted retained headsEvery source being correct or a dishonest log never equivocating
Optional blockchain anchorAn externally witnessed checkpoint commitment under chain assumptionsIdentity 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

  1. Clarify product/current guarantee boundaries and versioned embedding contract (existing SYN-321).
  2. Implement sealed transitions and crash-safe migration; then persisted receipts/strict profiles.
  3. In parallel as an independent spike, evaluate typed decisions; ship only measured local/hosted options.
  4. Add device/subject binding and real signature/authority enforcement before strict-team acceptance.
  5. 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

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:

  1. preserving ADR-023’s invariant: a request string never reaches a SQL identifier position; field_name is bound as a value, never interpolated;
  2. keeping the index disposable and rebuildable (syn reindex drops and recreates it from the Markdown source of truth: see AGENTS.md);
  3. adding a new type or field staying purely additive (types.toml edit only, no schema migration, no ALTER 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

CriterionEAV (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 0006 adds entity_fields + its index. Additive; no change to existing rows.
  • reindex / upsert_entity populate entity_fields for a kind’s registered indexed fields (resolved from Registry), bounding field_name to the whitelist before insert.
  • query/list/search gain a registry-checked dynamic-field filter that resolves field_name against registry.indexed[kind] (a closed in-memory set) and binds it: the group_column allow-list in core::mcp::query extends 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

  1. SYN-330, hook/test fixes and numbering hygiene.
  2. SOMA-34 Rust account/authority foundation, then measured usage and adapter guarantees.
  3. Synapse transition integrity/identity and Soma capability/context/event contracts.
  4. Versioned knowledge integration, deterministic diagnostics and two-operator pilot.
  5. Retrieval/semantic/source/visual extensions and demand-gated infrastructure.
PriorityIssueNext evidence or dependency
P0SYN-330: resolve advisory and yanked dependency CI blockersCompatible security fixes; maintained YAML; all gates
P0SYN-328: isolate Git hook environment from temporary-repository testsImplemented in PR #315; awaiting security gate
P0SYN-329: keep audit test fixtures within the retention windowImplemented in PR #315; awaiting security gate
P0SYN-322: define the composable knowledge framework and trust architectureArchitecture in PR #315
P0SYN-236: renumber stale backlog issues off the colliding SYN-200..210 schemeResolve task ID collisions without changing historical commits
P1SYN-261: cryptographic SSHSIG commit verification (multi-user)Actual SSHSIG validity before treating presence as assurance
P1SYN-324: seal governed state transitions and detect unjournaled Markdown driftDurable governed transitions and source drift
P1SYN-325: persist signed receipts and add fail-closed verification profilesPersist receipts and explicit fail-closed policy; depends #309
P1SYN-318: Memory CI with explicit verification policy and coverageMemory CI coverage; depends #310
P1SYN-326: bind OIDC subjects to offline signing keys and scoped approval authorityIdentity binding and scoped authority; consume Soma account contract
P1SYN-231: shared knowledge authority and governed team acceptanceTeam authority, after transitions/identity
P1SYN-319: scoped contradiction diagnostics (CLI + MCP)Expose existing deterministic contradiction diagnostics; ML remains separate
P1SYN-321: embed Synapse in Soma through a versioned knowledge contractVersioned CLI knowledge contract; coordinated with SOMA-39
P2SYN-323: evaluate local NLI and Jev-style typed knowledge decisionsLocal NLI/typed decision benchmark with abstention and EN/PT fixtures
P2SYN-335: optional source adapters for documentation revision monitoringSource revision adapters, begin with Git and API schemas
P2SYN-327: track source revisions and scoped shared API knowledge impactShared API impact; depends #96 and #308
P2SYN-320: standard verification attestation envelopes (in-toto; C2PA optional)Attestation envelopes after persisted receipt contract
P2SYN-109: documentation site on GitHub PagesPublish docs with explicit delivered/proposed scope
P2SYN-302: syn graph serve - real-time interactive graph in the browserLocal graph UI; includes legacy #234 requirements
P2SYN-246: localhost graph UI server (Stage 2)Legacy stage covered by #290; consolidate without dropping security criteria
P2SYN-295: re-validate the flat-vector ceiling before HNSW (SYN-225)Measure actual flat vector limits before HNSW
P2SYN-307: on-device retrieval-model options - E5 asymmetric seam, EmbeddingGemma, MRL (ADR-045)Retrieval model seam/evidence; do not silently install gated models
P2SYN-313: spike - wire the discarded BGE-M3 sparse output as a retrieval legSparse output experiment; ship only on measured retrieval benefit
P2SYN-336: simultaneous multi-workspace from a single MCP serverMultiple workspace routing; authorization and mutation boundaries first
P2SYN-337: remote federation - syn remote add <git-url>Remote Git federation with explicit trust/fetch boundaries
P3SYN-331: encryption at rest for .synapse/index.dbIndex encryption is separate from source confidentiality; define threat model
P3SYN-334: support TOML frontmatter as alternative to YAMLTOML alternative after stable shared knowledge contract
P3SYN-235: MCP over HTTP/SSE - hosted gateway building blockHTTP MCP transport when a consumer justifies it; do not duplicate Soma authority
P3SYN-317: resident always-on MCP server (OS auto-start + catalog discovery)Resident daemon only after measured process-overhead trigger
P3SYN-174: restore Intel macOS (x86_64-apple-darwin) - ort-tract or ONNX from-sourceIntel substrate proof without adding an untested second runtime
P3SYN-233: verifiable identity / CV layer (signed, checkable attestations)Identity/CV evidence outside the initial operational product slice
P3SYN-232: declare explicit open-core boundary (governance/compliance edition)Commercial/open-core boundary requires product evidence and license decision
P3SYN-230: ‘syn sync’ - optional end-to-end-encrypted vault sync (hosted + self-hostable)Hosted encrypted sync after authority and durable protocol
P3SYN-286: (gate) HNSW vector index when the corpus crosses the budgetHNSW explicitly gated by #283 measurement and real demand
P3SYN-225: swap sqlite-vec flat scan for HNSW (vectorlite) - execute ADR-008 pre-staged planOlder HNSW issue superseded by explicit gate #274
P3SYN-333: desktop GUI (tauri)Desktop GUI demand evaluated after Soma integration
P3SYN-332: TUI (ratatui) interactive modeTUI demand evaluated after CLI/API workflow

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-DD is 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:

  1. Metadata header (date, author, scope, status, inputs).
  2. Executive summary: 3-4 sentences, no longer.
  3. Context and premisses: what triggered the brief; what stays off-limits (e.g. “v0.5 semantic layer is out of scope here”).
  4. Market scan: sectioned by question, each section ending in a one-line “what this means for Synapse”.
  5. Cross-check: what the market scan validates / challenges / adds versus the team’s prior hypotheses.
  6. Positioning: where this product fits versus named competitors; the moat to defend.
  7. Recommendation: the theme proposed, the priorities, what is explicitly not in scope.
  8. Disruptive bets: non-obvious ideas worth tracking even if they fall out of the current cycle.
  9. Ticket sketch (provisional IDs, blocks, sizes. Sketch) not issues until milestone is opened.
  10. Risk register: severities + mitigations.
  11. Open decisions: questions still requiring human direction.
  12. References: every external link cited above.

Index

DateBriefStatus
2026-05-25v0.3: Auditable AI second brain (multi-context devs)shipped v0.3.0; milestone #5 closed 18/18
2026-05-25v0.4: Refinement & extensibilityshipped v0.4.0 2026-05-27; milestone #6 closed 18/18 (12 core + 6 stretch)
2026-06-02v0.4 closure & roadmap reviewaccepted; input to README §Status refresh (b37a418) and to the v0.5 brief
2026-06-02v0.5: Semantic layer & second-brain enrichmentaccepted 2026-06-02; 23 issues filed in milestone #7; SYN-89 spike + ADR-008 shipped 640116e
2026-06-02Vector storage options vs sqlite-vecaccepted; confirms sqlite-vec for v0.5; vectorlite registered as v0.7+ upgrade path; LanceDB/SurrealDB rejected as v0.5 migrations
2026-06-14Agent-memory field comparisondraft; validates supersession/provenance/trust/flat-vector bets; recommends trust-in-ranking, contradiction→gate, batched embedding, proposal contract, invariant eval harness
2026-07-14Market positioning and Febasi substratehistorical 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

FieldValue
Date finalised2026-05-25
AuthorRômulo Febasi <romulo@febasi.com.br>
ScopeRoadmap brainstorm for Synapse v0.3.0: the bridge between v0.2 (daily polish, shipped 2026-05-25) and v0.5 (semantic layer, reserved).
Statusaccepted; milestone opened 2026-05-25
Decisions logOwner 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.
InputsWeb 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 scopeSemantic 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_commit indexed 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-lsp LSP 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

  1. v0.2 closed 24/24 tickets; the team needs a defensible next theme.
  2. The MCP spec 2026-07-28 release candidate moved several capabilities from optional to expected, opening a window to differentiate before SaaS catches up.
  3. 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-known discovery (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/manifest and resource://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/recent listing 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 connected and User information read events; 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:

  1. GitHub PRs + issues. Highest ROI by miles. PR titles, descriptions, review threads, merged commits = ground truth of “what shipped.”
  2. Linear issues > Jira. Devs want Linear ingestion; they have to live with Jira.
  3. Calendar (Google / Outlook). Mostly for “who did I meet about Project X this week”: surprisingly high ROI for consultants billing hours.
  4. Slack threads (selective). Whole-Slack ingestion is noise; thread-by-URL capture is the pattern that won (CodeRabbit, Granola).
  5. Browser history / page capture. Arc is dying; Dia / Comet / Atlas rising. Lightweight “save this tab to Synapse with a note” beats full history sync.
  6. 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.

  • 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:

HypothesisStatusReason
Block A: Time & audit (syn timeline, syn blame, syn at <ref>, syn pending log)ValidatedEU AI Act + Notion/Glean audit logs + reproducibility requirement (§2.3).
Block B: MCP resources + promptsValidated and expandedMCP 2026-07-28 RC made resources expected (§2.2). Add server-card.json + Tasks.
Block C: IngestorsValidated, sequencedGitHub > Linear > Calendar > Slack URL > meeting transcripts (§2.5). v0.3 ships GitHub + Linear; rest defers.
Block D: BriefingsReframed“Cross-workspace stale & uncommitted” advisory has higher pull than per-project briefings (§2.1).
Block E: TUI / fuzzy pickCutDoes not emerge from named pains. Moves to backlog.
Block F: Share entity (granular HTML)CutNot a stated need. syn export is enough.
Block G: Workspace lifecycle (fork/clone/archive)Partialsyn workspace clone <git-url> aligns with portable identity (§2.4); the rest defers.
D1: syn capture (stdin → pending)KeptLow-cost, disruptive UX. No market signal against it.
D2: syn agent (autonomous + batch approval)Deferred to v0.4Validated by batch-approval trend (§2.3) but architectural risk.
D3: Cross-workspace entity referencesKept (read-only)Aligns with capability-disclosure trend (§2.2). Write-side stays in v0.4.
D4: syn-lspCutWorth doing, wrong cycle. v0.4 or v1.0.
D5: Schema declarable per-workspaceCutHeavy 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.json with capability disclosure for federation.
  • MCP Tasks extension on reindex and federated_search so long-running work reports progress.

4. Positioning

AxisSynapse v0.3 targetNotionObsidianLinearGleanCursor
Local-first✓✗✓✗✗partial
AI-native (MCP)✓proprietaryplugin✗✓✓
Typed entities✓DB blockstags✓✓✗
Multi-workspace with privacy gate✓✗vaultsSaaS workspaces✗✗
Propose / sanction for AI writes✓✗✗✗✗✗
Source = git markdown✓✗✓✗✗partial
Auditable AI writes (EU AI Act-grade)v0.3 targetpartial (audit log)✗✗✓ (SaaS, org-bounded)✗
Portable knowledge identity across employersv0.3 prototype✗partial (vault copy)✗✗ (org-bounded by design)✗
Cross-workspace capability disclosurev0.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

#BlockJustificationCostDefensible?
1Audit 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 outputMediumStrong: git-diff-able audit, SaaS audit is vendor-locked
2MCP-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_searchMCP 2026-07-28 RC made these expected; window to ship before SaaS servers catch upMediumStrong: MCP-native is the moat
3Ingest GitHub + LinearCut 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.--
3Time queries: syn timeline <slug> (git log filtered by entity path); syn at <ref> -- <query> using indexed last_modified_commitReplayability + temporal audit; primitive already shipped in SYN-68LowMedium: only works because the entity is git-tracked
4Cross-workspace stale advisory: syn audit stale --over <window> ranks open commitments across federation by likely cost of droppingCross-context pain validated (§2.1); no SaaS sees across workspaces it does not ownMediumStrong: federation + capability disclosure required
5syn capture: stdin → pending capture_fact in one keystrokeRemoves the “I need to remember the syntax” barrier to capture; orthogonal to everything else; cheapLowWeak: UX win, easy to copy
6Portable identity prototype (me.synapse): syn identity export|import|inspect; manifest declares the self person; optional ed25519 signingSee §6 Bet 1 expanded; owner greenlit prototype in v0.3 on 2026-05-25MediumStrong: see Bet 1 explanation

Explicit non-goals for v0.3

  • Embeddings / semantic search (v0.5).
  • syn agent autonomous loop (v0.4).
  • TUI / syn ui (backlog).
  • syn-lsp LSP 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 person entity (email, social handles, bio, skills as tags).
  • Attestations of past involvement: “I worked on api-pagamentos at cliente-alfa in 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.

DifferentiatorSynapseGleanLinkedIn
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 workspace person that the manifest marks as self: true.
  • syn identity import FILE: create / update the person in 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 = false flag 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_snapshot column (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 IDBlockTitleSize
SYN-110Auditaudit.ndjson schema + atomic appender + rotationM
SYN-111AuditFrontmatter stamps x-ai-* on accepted proposalsS
SYN-112Auditsyn audit show / list / prune CLIS
SYN-113Auditsyn audit replay <id> (depends on SYN-114)M
SYN-114Auditpending.context_snapshot column + payloadS
SYN-115MCPResources surface (workspace/manifest, entity/{id}, audit/recent)M
SYN-116MCPPrompts (capture-decision, daily-review, weekly-brief)M
SYN-117MCP.synapse/server-card.json + capability disclosure schemaS
SYN-118MCPTasks extension on reindex (feature-gated)M
SYN-119Ingestorssyn ingest github <repo>: cut from v0.3 on 2026-05-25, deferred to post-v0.5-
SYN-120Ingestorssyn ingest linear <team>: cut from v0.3 on 2026-05-25, deferred to post-v0.5-
SYN-121Timesyn timeline <slug> (git log filtered by entity path)S
SYN-122Timesyn at <ref> -- <query> (subset: search + show)M
SYN-123Stalesyn audit stale cross-workspace rankingM
SYN-124Capturesyn capture (stdin → pending capture_fact)S
SYN-125Identityme.synapse bundle export / consume (prototype)L
SYN-126ADRsADR-012: provenance frontmatter conventionM
SYN-127ADRsADR-013: audit.ndjson format + retentionM
SYN-128ADRsADR-014: portable identity bundle (me.synapse)M
SYN-129Docsdocs/AUDITABILITY.md + docs/PROVENANCE.mdM

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

RiskSeverityMitigation
Scope creeps past the 2026-08-02 EU AI Act windowHIGHCut blocks #5 (stale advisory) and #6 (capture) if behind schedule. Blocks #1–#4 are the core auditable and must ship.
audit.ndjson grows without boundMEDIUMDefault 90-day rotation per workspace; explicit syn audit prune
MCP spec 2026-07-28 is still RC at landing timeMEDIUMImplement only parts marked stable in the RC: resources + prompts + server-card. Tasks ships behind a feature flag.
Decision-trace frontmatter pollutes user-edited markdownMEDIUMAll stamps under x-ai-* namespace (extension-safe). [provenance] inline = false config moves stamps to audit.ndjson only.
GitHub / Linear ingestors become cloud-API dependenciesHIGHPrefer user-side CLI delegation (gh, linear); never store API tokens; if HTTP is unavoidable, document the trust boundary clearly.
EU AI Act enforcement is delayedLOWAudit + provenance are still differentiating regardless of the deadline; the theme survives the schedule slip.
me.synapse portable identity bundle leaks signed claimsHIGHSign only at export time; bundle is opt-in per command; clear-text inspection always possible (syn identity inspect <bundle>).
Ingestor adapters become a maintenance treadmillMEDIUMEach 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:

#QuestionDecision
1Deadline lock-in for the core auditableTarget 2026-08-02 (EU AI Act Article 50) for blocks #1, #2, #3 + ADRs
2Portable identity scope in v0.3Prototype ships in v0.3 (export / import / inspect + optional ed25519). Mandatory signing + revocation push to v0.4.
3Ingestor strategyCut from v0.3. External-API dependency conflicts with local-first invariant. Defer to post-v0.5 when the surface stabilises.
4Version numberv0.3.0 (no skip).
5Milestone openingOpen 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

Auditability and regulation

Knowledge graphs and context

Integrations and adoption

Pain points and ergonomics

v0.4: Refinement & extensibility: finish what we started

FieldValue
Date finalised2026-05-25
AuthorRômulo Febasi <romulo@febasi.com.br>
ScopeRoadmap 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.
Statusshipped 2026-05-26: 18/18 issues (12 core + 6 stretch); milestone #6 closed; tag v0.4.0 pending
Decisions logOwner 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.
InputsThe 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 scopeSemantic 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_tasks feature flag; audit trail (.synapse/audit.ndjson) with 90-day rotation; x-ai-* provenance stamps with [provenance] inline = false escape; portable identity bundle prototype (syn identity export|import|inspect); capability disclosure (.synapse/server-card.json); time-travel syn timeline + syn at <ref> subset; cross-workspace syn audit stale; syn capture.
  • 99 tests green, clippy clean, cargo fmt clean. CI: release + mirror pipelines work; pre-push hook now enforces cargo fmt --check per 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.rs split).

What is explicitly off-limits in this brief

  • Semantic search / embeddings: reserved for v0.5 behind --features vector (SYN-80..89).
  • syn agent autonomous 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

  1. 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.
  2. 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.
  3. 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.
  4. 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

ItemWhere it was deferredSeverityv0.4 ticket
syn audit replay does not re-run the model: only shows payload + snapshot + visual diffcli/audit.rs doc, AUDITABILITY.md §2, SYN-113 commitHigh: Bet 5 of v0.3 brief promised re-run; we shipped halfSYN-136
--no-network flag accepted as no-opcli/audit.rsCosmetic until --execute ships(subsumed by SYN-136)
x-ai-history for entities mutated twice: second accept overwrites stamps; full history lives only in audit trailADR-012 §Open questionMedium: breaks repro for sequential mutationsSYN-137
prev_hash Merkle chain in auditADR-013 §ConsequencesLow: slot reserved, no external demandstays deferred to v0.5+
syn audit verify: re-hash pending payloads against audit.payload_sha256Not implemented in v0.3High: without verify, the hash is decorative; CI cannot gateSYN-135
Stale ranking heuristic hand-tuned in risk_for()cli/audit.rsMedium: 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 rowNot implementedMedium: detects manual tampering or driftSYN-146

2.2 MCP debt

ItemWhere it was deferredSeverityv0.4 ticket
MCP Tasks placeholder: mcp_tasks feature flag declares intent, does NOT implement progress reportingSYN-118 commit + get_info().instructionsHigh: advertised but unfinished; users discover the gapSYN-138
Resources pagination via cursorlist_resources in mcp/server.rsMedium: workspaces with thousands of entities sufferSYN-139
Resource templates (URI templates per spec)Not implementedLow: current per-entity listing worksstays deferred
Tag-level federation redaction: only type-level shipped; SearchHit does not carry tagsSYN-117 commitHigh: Bet 4 promised tags + types; only types shippedSYN-140

2.3 Time-travel debt

ItemWhere it was deferredSeverityv0.4 ticket
syn at <ref> only search + show; graph, list, info subcommands missingSYN-122 commit explicitMedium: closes the surfaceSYN-141
Full as-of FTS rebuild: today filters current hits, NOT rebuilds indexSYN-122 commit + AUDITABILITY.mdHigh: current shape is misleading for compliance reviewSYN-142
syn timeline --all (project-wide, not per-entity)Not implementedLow: workflow optionstays deferred

2.4 Identity bundle debt

ItemWhere it was deferredSeverityv0.4 ticket
Mandatory ed25519 signing + syn identity keygenADR-014 §v0.3 explicit non-goalsHigh: without crypto the bundle verifies nothingSYN-131
Attestation revocation surfaceADR-014Medium: required before real-world useSYN-131 (combined)
Curriculum-like surface (syn experience add, auto-derived skills, exportable CV)ADR-014 §Future direction; owner-flaggedHigh: owner asked explicitly, schema already kept openSYN-132, SYN-133, SYN-134
Cross-bundle mergeADR-014Lowstays deferred to v0.5+
HTTP discoveryADR-014Backlogstays deferred
Sigstore-style transparency logADR-014Low: no consumer askingstays deferred to v0.5+

2.5 Federation debt

ItemStatusSeverityv0.4 ticket
Cross-workspace write entity references (D3 v0.3: “kept read-only”)Read shipped via SYN-117; write deferredMediumstays deferred: needs more user-side demand
syn ws clone <git-url> lifecycleNot implementedMedium: adoption / portability storySYN-143
syn ws fork, syn ws archiveNot implementedLowstays deferred

2.6 Extensibility (new in v0.4)

ItemStatusSource
syn-lsp editor companion (slug / type / tag completions)Already filedSYN-130 (v0.4 milestone since 2026-05-25)
mcp/server.rs refactor into submodules (ADR-015)Already filedSYN-150 (v0.4 milestone since 2026-05-25)

2.7 Documentation debt

ItemStatusv0.4 ticket
docs/IDENTITY.md user-facing (covers bundle + curriculum)Not writtenSYN-149
ADR-016: portable identity v2 (mandatory crypto + revocation)Not writtenSYN-147
ADR-017: curriculum schema (experience kind, skill derivation, CV format)Not writtenSYN-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 deferralStatus in v0.4Reason
Block A: Time & auditCompletion: SYN-135 / 136 / 137 / 141 / 142 / 146 close the gapsAll flagged High severity in §2
Block B: MCP resources + prompts + tasksCompletion: SYN-138 (Tasks real), SYN-139 (pagination), SYN-140 (tag redaction)All flagged High/Medium in §2
Block C: IngestorsStays deferred (post-v0.5)Owner decision 2026-05-25: external-API conflict
Block D: BriefingsStays as v0.3 shipped (syn audit stale is the briefing surface)No new signal
Bet 1: Portable identityCompletion + expansion: SYN-131 (crypto + revoke), SYN-132 (experience), SYN-133 (CV)Owner-flagged future direction in ADR-014
Bet 2: Decision-trace frontmatterAlready shipped; refinement via SYN-137 (x-ai-history)Open question from ADR-012
Bet 3: Stale advisoryAlready shipped; no changeHeuristic refinement out of scope
Bet 4: Capability disclosureShipped type-level; completion via SYN-140 (tag-level)Was promised in bet, half-shipped
Bet 5: syn audit replayShipped read-only; completion via SYN-136 (real --execute)Was promised in bet, half-shipped
D2: syn agentCut 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-130Owner greenlit during v0.3

New thread (not in v0.3 brief)

  • mcp/server.rs refactor (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:

Axisv0.3 statev0.4 target
Auditable AI writes (EU AI Act-grade)partial: replay is visual diff, no model re-run; no verifyfull: syn audit verify gates CI; syn audit replay --execute round-trips through MCP client
Portable knowledge identityprototype: no crypto, no revoke, no CVproduction-grade: ed25519 mandatory, revocation list, syn cv export
Cross-workspace capability disclosuretype-leveltag + type level (SYN-140)
MCP-native surface (resources / prompts / tasks)resources + prompts; Tasks placeholderTasks real (SYN-138); pagination (SYN-139)
Editor integrationnone (syn open only)LSP companion (SYN-130): completions in Cursor, Helix, Neovim, VSCode, Zed
Internal architecturemcp/server.rs red-tier per ADR-015split per use case, all files green-tier (SYN-150)
Curriculum surfacenonefirst-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

#BlockJustificationCostDefensible?
1Refactor mcp/server.rs (SYN-150)ADR-015 mandates it; blocks every other MCP ticket by merge-conflict risk. Lands FIRST.LArchitecture: internal, no marketing
2Identity 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.LVery strong: no SaaS can ship CV from data the human owns
3Audit 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+LStrong: git-diff-able + CI-gateable
4syn-lsp companion (SYN-130)Cheapest possible IDE integration; one binary, every LSP editor. Adoption play.LMedium: anyone can build, but Synapse owns the workspace semantics
5MCP 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+MStrong: MCP-native is the moat
6Time-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+LMedium: only meaningful in git-tracked workspaces
7Workspace lifecycle starter: syn ws clone <git-url> (SYN-143)Aligns with portable-identity story (human carries graph across machines). Forks / archives stay deferred.MMedium

Explicit non-goals for v0.4

Same as §1.2 plus reinforced:

  • syn agent autonomous 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 IDBlockTitleSize
SYN-150Refactormcp/server.rs split into submodules (ADR-015)L
SYN-130Extensibilitysyn-lsp companion (slug/type/tag completions in any LSP editor)L
SYN-131Identitysyn identity keygen + ed25519 mandatory signing + revocation listL
SYN-132Identitysyn experience add + skills auto-derive from tag frequencyM
SYN-133Identitysyn cv export Markdown deterministic from bundle + workspaceM
SYN-135Auditsyn audit verify: re-hash pending vs audit.payload_sha256, CI-friendlyM
SYN-136Auditsyn audit replay --execute: real model re-run via MCP clientL
SYN-138MCPMCP Tasks real (reindex + federated_search progress reporting)M
SYN-141Timesyn at <ref> -- graph|list|info subcommandsM
SYN-147ADRsADR-016 portable identity v2 (mandatory crypto + revocation)M
SYN-148ADRsADR-017 curriculum schema (experience kind, skill derivation, CV format)M
SYN-149Docsdocs/IDENTITY.md user-facing + curriculum sectionM

Stretch (lands if window allows; otherwise rolls to v0.5)

Sketch IDBlockTitleSize
SYN-137Auditx-ai-history chain on entity re-mutationsM
SYN-139MCPMCP resources pagination via cursorM
SYN-140MCPTag-level federation redaction (SearchHit carries tags)M
SYN-142TimeFull as-of FTS index reconstruction (sandbox index)L
SYN-143Lifecyclesyn ws clone <git-url>M
SYN-146Auditsyn doctor --lint-provenanceM

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

RiskSeverityMitigation
Scope creeps past 8 weeksHighStretch 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 surfaceHighAcceptance 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 callsHighImplement 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 pathHighv0.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.4MediumMarkdown-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 timeMediumKeep 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)LowUse gix::clone not subprocess; already in dep tree for SYN-68.
syn experience add overlaps with capture_fact semanticallyMediumsyn 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:

#QuestionDecision
1syn agent autonomous loop in v0.4?Cut. Lands in v0.6 on top of semantic layer.
2Curriculum surface (syn cv export): Markdown only or include PDF?Markdown only in v0.4. PDF is a v0.5 candidate.
3Identity 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.
4Version numberv0.4.0 (no skip).
5Milestone openingOpen immediately. Milestone #6 already exists; this brief fills it.

The brief is accepted and actioned.


10. References

Inputs internos

Inputs externos (carry-over from v0.3 brief)

v0.4 closure & roadmap review: what shipped, what slipped back, what comes next

FieldValue
Date finalised2026-06-02
AuthorRômulo Febasi <romulo@febasi.com.br> (analysis assisted by Claude Code)
ScopePost-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.
Statusaccepted: input to the v0.5 brief and to the README §Status refresh (commit b37a418)
InputsRepository 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 scopeThe 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 including reindex and replay_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-93b network 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

PathLinesNotes
src/mcp/server.rs844Carries 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

PathLinesTrajectory
src/core/identity/mod.rs650Stable. Concentrated crypto + bundle serde + canonical JSON. experience.rs already extracted. Split if v0.5 adds curriculum endorsements: otherwise leave it.
src/cli/at.rs620Grew 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.rs611list/show/prune/stale/verify/replay. Six subcommands in one file. Candidate for cli/audit/{listing,verify,replay,stale,prune}.rs split.
src/cli/ws.rs577Grew with syn ws clone (SYN-143). Still coherent. Don’t pre-emptively split.
src/registry/mod.rs562Schema + 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

GapImpactEffort to close
README §Status frozen at v0.1+v0.2 until 2026-06-02Three 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 READMEThe 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 exportThese 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 pointSYN-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 siteREADME is ~150 lines and growing; new contributors land and don’t know where to startSYN-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:

  1. 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), pure candle (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.
  2. Vector storage choice. SYN-81 says “sqlite-vec via load_extension”. sqlite-vec is real, recent, used in production. But the combination of rusqlite bundled + load_extension has known ABI gotchas (Alex Garcia documents them explicitly). If sqlite-vec fails the spike, the fallback is lance (full vector DB) or a hand-rolled flat / HNSW index: both larger architectural decisions than the issue title implies.
  3. 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.
  4. Search target. Is syn search hybrid 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

#ActionEffortWhy it goes first
1Write 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 backend2 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
2File 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½ dayADR-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.
3Visibility polish: README §Editor integration section + Homebrew tap (antecipate SYN-102) + asciinema demo of syn lsp1–2 daysv0.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

RiskSeverityMitigation
mcp/server.rs regrows again after the v0.5 dispatcher refactor because the macro contract still wants a single impl blockMediumDocument 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 ADRHighThe 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 urgentLow–MediumMake 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 didLowThe 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)MediumFile 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

  1. 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 pure candle (no ONNX, more flexibility, slower per-query)? Bias from the owner: local-first, no Ollama. Brief must show the trade-off with numbers.
  2. Vector storage: sqlite-vec (lean, in-process, recent), lance (full vector DB, heavier), or hand-rolled flat / HNSW? Spike before committing.
  3. Hybrid ranking: RRF (cold-start safe) or convex combination (better with labelled data: does a single-user workspace have those?).
  4. Default mode for syn search: hybrid by default, vector-only behind --semantic flag, or the inverse?
  5. 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 on syn search. The brief codifies this.
  6. 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.rs just crossed.
  • ADR-018: tower-lsp-server 0.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 b37a418 on 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

FieldValue
Date finalised2026-06-02
AuthorRô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)
ScopeRoadmap 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).
Statusfully 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
Inputsv0.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 scopesyn 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, kind meeting: “Reunião com Carina sobre a flag enable_v2_checkout do Remote Config. Acordamos rebatizar para checkout-v2-eligibility antes do próximo deploy.”

Note B: notes/firebase-stack.md, kind reference: “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:

SignalWhy 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_fact via MCP, …) hooks an enrichment pipeline that runs synchronously before the workspace transaction commits:

  1. Embed the entity’s title + body + tags with the configured embedding model; write the vector to the entities_vec table.
  2. 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).
  3. Resolve mentions: every candidate is matched against the live workspace index. A hit becomes a typed link row with rel = "mentions" or rel = "concept:<lemma>"; a miss becomes a pending proposal of kind link_entity (existing) or create_entity_from_ner (new, when the NER pass discovered a span whose mapped Synapse entity type does not yet exist).
  4. 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::tx so the wall-clock cost is max(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.
  5. 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:

SlotDefault (best)Opt-down
EmbeddingBGE-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
NERGLiNER-multi-base Q (~150 MB)GLiNER-base-en Q (~70 MB, English only): same trade-off
Vector storagesqlite-vec (no choice: see §2.2)-
Hybrid rankingRRF 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:

OptionWhat it isCost on diskCost on first useExternal deps
Ollama as runnerSynapse 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 minimumUser 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-wordsNo 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]):

  1. rusqlite bundled + load_extension interaction. Bundled SQLite does not enable run-time loadable extensions by default, the user has to call conn.load_extension_enable() before load_extension(…), and on older SQLite versions the vtab feature has to be enabled at the rusqlite Cargo level. The crate exposes the flag; documenting it in ADR-008 prevents a wasted afternoon.
  2. No HNSW yet: flat scan only. sqlite-vec currently 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 for sqlite-vec HNSW.

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.

DocFTS5 rankVector rankGraph rankRRF score (k=60)
Note B (Firebase reference)1511/(60+1) + 1/(60+5) + 1/(60+1) = 0.049
Note A (Carina meeting)(not in top-10)220 + 1/(60+2) + 1/(60+2) = 0.032
Random Firebase note212251/(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:

ApproachWhat it does at ingestionCost
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:

  1. Model is GLiNER-multi-base quantised (~150 MB) by default; GLiNER-base-en quantised (~70 MB) as the workspace opt-down per §1.3.
  2. 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.
  3. 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 issuePre-research titleStatus after briefReason
SYN-80feature flag vector + separate migration directoryKeep.The migration story (migrations_vec/) is the right shape; vector becomes off-by-default for v0.5, default-on in v0.6.
SYN-81sqlite-vec integration: load_extension + entities_vec tableKeep, retitle to clarify the bundled+vtab setup.Defaulted by §2.2.
SYN-82trait EmbeddingBackend (interface only)Keep.Right shape; the implementations come behind it.
SYN-83EmbeddingBackend implementations: Ollama, OpenAI, Voyage, configurableRewrite 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-84EmbeddingBackend stub: deterministic, no networkKeep.Needed for unit tests; deterministic SipHash-based fake vector.
SYN-85syn embed --all and syn embed <slug>Keep, expand to also expose syn embed --check for verifying index integrity.
SYN-86hybrid ranking: FTS5 + vector via RRFKeep, finalise k=60.Defaulted by §2.3.
SYN-87syn 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-88MCP tool semantic_search mirrors syn search --semanticReframe. 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-89ADR-008: chosen default embedding backend after benchmarkKeep. Brief mandates ADR-008 lands before SYN-83 code.Governance.
SYN-93bworkspace 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.rs dispatcher refactor. Move every #[tool] handler to a thin shim (3–8 lines) that delegates to a use-case module under src/core/mcp/ (or src/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.rs split. Break the 620-line file into cli/at/{mod,search,show,list,info,graph}.rs. Pure code move, no behaviour change.
  • SYN-153: cli/audit.rs split. Same pattern: cli/audit/{listing,verify,replay,stale,prune}.rs.
  • SYN-154: file-size CI lint. A 30-line script in .githooks/pre-push or a GitHub Actions step: walk src/, 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 before tx.commit(). Honours [enrichment] mode = "off" | "heuristic" | "ner". BR-13 codified.
  • SYN-91: heuristic linker module. No model dep. Documented detection patterns. Emits pending proposals 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_slugs per query.
  • SYN-93: benchmark runner + results doc. CI artifact: bench/results-<date>.json.

Stretch (lands if window allows, otherwise v0.6):

  • SYN-94: GLiNER NER backend behind --features ner. Comparable to the heuristic on the bench corpus.
  • SYN-95: EmbeddingBackend::OpenAI and Voyage impls (network-based, behind explicit opt-in).
  • SYN-96: syn search --grade to label query→relevant pairs for the v0.6 convex-combination polish.
  • SYN-97: MCP semantic_search tool, only if SYN-88 finds a client use case the unified search_knowledge cannot 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 a notes/<stack>.md reference 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

MetricDefinitionv0.5 ship threshold
P@1Top-1 hit is in the ground-truth setT1 = 1.0, T2 ≥ 0.6, T3 ≥ 0.3
P@5Any of top-5 is correctT1 = 1.0, T2 ≥ 0.85, T3 ≥ 0.6
MRRMean reciprocal rank over the 50 queriesoverall ≥ 0.55
nDCG@10Normalised discounted cumulative gainoverall ≥ 0.6
Write latencyTime 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 latencyTime for hybrid search (median, 10k corpus)≤ 150 ms
Binary size deltatarget/release/syn size vs v0.4 baseline (binary only, models cached separately under ~/.cache/synapse/models/)≤ +60 MB
Cold-start model downloadTotal 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.

BuildBinary on diskModels cached on first useWhen
syn (v0.4)~14 MBnonetoday
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 MBper-workspace choice
syn with Ollama instead of fastembed~14 MB binary + ~1 GB external (Ollama daemon + model)requires Ollama daemonconfigured 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 syn invocation 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

RiskSeverityMitigation
sqlite-vec load_extension setup proves harder than expected on Windows MSVCMediumSpike 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)MediumThe 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 UXHighBR-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.5LowThe 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 contractMediumThe 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.6MediumThe 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)

IDLayerSizeTitle
SYN-151mcpMrefactor mcp/server.rs to dispatcher pattern (thin #[tool] shims, logic moves to core::mcp::*)
SYN-152cliSsplit cli/at.rs into cli/at/{mod,search,show,list,info,graph}.rs
SYN-153cliSsplit cli/audit.rs into cli/audit/{listing,verify,replay,stale,prune}.rs
SYN-154ciXSCI lint rejecting src/** files above 800 lines (red), warning at 600 (yellow): ADR-015 enforcement

9.2 Semantic layer: core (must land)

IDLayerSizeTitle
SYN-89docsMADR-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-80index/buildMfeature flag vector + separate migrations_vec/ migration dir
SYN-81indexLsqlite-vec integration via bundled+vtab+load_extension; entities_vec virtual table; ADR-008 §gotchas
SYN-82coreMtrait 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-84core / testsSdeterministic EmbeddingBackend + NerBackend stubs (SipHash-based fake vector, regex-based fake NER) for unit tests
SYN-83coreMEmbeddingBackend impls: fastembed-rs with BGE-M3 default + BGE-small-EN-v1.5-Q opt-down per [enrichment] embedding_model; Ollama configurable
SYN-164coreMNerBackend 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-85cliMsyn embed --all / syn embed <slug> / syn embed --check / syn embed --reset-cache
SYN-160coreLenrichment 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-161coreMheuristic 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-86core / indexLhybrid ranking via RRF with k = 60 over FTS + vector + graph-neighbourhood signals
SYN-87cliSsyn search default = hybrid; --semantic = vector-only; --no-vector = FTS+graph only
SYN-162core / testsLbenchmark 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-163core / tests / docsMbenchmark 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-167docs / cliSREADME §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)

IDLayerSizeTitle
SYN-165coreMEmbeddingBackend impls for OpenAI + Voyage (network-based, behind explicit opt-in)
SYN-166cli / coreSsyn search --grade UX for capturing query → relevant labels; foundation for v0.6 convex-combination tuning
SYN-88mcpSMCP semantic_search tool: only if a client benefits over search_knowledge’s hybrid mode
SYN-194coreL(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

IDLayerSizeTitle
SYN-93bstore / mcpLworkspace 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.md as 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 agent autonomous 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

  1. ✅ Default embedding model: BGE-M3 multilingual. Owner answered the §2.1 question explicitly: ship the strongest model by default; BGE-small-EN-v1.5-Q is 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.
  2. ✅ GLiNER promoted from stretch to core. Owner answered the §2.4 question with option B: GLiNER-multi-base-Q default, GLiNER-base-en-Q opt-down per workspace. Hard-coded entity types, parallel embed+NER pipeline inside Workspace::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.toml picks its language = "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)

  1. ✅ syn search syntax 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.
  2. ✅ Ticket numbering: SYN-160..167 expansion block. Owner picked the safer remapping over reusing the v0.2-adjacent slots. Final allocation:
    • SYN-160 enrichment pipeline (BR-13)
    • SYN-161 heuristic + NER linker
    • SYN-162 benchmark corpus + queries
    • SYN-163 benchmark runner + report
    • SYN-164 NerBackend impl (GLiNER multi default + en-only opt-down)
    • SYN-165 (stretch) OpenAI + Voyage EmbeddingBackend impls
    • SYN-166 (stretch) syn search --grade
    • SYN-167 README §Editor integration adoption section
    • Plus the architecture-debt block already at SYN-151..154.
  3. ✅ 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 lsp registered for v0.5.1, not in this cycle but mapped.
  4. ✅ Six-week window accepted as the upper bound, owner expects earlier delivery.

12. References

Vector storage: is sqlite-vec really the best fit for Synapse?

FieldValue
Date finalised2026-06-02
AuthorRômulo Febasi <romulo@febasi.com.br> (analysis assisted by Claude Code)
ScopeOwner-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.
Statusaccepted: confirms ADR-008 stays as written; registers vectorlite as the documented upgrade path for v0.7+ if we hit the flat-scan ceiling
InputsADR-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 scopeMigrating 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:

  1. For Synapse’s actual workload (≤ 10k entities per workspace, federation handles scale by chunking), sqlite-vec is the right pick. It is recent, maintained, and fits our existing .synapse/index.db model cleanly. ADR-008 stays as written.
  2. 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.
  3. 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 than sqlite-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:

ConstraintSourceWhat it eliminates
Atomic FS + index transactions (Workspace::tx)BR-6, ADR-003 §ConsequencesAny backend without a transaction model that composes with the filesystem write
Append-only audit trail living next to the indexBR-12.3, ADR-013Any backend that requires a separate process or separate storage mount
FTS5 ranking already shipped in 121 testsv0.4 codebaseAny backend that forces a re-implementation of full-text from zero
Single binary, no daemonBR-1, BR-12, ADR-018’s “no second runtime” threadAnything that requires localhost:<port>
Cross-platform: aarch64+x86_64 macOS, aarch64+x86_64 Linux, x86_64 Windowsv0.4 release matrixAnything that ships only on a subset of OSes
Liberal OSS license (MIT or Apache 2.0)Synapse is MITAnything that ships proprietary or AGPL
Workspace ceiling 10k entities (federation chunks beyond)v0.5 brief §6.3Anything 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.

OptionWhat it isHard-no for Synapse?Soft-no for Synapse?Worth migrating from sqlite-vec?
sqlite-vec 0.1.9Alex 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 entitiesbaseline
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 entitiesNo for v0.5 (too early); yes if we ever exceed ~100k vectors per workspace
LanceDB embeddedApache 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 embeddedMulti-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 extensionColumnar 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.
usearchSingle-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 bindingNo 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 modeReal vector DB that has an embedded crate. Rust-native. HNSW.-Same daemon-shaped split as Ollama: heavy binary growth, separate API surface, ~80 MBNo: same reason we rejected Ollama-as-runner for embeddings
sqlite-vector by sqlite.aiCommercial-ish (sqliteai.com) vector extension; HNSW.License unclear (BSL? Commercial?)-No until license clarity
Tantivy + custom vectorLucene-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 modesNo: we’d be inventing LanceDB’s stack ourselves
Hand-rolled flat scan + SIMD cosinePure Rust, no dep.-Reinvents sqlite-vec for no upside; lacks the ANN index path we’d want at scaleNo

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

SubsystemLOC todayStays on sqlite-vecMoves to vectorliteMoves to LanceDB
Workspace::tx (atomic FS + SQLite transaction)~150unchangedunchangedrewrite: different transaction surface
FTS5 ranking + snippet~120unchangedunchangedreplace 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)~450unchangedunchangedrewrite the queries: Arrow columnar reads, no SQL
Pending queue~530unchangedunchangedrewrite
Federation registry + manifest cache (separate SQLite DB at ~/.config/synapse/workspaces.db)~560unchangedunchangedmigrate or keep on SQLite alongside: two DBs, two query languages
Schema migrations (0001..0004)~200 (incl. SQL)unchangedunchangedrewrite as Arrow-native migrations
FTS5 MATCH … ORDER BY rank callers~80 in core::search, cli/at, mcpunchangedunchangedrewrite all of them
core::reindex (4 migration tables, 6 entity types)~310unchangedunchangedrewrite
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_extension shape, 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:

TriggerWhat to do
A real user workspace exceeds ~100k entities and reports flat-scan latency on syn searchMigrate 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 inactiveSwitch 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+ rowsMigrate 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:

RiskSeverity nowMitigation already in place
Flat scan O(N) becomes visible at ≥ 50k vectors per workspaceLowBrief §6.3 caps the v0.5 workspace at 10k entities; federation chunks beyond
sqlite-vec author single-maintainer (Alex Garcia) goes inactiveMediumSwitch 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 materialisesLowDoesn’t block v0.5; if it never lands, vectorlite is the upgrade path
sqlite-vec v1.0 introduces breaking changes during the maintenance handoffMediumWe 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 collisionLowResolved 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:

IDWhatWhen
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 churnOpen ticket now, no milestone: moves into v0.7 when that milestone exists
ADR amendmentAppend 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

CLI output UX & agent-friendliness

FieldValue
Date finalised2026-06-05
AuthorRômulo Febasi romulo@febasi.com.br (Claude-assisted)
ScopeIs 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.
Statusdraft
InputsAudit 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 scopeThe 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_stderr are bare println!/eprintln!. The only styled output is print_error → red error: label, gated on stderr.is_terminal().
  • Globals: --verbose, --format text|json, --lang, --no-freshness. No --no-color, --quiet, --yes/--no-input.
  • --format json is 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 to UserError=1).
  • NO_COLOR / TERM=dumb are not honoured anywhere.
  • Prompts exist in syn setup and the syn init onboarding; they gate on is_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

CapabilityVercelgcloudghcargoSynapse 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 codespartialpartialpartial✓✓ (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

#BlockJustificationCostNotes
1TTY-gated styling layer (colour, ✓/✗/▲, bold/dim) honouring NO_COLOR/--no-color/TERM=dumb/SYNAPSE_NO_COLORCloses the whole visual gap in one module; clig.dev-compliantMNeeds a colour crate → ADR (anstyle/anstream, the clap/cargo stack, ~0 transitive weight)
2Non-interactive mode: --no-input (alias --yes/-y) + --quiet/-q; every prompt respects itAgents/CI must never block on a promptSThe syn init onboarding + syn setup are the prompts to gate
3--format json to 100% + machine-readable JSON errors (stderr) in json modeAgents get structured actions + errors, not just readsMToday print_error is always plain text
4Finer exit-code branching (not-found vs already-exists vs conflict)Agents branch on outcome without parsing textSKeep sysexits categories; add/Document sub-codes
5Tables + visual structure for list/search/info/doctor on TTYThe human-readability win; plain columns on pipeMLight table crate or hand-rolled; gate on TTY
6TTY progress (spinner/bar) for embed --all/reindex/watch; plain lines off-TTYLong ops feel alive; clig.dev: no animation off-TTYSindicatif 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

  1. --no-input as a first-class “agent mode” that also flips output to compact + no-colour + no-progress in one switch (a SYNAPSE_AGENT=1 env too), the single flag an autonomous runner sets.
  2. 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 IDBlockSize
SYN-186TTY-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 contractM
SYN-189Finer exit-code branching for agents (not-found/already-exists/conflict)S
SYN-190Tables + structured layout for list/search/info/doctor on TTYM
SYN-191TTY spinner/progress for embed –all / reindex / watchS
SYN-192Premium loading & animation experience (branded spinners, multi-phase bars, polished completion): AAA tierM
SYN-193Rich rendering: unicode graph tree (├─└─), entity panels/cards for show, clean search snippets (strip raw markdown)M
SYN-195Rendering opt-out contract: default AAA/rich; --plain/--raw + SYNAPSE_PLAIN for raw/token-economy; single is_rich() gateS

6. Risk register

RiskSeverityMitigation
Colour crate = new depLowanstyle/anstream are the clap stack (already transitively present); still write an ADR
Styling leaks into pipes/JSON, breaking scriptsHighSingle chokepoint; gate on TTY + NO_COLOR; never style JSON; snapshot-test plain output
Output-field changes break agentsMedFreeze the contract (block 3); additive-only after v1.0

7. Open decisions

  1. Colour crate: anstyle (style only, BYO writer) vs anstream (auto-stripping stream)? Recommend anstream (handles the strip-on-pipe rule for free).
  2. --no-input default 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.)
  3. Tables: add a crate (comfy-table/tabled) or hand-roll? (Hand-roll is dep-light but more code.)

8. References

MCP & agent ergonomics: token-efficiency and missing affordances

FieldValue
Date finalised2026-06-05
AuthorRômulo Febasi romulo@febasi.com.br (Claude-assisted)
ScopeHow 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.
Statusdraft
InputsCode 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 scopeThe 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.

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

AffordanceStatus todayGap
Bounded readssearch/federated cap; list_by_type does notAdd limit + cursor
Machine-readable resultsJSON as pretty text, no outputSchemastructuredContent + compact JSON
Lazy/cacheable readsentities are resources, but tools don’t link themResourceLink on hits + get_entity
Batch readsone get_entity per callget_entities / resource multi-read
Field projectionget_entity returns the whole markdownoptional fields projection
Orientationnone (agent must list+search to learn the shape)workspace_overview digest
Deltasnone (re-reads everything each session)recent_changes {since}
Batch writesone proposal per callpropose_batch → one accept
Pre-validationupdate_status validates at accept timevalid_statuses/dry-run to cut rejects
Compute-out-of-contextCLI has `exportjq`; 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

#BlockJustificationCostDefensible?
1Bound list_by_type (limit+cursor)Closes the one unbounded read; Anthropic principle #1SHygiene
2structuredContent + compact JSONToken + parse win on every read; spec-alignedMHygiene
3ResourceLink on readsLazy/cacheable reads; entities already are resourcesSLeverages existing resources
4workspace_overview digest toolKills the explore-cost; one call to orientMProduct
5recent_changes {since} deltaRecurring agents pull deltas, not the worldMProduct (audit/timeline exist)
6get_entities batch + fields projectionFewer round-trips; read only needed fieldsMHygiene
7propose_batch (one bundle, one accept)Fewer round-trips; one human sign-offMFits propose/accept
8read-only query tool (compute-out-of-context)The 98% pattern; biggest token leverLMoat-adjacent

Non-goals for now: protocol-level changes; a full code-execution sandbox; dropping any existing tool.

6. Disruptive bets

  1. 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.
  2. Synapse as a one-install Claude plugin: .claude-plugin/plugin.json bundling the skill + the MCP server config + hooks (e.g., a SessionStart hook that runs syn doctor/syn pending list). One cp/marketplace install wires the whole loop. Agents that don’t speak MCP still get the skill.
  3. Proposal “context snapshot” as a first-class agent contract: every write tool already accepts context; lean into it so syn audit replay can re-run a proposal against a newer model and flag drift. This is an auditability feature no hosted assistant offers: sell it.
  4. recent_changes as 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 IDBlockSize
SYN-176list_by_type limit + cursorS
SYN-177structuredContent + outputSchema + compact JSON on all readsM
SYN-178ResourceLink to synapse://entity/... on search hits + get_entityS
SYN-179workspace_overview digest tool (counts + key/recent)M
SYN-180recent_changes {since} delta tool (over audit/timeline)M
SYN-181get_entities batch + optional fields projectionM
SYN-182propose_batch (one pending bundle, one accept)M
SYN-183read-only query tool (compute-out-of-context): spike firstL
SYN-184Synapse Claude plugin package (skill + MCP + hooks)M

8. Risk register

RiskSeverityMitigation
structuredContent not supported by all clientsLowKeep the text block too (dual emit); spec says clients ignore unknown fields
query tool = injection / DoS surfaceHighRead-only, whitelisted ops, row/time caps; spike (SYN-183) before commit
Tool sprawl (15 → 20+) dilutes the catalogueMedNamespace (synapse_*); lean on the skill + Tool Search; retire overlaps
Batch tools complicate the audit trailMedOne pending row per logical write even under propose_batch; bundle only the accept UX

9. Open decisions

  1. Adopt structuredContent now, or wait until the dominant client (Claude Code) renders it? (Recommend: dual-emit now: cost is low.)
  2. Is the query tool worth the security surface, or is syn export --as json | jq (CLI) enough and we just teach agents to shell out? (Spike SYN-183 to decide.)
  3. Namespace the tools (synapse_*) now (breaking for existing configs) or at v1.0 with the frozen MCP surface?
  4. Should workspace_overview and recent_changes be tools or resources (or both)? Resources are cacheable; tools are discoverable.

10. References

Agent-memory field comparison: where Synapse is ahead, behind, and should adapt

FieldValue
Date finalised2026-06-14
AuthorRômulo Febasi romulo.silva@oboi.com.br
ScopeA 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.
Statusdraft
InputsPublic 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 scopeImplementation 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)StatusReason
Invalidation-not-deletion + supersession (ADR-027)ValidatedThe mature temporal-KG consensus; the destructive camps are the documented failure mode.
Provenance + reversibility is the moat, not the gate (v0.9 re-scope)ValidatedThe 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)ValidatedMirrors the ACT-R/forgetting-curve research while staying auditable; the field buries importance in opaque weights or has none.
Markdown-as-truth + disposable indexValidated (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 HNSWValidatedField consensus: flat is correct below ~100k; HNSW is a post-100k lever.
The accept-gate as headlineChallengedAn absolute every-write gate is market-rejected friction; reframe to auto-apply-reversible, gate-the-costly.
Transaction-time derived purely from gitChallengedFragile under batch commits; store an explicit belief axis, keep git for the tamper-evident audit axis.
Contradiction handled by overwriting a fieldAddsNeeds a semantic-candidate-retrieval step (reworded contradictions) and escalate-to-gate, not just same-field detection.
Trust score is a display featureAddsCheapest precision win is to inject it into retrieval ranking, not just blame.
No eval harnessAddsBuild a property/invariant harness (provenance/reversibility/temporal), not a QA leaderboard.
Extraction is the agent’s job, undocumentedAddsThe craft is real, transferable IP; publish a proposal-quality contract for client agents.
  1. Inject the ADR-029 trust score into retrieval fusion: cheapest precision upgrade, reuses shipped work; trust currently only displays.
  2. Reframe SYN-242 contradiction as deterministic-structural + semantic-candidate-retrieval → escalate to the gate (not LLM auto-resolve); pure date-arithmetic supersession on confirm.
  3. Batch embedding + watermark/mtime-gate scan + move-detection: extends the reindex fix; the field’s proven sub-linear-ingestion patterns.
  4. 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.
  5. 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)

  1. “Provable, not estimated” memory quality. Ship the invariant harness and surface its assertions (audit verify, a doctor-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.
  2. 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.
  3. 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.
  4. 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)

SketchBlockSize
Trust score → retrieval fusion boostcore/searchS
SYN-242 contradiction: structural + candidate-retrieval → gatecoreM
Batch embedding over content-changed entities onlycore/indexS
Watermark + mtime/size-before-checksum scan; move-detectioncore/indexM
Proposal-quality contract doc for MCP clientsdocs/mcpS
Property/invariant eval harness (syn verify or test-suite)core/ciM
Explicit belief transaction-time axis (x-recorded-at/x-invalidated-at)model/coreM
Reversible compaction: summary-fact-with-source-linkscoreM
Identity-hash dedup backbonecore/modelS
Anchor-distance graph rerankercore/searchS

7. Risk register

RiskSeverityMitigation
Trust-in-ranking changes search results subtly / regresses recallMedBehind a flag; add a search-quality property test before defaulting on.
Contradiction escalation floods the pending queueMedDeterministic + dedup short-circuit first; only genuinely ambiguous cases escalate.
Belief-axis fields bloat frontmatter / confuse usersLowStamp lazily on mutation (like provenance); keep git as the audit axis.
Eval harness over-fits to our own assumptionsMedMake invariants, not scores; cross-check with doctor/audit verify.
Provenance moat is time-boxed (others copy the crypto)MedDurability is the local-first + identity binding, not the crypto; keep shipping the integrated stack.

8. Open decisions

  1. Do we add the explicit belief transaction-time axis now (sharpens SYN-223/242) or keep deriving from git for v1.0 and revisit?
  2. Is the property/invariant harness a syn verify command (user-facing) or a CI test-suite (internal), or both?
  3. Does the proposal-quality contract ship as a doc, or also as a machine-readable schema the MCP server advertises to clients?
  4. Per-type “profile vs log” decay (SYN-248): worth the [policy.<kind>] surface, or defer past v1.0?

9. References

v0.11 issue-direction study: question, reshape, and sequence the open work

FieldValue
Date finalised2026-06-15
AuthorRômulo Febasi romulo@febasi.com.br (4 specialised research subagents)
ScopeCritically question every open v0.11 issue (market, technical state-of-art, user-problem, adversarial), then reshape/sequence each. Implementation follows.
Statusaccepted
Inputs4 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 scopeCutting 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

IssueSubagent consensusDecision (this study)
SYN-260 hygieneContains 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 retrievalPart (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 ingestionReal 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 dedupScope 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 edgesSelf-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 rerankDependency-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 policyReshape 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 verifyMulti-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 replayDon’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)StatusReason
v0.11 = scale & graph is the right next themechallenged3/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 workchallengedSYN-259(2) shipped; SYN-245 blocker resolved: reconcile before building.
HNSW / graph-UI deferralvalidatedMarket moved to token-efficiency; flat-vector correct < ~100k.
Deterministic-first contradiction/dedupvalidatedHaluMem auto-update <26% unchanged; gate-don’t-resolve is correct.

Provable non-corruption: the foundations of syn verify (v1.0)

FieldValue
Date finalised2026-06-16
AuthorRômulo Febasi romulo@febasi.com.br (3 specialised research subagents, web)
ScopeWhitepapers, projects, and theory for reproducible, auditable, testable non-corruption: to define the v1.0-hardening issues headlined by syn verify.
Statusaccepted
Inputs3 parallel subagents (theory/whitepapers; real-systems prior-art; eval/testability), all web-enabled + repo-grounded.
Out of scopeImplementation (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)

TheoryVerdictUse 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)ADAPTA 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 heartThe 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 replayADOPT 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)ADOPTNo oracle for “memory is uncorrupted”, but strong oracles for “reindex twice == once” and “CLI == MCP”.
in-toto / SLSA / DSSE attestationADAPT the data modelThe 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-signersADOPT (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/CIDINSPIRATION / REJECTSingle-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.

  1. 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 to model@precision). The keystone; the index is a reproducible build of the files.
  2. INV-2 Content integrity: every fact’s payload_sha256 matches the Markdown blob at the commit that introduced it; no orphan facts, no orphan audit rows.
  3. 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 as audit verify; formalise.)
  4. INV-4 Reversibility: replaying the audit reconstructs the current store state (frontmatter-generic). (Riskiest: see §4.)
  5. 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.
  6. INV-6 Port parity: CLI ≡ MCP for every read (metamorphic; the AGENTS.md “two doors, one core” rule as a test).
  7. INV-7 Temporal correctness: at <ref> -- search --as-of <date> reconstructs the state that held. (Seeded by SYN-258.)
  8. 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 verify tells 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_checksum for 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 verify must 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 --deep replay; 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)

IDTitleAC (one line)
SYN-262core::verify engine + canonical logical-store dump (foundation + report schema ADR)dump(ws) byte-stable across calls and a reindex --full
SYN-263syn verify runtime command + signed report (both doors): headlineclean ws exits 0 + signed report; corruption → exit 1 + offending invariant
SYN-264Port-parity differential suite (CLI ≡ MCP)adding a door to one side without the other fails CI
SYN-265Invariant proptest suite (coherence + governance: I-1/2/3/5/7/9/10)incremental ≡ full + round-trip over ≥256 cases
SYN-266Deterministic replay invariant + clock/identity seam (verify ADR-027 prereq first)replay-into-empty == current store
SYN-267Stateful supersession + temporal proptest (I-8/11)order-independence + bitemporal axis-independence
SYN-268Fuzz the untrusted boundary (proptest-first; cargo-fuzz opt-in + ADR)arbitrary bytes never panic / never violate round-trip over ≥1024 cases
SYN-269Verifiable-rebuild attestation (syn verify --rebuild, diffoscope-style localized diff)rebuild manifest mismatch names the diverging facts
SYN-270Witness 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

HypothesisStatusReason
syn verify is genuine white spacevalidatedNo 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 primitivesvalidatedaudit verify (content+chain), reindex (rebuild), at/--as-of (temporal), receipts (signing) already exist.
Crypto provenance is being commoditisedreinforcedAnthropic 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 partvalidatedADR-027’s generic-frontmatter assumption is unverified; gate SYN-266 on it.

Benchmark redesign: scaling, resources, and correctness-at-scale

FieldValue
Date2026-06-21
AuthorRômulo Febasi (4 specialised research subagents, web)
ScopeReplace 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.
Statusaccepted (drives a v1.1 benchmark-v2 plan + 2 code-finding tickets)
Inputs4 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)

  1. 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.
  2. No resources. Nothing on RAM (the BGE-M3 model floor alone is ~2.5 GB!), CPU, or .synapse/index.db size. For a local tool, “does it fit on my machine?” is the question.
  3. stub hides 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.
  4. 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.
  5. No workload profiles, no breaking point. No “daily (medium)” vs “extreme (stress)”, no cliff.
  6. 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).

SubsystemComplexityClassNote
Flat vector KNNO(N·d)linearthe headline wall: full scan every query (hybrid.rs embedding MATCH … ORDER BY distance)
FTS5 BM25~O(matched·log N)sub-lineartracks the result set, not N: essentially never the bottleneck
Graph 1-hopO(log E + deg)~logindex-backed (idx_links_from/to)
Ego-graph / connectionsO(Σ deg over frontier)O(N) on hubsdegree-driven; a mega-hub is the only graph cliff
EAV filterO(log F + matches)~logindex-backed
RRF fusionO(pool)~O(1) in Ntrivial
Federated searchO(W·N·d)linear in Weach ws pays its own full vector scan, serially
Full reindexO(N) parse + O(N·d) embedlinearembed dominates; content-hash-gated incremental (SYN-251)
DB sizeO(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 vectorsflat-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.

TierPersonaEntitiesEdgesPeople/ProjectsWordsWS
T0 Smoke(current bench)300~60020 / 5~150k1
T1 Dailysolo dev, months in~500–2k~3–6k40–150 / 8–20150k–1M1–3
T2 Powerheavy user @ 1yr~6k–20k~20k–80k250–1k / 30–1001.5M–10M3
T3 Heavymulti-year, near ceiling~20k–100k~200k–500k5k / 50010M–50M5
T4 Stresssynthetic stress100k–500k~3M20k / 2k250M10
T5 Breakpast every wall1M (+ a 10k-degree hub)~8M50k / 5k500M25

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):

Componentper entitynote
Vectors (fp32)4.0 KB1024×4 B; int8 → 1 KB; binary → 128 B
FTS5 (self-content)~4 KBtext 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
NDB ≈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)
  1. Baseline: RSS of a no-op (syn --version).
  2. 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.
  3. 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.

#ChartyProvesHero?
1Search latency vs Np50 & p95 (log): 3 lines: FTS / vector-flat / hybridhow each mode degrades; where flat goes linear★
2Scale-tiers stripbanded usable·interactive·breaking over N tiersone-glance “what tier am I in”★
3RAM vs Npeak RSS: floor line + slope, fp32 vs int8the resource ceiling / fit★
4Index size vs Nstacked FTS / vector / graphdisk cost + what dominates
5Reindex time vs Nwall: full vs incrementalthe disposable-index claim is credible only if bounded
6Embedding throughputdocs/sec, fp32 vs int8, cold-load separateonboarding cost
7Recall/quality vs Nrecall@k / nDCG@10 vs a labelled gold setretrieval stays correct as the graph grows★
8Latency CDF (small multiples per tier)the tail, not just p50the 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.sql declares fts5(…) with no content= 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.

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 + VmHWM checkpoints; 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

FieldValue
Date2026-07-03
AuthorRômulo Febasi (2 web research agents + a live proof-of-concept)
ScopeDesign 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.
Statusaccepted (drives plan 022 / SYN-298; Phase 0 root-cause fix filed as SYN-297)
Inputsclaude-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)

  1. 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 verify already emits a canonical logical dump, a drop-in analog of τ-bench’s get_data_hash().

  2. 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 contradicts signal + BR-12.3. We get to define the metric. Do NOT grade contradiction as “latest wins” (that’s a correction); grade the surfacing.

  3. 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 verify INV-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; _oracle split = 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: contradicts fires ∧ routed to the gate (no auto-apply even under auto_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 verify canonical dump (τ-bench style) + INV invariants. No judge variance. Lean on this.
  • 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_usd per 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):

  1. Approval / trust. A project .mcp.json (or inline --mcp-config) server sits at ⏸ Pending approval in headless and its tools never load; only its resources do. Since v2.1.196 enableAllProjectMcpServers in 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 user has open bugs #32939/#54803).
  2. Schema validation. Claude Code ≥2.1.x validates each tool’s outputSchema and drops the entire tool list if any fails. Synapse tripped this (see PoC / SYN-297). Fix landed in src/mcp/output_schema.rs.
  3. 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 -p against the synapse MCP, captured the full stream-json trajectory (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-card returned 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 called ToolSearch 16× and mcp__synapse__propose_batch returned “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 became Connected · tools fetch failed with Invalid input (at tools.2.outputSchema.properties.result): Synapse’s SYN-177 read envelope emitted outputSchema with result: true (the boolean schema serde_json::Value derives). Draft 2020-12 permits true; 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 in src/mcp/output_schema.rs (a permissive AnyValue → {}, never true; regression test); filed as SYN-297. After the fix + the working recipe above, a headless claude -p called mcp__synapse__capture_fact and 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)

  1. 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-of correctness (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

FieldValue
Date2026-07-04
AuthorRômulo Febasi
ScopeFirst real runs of the synapse-bench drive headless driver (SYN-298, plan 022) against Claude Code, over the insert and contradict scenarios. Owner-run.
Substrateclaude 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.
Statusfindings: drives the next plan-022 iteration.

Headline numbers

ScenarioNpass@1pass^kcostnote
insert-01 (plant a person amid chit-chat)1580%no$3.65structural extraction (entity landed) = 13/15 (87%)
contradict-01 (conflicting claim vs seeded fact)10100% 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=false recipe, 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:

  1. 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.
  2. 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-contains case-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-01 reframed 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.

MeasurementNresultreads what it should
insert-01 (A)15pass@1 80% (CI 55–93%)extract the fact, resist the chit-chat
correct-01 (B)8pass@1 88% (CI 53–98%)agent chose update_status on the existing entity → status archived; no duplicate
abstain-01 (D leg)8pass@1 88% (CI 53–98%)“Não encontrei registro de Camila Duarte”: abstains, fabricates nothing
contradict-01 (C, agent)10non-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_status overwrites the field in place; it is NOT bitemporal. After an agent correction (Aurora active → archived), the entity row is valid_from=<created>, valid_until=NULL, supersedes=NULL and search --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-level update_status corrections. 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-of filter working before F2 (F1-only ✓), but after F2 both facts return: F1’s valid_until was 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 (like gate-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 with x-supersedes
    • a later x-valid-from, accept both (the accept’s resolve_supersession closes F1), rebuild, and assert F1 closed-not-deleted, F2 open, --as-of before→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.
  • 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-needle claude -p blip). 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)

  1. ✅ gate-probe: SYN-242 detect+gate in isolation. Done.
  2. ✅ Driver zero-tool + crash retry: absorbs the headless flakes. Done.
  3. ✅ Scenario B / D: abstention + three-leg judge + --distractors noise sweep + the deterministic bitemporal-probe (the --as-of signal the agent path can’t drive).
  4. ✅ 95% Wilson CIs + cost-per-pass. Done.
  5. 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_status corrections 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 + λ·familiarity multiplier 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 open blocks 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 verify dump (like vectors and mentions:*), 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 that verify is 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:

  1. SYN-292: syn undo Phase 2 (reversibility pillar, still half-built).
  2. SYN-271: reindex regenerating mentions:* enrichment (a correctness gap in the reproducible-build keystone the provability moat depends on).
  3. 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 --all regenerate). 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 verify stops 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)

FieldValue
Date finalised2026-07-07
AuthorRômulo Febasi romulo.silva@oboi.com.br
ScopeDecide 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.
Statusaccepted (recommendation: GO, ship in verify --deep, own follow-up issue)
Inputssrc/core/contradiction.rs (SYN-242 propose-time detection), src/core/verify/ (INV engine), ADR-027/031 (bitemporal), plan 012
Out of scopeSemantic/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_history from ADR-046) and the verify engine (INV-1/2/3/5, plus --deep INV-4 and --since INV-12). SYN-242 already detects contradictions at propose-time and forces the gate; there is no invariant verify can 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 with valid_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-supersedes forms a cycle, or a fact is closed (valid_until set, x-invalidated-at) but nothing supersedes it and it is not a correction, an orphaned invalidation. Graph check over the supersedes column.
  • 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 --rebuild list; 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 (status is single-valued).

Multi-user / team governance seam (spike, SYN-285 / #273)

FieldValue
Date finalised2026-07-07
AuthorRômulo Febasi romulo.silva@oboi.com.br
ScopeTurn 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.
Statusaccepted (recommendation: Commercial-line MVP, git-merge model; do not build in open-core v1.x)
InputsADR-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 scopeE2EE 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

FieldValue
Date finalised2026-07-14
AuthorRômulo Febasi romulo.silva@oboi.com.br
Scope2026 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).
Statusdraft
InputsThree 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 scopeExecuting 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.

PlayerPositioningHosted/LocalOSS/ClosedIntegrity story
Mem0“Universal memory layer”Hosted + self-host + OpenMemory MCPApache core, cloud closedEstimate. LLM add/update/delete, most-recent-wins, mutable. Audit = access logs.
Zep / Graphiti“Agent memory at enterprise scale,” temporal graphZep Cloud primary; Graphiti self-hostGraphiti Apache, Zep closedClosest neighbor. Real bitemporal + episode provenance, but contradiction is LLM-judged, no crypto verify, no gate.
Letta (MemGPT)Stateful agent runtime, agent is its memoryCloud + self-hostApache open-coreNone. LLM self-edits its own memory. A Merkle-audit request was closed as spam.
Cognee“Open-source memory platform,” graph ECLSelf-host + cloudApache lib, cloud closedEstimate. Best trust primitive is a boolean ontology_valid. Building a Rust edge engine.
Supermemory“The memory API,” Memory RouterHosted + offline single binaryMIT open-coreEstimate. Timestamp recency-wins, TTL decay. Closest local philosophy.
LangMem (LangChain)LangGraph long-term memory SDKSDK + hosted platformSDK MIT, runtime Elastic 2.0None. Conflict = LLM upsert. “Time travel” is checkpoint replay.
Anthropic memory toolClient-side file ops in /memoriesClient-side, you hold dataContract open, weights closedNone; explicitly your problem. Punts versioning to “git recovery on your side.”
OpenAI/ChatGPT memorySaved memories + history miningFully hosted, closed, no exportClosedNone; opposite. Server store the model rewrites silently.
Memoria (MatrixOrigin)“The World’s First Git for AI Agent Memory”Cloud + self-host on MatrixOneApache, RustEstimate, 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, closedClosedNone stated; pitch is token-cost efficiency.
Memvid (newcomer)No-DB append-only .mv2 fileLocal fileOSSPartial. 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 verify output 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

HypothesisStatusReason
Moat is the gate + syn verify, not syncValidatedProof/governance axis is empty across all competitors; sync is copyable and off-limits by non-goal.
Do not lead with “Ctrl+Z” / undoValidatedMemoria owns rollback, Anthropic defers to git recovery, LangGraph has checkpoint replay.
Bitemporal is a differentiatorChallengedZep/Graphiti productized valid-time invalidation; it is table stakes, not a wedge.
Regulated teams are the buyerValidated + sharpenedBoth the competitive and themes scans converge on regulated/high-stakes engineering teams.
Local-first Rust is a durable edgeChallengedCognee 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

#BlockJustificationCostDefensible?
1Reposition headline to proof (syn verify + gate)Defends the one pillar Zep cannot cheaply copy; rides ASI06 + Article 12S (docs/brand)Yes
2“Memory CI” + syn contradictions as top-of-funnelDevs adopt a check faster than a database; the Action already existsMYes
3Standard 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/MYes
4EU AI Act / ISO 42001 evidence pack (syn verify --report ...)Maps existing audit fields to controls; template not engineeringSYes
5Soma x Synapse integration (MCP stdio)Dogfood; first proof of Synapse-as-agent-substrate; no new infraMYes
6Shared verify/provenance contract with EvedOne governance vocabulary across the portfolio without a runtime dependencySYes

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

  1. Reposition from “personal memory” to “the poisoning-proof memory of record,” with syn verify as 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.
  2. 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.
  3. Portable signed developer credential, assembled from three Febasi parts. Synapse identity + syn cv export for the crypto, febasi-api-auth for issuance/verification, Vero for activity attestation. Rides AI resume-fraud. Risk: unproven demand at individual-developer scale; validate before building.
  4. 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 IDBlockSize
SYN-318Memory CI: synapse-check fails a PR on newly introduced contradiction/poisoning/provenance break, with a human-readable diff commentM
SYN-319syn contradictions first-class CLI/MCP verb (narrow, demo-able wedge)S
SYN-320syn verify --format in-toto (ITE-6) and a C2PA-text manifest optionM
SYN-321Soma x Synapse: register syn MCP in the agent Soma spawns; capture run ADRs/diffs as governed memoryM
SYN-322EU AI Act Article 12 / ISO 42001 evidence-pack report modeS
SYN-323Shared verify-report + x-ai-* provenance contract adopted by Eved audit_logs (spec, not binary)S

8. Risk register

RiskSeverityMitigation
Market not yet asking for proofHighRide dated mandates (Article 12, ISO 42001); lead top-of-funnel with a free check, not a paid proof.
Zep adds gate + invariant checkerHighOwn “offline reproducible signed verify,” the pillar hard to retrofit on hosted stores; move on positioning now.
Proof illegible to non-CLI buyersMediumEmit standard envelopes and a CI comment; do not rely on a bespoke report.
TAM capped by non-goalsMediumPrice as a tool/enterprise license; accept the trade to avoid the recall/hosting races.
Hosted spine erodes local-firstMediumKeep any hosted mode an opt-in team tier gated on SYN-235; never the core.
Competitor funding/traction figures unreliableLowTreat all vendor numbers as directional; the regulatory/OWASP tailwinds are the verified part.

9. Open decisions

  1. 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.)
  2. Is “Memory CI” a free growth product or a paid team plan from day one?
  3. Does the Soma integration ship as an opt-in Soma feature or a documented recipe first?
  4. 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.

  1. Define a finite relation vocabulary and allowed actions. Model suggests review; code controls permission/acceptance.
  2. Produce a human-reviewed EN/PT corpus of architecture and shared API facts, with source/environment/version/date scopes.
  3. Split by source/project/claim families to prevent near-duplicate leakage. Freeze test data before threshold tuning.
  4. Compare exact rules + retrieval, local English NLI, multilingual NLI and optional consented Jev under identical candidate sets.
  5. Measure candidate retrieval recall separately from pair-classifier recall. Test joint pipeline as well as oracle candidate pairs.
  6. Evaluate both pair directions when entailment matters; distinguish genuine conflict from later valid change or separate environments.
  7. Freeze model/rubric/schema/input digests and hardware stamp; retain sanitized artifacts and repo commit.
  8. 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.

ProviderHeldout alert precisionConflict recallAbstentionWarm model-only p95Peak process RAMCold model initialization
Exact-text + scoped filtersundefined (no alerts)0%87.5%not applicable25 MiBnot applicable
English DeBERTa smallundefined (disabled by training threshold)0%87.5%47.79 ms932 MiB4.18 s
Multilingual MiniLM six layers100% (9 alerts)25%50.0%10.90 ms1269 MiB4.58 s
Jevnot measurednot measurednot measurednot measuredhostednot 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.